#version 120

// DRAWING STYLE SYSTEM
#define DRAWING_STYLE 4 // Drawing Style [1 2 3 4]

// Style-dependent settings resolution
#if DRAWING_STYLE == 1
    // Pencil on Paper Style
	#define STYLE_HATCHING_ENABLED 1
	#define STYLE_HATCHING_STRENGTH 0.4
	#define STYLE_GEOMETRIC_THICKNESS 3.0
	#define STYLE_TEXTURE_THICKNESS 2.0
	#define STYLE_ENABLE_SKY_TEXTURE_OUTLINES 1
#elif DRAWING_STYLE == 2
    // Colorful Sketch Style
	#define STYLE_HATCHING_ENABLED 1
	#define STYLE_HATCHING_STRENGTH 0.4
	#define STYLE_GEOMETRIC_THICKNESS 2.75
	#define STYLE_TEXTURE_THICKNESS 2.0
	#define STYLE_ENABLE_SKY_TEXTURE_OUTLINES 1
#elif DRAWING_STYLE == 3
	// Blue Pen Style
	#define STYLE_HATCHING_ENABLED 1
	#define STYLE_HATCHING_STRENGTH 1.0
	#define STYLE_GEOMETRIC_THICKNESS 2.0
	#define STYLE_TEXTURE_THICKNESS 1.5
	#define STYLE_ENABLE_SKY_TEXTURE_OUTLINES 1
#elif DRAWING_STYLE == 4
	// Painterly (Oil Paint) Style - uses Kuwahara filter
	#define STYLE_HATCHING_ENABLED 0
	#define STYLE_HATCHING_STRENGTH 0.4
	#define STYLE_GEOMETRIC_THICKNESS 1.5
	#define STYLE_TEXTURE_THICKNESS 1.5
	#define STYLE_ENABLE_SKY_TEXTURE_OUTLINES 0

#else
    // Fallback to Pencil style
	#define STYLE_HATCHING_ENABLED 1
	#define STYLE_HATCHING_STRENGTH 0.4
	#define STYLE_GEOMETRIC_THICKNESS 3.0
	#define STYLE_TEXTURE_THICKNESS 2.0
	#define STYLE_ENABLE_SKY_TEXTURE_OUTLINES 1
#endif

// Geometric outline settings
#define GEOMETRIC_OUTLINES 1 // Enable geometric outlines [0 1]
#define LINE_THRESHOLD_DEPTH 0.02 // Depth edge threshold [0.01 0.02 0.03 0.04 0.05 0.07 0.09 0.1 0.12 0.14 0.15]
#define LINE_THRESHOLD_NORMAL 0.15 // Normal edge threshold [0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5 0.55 0.6 0.65 0.7]
#define GEOMETRIC_THICKNESS STYLE_GEOMETRIC_THICKNESS // Geometric outline thickness [0.5 0.75 1.0 1.25 1.5 1.75 2.0 2.25 2.5 2.75 3.0]

// Texture outline settings  
#define TEXTURE_OUTLINES 1 // Enable texture outlines [0 1]
#define LINE_THRESHOLD_CONTRAST 0.04 // Texture contrast threshold [0.01 0.02 0.03 0.04 0.05 0.075 0.1 0.125 0.15 0.175 0.2 0.25 0.3]
#define LIGHTING_ADAPTIVE_THRESHOLD_DARK 5.0 // Threshold multiplier in dark areas [1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0]
#define LIGHTING_ADAPTIVE_THRESHOLD_BRIGHT 18.0 // Threshold multiplier in bright areas [8.0 10.0 12.0 14.0 16.0 18.0 20.0 22.0 24.0 26.0 28.0 30.0]
#define TEXTURE_THICKNESS STYLE_TEXTURE_THICKNESS // Texture outline thickness [0.25 0.5 0.75 1.0 1.5 2.0 2.5 3.0]
#define TEXTURE_DISTANCE_FADE 1.5 // Distance fade for texture outlines [0.5 1.0 1.5 2.0]
#define TEXTURE_BRIGHTNESS_SENSITIVITY 0.7  // How much brightness affects intensity [0.0 0.3 0.5 0.7 1.0]
#define TEXTURE_MIN_INTENSITY 0.2           // Minimum outline intensity [0.1 0.2 0.3 0.4 0.5]
#define TEXTURE_MAX_INTENSITY 1.5           // Maximum outline intensity [0.8 1.0 1.2 1.5]

#define SPEED_LINES_ENABLED 0 // Enable radial speed lines [0 1]


// Sky settings
#define ENABLE_SKY_TEXTURE_OUTLINES STYLE_ENABLE_SKY_TEXTURE_OUTLINES // Enable texture outlines in sky [0 1]


// Global pencil thickness multiplier
#define COMPOSITE_THICKNESS_BOOST 1.0 // Overall thickness multiplier [1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0]

// Distance-based outline simplification
#define TEXTURE_SIMPLIFY_DISTANCE 0.4 // Distance where texture outlines start simplifying [0.2 0.4 0.6 0.8]
#define GEOMETRIC_ONLY_DISTANCE 0.8 // Distance where only geometric outlines remain [0.6 0.8 1.0 1.2]

// Pencil line geometric variation settings (applied during edge detection)
#define PENCIL_LINE_VARIATION 1 // Enable pencil line variation [0 1]
#define LINE_THICKNESS_VARIATION 0.8 // Thickness variation amount [0.1 0.3 0.5 0.8]
#define LINE_DIRECTION_VARIATION 0.4 // Direction variation amount [0.05 0.15 0.25 0.4]
#define LINE_GRAIN_STRENGTH 0.6 // Line grain/transparency [0.2 0.4 0.6 0.8]
#define PAPER_LINE_MASKING 0.4 // How much paper texture affects lines [0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]

// Hatching settings
#define HATCHING_ENABLED STYLE_HATCHING_ENABLED // Enable hatching in dark areas [0 1]
#define SCALE_HATCHING_WITH_DARKNESS 1 // Scale thickness and strength with darkness [0 1]
#define HATCHING_DARKNESS_THRESHOLD 0.5 // Darkness level where hatching starts [0.2 0.3 0.4 0.5 0.6 0.7 0.8]
#define HATCHING_DENSITY 8.0 // How densely packed the hatching lines are [4.0 6.0 8.0 12.0 16.0 24.0 32.0]
#define HATCHING_ANGLE1 45.0 // Primary hatching angle in degrees [0.0 45.0 90.0]
#define HATCHING_ANGLE2 135.0 // Secondary hatching angle (for cross-hatching) [90.0 135.0 180.0]
#define HATCHING_THICKNESS 0.6 // Base thickness of hatching lines [0.3 0.4 0.5 0.6 0.75 0.9 1.0 1.2 1.5]
#define HATCHING_STRENGTH STYLE_HATCHING_STRENGTH // Overall intensity of hatching [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5]
#define CROSS_HATCHING_THRESHOLD 0.25 // Darkness level for cross-hatching [0.1 0.15 0.2 0.25 0.3 0.35 0.4]
#define HATCHING_IRREGULARITY 0.2 // How irregular/organic the hatching is [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]
#define HATCHING_THICKNESS_VARIATION 1.0 // Thickness variation per line [0.0 0.2 0.4 0.6 0.8 1.0 1.2]
#define HATCHING_SPACING_VARIATION 0.6 // Spacing irregularity between lines [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]
#define HATCHING_PRESSURE_VARIATION 0.5 // Simulate pencil pressure variation [0.0 0.25 0.5 0.75 1.0 1.25 1.5]
#define HATCHING_LINE_BREAKS 0.3 // Random breaks in hatching lines [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8]
#define MAX_HATCHING_DIRECTIONS 4 // Maximum number of hatching directions [2 3 4 5 6]
#define HATCHING_DIRECTION_SPACING 0.1 // Darkness interval for each new direction [0.01 0.025 0.05 0.075 0.1 0.125 0.15 0.175 0.2 0.225 0.25]
#define GENERATE_SURFACE_HATCHING 0 // Generate surface hatching, experimental [0 1]
#define HATCHING_LINE_SKIP_ENABLED 1 // Enable random line skipping [0 1]
#define HATCHING_LINE_SKIP_BASE_CHANCE 0.05 // Base chance to skip a line [0.05 0.1 0.15 0.2 0.25 0.3]
#define HATCHING_LINE_SKIP_INCREMENT 0.1 // Additional skip chance per direction [0.05 0.1 0.15 0.2 0.25 0.3]
#define HATCHING_LINE_SKIP_MAX_CHANCE 0.6 // Maximum skip chance [0.4 0.5 0.6 0.7 0.8]

