precision mediump float; uniform sampler2D Texture; uniform sampler2D uShadowMap; uniform vec3 uLightDir; uniform float uAlpha; uniform int uPointLightCount; uniform vec3 uPointLightPos[4]; uniform vec3 uPointLightDir[4]; uniform vec3 uPointLightColor[4]; uniform vec2 uPointLightWorldXZ[4]; uniform vec2 uPointLightLimitXZ[4]; uniform vec3 uAmbientColor; uniform vec3 uFogColor; varying vec2 texCoord; varying vec4 fragPosLightSpace; varying vec3 fragNormal; varying float fogDistance; varying vec3 fragViewPos; varying vec2 fragWorldXZ; const float SPOT_COS_OUTER = 0.7071; const float SPOT_COS_INNER = 0.82; float computeShadow(vec4 lightSpacePos, vec3 normal) { vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w; projCoords = projCoords * 0.5 + 0.5; if (projCoords.x < 0.0 || projCoords.x > 1.0 || projCoords.y < 0.0 || projCoords.y > 1.0 || projCoords.z > 1.0) { return 0.0; } float currentDepth = projCoords.z; float bias = 0.0004; if (dot(normal, normal) > 0.001) { float cosTheta = dot(normalize(normal), -normalize(uLightDir)); bias = max(0.002 * (1.0 - cosTheta), 0.0002); } float shadow = 0.0; float texelSize = 1.0 / 2048.0; for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { float pcfDepth = texture2D(uShadowMap, projCoords.xy + vec2(float(x), float(y)) * texelSize).r; shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; } } shadow /= 9.0; return shadow; } void main() { vec4 color = texture2D(Texture, texCoord); if (color.a < 0.1) discard; vec3 lighting = uAmbientColor; bool hasNormal = dot(fragNormal, fragNormal) > 0.001; vec3 N = hasNormal ? normalize(fragNormal) : vec3(0.0); float shadow = computeShadow(fragPosLightSpace, fragNormal); for (int i = 0; i < 4; i++) { if (i >= uPointLightCount) break; vec3 lightVec = uPointLightPos[i] - fragViewPos; float dist = length(lightVec); vec3 lightDir = normalize(lightVec); float cosTheta = dot(-lightDir, normalize(uPointLightDir[i])); float spotAtten = smoothstep(SPOT_COS_OUTER, SPOT_COS_INNER, cosTheta); if (spotAtten <= 0.0) continue; float atten = 1.0 / (1.0 + 0.35 * dist + 0.15 * dist * dist); float diff = hasNormal ? max(dot(N, lightDir), 0.0) : 1.0; vec2 worldDelta = abs(fragWorldXZ - uPointLightWorldXZ[i]); float rectX = 1.0 - smoothstep(uPointLightLimitXZ[i].x - 0.5, uPointLightLimitXZ[i].x + 0.5, worldDelta.x); float rectY = 1.0 - smoothstep(uPointLightLimitXZ[i].y - 0.5, uPointLightLimitXZ[i].y + 0.5, worldDelta.y); lighting += uPointLightColor[i] * diff * atten * spotAtten * rectX * rectY * (1.0 - shadow); } color.rgb *= lighting; vec3 fogColor = uFogColor; float fogFactor = clamp((fogDistance - 15.0) / 8.0, 0.0, 1.0); color.rgb = mix(color.rgb, fogColor, fogFactor); gl_FragColor = vec4(color.rgb, color.a * uAlpha); }