diff --git a/Apps/Sandcastle/gallery/development/Shadows.html b/Apps/Sandcastle/gallery/development/Shadows.html index 4422ca790b9b..7d3caf36712e 100644 --- a/Apps/Sandcastle/gallery/development/Shadows.html +++ b/Apps/Sandcastle/gallery/development/Shadows.html @@ -118,7 +118,7 @@ freeze : false, cascadeColors : false, fitNearFar : true, - softShadows : true, + softShadows : false, cascadeOptions : [1, 4], cascades : 4, lightSourceOptions : ['Freeform', 'Sun', 'Fixed', 'Point'], diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js index 26a7eaf95a26..ab46fe113a71 100644 --- a/Source/Renderer/AutomaticUniforms.js +++ b/Source/Renderer/AutomaticUniforms.js @@ -1535,30 +1535,16 @@ define([ }), /** - * An automatic GLSL uniform representing the shadow map cascade offsets. + * An automatic GLSL uniform representing the shadow map cascade matrices. * - * @alias czm_shadowMapCascadeOffsets + * @alias czm_shadowMapCascadeMatrices * @glslUniform */ - czm_shadowMapCascadeOffsets : new AutomaticUniform({ + czm_shadowMapCascadeMatrices : new AutomaticUniform({ size : 4, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.shadowMap.cascadeOffsets; - } - }), - - /** - * An automatic GLSL uniform representing the shadow map cascade scales. - * - * @alias czm_shadowMapCascadeScales - * @glslUniform - */ - czm_shadowMapCascadeScales : new AutomaticUniform({ - size : 4, - datatype : WebGLConstants.FLOAT_VEC3, + datatype : WebGLConstants.FLOAT_MAT4, getValue : function(uniformState) { - return uniformState.shadowMap.cascadeScales; + return uniformState.shadowMap.cascadeMatrices; } }), diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 5a251533585a..6050f24e8364 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -1583,10 +1583,6 @@ define([ us.updateFrustum(frustum); - if (scene.shadowMap.enabled) { - scene.shadowMap.updateShadowMapMatrix(us); - } - clearDepth.execute(context, passState); us.updatePass(Pass.GLOBE); @@ -1636,10 +1632,6 @@ define([ // Do not overlap frustums in the translucent pass to avoid blending artifacts frustum.near = frustumCommands.near; us.updateFrustum(frustum); - - if (scene.shadowMap.enabled) { - scene.shadowMap.updateShadowMapMatrix(us); - } } us.updatePass(Pass.TRANSLUCENT); diff --git a/Source/Scene/ShadowMap.js b/Source/Scene/ShadowMap.js index ba3d224080ba..236d52332b8a 100644 --- a/Source/Scene/ShadowMap.js +++ b/Source/Scene/ShadowMap.js @@ -166,8 +166,7 @@ define([ // Uniforms this._cascadeSplits = [new Cartesian4(), new Cartesian4()]; - this._cascadeOffsets = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()]; - this._cascadeScales = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()]; + this._cascadeMatrices = [new Matrix4(), new Matrix4(), new Matrix4(), new Matrix4()]; var numberOfPasses; if (this._isPointLight) { @@ -182,6 +181,7 @@ define([ this._passCameras = new Array(numberOfPasses); this._passStates = new Array(numberOfPasses); this._passFramebuffers = new Array(numberOfPasses); + this._passTextureOffsets = new Array(numberOfPasses); for (var i = 0; i < numberOfPasses; ++i) { this._passCameras[i] = new ShadowMapCamera(); @@ -259,9 +259,10 @@ define([ return this._shadowMapTexture; } }, + /** * The shadow map matrix used in shadow receive programs. - * It converts gl_Position to shadow map texture space. + * Transforms from eye space to shadow texture space. * * @memberof ShadowMap.prototype * @type {Matrix4} @@ -334,16 +335,13 @@ define([ return this._cascadeSplits; } }, - cascadeOffsets : { - get : function() { - return this._cascadeOffsets; - } - }, - cascadeScales : { + + cascadeMatrices : { get : function() { - return this._cascadeScales; + return this._cascadeMatrices; } }, + lightDirectionEC : { get : function() { return this._lightDirectionEC; @@ -531,13 +529,13 @@ define([ if (this._isPointLight && this._usesCubeMap) { textureSize.x = size; textureSize.y = size; - var viewport = new BoundingRectangle(0, 0, size, size); - this._passStates[0].viewport = viewport; - this._passStates[1].viewport = viewport; - this._passStates[2].viewport = viewport; - this._passStates[3].viewport = viewport; - this._passStates[4].viewport = viewport; - this._passStates[5].viewport = viewport; + var faceViewport = new BoundingRectangle(0, 0, size, size); + this._passStates[0].viewport = faceViewport; + this._passStates[1].viewport = faceViewport; + this._passStates[2].viewport = faceViewport; + this._passStates[3].viewport = faceViewport; + this._passStates[4].viewport = faceViewport; + this._passStates[5].viewport = faceViewport; } else if (numberOfPasses === 1) { // +----+ // | 1 | @@ -575,6 +573,16 @@ define([ // Update clear pass state this._clearPassState.viewport = new BoundingRectangle(0, 0, textureSize.x, textureSize.y); + + // Transforms shadow coordinates [0, 1] into the pass's region of the texture + for (var i = 0; i < numberOfPasses; ++i) { + var viewport = this._passStates[i].viewport; + var biasX = viewport.x / textureSize.x; + var biasY = viewport.y / textureSize.y; + var scaleX = viewport.width / textureSize.x; + var scaleY = viewport.height / textureSize.y; + this._passTextureOffsets[i] = new Matrix4(scaleX, 0.0, 0.0, biasX, 0.0, scaleY, 0.0, biasY, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0); + } }; var scratchViewport = new BoundingRectangle(); @@ -848,6 +856,7 @@ define([ var far = shadowMapCamera.frustum.far; var cascadeSubFrustum = sceneCamera.frustum.clone(scratchFrustum); + var shadowViewProjection = shadowMapCamera.getViewProjection(); for (var j = 0; j < numberOfCascades; ++j) { // Find the bounding box of the camera sub-frustum in shadow map texture space @@ -855,7 +864,7 @@ define([ cascadeSubFrustum.far = splitsFar[j]; var viewProjection = Matrix4.multiply(cascadeSubFrustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix); var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix); - var cascadeMatrix = Matrix4.multiply(shadowMapCamera.getViewProjection(), inverseViewProjection, scratchMatrix); + var shadowMapMatrix = Matrix4.multiply(shadowViewProjection, inverseViewProjection, scratchMatrix); // Project each corner from camera NDC space to shadow map texture space. Min and max will be from 0 to 1. var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin); @@ -863,7 +872,7 @@ define([ for (var k = 0; k < 8; ++k) { var corner = Cartesian4.clone(frustumCornersNDC[k], scratchFrustumCorners[k]); - Matrix4.multiplyByVector(cascadeMatrix, corner, corner); + Matrix4.multiplyByVector(shadowMapMatrix, corner, corner); Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide Cartesian3.minimumByComponent(corner, min, min); Cartesian3.maximumByComponent(corner, max, max); @@ -885,16 +894,10 @@ define([ cascadeCamera.frustum.near = near + min.z * (far - near); cascadeCamera.frustum.far = near + max.z * (far - near); - // Scale and offset are used to transform shadowPosition to the proper cascade in the shader - var cascadeScale = shadowMap._cascadeScales[j]; - cascadeScale.x = 1.0 / (max.x - min.x); - cascadeScale.y = 1.0 / (max.y - min.y); - cascadeScale.z = 1.0 / (max.z - min.z); - - var cascadeOffset = shadowMap._cascadeOffsets[j]; - cascadeOffset.x = -min.x; - cascadeOffset.y = -min.y; - cascadeOffset.z = -min.z; + // Transforms from eye space to the cascade's texture space + var cascadeMatrix = shadowMap._cascadeMatrices[j]; + Matrix4.multiply(cascadeCamera.getViewProjection(), sceneCamera.inverseViewMatrix, cascadeMatrix); + Matrix4.multiply(shadowMap._passTextureOffsets[j], cascadeMatrix, cascadeMatrix); } } @@ -1004,9 +1007,6 @@ define([ frustum.far = shadowMap._radius; frustum.aspectRatio = 1.0; - // Re-purpose the shadow map matrix - Matrix4.inverse(frustum.projectionMatrix, shadowMap._shadowMapMatrix); - for (var i = 0; i < 6; ++i) { var camera = shadowMap._passCameras[i]; camera.positionWC = shadowMap._shadowMapCamera.positionWC; @@ -1081,6 +1081,12 @@ define([ this._passCameras[0].clone(this._shadowMapCamera); } + // Transforms from eye space to shadow texture space + if (!this._isPointLight && this._numberOfCascades <= 1) { + var inverseView = this._sceneCamera.inverseViewMatrix; + Matrix4.multiply(this._shadowMapCamera.getViewProjection(), inverseView, this._shadowMapMatrix); + } + if (this.debugShow) { applyDebugSettings(this, frameState); } @@ -1093,17 +1099,6 @@ define([ } }; - ShadowMap.prototype.updateShadowMapMatrix = function(uniformState) { - if (this._isPointLight) { - // Point light shadows do not project into light space - return; - } - // Calculate shadow map matrix. It converts gl_Position to shadow map texture space. - // Needs to be updated for each frustum in multi-frustum rendering because the projection matrix changes. - var shadowViewProjection = this._shadowMapCamera.getViewProjection(); - Matrix4.multiply(shadowViewProjection, uniformState.inverseViewProjection, this._shadowMapMatrix); - }; - ShadowMap.prototype.isDestroyed = function() { return false; }; diff --git a/Source/Scene/ShadowMapShader.js b/Source/Scene/ShadowMapShader.js index f1dfdc2cb6c7..2a94c2493c8a 100644 --- a/Source/Scene/ShadowMapShader.js +++ b/Source/Scene/ShadowMapShader.js @@ -75,18 +75,6 @@ define([ }; ShadowMapShader.createShadowReceiveVertexShader = function(vs, frameState) { - var isPointLight = frameState.shadowMap.isPointLight; - if (!isPointLight) { - vs = ShaderSource.replaceMain(vs, 'czm_shadow_main'); - vs += - 'varying vec3 v_shadowPosition; \n' + - 'void main() \n' + - '{ \n' + - ' czm_shadow_main(); \n' + - ' v_shadowPosition = (czm_shadowMapMatrix * gl_Position).xyz; \n' + - '} \n'; - } - return vs; }; @@ -109,26 +97,12 @@ define([ ' vec4 far = step(depthEye, czm_shadowMapCascadeSplits[1]); \n' + ' return near * far; \n' + '} \n' + - 'vec4 getCascadeViewport(vec4 weights) \n' + - '{ \n' + - ' return vec4(0.0, 0.0, 0.5, 0.5) * weights.x + \n' + - ' vec4(0.5, 0.0, 0.5, 0.5) * weights.y + \n' + - ' vec4(0.0, 0.5, 0.5, 0.5) * weights.z + \n' + - ' vec4(0.5, 0.5, 0.5, 0.5) * weights.w; \n' + - '} \n' + - 'vec3 getCascadeOffset(vec4 weights) \n' + + 'mat4 getCascadeMatrix(vec4 weights) \n' + '{ \n' + - ' return czm_shadowMapCascadeOffsets[0] * weights.x + \n' + - ' czm_shadowMapCascadeOffsets[1] * weights.y + \n' + - ' czm_shadowMapCascadeOffsets[2] * weights.z + \n' + - ' czm_shadowMapCascadeOffsets[3] * weights.w; \n' + - '} \n' + - 'vec3 getCascadeScale(vec4 weights) \n' + - '{ \n' + - ' return czm_shadowMapCascadeScales[0] * weights.x + \n' + - ' czm_shadowMapCascadeScales[1] * weights.y + \n' + - ' czm_shadowMapCascadeScales[2] * weights.z + \n' + - ' czm_shadowMapCascadeScales[3] * weights.w; \n' + + ' return czm_shadowMapCascadeMatrices[0] * weights.x + \n' + + ' czm_shadowMapCascadeMatrices[1] * weights.y + \n' + + ' czm_shadowMapCascadeMatrices[2] * weights.z + \n' + + ' czm_shadowMapCascadeMatrices[3] * weights.w; \n' + '} \n' + 'vec4 getCascadeColor(vec4 weights) \n' + '{ \n' + @@ -138,12 +112,12 @@ define([ ' vec4(1.0, 0.0, 1.0, 1.0) * weights.w; \n' + '} \n' + ' \n' + - 'float getDepthEye() \n' + + 'vec4 getPositionEC() \n' + '{ \n' + (hasPositionVarying ? - ' return -' + positionVaryingName + '.z; \n' : - ' return czm_projection[3][2] / ((gl_FragCoord.z * 2.0 - 1.0) + czm_projection[2][2]); \n') + + ' return vec4(' + positionVaryingName + ', 1.0); \n' : + ' return czm_windowToEyeCoordinates(gl_FragCoord); \n') + '} \n' + ' \n' + @@ -171,10 +145,9 @@ define([ (isPointLight && usesCubeMap ? 'float getVisibility(vec3 uv, float depth, vec3 lightDirectionEC)' : 'float getVisibility(vec2 uv, float depth, vec3 lightDirectionEC)') + - '{ \n' + - (softShadows ? + (softShadows && !isPointLight ? ' float radius = 1.0; \n' + ' float dx0 = -czm_shadowMapTexelStepSize.x * radius; \n' + ' float dy0 = -czm_shadowMapTexelStepSize.y * radius; \n' + @@ -226,14 +199,11 @@ define([ 'void main() \n' + '{ \n' + ' czm_shadow_main(); \n' + - - (hasPositionVarying ? - ' vec3 positionEC = ' + positionVaryingName + '; \n' : - ' vec3 positionEC = czm_windowToEyeCoordinates(gl_FragCoord).xyz; \n') + - - ' vec3 directionEC = positionEC - czm_shadowMapLightPositionEC.xyz; \n' + + ' vec4 positionEC = getPositionEC(); \n' + + ' vec3 directionEC = positionEC.xyz - czm_shadowMapLightPositionEC.xyz; \n' + ' float distance = length(directionEC); \n' + ' float radius = czm_shadowMapLightPositionEC.w; \n' + + ' // Stop early if the fragment is beyond the point light radius \n' + ' if (distance > radius) { \n' + ' return; \n' + ' } \n' + @@ -248,38 +218,41 @@ define([ ' gl_FragColor.rgb *= visibility; \n' + '} \n'; - } else { + } else if (hasCascades) { fs += - 'varying vec3 v_shadowPosition; \n' + 'void main() \n' + '{ \n' + ' czm_shadow_main(); \n' + - ' vec3 shadowPosition = v_shadowPosition; \n' + - ' // Do not apply shadowing if outside of the shadow map bounds \n' + - ' if (any(lessThan(shadowPosition, vec3(0.0))) || any(greaterThan(shadowPosition, vec3(1.0)))) { \n' + + ' vec4 positionEC = getPositionEC(); \n' + + ' // Get the cascade based on the eye-space depth \n' + + ' float depth = -positionEC.z; \n' + + ' // Stop early if the eye depth exceeds the last cascade \n' + + ' if (depth > czm_shadowMapCascadeSplits[1].w) { \n' + ' return; \n' + ' } \n' + - ' \n' + - - (hasCascades ? - ' // Get the cascade \n' + - ' float depthEye = getDepthEye(); \n' + - ' vec4 weights = getCascadeWeights(depthEye); \n' + - ' \n' + - ' // Transform shadowPosition into the cascade \n' + - ' shadowPosition += getCascadeOffset(weights); \n' + - ' shadowPosition *= getCascadeScale(weights); \n' + - ' \n' + - ' // Modify texture coordinates to read from the correct cascade in the texture atlas \n' + - ' vec4 viewport = getCascadeViewport(weights); \n' + - ' shadowPosition.xy = shadowPosition.xy * viewport.zw + viewport.xy; \n' + - ' \n' + + ' vec4 weights = getCascadeWeights(depth); \n' + + ' // Transform position into the cascade \n' + + ' vec4 shadowPosition = getCascadeMatrix(weights) * positionEC; \n' + (debugVisualizeCascades ? ' // Draw cascade colors for debugging \n' + - ' gl_FragColor *= getCascadeColor(weights); \n' : '') : '') + + ' gl_FragColor *= getCascadeColor(weights); \n' : '') + - ' \n' + + ' // Apply shadowing \n' + + ' float visibility = getVisibility(shadowPosition.xy, shadowPosition.z, czm_shadowMapLightDirectionEC); \n' + + ' gl_FragColor.rgb *= visibility; \n' + + '} \n'; + } else { + fs += + 'void main() \n' + + '{ \n' + + ' czm_shadow_main(); \n' + + ' vec4 positionEC = getPositionEC(); \n' + + ' vec4 shadowPosition = czm_shadowMapMatrix * positionEC; \n' + + ' // Stop early if the fragment is not in the shadow bounds \n' + + ' if (any(lessThan(shadowPosition, vec4(0.0))) || any(greaterThan(shadowPosition, vec4(1.0)))) { \n' + + ' return; \n' + + ' } \n' + ' // Apply shadowing \n' + ' float visibility = getVisibility(shadowPosition.xy, shadowPosition.z, czm_shadowMapLightDirectionEC); \n' + ' gl_FragColor.rgb *= visibility; \n' +