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
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;
|