#define DRAW_ON_ENABLED 1 // Enable draw-on effect [0 1]
#define DRAW_ON_SPEED 0.2 // Speed of line drawing effect [0.1 0.2 0.3 0.5 0.8]
#define DRAW_ON_SOFTNESS 0.03 // How soft the drawing edge is [0.05 0.1 0.2 0.3]
#define DRAW_ON_START_TIME 1.0 // When the drawing effect begins [0.0 1.0 2.0 3.0 4.0 5.0]
#define DRAW_ON_TOTAL_DURATION 4.0 // Total time for complete drawing [4.0 6.0 8.0 10.0 12.0 15.0]
#define OUTLINE_EARLY_START 0.5 // How much earlier all outlines start (seconds) [0.5 1.0 1.5 2.0]
#define GEOMETRIC_EARLY_START 1.5 // How much earlier geometric outlines start (seconds)
#define HATCHING_LAYER_DELAY 0.5 // Delay between hatching layers (seconds) [0.3 0.5 0.8 1.0 1.2]
#define OUTLINE_FADE_DURATION 2.0 // How long outlines take to fully appear [1.0 1.5 2.0 2.5 3.0]
#define HATCHING_FADE_DURATION 1.5 // How long each hatching layer takes [0.8 1.0 1.5 2.0 2.5]

// Add vignette settings (matching final.fsh)
#define WHITE_VIGNETTE_ENABLED 0 // Enable white vignette effect [0 1]
#define VIGNETTE_STRENGTH 1.0 // Overall vignette intensity [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0]
#define VIGNETTE_RADIUS 1.0 // Vignette falloff radius [0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2]
#define VIGNETTE_SOFTNESS 0.6 // Edge softness [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8]

// Vignette effect intensity for different elements
#define OUTLINE_VIGNETTE_INTENSITY 1.0 // How much vignette affects outlines [0.0 0.2 0.4 0.6 0.8 1.0]
#define HATCHING_VIGNETTE_INTENSITY 1.0 // How much vignette affects hatching [0.0 0.2 0.4 0.6 0.8 1.0]

// Shadow defines
#define DRAW_SHADOW_MAP gcolor //Configures which buffer to draw to the screen [gcolor shadowcolor0 shadowtex0 shadowtex1]

uniform sampler2D noisetex;
uniform sampler2D colortex0; // albedo
uniform sampler2D colortex3; // normals, entity mask
uniform sampler2D colortex4; // unshadowed color
uniform sampler2D depthtex0;
uniform sampler2D gcolor;
uniform sampler2D shadowcolor0;
uniform sampler2D shadowtex0;
uniform sampler2D shadowtex1;
uniform float viewWidth;
uniform float viewHeight;
uniform float near;
uniform float far;
uniform float frameTimeCounter;
uniform mat4 gbufferModelViewInverse;
uniform mat4 gbufferProjectionInverse;
uniform vec3 cameraPosition;
uniform vec3 previousCameraPosition;
uniform ivec2 eyeBrightnessSmooth;

/* RENDERTARGETS:0,1 */

varying vec2 texcoord;

const float edge_kernel[9] = float[](-1.0, -1.0, -1.0, -1.0, 8.0, -1.0, -1.0, -1.0, -1.0);

float calculateCameraSpeed() {
    vec3 cameraDelta = cameraPosition - previousCameraPosition;
    vec3 cameraForward = -normalize(gbufferModelViewInverse[2].xyz);
    float forwardSpeed = dot(cameraDelta, cameraForward);
    return max(forwardSpeed, 0.0); // Only count forward motion
}

float getResolutionScale() {
    // Use 1440p (2560x1440) as the reference resolution
    float referenceHeight = 1440.0;
    float currentHeight = viewHeight;
    
    // Scale based on height (more consistent for different aspect ratios)
    return currentHeight / referenceHeight;
}

float getOutlineScale() {
    // Use the same resolution scaling as hatching for consistency
    return getResolutionScale();
}

// Helper function to scale outline sampling coordinates
vec2 getScaledOutlinePixelSize(vec2 basePixelSize, float thicknessMultiplier) {
    float outlineScale = getOutlineScale();
    
    // Scale the pixel size to maintain consistent visual thickness across resolutions
    // At lower resolutions, we need larger pixel steps
    // At higher resolutions, we need smaller pixel steps
    return basePixelSize * thicknessMultiplier * outlineScale;
}

// Helper function to scale noise frequencies for consistent appearance across resolutions
float getNoiseScale() {
    // Use the same resolution scaling as hatching
    return getResolutionScale();
}

// Apply scaled noise sampling - use this instead of direct screenPos multiplication
vec2 getScaledNoiseCoord(vec2 screenPos, float baseScale) {
    float noiseScale = getNoiseScale();
    // Scale the noise coordinate to maintain consistent visual frequency
    return screenPos * (baseScale / noiseScale);
}

// Simple hash function for noise generation
float hash(vec2 p) {
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

// 2D noise function
float noise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    
    return mix(mix(hash(i), hash(i + vec2(1,0)), f.x),
               mix(hash(i + vec2(0,1)), hash(i + vec2(1,1)), f.x), f.y);
}

// Simplified geometric variation noise (for line shape variations only)
float geometricNoise(vec2 uv, float scale, float timeOffset) {
    float timeStep = floor(frameTimeCounter * 2.0); // Match paper texture speed
    float timeSeed = hash(vec2(timeStep, timeOffset)) * 1000.0;
    
    float n = 0.0;
    n += noise(uv * scale + timeSeed) * 0.5;
    n += noise(uv * scale * 2.0 + timeSeed * 1.3) * 0.25;
    n += noise(uv * scale * 4.0 + timeSeed * 1.7) * 0.125;
    return n;
}

// Simplified paper masking (just for line breaking, not full paper texture)
float paperLineMask(vec2 uv) {
    float timeStep = floor(frameTimeCounter * 2.0);
    float seed = hash(vec2(timeStep, 5.0)) * 1000.0;
    
    // Simple crinkle pattern for line breaking
    float crinkles = abs(noise(uv * 8.0 + seed) * 2.0 - 1.0);
    return smoothstep(-0.3, 0.3, crinkles * PAPER_LINE_MASKING);
}


// depth linearization
float linearizeDepth(float dist) {
    return (2.0 * near) / (far + near - dist * (far - near));
}

// Convert degrees to radians
float degToRad(float degrees) {
    return degrees * 3.14159265 / 180.0;
}

// Calculate normalized time progress for different elements
float calculateDrawProgress(float currentTime, float startTime, float duration) {
    if (currentTime < startTime) return 0.0;
    if (currentTime >= startTime + duration) return 1.0;
    return (currentTime - startTime) / duration;
}

// Enhanced distance mask with staggered timing
float calculateStaggeredDistanceDrawOnMask(vec2 texcoord, float currentTime, int elementType) {
    #if DRAW_ON_ENABLED == 0
    return 1.0;
    #endif
    
    float depth = texture2D(depthtex0, texcoord).r;
    
    // Sky gets special late timing
	if (depth >= 0.99999) {  // Much stricter sky detection - only true sky pixels
		float skyStartTime = DRAW_ON_START_TIME + DRAW_ON_TOTAL_DURATION;
		float skyDuration = DRAW_ON_TOTAL_DURATION * 0.3;
		return calculateDrawProgress(currentTime, skyStartTime, skyDuration);
	}
    
    float linearDepth = linearizeDepth(depth);
    
    // Calculate base timing based on element type
    float elementStartTime = DRAW_ON_START_TIME;
    float elementDuration = DRAW_ON_TOTAL_DURATION;
    
    // Type 0: Outlines (start early)
    // Type 1: Hatching layer 1
    // Type 2: Hatching layer 2, etc.
    if (elementType == 0) {
        // Outlines start earlier
        elementStartTime -= OUTLINE_EARLY_START;
        elementDuration = OUTLINE_FADE_DURATION;
    } else if (elementType > 0) {
        // Hatching layers start progressively later
        elementStartTime += (float(elementType - 1) * HATCHING_LAYER_DELAY);
        elementDuration = HATCHING_FADE_DURATION;
    }
    
    // Depth-based wave timing - closer objects drawn first
    float depthWaveTime = elementStartTime + (linearDepth * elementDuration * 0.7);
    
    // Create smooth fade-in from the wave
    float progress = calculateDrawProgress(currentTime, depthWaveTime - DRAW_ON_SOFTNESS, DRAW_ON_SOFTNESS * 2.0);
    
    return smoothstep(0.0, 1.0, progress);
}

