diff --git a/Apps/Sandcastle/gallery/development/Shadows.html b/Apps/Sandcastle/gallery/development/Shadows.html index 37a7c332bfd8..3d01d8fe4538 100644 --- a/Apps/Sandcastle/gallery/development/Shadows.html +++ b/Apps/Sandcastle/gallery/development/Shadows.html @@ -259,6 +259,8 @@ var center; var height; +scene.debugShowFramesPerSecond = true; + var fixedLightCamera = new Cesium.Camera(scene); var freeformLightCamera = new Cesium.Camera(scene); var sunCamera = scene._sunCamera; diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js index 826e3cfc1d19..11214d592dde 100644 --- a/Source/Renderer/AutomaticUniforms.js +++ b/Source/Renderer/AutomaticUniforms.js @@ -1492,6 +1492,20 @@ define([ } }), + /** + * An automatic GLSL uniform representing the sun's shadow map texture as a cube map. + * + * @alias czm_sunShadowMapTextureCube + * @glslUniform + */ + czm_sunShadowMapTextureCube : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_CUBE, + getValue : function(uniformState) { + return uniformState.shadowMap.shadowMapTexture; + } + }), + /** * An automatic GLSL uniform representing the sun's shadow map matrix. * @@ -1563,31 +1577,17 @@ define([ }), /** - * An automatic GLSL uniform representing the sun's shadow map light position in eye coordinates. + * An automatic GLSL uniform that stores the point light's eye space position and radius. * * @alias czm_sunShadowMapLightPositionEC * @glslUniform */ czm_sunShadowMapLightPositionEC : new AutomaticUniform({ size : 1, - datatype : WebGLConstants.FLOAT_VEC3, + datatype : WebGLConstants.FLOAT_VEC4, getValue : function(uniformState) { return uniformState.shadowMap.lightPositionEC; } - }), - - /** - * An automatic GLSL uniform representing the sun's shadow map radius. - * - * @alias czm_sunShadowMapRadius - * @glslUniform - */ - czm_sunShadowMapRadius : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.shadowMap.radius; - } }) }; diff --git a/Source/Renderer/Context.js b/Source/Renderer/Context.js index 9e522ea3eff8..09cdefe2b2d8 100644 --- a/Source/Renderer/Context.js +++ b/Source/Renderer/Context.js @@ -989,6 +989,10 @@ define([ } }; + Context.prototype.bindFramebuffer = function(framebuffer) { + bindFramebuffer(this, framebuffer); + }; + Context.prototype.readPixels = function(readState) { var gl = this._gl; diff --git a/Source/Renderer/CubeMap.js b/Source/Renderer/CubeMap.js index ecf5dcab9d81..3f033e3391d8 100644 --- a/Source/Renderer/CubeMap.js +++ b/Source/Renderer/CubeMap.js @@ -169,7 +169,7 @@ define([ this._positiveZ = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_POSITIVE_Z, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY); this._negativeZ = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY); - this.sampler = new Sampler(); + this.sampler = defined(options.sampler) ? options.sampler : new Sampler(); } defineProperties(CubeMap.prototype, { diff --git a/Source/Renderer/Framebuffer.js b/Source/Renderer/Framebuffer.js index 2e2c1f97406a..7197df4ba3f4 100644 --- a/Source/Renderer/Framebuffer.js +++ b/Source/Renderer/Framebuffer.js @@ -308,6 +308,11 @@ define([ gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; + Framebuffer.prototype._attachTexture = function(context, attachment, texture) { + context.bindFramebuffer(this); + attachTexture(this, attachment, texture); + }; + Framebuffer.prototype._getActiveColorAttachments = function() { return this._activeColorAttachments; }; diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index 41d64a5ecef9..9edda2198010 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -286,8 +286,8 @@ define([ vs.sources.push(getPositionMode(sceneMode)); vs.sources.push(get2DYPositionFraction(useWebMercatorProjection)); - vs.sources[1] = ShadowMapShader.createShadowCastVertexShader(vs.sources[1]); - fs.sources[0] = ShadowMapShader.createShadowCastFragmentShader(fs.sources[0], frameState, true); + vs.sources[1] = ShadowMapShader.createShadowCastVertexShader(vs.sources[1], frameState, 'v_positionEC'); + fs.sources[0] = ShadowMapShader.createShadowCastFragmentShader(fs.sources[0], frameState, true, 'v_positionEC'); shadowCastShader = this._shadowCastPrograms[flags] = ShaderProgram.fromCache({ context : frameState.context, diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index b056aabd1c97..76c48f27a40b 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -1502,8 +1502,8 @@ define([ // Create shadow cast program var shadowsEnabled = defined(frameState.shadowMap) && frameState.shadowMap.enabled; if (shadowsEnabled && model.castShadows) { - var shadowCastVS = ShadowMapShader.createShadowCastVertexShader(drawVS); - var shadowCastFS = ShadowMapShader.createShadowCastFragmentShader(drawFS, frameState, false); + var shadowCastVS = ShadowMapShader.createShadowCastVertexShader(drawVS, frameState, 'v_positionEC'); + var shadowCastFS = ShadowMapShader.createShadowCastFragmentShader(drawFS, frameState, false, 'v_positionEC'); model._rendererResources.shadowCastPrograms[id] = ShaderProgram.fromCache({ context : context, vertexShaderSource : shadowCastVS, diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index 493acd885242..22e96d7880d6 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -1113,8 +1113,8 @@ define([ // Create shadow cast program var shadowsEnabled = defined(frameState.shadowMap) && frameState.shadowMap.enabled; if (!defined(primitive._shadowCastSP) && shadowsEnabled && primitive._castShadows) { - var shadowCastVS = ShadowMapShader.createShadowCastVertexShader(vs); - var shadowCastFS = ShadowMapShader.createShadowCastFragmentShader(fs, frameState, false); + var shadowCastVS = ShadowMapShader.createShadowCastVertexShader(vs, frameState, 'v_positionEC'); + var shadowCastFS = ShadowMapShader.createShadowCastFragmentShader(fs, frameState, false, 'v_positionEC'); primitive._shadowCastSP = ShaderProgram.fromCache({ context : context, vertexShaderSource : shadowCastVS, diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 4845e7eb1331..a0af911eeeaf 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -1699,15 +1699,13 @@ define([ var shadowMap = scene.sunShadowMap; var renderState = shadowMap.renderState; - // Clear shadow depth - shadowMap.clearCommand.execute(context); - var commands = getShadowMapCommands(scene); var numberOfCommands = commands.length; var numberOfPasses = shadowMap.numberOfPasses; for (var i = 0; i < numberOfPasses; ++i) { uniformState.updateCamera(shadowMap.passCameras[i]); var passState = shadowMap.passStates[i]; + shadowMap.updatePass(context, i); for (var j = 0; j < numberOfCommands; ++j) { var command = commands[j]; executeCommand(command, scene, context, passState, renderState, command.shadowCastProgram); diff --git a/Source/Scene/ShadowMap.js b/Source/Scene/ShadowMap.js index 6cfe0ecf7d54..309e9ed08855 100644 --- a/Source/Scene/ShadowMap.js +++ b/Source/Scene/ShadowMap.js @@ -24,6 +24,7 @@ define([ '../Core/PrimitiveType', '../Core/SphereOutlineGeometry', '../Renderer/ClearCommand', + '../Renderer/CubeMap', '../Renderer/Framebuffer', '../Renderer/PassState', '../Renderer/PixelDatatype', @@ -70,6 +71,7 @@ define([ PrimitiveType, SphereOutlineGeometry, ClearCommand, + CubeMap, Framebuffer, PassState, PixelDatatype, @@ -138,7 +140,7 @@ define([ this._shadowMapMatrix = new Matrix4(); this._shadowMapTexture = undefined; this._lightDirectionEC = new Cartesian3(); - this._lightPositionEC = new Cartesian3(); + this._lightPositionEC = new Cartesian4(); this._framebuffer = undefined; this._shadowMapSize = defaultValue(options.size, 1024); @@ -150,9 +152,10 @@ define([ this._isPointLight = defaultValue(options.isPointLight, false); this._radius = defaultValue(options.radius, 100.0); + this._usesCubeMap = true; this._cascadesEnabled = this._isPointLight ? false : defaultValue(options.cascadesEnabled, true); - this._numberOfCascades = !this._cascadesEnabled ? 0 :defaultValue(options.numberOfCascades, 4); + this._numberOfCascades = !this._cascadesEnabled ? 0 : defaultValue(options.numberOfCascades, 4); this._fitNearFar = true; this._distance = 1000.0; @@ -173,6 +176,7 @@ define([ this._numberOfPasses = numberOfPasses; this._passCameras = new Array(numberOfPasses); this._passStates = new Array(numberOfPasses); + this._passFaces = new Array(numberOfPasses); // For point shadows for (var i = 0; i < numberOfPasses; ++i) { this._passCameras[i] = new ShadowMapCamera(); @@ -190,6 +194,11 @@ define([ // Only enable the color mask if the depth texture extension is not supported this._usesDepthTexture = context.depthTexture; + + if (this._isPointLight && this._usesCubeMap) { + this._usesDepthTexture = false; + } + var colorMask = !this._usesDepthTexture; // For shadow casters @@ -341,9 +350,9 @@ define([ return this._isPointLight; } }, - radius : { + usesCubeMap : { get : function() { - return this._radius; + return this._usesCubeMap; } }, usesDepthTexture : { @@ -354,7 +363,12 @@ define([ }); function destroyFramebuffer(shadowMap) { - shadowMap._framebuffer = shadowMap._framebuffer && !shadowMap._framebuffer.isDestroyed() && shadowMap._framebuffer.destroy(); + shadowMap._framebuffer = shadowMap._framebuffer && shadowMap._framebuffer.destroy(); + + // Need to destroy cube map separately + if (shadowMap._isPointLight && shadowMap._usesCubeMap) { + shadowMap._shadowMapTexture = shadowMap._shadowMapTexture && shadowMap._shadowMapTexture.destroy(); + } } function createSampler() { @@ -413,9 +427,49 @@ define([ return framebuffer; } + function createFramebufferCube(shadowMap, context) { + var depthRenderbuffer = new Renderbuffer({ + context : context, + width : shadowMap._shadowMapSize, + height : shadowMap._shadowMapSize, + format : RenderbufferFormat.DEPTH_COMPONENT16 + }); + + var cubeMap = new CubeMap({ + context : context, + width : shadowMap._shadowMapSize, + height : shadowMap._shadowMapSize, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.UNSIGNED_BYTE, + sampler : createSampler() + }); + + var framebuffer = new Framebuffer({ + context : context, + depthRenderbuffer : depthRenderbuffer, + colorTextures : [cubeMap.positiveX] + }); + + shadowMap._passFaces[0] = cubeMap.negativeX; + shadowMap._passFaces[1] = cubeMap.negativeY; + shadowMap._passFaces[2] = cubeMap.negativeZ; + shadowMap._passFaces[3] = cubeMap.positiveX; + shadowMap._passFaces[4] = cubeMap.positiveY; + shadowMap._passFaces[5] = cubeMap.positiveZ; + + shadowMap._shadowMapTexture = cubeMap; + return framebuffer; + } + function createFramebuffer(shadowMap, context) { - var createFunction = (shadowMap._usesDepthTexture) ? createFramebufferDepth : createFramebufferColor; - var framebuffer = createFunction(shadowMap, context); + var framebuffer; + if (shadowMap._isPointLight && shadowMap._usesCubeMap) { + framebuffer = createFramebufferCube(shadowMap, context); + } else if (shadowMap._usesDepthTexture) { + framebuffer = createFramebufferDepth(shadowMap, context); + } else { + framebuffer = createFramebufferColor(shadowMap, context); + } shadowMap._framebuffer = framebuffer; shadowMap._clearCommand.framebuffer = framebuffer; for (var i = 0; i < shadowMap._numberOfPasses; ++i) { @@ -447,7 +501,17 @@ define([ var numberOfPasses = this._numberOfPasses; var textureSize = this._textureSize; - if (numberOfPasses === 1) { + 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; + } else if (numberOfPasses === 1) { // +----+ // | 1 | // +----+ @@ -499,17 +563,63 @@ define([ var height = size; if (!defined(shadowMap._debugShadowViewCommand)) { - var fs = - 'varying vec2 v_textureCoordinates; \n' + - 'void main() \n' + - '{ \n' + - - (shadowMap._usesDepthTexture ? - ' float shadow = texture2D(czm_sunShadowMapTexture, v_textureCoordinates).r; \n' : - ' float shadow = czm_unpackDepth(texture2D(czm_sunShadowMapTexture, v_textureCoordinates)); \n') + - - ' gl_FragColor = vec4(vec3(shadow), 1.0); \n' + - '} \n'; + var fs; + if (shadowMap._isPointLight && shadowMap._usesCubeMap) { + fs = 'varying vec2 v_textureCoordinates; \n' + + 'void main() \n' + + '{ \n' + + ' vec2 uv = v_textureCoordinates; \n' + + ' vec3 dir; \n' + + ' \n' + + ' if (uv.y < 0.5) { \n' + + ' if (uv.x < 0.333) { \n' + + ' dir.x = -1.0; \n' + + ' dir.y = uv.x * 6.0 - 1.0; \n' + + ' dir.z = uv.y * 4.0 - 1.0; \n' + + ' } \n' + + ' else if (uv.x < 0.666) { \n' + + ' dir.y = -1.0; \n' + + ' dir.x = uv.x * 6.0 - 3.0; \n' + + ' dir.z = uv.y * 4.0 - 1.0; \n' + + ' } \n' + + ' else { \n' + + ' dir.z = -1.0; \n' + + ' dir.x = uv.x * 6.0 - 5.0; \n' + + ' dir.y = uv.y * 4.0 - 1.0; \n' + + ' } \n' + + ' } else { \n' + + ' if (uv.x < 0.333) { \n' + + ' dir.x = 1.0; \n' + + ' dir.y = uv.x * 6.0 - 1.0; \n' + + ' dir.z = uv.y * 4.0 - 3.0; \n' + + ' } \n' + + ' else if (uv.x < 0.666) { \n' + + ' dir.y = 1.0; \n' + + ' dir.x = uv.x * 6.0 - 3.0; \n' + + ' dir.z = uv.y * 4.0 - 3.0; \n' + + ' } \n' + + ' else { \n' + + ' dir.z = 1.0; \n' + + ' dir.x = uv.x * 6.0 - 5.0; \n' + + ' dir.y = uv.y * 4.0 - 3.0; \n' + + ' } \n' + + ' } \n' + + ' \n' + + ' float shadow = czm_unpackDepth(textureCube(czm_sunShadowMapTextureCube, dir)); \n' + + ' gl_FragColor = vec4(vec3(shadow), 1.0); \n' + + '} \n'; + } else { + fs = 'varying vec2 v_textureCoordinates; \n' + + 'void main() \n' + + '{ \n' + + + (shadowMap._usesDepthTexture ? + ' float shadow = texture2D(czm_sunShadowMapTexture, v_textureCoordinates).r; \n' : + ' float shadow = czm_unpackDepth(texture2D(czm_sunShadowMapTexture, v_textureCoordinates)); \n') + + + ' gl_FragColor = vec4(vec3(shadow), 1.0); \n' + + '} \n'; + } // Set viewport now to avoid using a cached render state var renderState = RenderState.fromCache({ @@ -900,6 +1010,7 @@ define([ // Get the light position in eye coordinates Matrix4.multiplyByPoint(camera.viewMatrix, shadowMapCamera.positionWC, shadowMap._lightPositionEC); + shadowMap._lightPositionEC.w = shadowMap._radius; // Get the near and far of the scene camera var near; @@ -944,6 +1055,15 @@ define([ } }; + ShadowMap.prototype.updatePass = function(context, pass) { + if (this._isPointLight && this._usesCubeMap) { + this._framebuffer._attachTexture(context, WebGLConstants.COLOR_ATTACHMENT0, this._passFaces[pass]); + this._clearCommand.execute(context); + } else if (pass === 0) { + this._clearCommand.execute(context); + } + }; + ShadowMap.prototype.updateShadowMapMatrix = function(uniformState) { if (this._isPointLight) { // Point light shadows do not project into light space diff --git a/Source/Scene/ShadowMapShader.js b/Source/Scene/ShadowMapShader.js index e7ae35745a86..503d64968e05 100644 --- a/Source/Scene/ShadowMapShader.js +++ b/Source/Scene/ShadowMapShader.js @@ -15,18 +15,36 @@ define([ function ShadowMapShader() { } - ShadowMapShader.createShadowCastVertexShader = function(vs) { + ShadowMapShader.createShadowCastVertexShader = function(vs, frameState, positionVaryingName) { + var hasPositionVarying = defined(positionVaryingName) && (vs.indexOf(positionVaryingName) > -1); + var isPointLight = frameState.shadowMap.isPointLight; + var usesCubeMap = frameState.shadowMap.usesCubeMap; + + if (isPointLight && usesCubeMap && !hasPositionVarying) { + vs = ShaderSource.replaceMain(vs, 'czm_shadow_main'); + vs += + 'varying vec3 v_positionEC; \n' + + 'void main() \n' + + '{ \n' + + ' czm_shadow_main(); \n' + + ' v_positionEC = (czm_inverseProjection * gl_Position).xyz; \n' + + '}'; + } + return vs; }; - ShadowMapShader.createShadowCastFragmentShader = function(fs, frameState, opaque) { + ShadowMapShader.createShadowCastFragmentShader = function(fs, frameState, opaque, positionVaryingName) { // TODO : is there an easy way to tell if a model or primitive is opaque before going here? + var hasPositionVarying = defined(positionVaryingName) && (fs.indexOf(positionVaryingName) > -1); + positionVaryingName = hasPositionVarying ? positionVaryingName : 'v_positionEC'; + var isPointLight = frameState.shadowMap.isPointLight; + var usesCubeMap = frameState.shadowMap.usesCubeMap; var usesDepthTexture = frameState.shadowMap.usesDepthTexture; + if (opaque) { fs = 'void main() \n' + - '{ \n' + - ' gl_FragColor = ' + (usesDepthTexture ? 'vec4(1.0)' : 'czm_packDepth(gl_FragCoord.z)') + '; \n' + - '}'; + '{ \n'; } else { fs = ShaderSource.replaceMain(fs, 'czm_shadow_main'); fs += @@ -35,9 +53,24 @@ define([ ' czm_shadow_main(); \n' + ' if (gl_FragColor.a == 0.0) { \n' + ' discard; \n' + - ' } \n' + - ' gl_FragColor = ' + (usesDepthTexture ? 'vec4(1.0)' : 'czm_packDepth(gl_FragCoord.z)') + '; \n' + - '}'; + ' } \n'; + } + + if (isPointLight && usesCubeMap) { + fs += + 'float distance = length(' + positionVaryingName + '); \n' + + 'distance /= czm_sunShadowMapLightPositionEC.w; // radius \n' + + 'gl_FragColor = czm_packDepth(distance); \n'; + } else if (usesDepthTexture) { + fs += 'gl_FragColor = vec4(1.0); \n'; + } else { + fs += 'gl_FragColor = czm_packDepth(gl_FragCoord.z); \n'; + } + + fs += '}'; + + if (isPointLight && usesCubeMap && !hasPositionVarying) { + fs = 'varying vec3 v_positionEC; \n' + fs; } return fs; @@ -64,6 +97,7 @@ define([ var hasPositionVarying = defined(positionVaryingName) && (fs.indexOf(positionVaryingName) > -1); var usesDepthTexture = frameState.shadowMap.usesDepthTexture; var isPointLight = frameState.shadowMap.isPointLight; + var usesCubeMap = frameState.shadowMap.usesCubeMap; var hasCascades = frameState.shadowMap.numberOfCascades > 1; var debugVisualizeCascades = frameState.shadowMap.debugVisualizeCascades; @@ -114,24 +148,29 @@ define([ '} \n' + ' \n' + + + (isPointLight && usesCubeMap ? + 'float sampleTexture(vec3 d) \n' + + '{ \n' + + ' return czm_unpackDepth(textureCube(czm_sunShadowMapTextureCube, d)); \n' + + '} \n' : 'float sampleTexture(vec2 uv) \n' + '{ \n' + - (usesDepthTexture ? ' return texture2D(czm_sunShadowMapTexture, uv).r; \n' : ' return czm_unpackDepth(texture2D(czm_sunShadowMapTexture, uv)); \n') + '} \n' + - ' \n' + + ' \n') + - (isPointLight ? - 'float getVisibility(vec3 shadowPosition, vec3 lightDirectionEC, vec2 faceUV)' : - 'float getVisibility(vec3 shadowPosition, vec3 lightDirectionEC)') + + (isPointLight ? (usesCubeMap ? + 'float getVisibility(vec3 uv, float depth, vec3 lightDirectionEC)' : + 'float getVisibility(vec2 uv, float depth, vec3 lightDirectionEC, vec2 faceUV)') : + 'float getVisibility(vec2 uv, float depth, vec3 lightDirectionEC)') + '{ \n' + - ' float depth = shadowPosition.z; \n' + - ' float shadowDepth = sampleTexture(shadowPosition.xy); \n' + + ' float shadowDepth = sampleTexture(uv); \n' + - (isPointLight ? + (isPointLight && !usesCubeMap ? ' vec4 shadowCoord = vec4(faceUV, shadowDepth, 1.0); \n' + ' shadowCoord = shadowCoord * 2.0 - 1.0; \n' + ' shadowCoord = czm_sunShadowMapMatrix * shadowCoord; \n' + @@ -178,15 +217,21 @@ define([ ' vec3 positionEC = ' + positionVaryingName + '; \n' : ' vec3 positionEC = czm_windowToEyeCoordinates(gl_FragCoord).xyz; \n') + - ' vec3 directionEC = positionEC - czm_sunShadowMapLightPositionEC; \n' + + ' vec3 directionEC = positionEC - czm_sunShadowMapLightPositionEC.xyz; \n' + ' float distance = length(directionEC); \n' + - ' if (distance > czm_sunShadowMapRadius) { \n' + + ' float radius = czm_sunShadowMapLightPositionEC.w; \n' + + ' if (distance > radius) { \n' + ' return; \n' + ' } \n' + ' vec3 directionWC = czm_inverseViewRotation * directionEC; \n' + + + (usesCubeMap ? + ' float visibility = getVisibility(directionWC, distance / radius, -directionEC); \n' : + ' vec2 faceUV; \n' + ' vec2 uv = directionToUV(directionWC, faceUV); \n' + - ' float visibility = getVisibility(vec3(uv, distance), -directionEC, faceUV); \n' + + ' float visibility = getVisibility(uv, distance, -directionEC, faceUV); \n') + + ' gl_FragColor.rgb *= visibility; \n' + '} \n'; } else { @@ -222,7 +267,7 @@ define([ ' \n' + ' // Apply shadowing \n' + - ' float visibility = getVisibility(shadowPosition, czm_sunShadowMapLightDirectionEC); \n' + + ' float visibility = getVisibility(shadowPosition.xy, shadowPosition.z, czm_sunShadowMapLightDirectionEC); \n' + ' gl_FragColor.rgb *= visibility; \n' + '} \n'; }