import defined from "../Core/defined.js"; import ShaderSource from "../Renderer/ShaderSource.js"; /** * @private */ function ShadowMapShader() {} ShadowMapShader.getShadowCastShaderKeyword = function ( isPointLight, isTerrain, usesDepthTexture, isOpaque ) { return `castShadow ${isPointLight} ${isTerrain} ${usesDepthTexture} ${isOpaque}`; }; ShadowMapShader.createShadowCastVertexShader = function ( vs, isPointLight, isTerrain ) { const defines = vs.defines.slice(0); const sources = vs.sources.slice(0); defines.push("SHADOW_MAP"); if (isTerrain) { defines.push("GENERATE_POSITION"); } const positionVaryingName = ShaderSource.findPositionVarying(vs); const hasPositionVarying = defined(positionVaryingName); if (isPointLight && !hasPositionVarying) { const length = sources.length; for (let j = 0; j < length; ++j) { sources[j] = ShaderSource.replaceMain(sources[j], "czm_shadow_cast_main"); } const shadowVS = "out vec3 v_positionEC; \n" + "void main() \n" + "{ \n" + " czm_shadow_cast_main(); \n" + " v_positionEC = (czm_inverseProjection * gl_Position).xyz; \n" + "}"; sources.push(shadowVS); } return new ShaderSource({ defines: defines, sources: sources, }); }; ShadowMapShader.createShadowCastFragmentShader = function ( fs, isPointLight, usesDepthTexture, opaque ) { const defines = fs.defines.slice(0); const sources = fs.sources.slice(0); defines.push("SHADOW_MAP"); let positionVaryingName = ShaderSource.findPositionVarying(fs); const hasPositionVarying = defined(positionVaryingName); if (!hasPositionVarying) { positionVaryingName = "v_positionEC"; } const length = sources.length; for (let i = 0; i < length; ++i) { sources[i] = ShaderSource.replaceMain(sources[i], "czm_shadow_cast_main"); } let fsSource = ""; if (isPointLight) { if (!hasPositionVarying) { fsSource += "in vec3 v_positionEC; \n"; } fsSource += "uniform vec4 shadowMap_lightPositionEC; \n"; } if (opaque) { fsSource += "void main() \n" + "{ \n"; } else { fsSource += "void main() \n" + "{ \n" + " czm_shadow_cast_main(); \n" + " if (out_FragColor.a == 0.0) \n" + " { \n" + " discard; \n" + " } \n"; } if (isPointLight) { fsSource += ` float distance = length(${positionVaryingName}); \n` + ` if (distance >= shadowMap_lightPositionEC.w) \n` + ` { \n` + ` discard; \n` + ` } \n` + ` distance /= shadowMap_lightPositionEC.w; // radius \n` + ` out_FragColor = czm_packDepth(distance); \n`; } else if (usesDepthTexture) { fsSource += " out_FragColor = vec4(1.0); \n"; } else { fsSource += " out_FragColor = czm_packDepth(gl_FragCoord.z); \n"; } fsSource += "} \n"; sources.push(fsSource); return new ShaderSource({ defines: defines, sources: sources, }); }; ShadowMapShader.getShadowReceiveShaderKeyword = function ( shadowMap, castShadows, isTerrain, hasTerrainNormal ) { const usesDepthTexture = shadowMap._usesDepthTexture; const polygonOffsetSupported = shadowMap._polygonOffsetSupported; const isPointLight = shadowMap._isPointLight; const isSpotLight = shadowMap._isSpotLight; const hasCascades = shadowMap._numberOfCascades > 1; const debugCascadeColors = shadowMap.debugCascadeColors; const softShadows = shadowMap.softShadows; return `receiveShadow ${usesDepthTexture}${polygonOffsetSupported}${isPointLight}${isSpotLight}${hasCascades}${debugCascadeColors}${softShadows}${castShadows}${isTerrain}${hasTerrainNormal}`; }; ShadowMapShader.createShadowReceiveVertexShader = function ( vs, isTerrain, hasTerrainNormal ) { const defines = vs.defines.slice(0); const sources = vs.sources.slice(0); defines.push("SHADOW_MAP"); if (isTerrain) { if (hasTerrainNormal) { defines.push("GENERATE_POSITION_AND_NORMAL"); } else { defines.push("GENERATE_POSITION"); } } return new ShaderSource({ defines: defines, sources: sources, }); }; ShadowMapShader.createShadowReceiveFragmentShader = function ( fs, shadowMap, castShadows, isTerrain, hasTerrainNormal ) { const normalVaryingName = ShaderSource.findNormalVarying(fs); const hasNormalVarying = (!isTerrain && defined(normalVaryingName)) || (isTerrain && hasTerrainNormal); const positionVaryingName = ShaderSource.findPositionVarying(fs); const hasPositionVarying = defined(positionVaryingName); const usesDepthTexture = shadowMap._usesDepthTexture; const polygonOffsetSupported = shadowMap._polygonOffsetSupported; const isPointLight = shadowMap._isPointLight; const isSpotLight = shadowMap._isSpotLight; const hasCascades = shadowMap._numberOfCascades > 1; const debugCascadeColors = shadowMap.debugCascadeColors; const softShadows = shadowMap.softShadows; const bias = isPointLight ? shadowMap._pointBias : isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias; const defines = fs.defines.slice(0); const sources = fs.sources.slice(0); const length = sources.length; for (let i = 0; i < length; ++i) { sources[i] = ShaderSource.replaceMain( sources[i], "czm_shadow_receive_main" ); } if (isPointLight) { defines.push("USE_CUBE_MAP_SHADOW"); } else if (usesDepthTexture) { defines.push("USE_SHADOW_DEPTH_TEXTURE"); } if (softShadows && !isPointLight) { defines.push("USE_SOFT_SHADOWS"); } // Enable day-night shading so that the globe is dark when the light is below the horizon if (hasCascades && castShadows && isTerrain) { if (hasNormalVarying) { defines.push("ENABLE_VERTEX_LIGHTING"); } else { defines.push("ENABLE_DAYNIGHT_SHADING"); } } if (castShadows && bias.normalShading && hasNormalVarying) { defines.push("USE_NORMAL_SHADING"); if (bias.normalShadingSmooth > 0.0) { defines.push("USE_NORMAL_SHADING_SMOOTH"); } } let fsSource = ""; if (isPointLight) { fsSource += "uniform samplerCube shadowMap_textureCube; \n"; } else { fsSource += "uniform sampler2D shadowMap_texture; \n"; } let returnPositionEC; if (hasPositionVarying) { returnPositionEC = ` return vec4(${positionVaryingName}, 1.0); \n`; } else { returnPositionEC = "#ifndef LOG_DEPTH \n" + " return czm_windowToEyeCoordinates(gl_FragCoord); \n" + "#else \n" + " return vec4(v_logPositionEC, 1.0); \n" + "#endif \n"; } fsSource += `${ "uniform mat4 shadowMap_matrix; \n" + "uniform vec3 shadowMap_lightDirectionEC; \n" + "uniform vec4 shadowMap_lightPositionEC; \n" + "uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness; \n" + "uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth; \n" + "#ifdef LOG_DEPTH \n" + "in vec3 v_logPositionEC; \n" + "#endif \n" + "vec4 getPositionEC() \n" + "{ \n" }${returnPositionEC}} \n` + `vec3 getNormalEC() \n` + `{ \n${ hasNormalVarying ? ` return normalize(${normalVaryingName}); \n` : " return vec3(1.0); \n" }} \n` + // Offset the shadow position in the direction of the normal for perpendicular and back faces `void applyNormalOffset(inout vec4 positionEC, vec3 normalEC, float nDotL) \n` + `{ \n${ bias.normalOffset && hasNormalVarying ? " float normalOffset = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.x; \n" + " float normalOffsetScale = 1.0 - nDotL; \n" + " vec3 offset = normalOffset * normalOffsetScale * normalEC; \n" + " positionEC.xyz += offset; \n" : "" }} \n`; fsSource += "void main() \n" + "{ \n" + " czm_shadow_receive_main(); \n" + " vec4 positionEC = getPositionEC(); \n" + " vec3 normalEC = getNormalEC(); \n" + " float depth = -positionEC.z; \n"; fsSource += " czm_shadowParameters shadowParameters; \n" + " shadowParameters.texelStepSize = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy; \n" + " shadowParameters.depthBias = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z; \n" + " shadowParameters.normalShadingSmooth = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w; \n" + " shadowParameters.darkness = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w; \n"; if (isTerrain) { // Scale depth bias based on view distance to reduce z-fighting in distant terrain fsSource += " shadowParameters.depthBias *= max(depth * 0.01, 1.0); \n"; } else if (!polygonOffsetSupported) { // If polygon offset isn't supported push the depth back based on view, however this // causes light leaking at further away views fsSource += " shadowParameters.depthBias *= mix(1.0, 100.0, depth * 0.0015); \n"; } if (isPointLight) { fsSource += " vec3 directionEC = positionEC.xyz - shadowMap_lightPositionEC.xyz; \n" + " float distance = length(directionEC); \n" + " directionEC = normalize(directionEC); \n" + " float radius = shadowMap_lightPositionEC.w; \n" + " // Stop early if the fragment is beyond the point light radius \n" + " if (distance > radius) \n" + " { \n" + " return; \n" + " } \n" + " vec3 directionWC = czm_inverseViewRotation * directionEC; \n" + " shadowParameters.depth = distance / radius; \n" + " shadowParameters.nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); \n" + " shadowParameters.texCoords = directionWC; \n" + " float visibility = czm_shadowVisibility(shadowMap_textureCube, shadowParameters); \n"; } else if (isSpotLight) { fsSource += " vec3 directionEC = normalize(positionEC.xyz - shadowMap_lightPositionEC.xyz); \n" + " float nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); \n" + " applyNormalOffset(positionEC, normalEC, nDotL); \n" + " vec4 shadowPosition = shadowMap_matrix * positionEC; \n" + " // Spot light uses a perspective projection, so perform the perspective divide \n" + " shadowPosition /= shadowPosition.w; \n" + " // Stop early if the fragment is not in the shadow bounds \n" + " if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) \n" + " { \n" + " return; \n" + " } \n" + " shadowParameters.texCoords = shadowPosition.xy; \n" + " shadowParameters.depth = shadowPosition.z; \n" + " shadowParameters.nDotL = nDotL; \n" + " float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n"; } else if (hasCascades) { fsSource += `${ " float maxDepth = shadowMap_cascadeSplits[1].w; \n" + " // Stop early if the eye depth exceeds the last cascade \n" + " if (depth > maxDepth) \n" + " { \n" + " return; \n" + " } \n" + " // Get the cascade based on the eye-space depth \n" + " vec4 weights = czm_cascadeWeights(depth); \n" + " // Apply normal offset \n" + " float nDotL = clamp(dot(normalEC, shadowMap_lightDirectionEC), 0.0, 1.0); \n" + " applyNormalOffset(positionEC, normalEC, nDotL); \n" + " // Transform position into the cascade \n" + " vec4 shadowPosition = czm_cascadeMatrix(weights) * positionEC; \n" + " // Get visibility \n" + " shadowParameters.texCoords = shadowPosition.xy; \n" + " shadowParameters.depth = shadowPosition.z; \n" + " shadowParameters.nDotL = nDotL; \n" + " float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n" + " // Fade out shadows that are far away \n" + " float shadowMapMaximumDistance = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.z; \n" + " float fade = max((depth - shadowMapMaximumDistance * 0.8) / (shadowMapMaximumDistance * 0.2), 0.0); \n" + " visibility = mix(visibility, 1.0, fade); \n" }${ debugCascadeColors ? " // Draw cascade colors for debugging \n" + " out_FragColor *= czm_cascadeColor(weights); \n" : "" }`; } else { fsSource += " float nDotL = clamp(dot(normalEC, shadowMap_lightDirectionEC), 0.0, 1.0); \n" + " applyNormalOffset(positionEC, normalEC, nDotL); \n" + " vec4 shadowPosition = shadowMap_matrix * positionEC; \n" + " // Stop early if the fragment is not in the shadow bounds \n" + " if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) \n" + " { \n" + " return; \n" + " } \n" + " shadowParameters.texCoords = shadowPosition.xy; \n" + " shadowParameters.depth = shadowPosition.z; \n" + " shadowParameters.nDotL = nDotL; \n" + " float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n"; } fsSource += " out_FragColor.rgb *= visibility; \n" + "} \n"; sources.push(fsSource); return new ShaderSource({ defines: defines, sources: sources, }); }; export default ShadowMapShader;