// Sky-specific timing
float calculateSkyStaggeredMask(float currentTime, int elementType) {
    #if DRAW_ON_ENABLED == 0
    return 1.0;
    #endif
    
    // Make sky start much earlier - maybe 20% into the total duration instead of at the end
    float skyStartTime = DRAW_ON_START_TIME + (DRAW_ON_TOTAL_DURATION * 0.05);
    
    if (elementType == 0) {
        // Sky outlines
        skyStartTime -= OUTLINE_EARLY_START * 0.5; // Less early start for sky
        return calculateDrawProgress(currentTime, skyStartTime, OUTLINE_FADE_DURATION);
    } else if (elementType > 0) {
        // Sky hatching layers
        skyStartTime += (float(elementType - 1) * HATCHING_LAYER_DELAY);
        return calculateDrawProgress(currentTime, skyStartTime, HATCHING_FADE_DURATION);
    }
    
    return 1.0;
}

// Function to find the angle that maximizes minimum distance from existing angles
float findOptimalHatchAngle(float existingAngles[6], int numExisting) {
    float bestAngle = 0.0;
    float maxMinDistance = -1.0;
    
    // Test angles from 0 to 180 degrees (since hatching lines are symmetric)
    for (int testDegrees = 0; testDegrees < 180; testDegrees += 5) {
        float testAngle = float(testDegrees);
        float minDistance = 180.0; // Maximum possible distance
        
        // Find minimum distance to all existing angles
        for (int i = 0; i < numExisting; i++) {
            float distance = abs(testAngle - existingAngles[i]);
            // Handle wrap-around (e.g., 175° and 5° are only 30° apart)
            if (distance > 90.0) {
                distance = 180.0 - distance;
            }
            minDistance = min(minDistance, distance);
        }
        
        // Keep track of the angle with the largest minimum distance
        if (minDistance > maxMinDistance) {
            maxMinDistance = minDistance;
            bestAngle = testAngle;
        }
    }
    
    return bestAngle;
}

