#version 330 core in vec2 vUV; in vec3 vNormal; in vec3 vFragPos; in vec4 vFragPosLightSpace; out vec4 FragColor; uniform sampler2D albedo; uniform sampler2D normalMap; uniform int normalEnabled; uniform float normalStrength; uniform vec3 tint; uniform int radialEnabled; uniform float radialInner; uniform float radialOuter; struct DirectionalLight { vec3 direction; vec3 color; float intensity; }; uniform DirectionalLight dirLight; uniform vec3 ambientColor = vec3(0.15, 0.15, 0.15); uniform sampler2D shadowMap; uniform float shadowBiasMin; uniform float shadowBiasScale; uniform int pcfRadius; uniform sampler2D ssao; uniform float aoStrength; uniform float screenWidth; uniform float screenHeight; // Fog uniform vec3 cameraPos; uniform vec3 fogColor; uniform float fogDensity; // e.g. 0.02 uniform float fogAmount; // 0..1 blend factor float ShadowCalculation(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) { vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; projCoords = projCoords * 0.5 + 0.5; if (projCoords.z > 1.0) return 0.0; float currentDepth = projCoords.z; float bias = max(shadowBiasScale * (1.0 - max(dot(normal, lightDir), 0.0)), shadowBiasMin); ivec2 texSize = textureSize(shadowMap, 0); vec2 texelSize = 1.0 / vec2(texSize); float vis = 0.0; int samples = 0; for (int x = -pcfRadius; x <= pcfRadius; ++x) { for (int y = -pcfRadius; y <= pcfRadius; ++y) { vec2 offset = vec2(x, y) * texelSize; float depthSample = texture(shadowMap, projCoords.xy + offset).r; // texture returns stored light-space depth. If our fragment's depth (minus bias) // is less than or equal to the sampled depth, it's visible to the light. vis += (currentDepth - bias <= depthSample) ? 1.0 : 0.0; samples++; } } float visibility = vis / float(samples); float shadow = 1.0 - visibility; return shadow; } void main() { vec4 tex = texture(albedo, vUV); vec3 color = tex.rgb * tint; float alpha = tex.a; vec3 finalNormal = vec3(0.0, 0.0, 1.0); if (normalEnabled == 1) { vec3 n = texture(normalMap, vUV).rgb; n = n * 2.0 - 1.0; finalNormal = normalize(vec3(n.x * normalStrength, n.y * normalStrength, 1.0)); } if (radialEnabled == 1) { float dist = distance(vUV, vec2(0.5, 0.5)); float mask = 1.0 - smoothstep(radialInner, radialOuter, dist); alpha *= mask; } vec3 N = normalize(finalNormal); vec3 L = normalize(dirLight.direction); float diff = max(dot(N, L), 0.0); vec3 diffuse = dirLight.color * dirLight.intensity * diff; float shadow = ShadowCalculation(vFragPosLightSpace, N, L); float shadowFactor = 1.0 - shadow * 0.9; float ao = 1.0; if (aoStrength > 0.0) { vec2 ssaoUV = gl_FragCoord.xy / vec2(screenWidth, screenHeight); ao = texture(ssao, ssaoUV).r; } float aoFactor = mix(1.0, ao, aoStrength); vec3 result = color * (ambientColor * aoFactor + diffuse * shadowFactor); // Compute fog based on distance from camera float dist = length(vFragPos - cameraPos); float fogFactor = 1.0 - exp(-fogDensity * dist); fogFactor = clamp(fogFactor * fogAmount, 0.0, 1.0); vec3 finalColor = mix(result, fogColor, fogFactor); FragColor = vec4(finalColor, alpha); }