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) { var defines = vs.defines.slice(0); var sources = vs.sources.slice(0); defines.push('SHADOW_MAP'); if (isTerrain) { defines.push('GENERATE_POSITION'); } var positionVaryingName = ShaderSource.findPositionVarying(vs); var hasPositionVarying = defined(positionVaryingName); if (isPointLight && !hasPositionVarying) { var length = sources.length; for (var j = 0; j < length; ++j) { sources[j] = ShaderSource.replaceMain(sources[j], 'czm_shadow_cast_main'); } var shadowVS = 'varying 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) { var defines = fs.defines.slice(0); var sources = fs.sources.slice(0); var positionVaryingName = ShaderSource.findPositionVarying(fs); var hasPositionVarying = defined(positionVaryingName); if (!hasPositionVarying) { positionVaryingName = 'v_positionEC'; } var length = sources.length; for (var i = 0; i < length; ++i) { sources[i] = ShaderSource.replaceMain(sources[i], 'czm_shadow_cast_main'); } var fsSource = ''; if (isPointLight) { if (!hasPositionVarying) { fsSource += 'varying 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 (gl_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' + ' gl_FragColor = czm_packDepth(distance); \n'; } else if (usesDepthTexture) { fsSource += ' gl_FragColor = vec4(1.0); \n'; } else { fsSource += ' gl_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) { var usesDepthTexture = shadowMap._usesDepthTexture; var polygonOffsetSupported = shadowMap._polygonOffsetSupported; var isPointLight = shadowMap._isPointLight; var isSpotLight = shadowMap._isSpotLight; var hasCascades = shadowMap._numberOfCascades > 1; var debugCascadeColors = shadowMap.debugCascadeColors; var softShadows = shadowMap.softShadows; return 'receiveShadow ' + usesDepthTexture + polygonOffsetSupported + isPointLight + isSpotLight + hasCascades + debugCascadeColors + softShadows + castShadows + isTerrain + hasTerrainNormal; }; ShadowMapShader.createShadowReceiveVertexShader = function(vs, isTerrain, hasTerrainNormal) { var defines = vs.defines.slice(0); var 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) { var normalVaryingName = ShaderSource.findNormalVarying(fs); var hasNormalVarying = (!isTerrain && defined(normalVaryingName)) || (isTerrain && hasTerrainNormal); var positionVaryingName = ShaderSource.findPositionVarying(fs); var hasPositionVarying = defined(positionVaryingName); var usesDepthTexture = shadowMap._usesDepthTexture; var polygonOffsetSupported = shadowMap._polygonOffsetSupported; var isPointLight = shadowMap._isPointLight; var isSpotLight = shadowMap._isSpotLight; var hasCascades = shadowMap._numberOfCascades > 1; var debugCascadeColors = shadowMap.debugCascadeColors; var softShadows = shadowMap.softShadows; var bias = isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias); var defines = fs.defines.slice(0); var sources = fs.sources.slice(0); var length = sources.length; for (var 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'); } } var fsSource = ''; if (isPointLight) { fsSource += 'uniform samplerCube shadowMap_textureCube; \n'; } else { fsSource += 'uniform sampler2D shadowMap_texture; \n'; } var 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' + 'varying 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' + ' gl_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 += ' gl_FragColor.rgb *= visibility; \n' + '} \n'; sources.push(fsSource); return new ShaderSource({ defines : defines, sources : sources }); }; export default ShadowMapShader;