You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
13 KiB
JavaScript

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;