// Function to reconstruct world position from depth
vec3 getWorldPosition(vec2 texcoord, float depth) {
    // Convert screen coordinates to NDC
    vec4 ndcPos = vec4(texcoord * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
    
    // Transform to view space
    vec4 viewPos = gbufferProjectionInverse * ndcPos;
    viewPos /= viewPos.w;
    
    // Transform to world space
    vec4 worldPos = gbufferModelViewInverse * viewPos;
    
    return worldPos.xyz + cameraPosition;
}

// Surface-aligned hatching using proper tangent space
float generateSurfaceAlignedHatching(vec2 texcoord, float darkness) {
    #if HATCHING_ENABLED == 0
    return 0.0;
    #endif
    
    if (darkness > HATCHING_DARKNESS_THRESHOLD) {
        return 0.0;
    }
    
    vec2 screenPos = texcoord * vec2(viewWidth, viewHeight);
    
    // Calculate how much darker this area is
    float hatchingIntensity = (HATCHING_DARKNESS_THRESHOLD - darkness) / HATCHING_DARKNESS_THRESHOLD;
    hatchingIntensity = clamp(hatchingIntensity, 0.0, 1.0);
    
    // Determine number of hatching directions based on darkness
    int numDirections = 1;
    if (darkness < CROSS_HATCHING_THRESHOLD) {
        numDirections = 2;
        float additionalDarkness = CROSS_HATCHING_THRESHOLD - darkness;
        int extraDirections = int(additionalDarkness / HATCHING_DIRECTION_SPACING);
        numDirections = min(numDirections + extraDirections, MAX_HATCHING_DIRECTIONS);
    }
    
	float depth = texture2D(depthtex0, texcoord).r;
	vec3 worldPos = getWorldPosition(texcoord, depth);
	vec3 surfaceNormal = normalize(texture2D(colortex3, texcoord).rgb * 2.0 - 1.0);
    
    // Create a tangent space on the surface for hatching
    // Find two vectors perpendicular to the normal (tangent and bitangent)
    vec3 worldUp = vec3(0.0, 1.0, 0.0);
    vec3 worldRight = vec3(1.0, 0.0, 0.0);
    
    // Choose the more perpendicular world axis as starting tangent
    vec3 tangent = abs(dot(surfaceNormal, worldUp)) < abs(dot(surfaceNormal, worldRight)) 
                   ? worldUp : worldRight;
    
    // Gram-Schmidt orthogonalization to get proper tangent
    tangent = normalize(tangent - dot(tangent, surfaceNormal) * surfaceNormal);
    vec3 bitangent = normalize(cross(surfaceNormal, tangent));
    
    // Set up base angles
    float hatchAngles[6];
    hatchAngles[0] = HATCHING_ANGLE1;
    if (numDirections > 1) {
        hatchAngles[1] = HATCHING_ANGLE2;
    }
    
    // Calculate optimal angles for additional directions
    for (int dir = 2; dir < numDirections; dir++) {
        if (dir < 6) {
            hatchAngles[dir] = findOptimalHatchAngle(hatchAngles, dir);
        }
    }
    
    float finalHatching = 0.0;
    
    // Generate hatching for each direction
    for (int dir = 0; dir < numDirections && dir < 6; dir++) {
        // Calculate intensity for this direction
        float directionIntensity = hatchingIntensity;
        if (dir == 1) {
            directionIntensity = (CROSS_HATCHING_THRESHOLD - darkness) / CROSS_HATCHING_THRESHOLD;
            directionIntensity = clamp(directionIntensity, 0.0, 1.0);
        } else if (dir > 1) {
            float minDarkness = CROSS_HATCHING_THRESHOLD - (float(dir - 1) * HATCHING_DIRECTION_SPACING);
            directionIntensity = (minDarkness - darkness) / HATCHING_DIRECTION_SPACING;
            directionIntensity = clamp(directionIntensity, 0.0, 1.0);
        }
        
        if (directionIntensity <= 0.0) continue;
        
        // Create hatching direction in surface tangent space
        float angleRad = degToRad(hatchAngles[dir]);
        vec3 surfaceHatchDir3D = tangent * cos(angleRad) + bitangent * sin(angleRad);
        
        // Add irregularity in 3D space
        float irregularityNoise = geometricNoise(screenPos * 0.01, 1.0, 100.0 + float(dir) * 20.0);
        float angleVariation = (irregularityNoise * 2.0 - 1.0) * HATCHING_IRREGULARITY/64;
        float variedAngle = angleRad + degToRad(angleVariation);
        surfaceHatchDir3D = tangent * cos(variedAngle) + bitangent * sin(variedAngle);
        
        // Project the 3D hatching direction onto the screen to get 2D hatching direction
        // This is the key part - we're working in world space but projecting to screen space
        vec2 hatchDir = normalize(surfaceHatchDir3D.xy); // Simple projection (could be improved with proper view transform)
        
        // Fallback to screen space if the projection is too small
        if (length(surfaceHatchDir3D.xy) < 0.1) {
            hatchDir = vec2(cos(variedAngle), sin(variedAngle));
        }
        
        // Use world position projected onto hatching direction for more consistent spacing
        float baseCoord = dot(worldPos, surfaceHatchDir3D) * 50.0; // Scale factor for reasonable density
        
        float bandSpacing = HATCHING_DENSITY;
        float bandIndex = floor(baseCoord / bandSpacing);
        
        // Line skipping logic
        #if HATCHING_LINE_SKIP_ENABLED == 1
        float skipChance = HATCHING_LINE_SKIP_BASE_CHANCE + (float(dir) * HATCHING_LINE_SKIP_INCREMENT);
        skipChance = min(skipChance, HATCHING_LINE_SKIP_MAX_CHANCE);
        
        if (skipChance > 0.0) {
            float timeStep = floor(frameTimeCounter * 2.0);
            float timeBasedSeed = hash(vec2(timeStep, float(dir) * 77.7 + 333.0)) * 1000.0;
            float lineSkipNoise = hash(vec2(bandIndex + timeBasedSeed, float(dir) * 100.0 + 1000.0));
            
            if (lineSkipNoise < skipChance) {
                continue;
            }
        }
        #endif
        
        float bandNoise = geometricNoise(vec2(bandIndex, float(dir) * 50.0), 1.0, 555.0);
        float jitter = (bandNoise * 2.0 - 1.0) * HATCHING_SPACING_VARIATION * bandSpacing;
        
        float hatchDistance = abs(baseCoord + jitter) / HATCHING_DENSITY;
        
        // Add noise to make lines more organic
        float lineNoise = geometricNoise(screenPos * 0.05, 2.0, 110.0 + float(dir) * 25.0);
        hatchDistance += (lineNoise * 2.0 - 1.0) * HATCHING_IRREGULARITY * 1.0;
        
        // Apply spacing variation
        float spacingNoise = geometricNoise(screenPos * 0.008, 1.2, 105.0 + float(dir) * 15.0);
        float spacingWarp = (spacingNoise * 2.0 - 1.0) * HATCHING_SPACING_VARIATION * 0.5;
        
        float warpedDistance = hatchDistance + spacingWarp;
        float fractPart = fract(warpedDistance);
        
        // Variable thickness
        float thicknessNoise = geometricNoise(screenPos * 0.012, 2.5, 115.0 + float(dir) * 30.0);
        float thicknessVariation = 1.0 + (thicknessNoise * 2.0 - 1.0) * HATCHING_THICKNESS_VARIATION;
        
        #if SCALE_HATCHING_WITH_DARKNESS == 1
        float darknessScale = 1.0 - darkness;
        darknessScale = smoothstep(0.0, HATCHING_DARKNESS_THRESHOLD, darknessScale);
        float scaledBaseThickness = HATCHING_THICKNESS * darknessScale;
        #else
        float scaledBaseThickness = HATCHING_THICKNESS;
        #endif
        
        float variedThickness = scaledBaseThickness * thicknessVariation;
        
        // Pressure variation
        float pressureNoise = geometricNoise(screenPos * 0.02, 1.8, 120.0 + float(dir) * 35.0);
        float pressureIntensity = 1.0 + (pressureNoise * 2.0 - 1.0) * HATCHING_PRESSURE_VARIATION * 0.5;
        pressureIntensity = clamp(pressureIntensity, 0.2, 1.5);
        
        // Create line pattern
        float hatchLine = smoothstep(variedThickness * 0.5, 0.0, abs(fractPart - 0.5));
        hatchLine *= pressureIntensity;
        
        // Random line breaks
        float breakNoise = geometricNoise(screenPos * 0.03, 3.0, 125.0 + float(dir) * 40.0);
        float lineBreak = smoothstep(HATCHING_LINE_BREAKS - 0.1, HATCHING_LINE_BREAKS + 0.1, breakNoise);
        hatchLine *= lineBreak;
        
        // Paper masking
        vec2 paperUV = screenPos / (512.0 * (viewHeight / 1440.0));
        float paperBreak = paperLineMask(paperUV + vec2(0.1 * float(dir), 0.1 * float(dir)));
        hatchLine *= paperBreak;
        
        // Add to final hatching
        finalHatching = max(finalHatching, hatchLine * directionIntensity);
    }
    
    #if SCALE_HATCHING_WITH_DARKNESS == 1
    float darknessScale = 1.0 - darkness;
    darknessScale = smoothstep(0.0, HATCHING_DARKNESS_THRESHOLD, darknessScale);
    return finalHatching * HATCHING_STRENGTH * darknessScale;
    #else
    return finalHatching * HATCHING_STRENGTH;
    #endif
}

// Generate multi-directional hatching based on darkness
float getScaledHatchingDensity(float resolutionScale) {
    
    // Scale density to maintain consistent visual appearance
    // At lower resolutions, we need tighter spacing (higher density values)
    // At higher resolutions, we can use looser spacing (lower density values)
    return HATCHING_DENSITY * resolutionScale;
}

// Hatching function
float generateStaggeredHatching(vec2 texcoord, float darkness, bool isSkyPixel) {
    #if HATCHING_ENABLED == 0
    return 0.0;
    #endif
	float resolutionScale = getResolutionScale();
    
    if (darkness > HATCHING_DARKNESS_THRESHOLD) {
        return 0.0;
    }
    
    vec2 screenPos = texcoord * vec2(viewWidth, viewHeight);
    
    // Get resolution-scaled density
    float scaledDensity = getScaledHatchingDensity(resolutionScale);

    float hatchingIntensity = (HATCHING_DARKNESS_THRESHOLD - darkness) / HATCHING_DARKNESS_THRESHOLD;
    hatchingIntensity = clamp(hatchingIntensity, 0.0, 1.0);
    
    int numDirections = 1;
    if (darkness < CROSS_HATCHING_THRESHOLD) {
        numDirections = 2;
        float additionalDarkness = CROSS_HATCHING_THRESHOLD - darkness;
        int extraDirections = int(additionalDarkness / HATCHING_DIRECTION_SPACING);
        numDirections = min(numDirections + extraDirections, MAX_HATCHING_DIRECTIONS);
    }
    
    float hatchAngles[6];
    hatchAngles[0] = HATCHING_ANGLE1;
    if (numDirections > 1) {
        hatchAngles[1] = HATCHING_ANGLE2;
    }
    
    for (int dir = 2; dir < numDirections; dir++) {
        if (dir < 6) {
            hatchAngles[dir] = findOptimalHatchAngle(hatchAngles, dir);
        }
    }
    
    float finalHatching = 0.0;
    
    for (int dir = 0; dir < numDirections && dir < 6; dir++) {
        float layerMask;
        if (isSkyPixel) {
            layerMask = calculateSkyStaggeredMask(frameTimeCounter, dir + 1);
        } else {
            layerMask = calculateStaggeredDistanceDrawOnMask(texcoord, frameTimeCounter, dir + 1);
        }
        
        if (layerMask <= 0.0) continue;
        
        float directionIntensity = hatchingIntensity;
        if (dir == 1) {
            directionIntensity = (CROSS_HATCHING_THRESHOLD - darkness) / CROSS_HATCHING_THRESHOLD;
            directionIntensity = clamp(directionIntensity, 0.0, 1.0);
        } else if (dir > 1) {
            float minDarkness = CROSS_HATCHING_THRESHOLD - (float(dir - 1) * HATCHING_DIRECTION_SPACING);
            directionIntensity = (minDarkness - darkness) / HATCHING_DIRECTION_SPACING;
            directionIntensity = clamp(directionIntensity, 0.0, 1.0);
        }
        
        if (directionIntensity <= 0.0) continue;
        
        float angleRad = degToRad(hatchAngles[dir]);
        vec2 hatchDir = vec2(cos(angleRad), sin(angleRad));
        
        float irregularityNoise = geometricNoise(getScaledNoiseCoord(screenPos, 0.01), 1.0, 100.0 + float(dir) * 20.0);
        float angleVariation = (irregularityNoise * 2.0 - 1.0) * HATCHING_IRREGULARITY * 2.0;
        float variedAngle = angleRad + degToRad(angleVariation);
        hatchDir = vec2(cos(variedAngle), sin(variedAngle));
        
        float baseCoord = dot(screenPos, hatchDir);
        float bandSpacing = scaledDensity;
        float bandIndex = floor(baseCoord / bandSpacing);
        
        #if HATCHING_LINE_SKIP_ENABLED == 1
        float skipChance = HATCHING_LINE_SKIP_BASE_CHANCE + (float(dir) * HATCHING_LINE_SKIP_INCREMENT);
        skipChance = min(skipChance, HATCHING_LINE_SKIP_MAX_CHANCE);
        
        if (skipChance > 0.0) {
            float timeStep = floor(frameTimeCounter * 2.0);
            float timeBasedSeed = hash(vec2(timeStep, float(dir) * 77.7 + 333.0)) * 1000.0;
            float lineSkipNoise = hash(vec2(bandIndex + timeBasedSeed, float(dir) * 100.0 + 1000.0));
            
            if (lineSkipNoise < skipChance) {
                continue;
            }
        }
        #endif
        
        float bandNoise = geometricNoise(vec2(bandIndex, float(dir) * 50.0), 1.0, 555.0);
        float jitter = (bandNoise * 2.0 - 1.0) * HATCHING_SPACING_VARIATION * bandSpacing;
        
        float hatchDistance = abs(baseCoord + jitter) / scaledDensity;
        
        float lineNoise = geometricNoise(getScaledNoiseCoord(screenPos, 0.05), 2.0, 110.0 + float(dir) * 25.0);
        hatchDistance += (lineNoise * 2.0 - 1.0) * HATCHING_IRREGULARITY * 1.0;
        
        float spacingNoise = geometricNoise(getScaledNoiseCoord(screenPos, 0.008), 1.2, 105.0 + float(dir) * 15.0);
        float spacingWarp = (spacingNoise * 2.0 - 1.0) * HATCHING_SPACING_VARIATION * 0.5;
        
        float warpedDistance = hatchDistance + spacingWarp;
        float fractPart = fract(warpedDistance);
        
        float thicknessNoise = geometricNoise(getScaledNoiseCoord(screenPos, 0.012), 2.5, 115.0 + float(dir) * 30.0);
        float thicknessVariation = 1.0 + (thicknessNoise * 2.0 - 1.0) * HATCHING_THICKNESS_VARIATION;
        
        #if SCALE_HATCHING_WITH_DARKNESS == 1
        float darknessScale = 1.0 - darkness;
        darknessScale = smoothstep(0.0, HATCHING_DARKNESS_THRESHOLD, darknessScale);
        float scaledBaseThickness = HATCHING_THICKNESS * darknessScale;
        #else
        float scaledBaseThickness = HATCHING_THICKNESS;
        #endif
        
        scaledBaseThickness *= resolutionScale;
        
        float variedThickness = scaledBaseThickness * thicknessVariation;
        
        float pressureNoise = geometricNoise(getScaledNoiseCoord(screenPos, 0.02), 1.8, 120.0 + float(dir) * 35.0);
        float pressureIntensity = 1.0 + (pressureNoise * 2.0 - 1.0) * HATCHING_PRESSURE_VARIATION * 0.5;
        pressureIntensity = clamp(pressureIntensity, 0.2, 1.5);
        
        float hatchLine = smoothstep(variedThickness * 0.5, 0.0, abs(fractPart - 0.5));
        hatchLine *= pressureIntensity;
        
        float breakNoise = geometricNoise(getScaledNoiseCoord(screenPos, 0.03), 3.0, 125.0 + float(dir) * 40.0);
        float lineBreak = smoothstep(HATCHING_LINE_BREAKS - 0.1, HATCHING_LINE_BREAKS + 0.1, breakNoise);
        hatchLine *= lineBreak;
        
        // Paper masking (this uses a different coordinate system so may not need scaling)
        vec2 paperUV = screenPos / (512.0 * (viewHeight / 1440.0));
        float paperBreak = paperLineMask(paperUV + vec2(0.1 * float(dir), 0.1 * float(dir)));
        hatchLine *= paperBreak;
        
        hatchLine *= layerMask;
        finalHatching = max(finalHatching, hatchLine * directionIntensity);
    }
    
    #if SCALE_HATCHING_WITH_DARKNESS == 1
    float darknessScale = 1.0 - darkness;
    darknessScale = smoothstep(0.0, HATCHING_DARKNESS_THRESHOLD, darknessScale);
    return finalHatching * HATCHING_STRENGTH * darknessScale;
    #else
    return finalHatching * HATCHING_STRENGTH;
    #endif
}

vec2 calculateEdgeDirection(vec2 texcoord, vec2 pixelSize) {
    // First try the original depth-based method
    float tl = texture2D(depthtex0, texcoord + pixelSize * vec2(-1, -1)).r;
    float tm = texture2D(depthtex0, texcoord + pixelSize * vec2( 0, -1)).r;
    float tr = texture2D(depthtex0, texcoord + pixelSize * vec2( 1, -1)).r;
    float ml = texture2D(depthtex0, texcoord + pixelSize * vec2(-1,  0)).r;
    float mr = texture2D(depthtex0, texcoord + pixelSize * vec2( 1,  0)).r;
    float bl = texture2D(depthtex0, texcoord + pixelSize * vec2(-1,  1)).r;
    float bm = texture2D(depthtex0, texcoord + pixelSize * vec2( 0,  1)).r;
    float br = texture2D(depthtex0, texcoord + pixelSize * vec2( 1,  1)).r;
    
    // Count sky pixels - if too many, use alternative method
    int skyCount = 0;
    if (tl > 0.999) skyCount++;
    if (tm > 0.999) skyCount++;
    if (tr > 0.999) skyCount++;
    if (ml > 0.999) skyCount++;
    if (mr > 0.999) skyCount++;
    if (bl > 0.999) skyCount++;
    if (bm > 0.999) skyCount++;
    if (br > 0.999) skyCount++;
    
    vec2 edgeDir = vec2(0.0);
    
    // If we have a reasonable number of non-sky pixels, use depth method
    if (skyCount <= 6) {
        // Linearize depths (replace sky depths with center depth to avoid artifacts)
        float centerDepth = texture2D(depthtex0, texcoord).r;
        if (tl > 0.999) tl = centerDepth;
        if (tm > 0.999) tm = centerDepth;
        if (tr > 0.999) tr = centerDepth;
        if (ml > 0.999) ml = centerDepth;
        if (mr > 0.999) mr = centerDepth;
        if (bl > 0.999) bl = centerDepth;
        if (bm > 0.999) bm = centerDepth;
        if (br > 0.999) br = centerDepth;
        
        float ltl = linearizeDepth(tl), ltm = linearizeDepth(tm), ltr = linearizeDepth(tr);
        float lml = linearizeDepth(ml), lmr = linearizeDepth(mr);
        float lbl = linearizeDepth(bl), lbm = linearizeDepth(bm), lbr = linearizeDepth(br);
        
        // Sobel operators for edge direction
        edgeDir = vec2(
            (ltr + 2.0 * lmr + lbr) - (ltl + 2.0 * lml + lbl),
            (lbl + 2.0 * lbm + lbr) - (ltl + 2.0 * ltm + ltr)
        );
    }
    
    // If depth method failed or we have too many sky pixels, try color-based method
    if (length(edgeDir) < 0.01 || skyCount > 6) {
        // Use color gradients instead
        vec3 ctL = texture2D(colortex0, texcoord + pixelSize * vec2(-1, 0)).rgb;
        vec3 ctR = texture2D(colortex0, texcoord + pixelSize * vec2( 1, 0)).rgb;
        vec3 ctU = texture2D(colortex0, texcoord + pixelSize * vec2( 0,-1)).rgb;
        vec3 ctD = texture2D(colortex0, texcoord + pixelSize * vec2( 0, 1)).rgb;
        
        // Convert to luminance
        float lumL = dot(ctL, vec3(0.299, 0.587, 0.114));
        float lumR = dot(ctR, vec3(0.299, 0.587, 0.114));
        float lumU = dot(ctU, vec3(0.299, 0.587, 0.114));
        float lumD = dot(ctD, vec3(0.299, 0.587, 0.114));
        
        edgeDir = vec2(lumR - lumL, lumD - lumU);
    }
    
    // If color method also failed, try normal-based method
    if (length(edgeDir) < 0.01) {
        vec3 ntl = texture2D(colortex3, texcoord + pixelSize * vec2(-1, -1)).rgb;
        vec3 ntr = texture2D(colortex3, texcoord + pixelSize * vec2( 1, -1)).rgb;
        vec3 nbl = texture2D(colortex3, texcoord + pixelSize * vec2(-1,  1)).rgb;
        vec3 nbr = texture2D(colortex3, texcoord + pixelSize * vec2( 1,  1)).rgb;
        
        edgeDir = vec2(
            dot(ntr + nbr - ntl - nbl, vec3(1.0)),
            dot(nbl + nbr - ntl - ntr, vec3(1.0))
        );
    }
    
    // Final fallback - use a default horizontal direction
    if (length(edgeDir) < 0.001) {
        // Use screen position to create a consistent but varied direction
        float angle = fract(dot(texcoord, vec2(127.1, 311.7))) * 3.14159265;
        edgeDir = vec2(cos(angle), sin(angle));
    }
    
    return normalize(edgeDir);
}

// Fixed function to handle sky and far objects properly
float calculateDistanceDrawOnMask(vec2 texcoord, float currentTime) {
    #if DRAW_ON_ENABLED == 0
    return 1.0;
    #endif
    
    float depth = texture2D(depthtex0, texcoord).r;
    
    // Sky pixels should be drawn LAST, not first
    if (depth >= 0.999) {
        // Treat sky as having maximum depth in the sequence
        // Map it to the very end of the drawing progression
        float skyDepth = 0.3; // Beyond normal linearized depth range
        float skyWave = currentTime * DRAW_ON_SPEED;
        // Use same logic as other objects: draw when wave reaches the sky depth
        return smoothstep(skyDepth - DRAW_ON_SOFTNESS, skyDepth, skyWave);
    }
    
    float linearDepth = linearizeDepth(depth);
    
    // Create multiple drawing waves for more organic effect
    float primaryWave = currentTime * DRAW_ON_SPEED;
    float secondaryWave = (currentTime - 0.5) * DRAW_ON_SPEED * 0.8;
    float tertiaryWave = (currentTime - 1.0) * DRAW_ON_SPEED * 0.6;
    
    // Each wave affects different depth ranges
    float primaryMask = 1.0 - smoothstep(primaryWave - DRAW_ON_SOFTNESS, primaryWave, linearDepth);
    float secondaryMask = 1.0 - smoothstep(secondaryWave - DRAW_ON_SOFTNESS * 0.7, secondaryWave, linearDepth * 0.8);
    float tertiaryMask = 1.0 - smoothstep(tertiaryWave - DRAW_ON_SOFTNESS * 0.5, tertiaryWave, linearDepth * 0.6);
    
    // Combine waves for more complex drawing pattern
    return clamp(primaryMask + secondaryMask * 0.3 + tertiaryMask * 0.2, 0.0, 1.0);
}

// Calculate brightness-reactive intensity for texture outlines
float calculateTextureOutlineIntensity(float contrast, float threshold, float brightness) {
    // Base intensity from contrast strength
    float baseIntensity = smoothstep(threshold * 0.4, threshold * 2.0, contrast);
    
    // Only boost areas that are actually dark (brightness < 0.6)
    float darkAreaMask = smoothstep(0.6, 0.3, brightness); // 1.0 for dark, 0.0 for bright
    
    // Apply exponential boost only to dark areas
    float darkBoost = 1.0 + (pow(darkAreaMask, 0.5) * 1.5); // Up to 150% boost for very dark areas
    
    // Original brightness scaling for consistency
    float brightnessInfluence = 1.0 - brightness;
    float brightnessScale = mix(1.0, brightnessInfluence, TEXTURE_BRIGHTNESS_SENSITIVITY);
    brightnessScale = clamp(brightnessScale, TEXTURE_MIN_INTENSITY, TEXTURE_MAX_INTENSITY);
    
    return baseIntensity * brightnessScale * darkBoost;
}

// Calculate white vignette factor (same as in final.fsh)
float calculateWhiteVignette(vec2 texcoord) {
    #if WHITE_VIGNETTE_ENABLED == 0
    return 0.0;
    #endif
    
    // Convert to centered coordinates (-1 to 1)
    vec2 centered = (texcoord - 0.5) * 2.0;
    
    // Calculate distance from center
    float distance = length(centered);
    
    // Create vignette falloff
    float vignette = smoothstep((VIGNETTE_RADIUS-0.2) - VIGNETTE_SOFTNESS, (VIGNETTE_RADIUS-0.2) + VIGNETTE_SOFTNESS, distance);
    
    return vignette * VIGNETTE_STRENGTH;
}


void main() {
    // Calculate speed once for the entire screen
    #if SPEED_LINES_ENABLED == 1
		float cameraSpeed = calculateCameraSpeed();
	#else
		float cameraSpeed = 0.0;
	#endif
	
	
    vec2 pixelSize = vec2(1.0 / viewWidth, 1.0 / viewHeight);
    
    // Sample the original color
    vec3 originalColor = texture2D(colortex0, texcoord).rgb;
    
    // Calculate brightness for hatching
    float brightness = dot(originalColor, vec3(0.299, 0.587, 0.114));
    
    // Calculate vignette effect
    float vignetteAmount = calculateWhiteVignette(texcoord);
	
    // Initialize outline value
    float finalOutline = 0.0;

    // Skip sky pixels for outline detection
    float centerDepth = texture2D(depthtex0, texcoord).r;
    bool isSky = centerDepth >= 1.0;
	
	
	float resolutionScale = getOutlineScale();
	
	// Calculate distance-based draw-on mask
    float distanceDrawMask = calculateDistanceDrawOnMask(texcoord, frameTimeCounter);
    if(isSky) {
        // For sky pixels, we still want hatching and some special processing
        vec3 skyColor = texture2D(colortex0, texcoord).rgb;
        float skyBrightness = dot(skyColor, vec3(0.299, 0.587, 0.114));
        
        // Generate hatching for dark sky areas (night sky, storm clouds)
		float skyHatching = generateStaggeredHatching(texcoord, skyBrightness, true);		
        
        // Try to detect cloud/sun/moon edges using color contrast
        float skyOutline = 0.0;
        
        // Define screenUV once at the beginning if PENCIL_LINE_VARIATION is enabled
        #if PENCIL_LINE_VARIATION == 1
			vec2 screenUV = texcoord * vec2(viewWidth, viewHeight);
        #endif
		
        
		#if ENABLE_SKY_TEXTURE_OUTLINES == 1
			// Use resolution-scaled pixel size for sky texture outlines
			vec2 skyPixelSize = getScaledOutlinePixelSize(pixelSize, pow(TEXTURE_THICKNESS * COMPOSITE_THICKNESS_BOOST, 1.3));
			
			#if PENCIL_LINE_VARIATION == 1
			// Apply the SAME thickness variation as regular outlines with scaled noise
			float thicknessNoise = (geometricNoise(getScaledNoiseCoord(screenUV, 0.025), 1.2, 40.0) * 2.0 - 1.0) * LINE_THICKNESS_VARIATION;
			skyPixelSize *= (1.0 + thicknessNoise * 0.5);
			#endif
			
			vec3 colorEdge = vec3(0.0);
			for(int y = 0; y < 3; y++) {
				for(int x = 0; x < 3; x++) {
					vec2 offset = skyPixelSize * vec2(float(x) - 1.0, float(y) - 1.0);
					
					#if PENCIL_LINE_VARIATION == 1
					// Apply scaled directional variation
					float directionNoise = geometricNoise(getScaledNoiseCoord(screenUV, 0.015) + vec2(float(x), float(y)), 0.8, 50.0);
					offset += offset * (directionNoise * 2.0 - 1.0) * LINE_DIRECTION_VARIATION * 0.5;
					#endif
					
					vec3 sampleColor = texture2D(colortex0, texcoord + offset).rgb;
					colorEdge += sampleColor * edge_kernel[y * 3 + x];
				}
			}
			
			float colorGrey = dot(abs(colorEdge), vec3(0.21, 0.72, 0.07));
			
			skyOutline = colorGrey > (LINE_THRESHOLD_CONTRAST * 0.5) ? 1.0 : 0.0;
		#endif

        
        // Add geometric sky outlines
		// Check for depth edges at sky boundaries (where sky meets terrain/objects)
		vec2 geometricSkyPixelSize = getScaledOutlinePixelSize(pixelSize, pow(GEOMETRIC_THICKNESS * COMPOSITE_THICKNESS_BOOST, 1.3));
		
		#if PENCIL_LINE_VARIATION == 1
			float thicknessNoise2 = (geometricNoise(getScaledNoiseCoord(screenUV, 0.02), 1.5, 10.0) * 2.0 - 1.0) * LINE_THICKNESS_VARIATION;
			geometricSkyPixelSize *= (1.0 + thicknessNoise2);
		#endif
		
		float skyDepthEdge = 0.0;
		for(int y = 0; y < 3; y++) {
			for(int x = 0; x < 3; x++) {
				vec2 offset = geometricSkyPixelSize * vec2(float(x) - 1.0, float(y) - 1.0);
				
				#if PENCIL_LINE_VARIATION == 1
				float directionNoise2 = geometricNoise(getScaledNoiseCoord(screenUV, 0.01) + vec2(float(x), float(y)), 1.0, 20.0);
				offset += offset * (directionNoise2 * 2.0 - 1.0) * LINE_DIRECTION_VARIATION;
				#endif
				
				float sampleDepth = texture2D(depthtex0, texcoord + offset).r;
				// Look for non-sky pixels (create outline where sky meets objects)
				if (sampleDepth < 0.999) {
					skyDepthEdge += (1.0 - sampleDepth) * abs(edge_kernel[y * 3 + x]) * 0.1;
				}
			}
		}
		
		float skyGeometricOutline = skyDepthEdge > LINE_THRESHOLD_DEPTH ? 1.0 : 0.0;
		skyOutline = max(skyOutline, skyGeometricOutline);  // Combine first
		float skyOutlineMask = calculateSkyStaggeredMask(frameTimeCounter, 0);
		skyOutline *= skyOutlineMask;
        
        // Apply the SAME variation effects as regular outlines
        #if PENCIL_LINE_VARIATION == 1
        if (skyOutline > 0.0) {
            // Line grain for organic breaks
            float lineGrain = geometricNoise(getScaledNoiseCoord(screenUV, 0.1), 2.5, 60.0);
            float grainMask = smoothstep(0.2, 0.8, lineGrain);
            
            // Paper texture masking for line breaks
            vec2 paperUV = screenUV / (512.0 * (viewHeight / 1440.0));
            float paperBreak = paperLineMask(paperUV);
            
            // Combine grain and paper effects
            float combinedMask = grainMask * paperBreak;
            float finalMask = mix(0.3, 1.0, mix(combinedMask, grainMask, 0.7));
            finalMask = mix(1.0, finalMask, LINE_GRAIN_STRENGTH);
            
            // Apply full masking to sky outlines
            skyOutline *= finalMask;
            
            // Final thickness variation
            float thicknessVar = geometricNoise(getScaledNoiseCoord(screenUV, 0.03), 1.0, 70.0);
            skyOutline *= (0.7 + thicknessVar * 0.6);
            
            // Boost final strength
            skyOutline = min(skyOutline * 1.3, 1.0);
			
			// Apply vignette
			skyOutline *= (1.0 - vignetteAmount * OUTLINE_VIGNETTE_INTENSITY);
        }
        
        // Apply lighter masking to sky hatching
        if (skyHatching > 0.0) {
            vec2 paperUV = screenUV / (512.0 * (viewHeight / 1440.0));
            float paperBreak = paperLineMask(paperUV);
            float lightMask = mix(0.7, 1.0, paperBreak);
			
            skyHatching *= lightMask;
			
			// Apply vignette
			skyHatching *= (1.0 - vignetteAmount * HATCHING_VIGNETTE_INTENSITY);
        }
        #endif
        
        // Calculate edge direction for sky elements
        vec2 edgeDirection = vec2(0.0);
        if (skyOutline > 0.0 || skyHatching > 0.0) {
            if (skyOutline > 0.0) {
                // For sky outlines, use the SAME direction calculation as regular outlines
                edgeDirection = calculateEdgeDirection(texcoord, pixelSize);
                // If that fails (common for sky), fall back to color gradient method
                if (length(edgeDirection) < 0.001) {
                    vec2 pixelSize = vec2(1.0 / viewWidth, 1.0 / viewHeight);
                    vec3 left = texture2D(colortex0, texcoord + vec2(-pixelSize.x, 0.0)).rgb;
                    vec3 right = texture2D(colortex0, texcoord + vec2(pixelSize.x, 0.0)).rgb;
                    vec3 up = texture2D(colortex0, texcoord + vec2(0.0, -pixelSize.y)).rgb;
                    vec3 down = texture2D(colortex0, texcoord + vec2(0.0, pixelSize.y)).rgb;
                    
                    vec2 gradient = vec2(
                        dot(right - left, vec3(1.0)),
                        dot(down - up, vec3(1.0))
                    );
                    
                    if (length(gradient) > 0.001) {
                        edgeDirection = normalize(gradient);
                    }
                }
                // Encode direction from -1,1 range to 0,1 range for storage
                edgeDirection = edgeDirection * 0.5 + 0.5;
            } else if (skyHatching > 0.0) {
                // Use hatching direction for hatching-only areas
                float angle1Rad = degToRad(HATCHING_ANGLE1);
                edgeDirection = vec2(cos(angle1Rad), sin(angle1Rad)) * 0.5 + 0.5;
            }
        }
        
        // Output sky with outlines and hatching
        gl_FragData[0] = vec4(skyColor, skyOutline);
        gl_FragData[1] = vec4(edgeDirection, skyHatching, cameraSpeed);
        return;
    }
    
    // Distance calculations
    float linearDepth = linearizeDepth(centerDepth);
    
	#if GEOMETRIC_OUTLINES == 1
		// Geometric edge detection with resolution scaling
		vec2 geometricPixelSize = getScaledOutlinePixelSize(pixelSize, pow(GEOMETRIC_THICKNESS * COMPOSITE_THICKNESS_BOOST, 1.3));
		
		#if PENCIL_LINE_VARIATION == 1
		// Add thickness variation to sampling size
		vec2 screenUV = texcoord * vec2(viewWidth, viewHeight);
		float thicknessNoise = (geometricNoise(getScaledNoiseCoord(screenUV, 0.02), 1.5, 10.0) * 2.0 - 1.0) * LINE_THICKNESS_VARIATION;
		geometricPixelSize *= (1.0 + thicknessNoise);
		#endif
		
		// Distance-based adjustments
		float depthScale = 0.8;
		if (linearDepth > 0.8) {
			depthScale = 0.3;
			geometricPixelSize *= 1.5;
		} else if (linearDepth > 0.4) {
			depthScale = 0.5;
			geometricPixelSize *= 1.2;
		}
		
		// Depth edge detection with scaled sampling
		float depthEdge = 0.0;
		for(int y = 0; y < 3; y++) {
			for(int x = 0; x < 3; x++) {
				vec2 offset = geometricPixelSize * vec2(float(x) - 1.0, float(y) - 1.0);
				
				#if PENCIL_LINE_VARIATION == 1
				// Use scaled noise coordinates for directional variation
				float directionNoise = geometricNoise(getScaledNoiseCoord(screenUV, 0.01) + vec2(float(x), float(y)), 1.0, 20.0);
				offset += offset * (directionNoise * 2.0 - 1.0) * LINE_DIRECTION_VARIATION;
				#endif
				
				float rawDepth = texture2D(depthtex0, texcoord + offset).r;
				if (rawDepth < 0.99999) {
					depthEdge += linearizeDepth(rawDepth) * edge_kernel[y * 3 + x];
				}
			}
		}
		depthEdge = abs(depthEdge) * depthScale;
		
		// Normal edge detection with scaled sampling
		vec3 normalEdge = vec3(0.0);
		for(int y = 0; y < 3; y++) {
			for(int x = 0; x < 3; x++) {
				vec2 offset = geometricPixelSize * vec2(float(x) - 1.0, float(y) - 1.0);
				
				#if PENCIL_LINE_VARIATION == 1
				float directionNoise = geometricNoise(getScaledNoiseCoord(screenUV, 0.01) + vec2(float(x), float(y)), 1.0, 30.0);
				offset += offset * (directionNoise * 2.0 - 1.0) * LINE_DIRECTION_VARIATION;
				#endif
				
				vec2 sampleCoord = texcoord + offset;
				if (texture2D(depthtex0, sampleCoord).r < 0.99) {
					normalEdge += texture2D(colortex3, sampleCoord).rgb * edge_kernel[y * 3 + x];
				}
			}
		}
		
		float normalGrey = dot(abs(normalEdge), vec3(1.0));
		
		// Apply resolution scaling to thresholds
		
		float depthThreshold = LINE_THRESHOLD_DEPTH;
		float normalThreshold = LINE_THRESHOLD_NORMAL;
		
		// Distance-adjusted thresholds
		if (linearDepth > 0.8) {
			depthThreshold *= 0.3;
			normalThreshold *= 0.4;
		} else if (linearDepth > 0.4) {
			depthThreshold *= 0.6;
			normalThreshold *= 0.7;
		}
		
		float depthLine = depthEdge > depthThreshold ? 1.0 : 0.0;
		float normalLine = normalGrey > normalThreshold ? 1.0 : 0.0;
		
		float geometricOutline = max(depthLine, normalLine);
		
		// Boost distant geometric outlines
		if (linearDepth > 0.6 && geometricOutline > 0.0) {
			geometricOutline = max(geometricOutline, 0.5);
		}
		#if DRAW_ON_ENABLED == 1
			// Apply distance mask to geometric outlines - use element type -1 to start earlier
			float geometricMask;
			if (isSky) {
				// For sky, use a special early timing for geometric outlines
				float skyGeometricStartTime = DRAW_ON_START_TIME + (DRAW_ON_TOTAL_DURATION * 0.02); // Very early
				geometricMask = calculateDrawProgress(frameTimeCounter, skyGeometricStartTime, OUTLINE_FADE_DURATION);
			} else {
				// For regular geometry, start geometric outlines even earlier than normal outlines
				float geometricStartTime = DRAW_ON_START_TIME - (OUTLINE_EARLY_START * GEOMETRIC_EARLY_START); // 50% earlier than normal outlines
				float geometricDuration = OUTLINE_FADE_DURATION;
				
				// Depth-based wave timing - closer objects drawn first
				float depthWaveTime = geometricStartTime + (linearDepth * geometricDuration * 0.7);
				geometricMask = calculateDrawProgress(frameTimeCounter, depthWaveTime - DRAW_ON_SOFTNESS, DRAW_ON_SOFTNESS * 2.0);
				geometricMask = smoothstep(0.0, 1.0, geometricMask);
			}
			geometricOutline *= geometricMask;
		#endif
		
		// Apply vignette
		if (geometricOutline > 0.0) {
            geometricOutline *= (1.0 - vignetteAmount * OUTLINE_VIGNETTE_INTENSITY);
        }
		finalOutline = max(finalOutline, geometricOutline);
	#endif

	#if TEXTURE_OUTLINES == 1
		// Use unshadowed color for texture outlines
		vec2 texturePixelSize = getScaledOutlinePixelSize(pixelSize, pow(TEXTURE_THICKNESS * COMPOSITE_THICKNESS_BOOST, 1.3));
		
		#if PENCIL_LINE_VARIATION == 1
		vec2 screenUV2 = texcoord * vec2(viewWidth, viewHeight);
		float thicknessNoise2 = (geometricNoise(getScaledNoiseCoord(screenUV2, 0.025), 1.2, 40.0) * 2.0 - 1.0) * LINE_THICKNESS_VARIATION;
		texturePixelSize *= (1.0 + thicknessNoise2 * 0.5);
		#endif
		
		// Distance-based simplification
		float simplifyFactor = smoothstep(0.0, TEXTURE_SIMPLIFY_DISTANCE, linearDepth);
		float farFactor = smoothstep(TEXTURE_SIMPLIFY_DISTANCE, GEOMETRIC_ONLY_DISTANCE, linearDepth);
		float veryFarFactor = smoothstep(GEOMETRIC_ONLY_DISTANCE, 1.0, linearDepth);
		
		float sizeMultiplier = 1.0 - (simplifyFactor * 0.2) - (farFactor * 0.2) - (veryFarFactor * 0.2);
		texturePixelSize *= sizeMultiplier;
		
		vec3 colorEdge = vec3(0.0);
		
		if (linearDepth < GEOMETRIC_ONLY_DISTANCE * 1.5) {
			float fullKernelWeight = 1.0 - simplifyFactor;
			
			if (fullKernelWeight > 0.0) {
				for(int y = 0; y < 3; y++) {
					for(int x = 0; x < 3; x++) {
						vec2 offset = texturePixelSize * vec2(float(x) - 1.0, float(y) - 1.0);
						
						#if PENCIL_LINE_VARIATION == 1
						float directionNoise2 = geometricNoise(getScaledNoiseCoord(screenUV2, 0.015) + vec2(float(x), float(y)), 0.8, 50.0);
						offset += offset * (directionNoise2 * 2.0 - 1.0) * LINE_DIRECTION_VARIATION * 0.5;
						#endif
						
						// Sample from colortex2 (unshadowed) instead of colortex0
						colorEdge += texture2D(colortex4, texcoord + offset).rgb * edge_kernel[y * 3 + x] * fullKernelWeight;
					}
				}
				colorEdge /= 4.5;
			}
		}
		
		float distanceMultiplier = 1.0 + (simplifyFactor * 0.5) + (farFactor * 1.5) + (veryFarFactor * 2.0);
		
		// Apply resolution scaling to texture threshold
		
		float textureThreshold = LINE_THRESHOLD_CONTRAST * distanceMultiplier;
		
		float intensityMultiplier = 1.0 - (simplifyFactor * 0.2) - (farFactor * 0.4) - (veryFarFactor * 0.6);
		intensityMultiplier = max(intensityMultiplier, 0.1);
		
		float colorGrey = dot(abs(colorEdge), vec3(0.21, 0.72, 0.07));
		
		// Sample both lit and unlit versions
		vec3 unlitColor = texture2D(colortex4, texcoord).rgb; // Separated unlit texture
		vec3 litColor = texture2D(colortex0, texcoord).rgb;   // Final lit result
		
		// Calculate contrast on unlit texture (for texture detail preservation)
		vec3 unlitColorEdge = vec3(0.0);
		for(int y = 0; y < 3; y++) {
			for(int x = 0; x < 3; x++) {
				vec2 offset = texturePixelSize * vec2(float(x) - 1.0, float(y) - 1.0);
				vec3 sampleColor = texture2D(colortex4, texcoord + offset).rgb;
				unlitColorEdge += sampleColor * edge_kernel[y * 3 + x];
			}
		}
		
		float unlitContrast = dot(abs(unlitColorEdge), vec3(0.21, 0.72, 0.07));
		float localBrightness = dot(texture2D(colortex0, texcoord).rgb, vec3(0.299, 0.587, 0.114));
		
		float adaptiveThreshold = LINE_THRESHOLD_CONTRAST * mix(LIGHTING_ADAPTIVE_THRESHOLD_DARK, LIGHTING_ADAPTIVE_THRESHOLD_BRIGHT, localBrightness);
		
		// Replace binary check with intensity calculation
		float colorLine = calculateTextureOutlineIntensity(unlitContrast, adaptiveThreshold, localBrightness);
		
		#if DRAW_ON_ENABLED == 1
			// Apply distance mask to texture outlines - use a later timing than geometric outlines
			float textureMask;
			if (isSky) {
				// For sky, texture outlines start later than geometric
				float skyTextureStartTime = DRAW_ON_START_TIME + (DRAW_ON_TOTAL_DURATION * 0.08); // Later than geometric
				textureMask = calculateDrawProgress(frameTimeCounter, skyTextureStartTime, OUTLINE_FADE_DURATION);
			} else {
				// For regular geometry, start texture outlines at the normal outline timing (after geometric)
				float textureStartTime = DRAW_ON_START_TIME - OUTLINE_EARLY_START; // Normal outline timing
				float textureDuration = OUTLINE_FADE_DURATION;
				
				// Depth-based wave timing - closer objects drawn first
				float depthWaveTime = textureStartTime + (linearDepth * textureDuration * 0.7);
				textureMask = calculateDrawProgress(frameTimeCounter, depthWaveTime - DRAW_ON_SOFTNESS, DRAW_ON_SOFTNESS * 2.0);
				textureMask = smoothstep(0.0, 1.0, textureMask);
			}
			colorLine *= textureMask;
		#endif
		
		// Apply vignette
		if (colorLine > 0.0) {
            colorLine *= (1.0 - vignetteAmount * OUTLINE_VIGNETTE_INTENSITY);
        }
		
		finalOutline = max(finalOutline, colorLine);
	#endif
    
    // Add hatching for dark areas
	#if GENERATE_SURFACE_HATCHING == 1
		float depth = texture2D(depthtex0, texcoord).r;
		vec3 worldPos = getWorldPosition(texcoord, depth);
		vec3 surfaceNormal = normalize(texture2D(colortex3, texcoord).rgb * 2.0 - 1.0);
	#endif
    float hatchingMask = distanceDrawMask;
	
	
	#if GENERATE_SURFACE_HATCHING == 1
		float hatchingOutline = generateSurfaceAlignedHatching(texcoord, brightness);
	#else
		float hatchingOutline = generateStaggeredHatching(texcoord, brightness, false);
	#endif 
	
	if (hatchingOutline > 0.0) {
        hatchingOutline *= (1.0 - vignetteAmount * HATCHING_VIGNETTE_INTENSITY);
    }
	
	#if PENCIL_LINE_VARIATION == 1
		if (finalOutline > 0.0) {
			vec2 screenUV3 = texcoord * vec2(viewWidth, viewHeight);
			
			// Line grain for organic breaks
			float lineGrain = geometricNoise(getScaledNoiseCoord(screenUV3, 0.1), 2.5, 60.0);
			float grainMask = smoothstep(0.2, 0.8, lineGrain);
			
			// Paper texture masking for line breaks
			vec2 paperUV = screenUV3 / (512.0 * (viewHeight / 1440.0));
			float paperBreak = paperLineMask(paperUV);
			
			// Combine grain and paper effects
			float combinedMask = grainMask * paperBreak;
			float finalMask = mix(0.3, 1.0, mix(combinedMask, grainMask, 0.7));
			finalMask = mix(1.0, finalMask, LINE_GRAIN_STRENGTH);
			
			// Apply full masking to regular outlines
			finalOutline *= finalMask;
			
			// Final thickness variation
			float thicknessVar = geometricNoise(getScaledNoiseCoord(screenUV3, 0.03), 1.0, 70.0);
			finalOutline *= (0.7 + thicknessVar * 0.6);
			
			// Boost final strength
			finalOutline = min(finalOutline * 1.3, 1.0);
		}
		
		// Apply lighter masking to hatching (so it's less broken up)
		if (hatchingOutline > 0.0) {
			vec2 screenUV3 = texcoord * vec2(viewWidth, viewHeight);
			vec2 paperUV = screenUV3 / (512.0 * (viewHeight / 1440.0));
			float paperBreak = paperLineMask(paperUV);
			
			// Much lighter masking for hatching - we want it more visible
			float lightMask = mix(0.7, 1.0, paperBreak);
			hatchingOutline *= lightMask;
		}
    #endif
    
    // Calculate edge direction where we have outlines
    vec2 edgeDirection = vec2(0.0);
    if (finalOutline > 0.0 || hatchingOutline > 0.0) {
        if (finalOutline > 0.0) {
            // For regular outlines, calculate actual edge direction
            edgeDirection = calculateEdgeDirection(texcoord, pixelSize);
        } else if (hatchingOutline > 0.0) {
            // For hatching-only areas, use the hatching direction as edge direction
            float angle1Rad = degToRad(HATCHING_ANGLE1);
            edgeDirection = vec2(cos(angle1Rad), sin(angle1Rad));
        }
        // Encode direction from -1,1 range to 0,1 range for storage
        edgeDirection = edgeDirection * 0.5 + 0.5;
    }
    
	// Apply shadows if enabled
	vec3 finalColor = originalColor;
	#if ENABLE_SHADOWS == 1
		finalColor = texture2D(DRAW_SHADOW_MAP, texcoord).rgb;
	#endif

	// Store color and outline in first target
	gl_FragData[0] = vec4(finalColor, finalOutline);
	// Store edge direction in RG, hatching in B, speed in A
	gl_FragData[1] = vec4(edgeDirection, hatchingOutline, cameraSpeed);
}