Source: Scene/ShadowMap.js

/*global define*/
define([
        '../Core/BoundingRectangle',
        '../Core/BoundingSphere',
        '../Core/BoxOutlineGeometry',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/clone',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/combine',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/FeatureDetection',
        '../Core/GeometryInstance',
        '../Core/Intersect',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/PixelFormat',
        '../Core/PrimitiveType',
        '../Core/Quaternion',
        '../Core/SphereOutlineGeometry',
        '../Renderer/ClearCommand',
        '../Renderer/ContextLimits',
        '../Renderer/CubeMap',
        '../Renderer/DrawCommand',
        '../Renderer/Framebuffer',
        '../Renderer/PassState',
        '../Renderer/PixelDatatype',
        '../Renderer/Renderbuffer',
        '../Renderer/RenderbufferFormat',
        '../Renderer/RenderState',
        '../Renderer/Sampler',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/Texture',
        '../Renderer/TextureMagnificationFilter',
        '../Renderer/TextureMinificationFilter',
        '../Renderer/TextureWrap',
        '../Renderer/WebGLConstants',
        './Camera',
        './CullFace',
        './CullingVolume',
        './DebugCameraPrimitive',
        './OrthographicFrustum',
        './Pass',
        './PerInstanceColorAppearance',
        './PerspectiveFrustum',
        './Primitive',
        './ShadowMapShader'
    ], function(
        BoundingRectangle,
        BoundingSphere,
        BoxOutlineGeometry,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        clone,
        Color,
        ColorGeometryInstanceAttribute,
        combine,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        FeatureDetection,
        GeometryInstance,
        Intersect,
        CesiumMath,
        Matrix4,
        PixelFormat,
        PrimitiveType,
        Quaternion,
        SphereOutlineGeometry,
        ClearCommand,
        ContextLimits,
        CubeMap,
        DrawCommand,
        Framebuffer,
        PassState,
        PixelDatatype,
        Renderbuffer,
        RenderbufferFormat,
        RenderState,
        Sampler,
        ShaderProgram,
        ShaderSource,
        Texture,
        TextureMagnificationFilter,
        TextureMinificationFilter,
        TextureWrap,
        WebGLConstants,
        Camera,
        CullFace,
        CullingVolume,
        DebugCameraPrimitive,
        OrthographicFrustum,
        Pass,
        PerInstanceColorAppearance,
        PerspectiveFrustum,
        Primitive,
        ShadowMapShader) {
    'use strict';

    /**
     * Creates a shadow map from the provided light camera.
     *
     * The normalOffset bias pushes the shadows forward slightly, and may be disabled
     * for applications that require ultra precise shadows.
     *
     * @alias ShadowMap
     * @constructor
     *
     * @param {Object} options An object containing the following properties:
     * @param {Context} options.context The context in which to create the shadow map.
     * @param {Camera} options.lightCamera A camera representing the light source.
     * @param {Boolean} [options.enabled=true] Whether the shadow map is enabled.
     * @param {Boolean} [options.isPointLight=false] Whether the light source is a point light. Point light shadows do not use cascades.
     * @param {Boolean} [options.pointLightRadius=100.0] Radius of the point light.
     * @param {Boolean} [options.cascadesEnabled=true] Use multiple shadow maps to cover different partitions of the view frustum.
     * @param {Number} [options.numberOfCascades=4] The number of cascades to use for the shadow map. Supported values are one and four.
     * @param {Number} [options.maximumDistance=5000.0] The maximum distance used for generating cascaded shadows. Lower values improve shadow quality.
     * @param {Number} [options.size=2048] The width and height, in pixels, of each shadow map.
     * @param {Boolean} [options.softShadows=false] Whether percentage-closer-filtering is enabled for producing softer shadows.
     * @param {Number} [options.darkness=0.3] The shadow darkness.
     *
     * @exception {DeveloperError} Only one or four cascades are supported.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Shadows.html|Cesium Sandcastle Shadows Demo}
     */
    function ShadowMap(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var context = options.context;

        //>>includeStart('debug', pragmas.debug);
        if (!defined(context)) {
            throw new DeveloperError('context is required.');
        }
        if (!defined(options.lightCamera)) {
            throw new DeveloperError('lightCamera is required.');
        }
        if (defined(options.numberOfCascades) && ((options.numberOfCascades !== 1) && (options.numberOfCascades !== 4))) {
            throw new DeveloperError('Only one or four cascades are supported.');
        }
        //>>includeEnd('debug');

        this._enabled = defaultValue(options.enabled, true);
        this._softShadows = defaultValue(options.softShadows, false);
        this.dirty = true;

        /**
         * Specifies whether the shadow map originates from a light source. Shadow maps that are used for analytical
         * purposes should set this to false so as not to affect scene rendering.
         *
         * @private
         */
        this.fromLightSource = defaultValue(options.fromLightSource, true);

        /**
         * Determines the darkness of the shadows.
         *
         * @type {Number}
         * @default 0.3
         */
        this.darkness = defaultValue(options.darkness, 0.3);
        this._darkness = this.darkness;

        /**
         * Determines the maximum distance of the shadow map. Only applicable for cascaded shadows. Larger distances may result in lower quality shadows.
         *
         * @type {Number}
         * @default 5000.0
         */
        this.maximumDistance = defaultValue(options.maximumDistance, 5000.0);

        this._outOfView = false;
        this._outOfViewPrevious = false;
        this._needsUpdate = true;

        // In IE11 polygon offset is not functional.
        // TODO : Also disabled for Chrome (ANGLE) temporarily. Re-enable once https://github.com/AnalyticalGraphicsInc/cesium/issues/4560 is resolved.
        var polygonOffsetSupported = true;
        if (FeatureDetection.isInternetExplorer() || (FeatureDetection.isChrome() && FeatureDetection.isWindows())) {
            polygonOffsetSupported = false;
        }
        this._polygonOffsetSupported = polygonOffsetSupported;

        this._terrainBias = {
            polygonOffset : polygonOffsetSupported,
            polygonOffsetFactor : 1.1,
            polygonOffsetUnits : 4.0,
            normalOffset : true,
            normalOffsetScale : 0.5,
            normalShading : true,
            normalShadingSmooth : 0.3,
            depthBias : 0.0001
        };

        this._primitiveBias = {
            polygonOffset : polygonOffsetSupported,
            polygonOffsetFactor : 1.1,
            polygonOffsetUnits : 4.0,
            normalOffset : true,
            normalOffsetScale : 0.1,
            normalShading : true,
            normalShadingSmooth : 0.05,
            depthBias : 0.00002
        };

        this._pointBias = {
            polygonOffset : false,
            polygonOffsetFactor : 1.1,
            polygonOffsetUnits : 4.0,
            normalOffset : false,
            normalOffsetScale : 0.0,
            normalShading : true,
            normalShadingSmooth : 0.1,
            depthBias : 0.0005
        };

        // Framebuffer resources
        this._depthAttachment = undefined;
        this._colorAttachment = undefined;

        // Uniforms
        this._shadowMapMatrix = new Matrix4();
        this._shadowMapTexture = undefined;
        this._lightDirectionEC = new Cartesian3();
        this._lightPositionEC = new Cartesian4();
        this._distance = 0.0;

        this._lightCamera = options.lightCamera;
        this._shadowMapCamera = new ShadowMapCamera();
        this._shadowMapCullingVolume = undefined;
        this._sceneCamera = undefined;
        this._boundingSphere = new BoundingSphere();

        this._isPointLight = defaultValue(options.isPointLight, false);
        this._pointLightRadius = defaultValue(options.pointLightRadius, 100.0);

        this._cascadesEnabled = this._isPointLight ? false : defaultValue(options.cascadesEnabled, true);
        this._numberOfCascades = !this._cascadesEnabled ? 0 : defaultValue(options.numberOfCascades, 4);
        this._fitNearFar = true;
        this._maximumCascadeDistances = [25.0, 150.0, 700.0, Number.MAX_VALUE];

        this._textureSize = new Cartesian2();

        this._isSpotLight = false;
        if (this._cascadesEnabled) {
            // Cascaded shadows are always orthographic. The frustum dimensions are calculated on the fly.
            this._shadowMapCamera.frustum = new OrthographicFrustum();
        } else if (defined(this._lightCamera.frustum.fov)) {
            // If the light camera uses a perspective frustum, then the light source is a spot light
            this._isSpotLight = true;
        }

        // Uniforms
        this._cascadeSplits = [new Cartesian4(), new Cartesian4()];
        this._cascadeMatrices = [new Matrix4(), new Matrix4(), new Matrix4(), new Matrix4()];
        this._cascadeDistances = new Cartesian4();

        var numberOfPasses;
        if (this._isPointLight) {
            numberOfPasses = 6; // One shadow map for each direction
        } else if (!this._cascadesEnabled) {
            numberOfPasses = 1;
        } else {
            numberOfPasses = this._numberOfCascades;
        }

        this._passes = new Array(numberOfPasses);
        for (var i = 0; i < numberOfPasses; ++i) {
            this._passes[i] = new ShadowPass(context);
        }

        this.debugShow = false;
        this.debugFreezeFrame = false;
        this._debugFreezeFrame = false;
        this._debugCascadeColors = false;
        this._debugLightFrustum = undefined;
        this._debugCameraFrustum = undefined;
        this._debugCascadeFrustums = new Array(this._numberOfCascades);
        this._debugShadowViewCommand = undefined;

        this._usesDepthTexture = context.depthTexture;

        if (this._isPointLight) {
            this._usesDepthTexture = false;
        }

        // Create render states for shadow casters
        this._primitiveRenderState = undefined;
        this._terrainRenderState = undefined;
        this._pointRenderState = undefined;
        createRenderStates(this);

        // For clearing the shadow map texture every frame
        this._clearCommand = new ClearCommand({
            depth : 1.0,
            color : new Color()
        });

        this._clearPassState = new PassState(context);

        this._size = defaultValue(options.size, 2048);
        this.size = this._size;
    }

    /**
     * Global maximum shadow distance used to prevent far off receivers from extending
     * the shadow far plane. This helps set a tighter near/far when viewing objects from space.
     *
     * @private
     */
    ShadowMap.MAXIMUM_DISTANCE = 20000.0;

    function ShadowPass(context) {
        this.camera = new ShadowMapCamera();
        this.passState = new PassState(context);
        this.framebuffer = undefined;
        this.textureOffsets = undefined;
        this.commandList = [];
        this.cullingVolume = undefined;
    }

    function createRenderState(colorMask, bias) {
        return RenderState.fromCache({
            cull : {
                enabled : true,
                face : CullFace.BACK
            },
            depthTest : {
                enabled : true
            },
            colorMask : {
                red : colorMask,
                green : colorMask,
                blue : colorMask,
                alpha : colorMask
            },
            depthMask : true,
            polygonOffset : {
                enabled : bias.polygonOffset,
                factor : bias.polygonOffsetFactor,
                units : bias.polygonOffsetUnits
            }
        });
    }

    function createRenderStates(shadowMap) {
        // Enable the color mask if the shadow map is backed by a color texture, e.g. when depth textures aren't supported
        var colorMask = !shadowMap._usesDepthTexture;
        shadowMap._primitiveRenderState = createRenderState(colorMask, shadowMap._primitiveBias);
        shadowMap._terrainRenderState = createRenderState(colorMask, shadowMap._terrainBias);
        shadowMap._pointRenderState = createRenderState(colorMask, shadowMap._pointBias);
    }

    /**
     * @private
     */
    ShadowMap.prototype.debugCreateRenderStates = function() {
        createRenderStates(this);
    };

    defineProperties(ShadowMap.prototype, {
        /**
         * Determines if the shadow map will be shown.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @default true
         */
        enabled : {
            get : function() {
                return this._enabled;
            },
            set : function(value) {
                this.dirty = this._enabled !== value;
                this._enabled = value;
            }
        },

        /**
         * Determines if soft shadows are enabled. Uses pcf filtering which requires more texture reads and may hurt performance.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @default false
         */
        softShadows : {
            get : function() {
                return this._softShadows;
            },
            set : function(value) {
                this.dirty = this._softShadows !== value;
                this._softShadows = value;
            }
        },

        /**
         * The width and height, in pixels, of each shadow map.
         *
         * @memberof ShadowMap.prototype
         * @type {Number}
         * @default 2048
         */
        size : {
            get : function() {
                return this._size;
            },
            set : function(value) {
                resize(this, value);
            }
        },

        /**
         * Whether the shadow map is out of view of the scene camera.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @readonly
         * @private
         */
        outOfView : {
            get : function() {
                return this._outOfView;
            }
        },

        /**
         * The culling volume of the shadow frustum.
         *
         * @memberof ShadowMap.prototype
         * @type {CullingVolume}
         * @readonly
         * @private
         */
        shadowMapCullingVolume : {
            get : function() {
                return this._shadowMapCullingVolume;
            }
        },

        /**
         * The passes used for rendering shadows. Each face of a point light or each cascade for a cascaded shadow map is a separate pass.
         *
         * @memberof ShadowMap.prototype
         * @type {ShadowPass[]}
         * @readonly
         * @private
         */
        passes : {
            get : function() {
                return this._passes;
            }
        },

        /**
         * Whether the light source is a point light.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @readonly
         * @private
         */
        isPointLight : {
            get : function() {
                return this._isPointLight;
            }
        },

        /**
         * Debug option for visualizing the cascades by color.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @default false
         * @private
         */
        debugCascadeColors : {
            get : function() {
                return this._debugCascadeColors;
            },
            set : function(value) {
                this.dirty = this._debugCascadeColors !== value;
                this._debugCascadeColors = value;
            }
        }
    });

    function destroyFramebuffer(shadowMap) {
        var length = shadowMap._passes.length;
        for (var i = 0; i < length; ++i) {
            var pass = shadowMap._passes[i];
            var framebuffer = pass.framebuffer;
            if (defined(framebuffer) && !framebuffer.isDestroyed()) {
                framebuffer.destroy();
            }
            pass.framebuffer = undefined;
        }

        // Destroy the framebuffer attachments
        shadowMap._depthAttachment = shadowMap._depthAttachment && shadowMap._depthAttachment.destroy();
        shadowMap._colorAttachment = shadowMap._colorAttachment && shadowMap._colorAttachment.destroy();
    }

    function createSampler() {
        return new Sampler({
            wrapS : TextureWrap.CLAMP_TO_EDGE,
            wrapT : TextureWrap.CLAMP_TO_EDGE,
            minificationFilter : TextureMinificationFilter.NEAREST,
            magnificationFilter : TextureMagnificationFilter.NEAREST
        });
    }

    function createFramebufferColor(shadowMap, context) {
        var depthRenderbuffer = new Renderbuffer({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            format : RenderbufferFormat.DEPTH_COMPONENT16
        });

        var colorTexture = new Texture({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
            sampler : createSampler()
        });

        var framebuffer = new Framebuffer({
            context : context,
            depthRenderbuffer : depthRenderbuffer,
            colorTextures : [colorTexture],
            destroyAttachments : false
        });

        var length = shadowMap._passes.length;
        for (var i = 0; i < length; ++i) {
            var pass = shadowMap._passes[i];
            pass.framebuffer = framebuffer;
            pass.passState.framebuffer = framebuffer;
        }

        shadowMap._shadowMapTexture = colorTexture;
        shadowMap._depthAttachment = depthRenderbuffer;
        shadowMap._colorAttachment = colorTexture;
    }

    function createFramebufferDepth(shadowMap, context) {
        var depthStencilTexture = new Texture({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            pixelFormat : PixelFormat.DEPTH_STENCIL,
            pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8,
            sampler : createSampler()
        });

        var framebuffer = new Framebuffer({
            context : context,
            depthStencilTexture : depthStencilTexture,
            destroyAttachments : false
        });

        var length = shadowMap._passes.length;
        for (var i = 0; i < length; ++i) {
            var pass = shadowMap._passes[i];
            pass.framebuffer = framebuffer;
            pass.passState.framebuffer = framebuffer;
        }

        shadowMap._shadowMapTexture = depthStencilTexture;
        shadowMap._depthAttachment = depthStencilTexture;
    }

    function createFramebufferCube(shadowMap, context) {
        var depthRenderbuffer = new Renderbuffer({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            format : RenderbufferFormat.DEPTH_COMPONENT16
        });

        var cubeMap = new CubeMap({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
            sampler : createSampler()
        });

        var faces = [cubeMap.negativeX, cubeMap.negativeY, cubeMap.negativeZ, cubeMap.positiveX, cubeMap.positiveY, cubeMap.positiveZ];

        for (var i = 0; i < 6; ++i) {
            var framebuffer = new Framebuffer({
                context : context,
                depthRenderbuffer : depthRenderbuffer,
                colorTextures : [faces[i]],
                destroyAttachments : false
            });
            var pass = shadowMap._passes[i];
            pass.framebuffer = framebuffer;
            pass.passState.framebuffer = framebuffer;
        }

        shadowMap._shadowMapTexture = cubeMap;
        shadowMap._depthAttachment = depthRenderbuffer;
        shadowMap._colorAttachment = cubeMap;
    }

    function createFramebuffer(shadowMap, context) {
        if (shadowMap._isPointLight) {
            createFramebufferCube(shadowMap, context);
        } else if (shadowMap._usesDepthTexture) {
            createFramebufferDepth(shadowMap, context);
        } else {
            createFramebufferColor(shadowMap, context);
        }
    }

    function checkFramebuffer(shadowMap, context) {
        // Attempt to make an FBO with only a depth texture. If it fails, fallback to a color texture.
        if (shadowMap._usesDepthTexture && (shadowMap._passes[0].framebuffer.status !== WebGLConstants.FRAMEBUFFER_COMPLETE)) {
            shadowMap._usesDepthTexture = false;
            createRenderStates(shadowMap);
            destroyFramebuffer(shadowMap);
            createFramebuffer(shadowMap, context);
        }
    }

    function updateFramebuffer(shadowMap, context) {
        if (!defined(shadowMap._passes[0].framebuffer) || (shadowMap._shadowMapTexture.width !== shadowMap._textureSize.x)) {
            destroyFramebuffer(shadowMap);
            createFramebuffer(shadowMap, context);
            checkFramebuffer(shadowMap, context);
            clearFramebuffer(shadowMap, context);
        }
    }

    function clearFramebuffer(shadowMap, context, shadowPass) {
        shadowPass = defaultValue(shadowPass, 0);
        if (shadowMap._isPointLight || (shadowPass === 0)) {
            shadowMap._clearCommand.framebuffer = shadowMap._passes[shadowPass].framebuffer;
            shadowMap._clearCommand.execute(context, shadowMap._clearPassState);
        }
    }

    function resize(shadowMap, size) {
        shadowMap._size = size;
        var passes = shadowMap._passes;
        var numberOfPasses = passes.length;
        var textureSize = shadowMap._textureSize;

        if (shadowMap._isPointLight) {
            size = (ContextLimits.maximumCubeMapSize >= size) ? size : ContextLimits.maximumCubeMapSize;
            textureSize.x = size;
            textureSize.y = size;
            var faceViewport = new BoundingRectangle(0, 0, size, size);
            passes[0].passState.viewport = faceViewport;
            passes[1].passState.viewport = faceViewport;
            passes[2].passState.viewport = faceViewport;
            passes[3].passState.viewport = faceViewport;
            passes[4].passState.viewport = faceViewport;
            passes[5].passState.viewport = faceViewport;
        } else if (numberOfPasses === 1) {
            // +----+
            // |  1 |
            // +----+
            size = (ContextLimits.maximumTextureSize >= size) ? size : ContextLimits.maximumTextureSize;
            textureSize.x = size;
            textureSize.y = size;
            passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
        } else if (numberOfPasses === 4) {
            // +----+----+
            // |  3 |  4 |
            // +----+----+
            // |  1 |  2 |
            // +----+----+
            size = (ContextLimits.maximumTextureSize >= size * 2) ? size : ContextLimits.maximumTextureSize / 2;
            textureSize.x = size * 2;
            textureSize.y = size * 2;
            passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
            passes[1].passState.viewport = new BoundingRectangle(size, 0, size, size);
            passes[2].passState.viewport = new BoundingRectangle(0, size, size, size);
            passes[3].passState.viewport = new BoundingRectangle(size, size, size, size);
        }

        // Update clear pass state
        shadowMap._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 pass = passes[i];
            var viewport = pass.passState.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;
            pass.textureOffsets = 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();

    function createDebugShadowViewCommand(shadowMap, context) {
        var fs;
        if (shadowMap._isPointLight) {
            fs = 'uniform samplerCube shadowMap_textureCube; \n' +
                 'varying vec2 v_textureCoordinates; \n' +
                 'void main() \n' +
                 '{ \n' +
                 '    vec2 uv = v_textureCoordinates; \n' +
                 '    vec3 dir; \n' +
                 ' \n' +
                 '    if (uv.y < 0.5) \n' +
                 '    { \n' +
                 '        if (uv.x < 0.333) \n' +
                 '        { \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' +
                 '        { \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' +
                 '        { \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' +
                 '    } \n' +
                 '    else \n' +
                 '    { \n' +
                 '        if (uv.x < 0.333) \n' +
                 '        { \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' +
                 '        { \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' +
                 '        { \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(shadowMap_textureCube, dir)); \n' +
                 '    gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
                 '} \n';
        } else {
            fs = 'uniform sampler2D shadowMap_texture; \n' +
                 'varying vec2 v_textureCoordinates; \n' +
                 'void main() \n' +
                 '{ \n' +

                 (shadowMap._usesDepthTexture ?
                 '    float shadow = texture2D(shadowMap_texture, v_textureCoordinates).r; \n' :
                 '    float shadow = czm_unpackDepth(texture2D(shadowMap_texture, v_textureCoordinates)); \n') +

                 '    gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
                 '} \n';
        }

        var drawCommand = context.createViewportQuadCommand(fs, {
            uniformMap : {
                shadowMap_texture : function() {
                    return shadowMap._shadowMapTexture;
                },
                shadowMap_textureCube : function() {
                    return shadowMap._shadowMapTexture;
                }
            }
        });
        drawCommand.pass = Pass.OVERLAY;
        return drawCommand;
    }

    function updateDebugShadowViewCommand(shadowMap, frameState) {
        // Draws the shadow map on the bottom-right corner of the screen
        var context = frameState.context;
        var screenWidth = frameState.context.drawingBufferWidth;
        var screenHeight = frameState.context.drawingBufferHeight;
        var size = Math.min(screenWidth, screenHeight) * 0.3;

        var viewport = scratchViewport;
        viewport.x = screenWidth - size;
        viewport.y = 0;
        viewport.width = size;
        viewport.height = size;

        var debugCommand = shadowMap._debugShadowViewCommand;
        if (!defined(debugCommand)) {
            debugCommand = createDebugShadowViewCommand(shadowMap, context);
            shadowMap._debugShadowViewCommand = debugCommand;
        }

        // Get a new RenderState for the updated viewport size
        if (!defined(debugCommand.renderState) || !BoundingRectangle.equals(debugCommand.renderState.viewport, viewport)) {
            debugCommand.renderState = RenderState.fromCache({
                viewport : BoundingRectangle.clone(viewport)
            });
        }

        frameState.commandList.push(shadowMap._debugShadowViewCommand);
    }

    var frustumCornersNDC = new Array(8);
    frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, -1.0, 1.0);
    frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, -1.0, 1.0);
    frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, -1.0, 1.0);
    frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, -1.0, 1.0);
    frustumCornersNDC[4] = new Cartesian4(-1.0, -1.0, 1.0, 1.0);
    frustumCornersNDC[5] = new Cartesian4(1.0, -1.0, 1.0, 1.0);
    frustumCornersNDC[6] = new Cartesian4(1.0, 1.0, 1.0, 1.0);
    frustumCornersNDC[7] = new Cartesian4(-1.0, 1.0, 1.0, 1.0);

    var scratchMatrix = new Matrix4();
    var scratchFrustumCorners = new Array(8);
    for (var i = 0; i < 8; ++i) {
        scratchFrustumCorners[i] = new Cartesian4();
    }

    function createDebugPointLight(modelMatrix, color) {
        var box = new GeometryInstance({
            geometry : new BoxOutlineGeometry({
                minimum : new Cartesian3(-0.5, -0.5, -0.5),
                maximum : new Cartesian3(0.5, 0.5, 0.5)
            }),
            attributes : {
                color : ColorGeometryInstanceAttribute.fromColor(color)
            }
        });

        var sphere = new GeometryInstance({
            geometry : new SphereOutlineGeometry({
                radius : 0.5
            }),
            attributes : {
                color : ColorGeometryInstanceAttribute.fromColor(color)
            }
        });

        return new Primitive({
            geometryInstances : [box, sphere],
            appearance : new PerInstanceColorAppearance({
                translucent : false,
                flat : true
            }),
            asynchronous : false,
            modelMatrix : modelMatrix
        });
    }

    var debugOutlineColors = [Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA];
    var scratchScale = new Cartesian3();

    function applyDebugSettings(shadowMap, frameState) {
        updateDebugShadowViewCommand(shadowMap, frameState);

        var enterFreezeFrame = shadowMap.debugFreezeFrame && !shadowMap._debugFreezeFrame;
        shadowMap._debugFreezeFrame = shadowMap.debugFreezeFrame;

        // Draw scene camera in freeze frame mode
        if (shadowMap.debugFreezeFrame) {
            if (enterFreezeFrame) {
                // Recreate debug camera when entering freeze frame mode
                shadowMap._debugCameraFrustum = shadowMap._debugCameraFrustum && shadowMap._debugCameraFrustum.destroy();
                shadowMap._debugCameraFrustum = new DebugCameraPrimitive({
                    camera : shadowMap._sceneCamera,
                    color : Color.CYAN,
                    updateOnChange : false
                });
            }
            shadowMap._debugCameraFrustum.update(frameState);
        }

        if (shadowMap._cascadesEnabled) {
            // Draw cascades only in freeze frame mode
            if (shadowMap.debugFreezeFrame) {
                if (enterFreezeFrame) {
                    // Recreate debug frustum when entering freeze frame mode
                    shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
                    shadowMap._debugLightFrustum = new DebugCameraPrimitive({
                        camera : shadowMap._shadowMapCamera,
                        color : Color.YELLOW,
                        updateOnChange : false
                    });
                }
                shadowMap._debugLightFrustum.update(frameState);

                for (var i = 0; i < shadowMap._numberOfCascades; ++i) {
                    if (enterFreezeFrame) {
                        // Recreate debug frustum when entering freeze frame mode
                        shadowMap._debugCascadeFrustums[i] = shadowMap._debugCascadeFrustums[i] && shadowMap._debugCascadeFrustums[i].destroy();
                        shadowMap._debugCascadeFrustums[i] = new DebugCameraPrimitive({
                            camera : shadowMap._passes[i].camera,
                            color : debugOutlineColors[i],
                            updateOnChange : false
                        });
                    }
                    shadowMap._debugCascadeFrustums[i].update(frameState);
                }
            }
        } else if (shadowMap._isPointLight) {
            if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
                var translation = shadowMap._shadowMapCamera.positionWC;
                var rotation = Quaternion.IDENTITY;
                var uniformScale = shadowMap._pointLightRadius * 2.0;
                var scale = Cartesian3.fromElements(uniformScale, uniformScale, uniformScale, scratchScale);
                var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(translation, rotation, scale, scratchMatrix);

                shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
                shadowMap._debugLightFrustum = createDebugPointLight(modelMatrix, Color.YELLOW);
            }
            shadowMap._debugLightFrustum.update(frameState);
        } else {
            if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
                shadowMap._debugLightFrustum = new DebugCameraPrimitive({
                    camera : shadowMap._shadowMapCamera,
                    color : Color.YELLOW,
                    updateOnChange : false
                });
            }
            shadowMap._debugLightFrustum.update(frameState);
        }
    }

    function ShadowMapCamera() {
        this.viewMatrix = new Matrix4();
        this.inverseViewMatrix = new Matrix4();
        this.frustum = undefined;
        this.positionCartographic = new Cartographic();
        this.positionWC = new Cartesian3();
        this.directionWC = Cartesian3.clone(Cartesian3.UNIT_Z);
        this.upWC = Cartesian3.clone(Cartesian3.UNIT_Y);
        this.rightWC = Cartesian3.clone(Cartesian3.UNIT_X);
        this.viewProjectionMatrix = new Matrix4();
    }

    ShadowMapCamera.prototype.clone = function(camera) {
        Matrix4.clone(camera.viewMatrix, this.viewMatrix);
        Matrix4.clone(camera.inverseViewMatrix, this.inverseViewMatrix);
        this.frustum = camera.frustum.clone(this.frustum);
        Cartographic.clone(camera.positionCartographic, this.positionCartographic);
        Cartesian3.clone(camera.positionWC, this.positionWC);
        Cartesian3.clone(camera.directionWC, this.directionWC);
        Cartesian3.clone(camera.upWC, this.upWC);
        Cartesian3.clone(camera.rightWC, this.rightWC);
    };

    // Converts from NDC space to texture space
    var scaleBiasMatrix = new Matrix4(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0);

    ShadowMapCamera.prototype.getViewProjection = function() {
        var view = this.viewMatrix;
        var projection = this.frustum.projectionMatrix;
        Matrix4.multiply(projection, view, this.viewProjectionMatrix);
        Matrix4.multiply(scaleBiasMatrix, this.viewProjectionMatrix, this.viewProjectionMatrix);
        return this.viewProjectionMatrix;
    };

    var scratchSplits = new Array(5);
    var scratchFrustum = new PerspectiveFrustum();
    var scratchCascadeDistances = new Array(4);

    function computeCascades(shadowMap, frameState) {
        var shadowMapCamera = shadowMap._shadowMapCamera;
        var sceneCamera = shadowMap._sceneCamera;
        var cameraNear = sceneCamera.frustum.near;
        var cameraFar = sceneCamera.frustum.far;
        var numberOfCascades = shadowMap._numberOfCascades;

        // Split cascades. Use a mix of linear and log splits.
        var i;
        var range = cameraFar - cameraNear;
        var ratio = cameraFar / cameraNear;

        var lambda = 0.9;
        var clampCascadeDistances = false;

        // When the camera is close to a relatively small model, provide more detail in the closer cascades.
        // If the camera is near or inside a large model, such as the root tile of a city, then use the default values.
        // To get the most accurate cascade splits we would need to find the min and max values from the depth texture.
        if (frameState.shadowHints.closestObjectSize < 200.0) {
            clampCascadeDistances = true;
            lambda = 0.9;
        }

        var cascadeDistances = scratchCascadeDistances;
        var splits = scratchSplits;
        splits[0] = cameraNear;
        splits[numberOfCascades] = cameraFar;

        // Find initial splits
        for (i = 0; i < numberOfCascades; ++i) {
            var p = (i + 1) / numberOfCascades;
            var logScale = cameraNear * Math.pow(ratio, p);
            var uniformScale = cameraNear + range * p;
            var split = CesiumMath.lerp(uniformScale, logScale, lambda);
            splits[i + 1] = split;
            cascadeDistances[i] = split - splits[i];
        }

        if (clampCascadeDistances) {
            // Clamp each cascade to its maximum distance
            for (i = 0; i < numberOfCascades; ++i) {
                cascadeDistances[i] = Math.min(cascadeDistances[i], shadowMap._maximumCascadeDistances[i]);
            }

            // Recompute splits
            var distance = splits[0];
            for (i = 0; i < numberOfCascades - 1; ++i) {
                distance += cascadeDistances[i];
                splits[i + 1] = distance;
            }
        }

        Cartesian4.unpack(splits, 0, shadowMap._cascadeSplits[0]);
        Cartesian4.unpack(splits, 1, shadowMap._cascadeSplits[1]);
        Cartesian4.unpack(cascadeDistances, 0, shadowMap._cascadeDistances);

        var shadowFrustum = shadowMapCamera.frustum;
        var left = shadowFrustum.left;
        var right = shadowFrustum.right;
        var bottom = shadowFrustum.bottom;
        var top = shadowFrustum.top;
        var near = shadowFrustum.near;
        var far = shadowFrustum.far;

        var position = shadowMapCamera.positionWC;
        var direction = shadowMapCamera.directionWC;
        var up = shadowMapCamera.upWC;

        var cascadeSubFrustum = sceneCamera.frustum.clone(scratchFrustum);
        var shadowViewProjection = shadowMapCamera.getViewProjection();

        for (i = 0; i < numberOfCascades; ++i) {
            // Find the bounding box of the camera sub-frustum in shadow map texture space
            cascadeSubFrustum.near = splits[i];
            cascadeSubFrustum.far = splits[i + 1];
            var viewProjection = Matrix4.multiply(cascadeSubFrustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
            var inverseViewProjection = Matrix4.inverse(viewProjection, 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);
            var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);

            for (var k = 0; k < 8; ++k) {
                var corner = Cartesian4.clone(frustumCornersNDC[k], scratchFrustumCorners[k]);
                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);
            }

            // Limit light-space coordinates to the [0, 1] range
            min.x = Math.max(min.x, 0.0);
            min.y = Math.max(min.y, 0.0);
            min.z = 0.0; // Always start cascade frustum at the top of the light frustum to capture objects in the light's path
            max.x = Math.min(max.x, 1.0);
            max.y = Math.min(max.y, 1.0);
            max.z = Math.min(max.z, 1.0);

            var pass = shadowMap._passes[i];
            var cascadeCamera = pass.camera;
            cascadeCamera.clone(shadowMapCamera); // PERFORMANCE_IDEA : could do a shallow clone for all properties except the frustum

            var frustum = cascadeCamera.frustum;
            frustum.left = left + min.x * (right - left);
            frustum.right = left + max.x * (right - left);
            frustum.bottom = bottom + min.y * (top - bottom);
            frustum.top = bottom + max.y * (top - bottom);
            frustum.near = near + min.z * (far - near);
            frustum.far = near + max.z * (far - near);

            pass.cullingVolume = cascadeCamera.frustum.computeCullingVolume(position, direction, up);

            // Transforms from eye space to the cascade's texture space
            var cascadeMatrix = shadowMap._cascadeMatrices[i];
            Matrix4.multiply(cascadeCamera.getViewProjection(), sceneCamera.inverseViewMatrix, cascadeMatrix);
            Matrix4.multiply(pass.textureOffsets, cascadeMatrix, cascadeMatrix);
        }
    }

    var scratchLightView = new Matrix4();
    var scratchRight = new Cartesian3();
    var scratchUp = new Cartesian3();
    var scratchMin = new Cartesian3();
    var scratchMax = new Cartesian3();
    var scratchTranslation = new Cartesian3();

    function fitShadowMapToScene(shadowMap, frameState) {
        var shadowMapCamera = shadowMap._shadowMapCamera;
        var sceneCamera = shadowMap._sceneCamera;

        // 1. First find a tight bounding box in light space that contains the entire camera frustum.
        var viewProjection = Matrix4.multiply(sceneCamera.frustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
        var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);

        // Start to construct the light view matrix. Set translation later once the bounding box is found.
        var lightDir = shadowMapCamera.directionWC;
        var lightUp = sceneCamera.directionWC; // Align shadows to the camera view.
        var lightRight = Cartesian3.cross(lightDir, lightUp, scratchRight);
        lightUp = Cartesian3.cross(lightRight, lightDir, scratchUp); // Recalculate up now that right is derived
        Cartesian3.normalize(lightUp, lightUp);
        Cartesian3.normalize(lightRight, lightRight);
        var lightPosition = Cartesian3.fromElements(0.0, 0.0, 0.0, scratchTranslation);

        var lightView = Matrix4.computeView(lightPosition, lightDir, lightUp, lightRight, scratchLightView);
        var cameraToLight = Matrix4.multiply(lightView, inverseViewProjection, scratchMatrix);

        // Project each corner from NDC space to light view space, and calculate a min and max in light view space
        var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
        var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);

        for (var i = 0; i < 8; ++i) {
            var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]);
            Matrix4.multiplyByVector(cameraToLight, corner, corner);
            Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
            Cartesian3.minimumByComponent(corner, min, min);
            Cartesian3.maximumByComponent(corner, max, max);
        }

        // 2. Set bounding box back to include objects in the light's view
        max.z += 1000.0; // Note: in light space, a positive number is behind the camera
        min.z -= 10.0; // Extend the shadow volume forward slightly to avoid problems right at the edge

        // 3. Adjust light view matrix so that it is centered on the bounding volume
        var translation = scratchTranslation;
        translation.x = -(0.5 * (min.x + max.x));
        translation.y = -(0.5 * (min.y + max.y));
        translation.z = -max.z;

        var translationMatrix = Matrix4.fromTranslation(translation, scratchMatrix);
        lightView = Matrix4.multiply(translationMatrix, lightView, lightView);

        // 4. Create an orthographic frustum that covers the bounding box extents
        var halfWidth = 0.5 * (max.x - min.x);
        var halfHeight = 0.5 * (max.y - min.y);
        var depth = max.z - min.z;

        var frustum = shadowMapCamera.frustum;
        frustum.left = -halfWidth;
        frustum.right = halfWidth;
        frustum.bottom = -halfHeight;
        frustum.top = halfHeight;
        frustum.near = 0.01;
        frustum.far = depth;

        // 5. Update the shadow map camera
        Matrix4.clone(lightView, shadowMapCamera.viewMatrix);
        Matrix4.inverse(lightView, shadowMapCamera.inverseViewMatrix);
        Matrix4.getTranslation(shadowMapCamera.inverseViewMatrix, shadowMapCamera.positionWC);
        frameState.mapProjection.ellipsoid.cartesianToCartographic(shadowMapCamera.positionWC, shadowMapCamera.positionCartographic);
        Cartesian3.clone(lightDir, shadowMapCamera.directionWC);
        Cartesian3.clone(lightUp, shadowMapCamera.upWC);
        Cartesian3.clone(lightRight, shadowMapCamera.rightWC);
    }

    var directions = [
        new Cartesian3(-1.0, 0.0, 0.0),
        new Cartesian3(0.0, -1.0, 0.0),
        new Cartesian3(0.0, 0.0, -1.0),
        new Cartesian3(1.0, 0.0, 0.0),
        new Cartesian3(0.0, 1.0, 0.0),
        new Cartesian3(0.0, 0.0, 1.0)
    ];

    var ups = [
        new Cartesian3(0.0, -1.0, 0.0),
        new Cartesian3(0.0, 0.0, -1.0),
        new Cartesian3(0.0, -1.0, 0.0),
        new Cartesian3(0.0, -1.0, 0.0),
        new Cartesian3(0.0, 0.0, 1.0),
        new Cartesian3(0.0, -1.0, 0.0)
    ];

    var rights = [
        new Cartesian3(0.0, 0.0, 1.0),
        new Cartesian3(1.0, 0.0, 0.0),
        new Cartesian3(-1.0, 0.0, 0.0),
        new Cartesian3(0.0, 0.0, -1.0),
        new Cartesian3(1.0, 0.0, 0.0),
        new Cartesian3(1.0, 0.0, 0.0)
    ];

    function computeOmnidirectional(shadowMap, frameState) {
        // All sides share the same frustum
        var frustum = new PerspectiveFrustum();
        frustum.fov = CesiumMath.PI_OVER_TWO;
        frustum.near = 1.0;
        frustum.far = shadowMap._pointLightRadius;
        frustum.aspectRatio = 1.0;

        for (var i = 0; i < 6; ++i) {
            var camera = shadowMap._passes[i].camera;
            camera.positionWC = shadowMap._shadowMapCamera.positionWC;
            camera.positionCartographic = frameState.mapProjection.ellipsoid.cartesianToCartographic(camera.positionWC, camera.positionCartographic);
            camera.directionWC = directions[i];
            camera.upWC = ups[i];
            camera.rightWC = rights[i];

            Matrix4.computeView(camera.positionWC, camera.directionWC, camera.upWC, camera.rightWC, camera.viewMatrix);
            Matrix4.inverse(camera.viewMatrix, camera.inverseViewMatrix);

            camera.frustum = frustum;
        }
    }

    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchBoundingSphere = new BoundingSphere();
    var scratchCenter = scratchBoundingSphere.center;

    function checkVisibility(shadowMap, frameState) {
        var sceneCamera = shadowMap._sceneCamera;
        var shadowMapCamera = shadowMap._shadowMapCamera;

        var boundingSphere = scratchBoundingSphere;

        // Check whether the shadow map is in view and needs to be updated
        if (shadowMap._cascadesEnabled) {
            // If the nearest shadow receiver is further than the shadow map's maximum distance then the shadow map is out of view.
            if (sceneCamera.frustum.near >= shadowMap.maximumDistance) {
                shadowMap._outOfView = true;
                shadowMap._needsUpdate = false;
                return;
            }

            // If the light source is below the horizon then the shadow map is out of view
            var surfaceNormal = frameState.mapProjection.ellipsoid.geodeticSurfaceNormal(sceneCamera.positionWC, scratchCartesian1);
            var lightDirection = Cartesian3.negate(shadowMapCamera.directionWC, scratchCartesian2);
            var dot = Cartesian3.dot(surfaceNormal, lightDirection);

            // Shadows start to fade out once the light gets closer to the horizon.
            // At this point the globe uses vertex lighting alone to darken the surface.
            var darknessAmount = CesiumMath.clamp(dot / 0.1, 0.0, 1.0);
            shadowMap._darkness = CesiumMath.lerp(1.0, shadowMap.darkness, darknessAmount);

            if (dot < 0.0) {
                shadowMap._outOfView = true;
                shadowMap._needsUpdate = false;
                return;
            }

            // By default cascaded shadows need to update and are always in view
            shadowMap._needsUpdate = true;
            shadowMap._outOfView = false;
        } else if (shadowMap._isPointLight) {
            // Sphere-frustum intersection test
            boundingSphere.center = shadowMapCamera.positionWC;
            boundingSphere.radius = shadowMap._pointLightRadius;
            shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
            shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
            BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
        } else {
            // Simplify frustum-frustum intersection test as a sphere-frustum test
            var frustumRadius = shadowMapCamera.frustum.far / 2.0;
            var frustumCenter = Cartesian3.add(shadowMapCamera.positionWC, Cartesian3.multiplyByScalar(shadowMapCamera.directionWC, frustumRadius, scratchCenter), scratchCenter);
            boundingSphere.center = frustumCenter;
            boundingSphere.radius = frustumRadius;
            shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
            shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
            BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
        }
    }

    function updateCameras(shadowMap, frameState) {
        var camera = frameState.camera; // The actual camera in the scene
        var lightCamera = shadowMap._lightCamera; // The external camera representing the light source
        var sceneCamera = shadowMap._sceneCamera; // Clone of camera, with clamped near and far planes
        var shadowMapCamera = shadowMap._shadowMapCamera; // Camera representing the shadow volume, initially cloned from lightCamera

        // Clone light camera into the shadow map camera
        if (shadowMap._cascadesEnabled) {
            Cartesian3.clone(lightCamera.directionWC, shadowMapCamera.directionWC);
        } else if (shadowMap._isPointLight) {
            Cartesian3.clone(lightCamera.positionWC, shadowMapCamera.positionWC);
        } else {
            shadowMapCamera.clone(lightCamera);
        }

        // Get the light direction in eye coordinates
        var lightDirection = shadowMap._lightDirectionEC;
        Matrix4.multiplyByPointAsVector(camera.viewMatrix, shadowMapCamera.directionWC, lightDirection);
        Cartesian3.normalize(lightDirection, lightDirection);
        Cartesian3.negate(lightDirection, lightDirection);

        // Get the light position in eye coordinates
        Matrix4.multiplyByPoint(camera.viewMatrix, shadowMapCamera.positionWC, shadowMap._lightPositionEC);
        shadowMap._lightPositionEC.w = shadowMap._pointLightRadius;

        // Get the near and far of the scene camera
        var near;
        var far;
        if (shadowMap._fitNearFar) {
            // shadowFar can be very large, so limit to shadowMap.maximumDistance
            // Push the far plane slightly further than the near plane to avoid degenerate frustum
            near = Math.min(frameState.shadowHints.nearPlane, shadowMap.maximumDistance);
            far = Math.min(frameState.shadowHints.farPlane, shadowMap.maximumDistance + 1.0);
        } else {
            near = camera.frustum.near;
            far = shadowMap.maximumDistance;
        }

        shadowMap._sceneCamera = Camera.clone(camera, sceneCamera);
        camera.frustum.clone(shadowMap._sceneCamera.frustum);
        shadowMap._sceneCamera.frustum.near = near;
        shadowMap._sceneCamera.frustum.far = far;
        shadowMap._distance = far - near;

        checkVisibility(shadowMap, frameState);

        if (!shadowMap._outOfViewPrevious && shadowMap._outOfView) {
            shadowMap._needsUpdate = true;
        }
        shadowMap._outOfViewPrevious = shadowMap._outOfView;
    }

    /**
     * @private
     */
    ShadowMap.prototype.update = function(frameState) {
        updateCameras(this, frameState);

        if (this._needsUpdate) {
            updateFramebuffer(this, frameState.context);

            if (this._isPointLight) {
                computeOmnidirectional(this, frameState);
            }

            if (this._cascadesEnabled) {
                fitShadowMapToScene(this, frameState);

                if (this._numberOfCascades > 1) {
                    computeCascades(this, frameState);
                }
            }

            if (!this._isPointLight) {
                // Compute the culling volume
                var shadowMapCamera = this._shadowMapCamera;
                var position = shadowMapCamera.positionWC;
                var direction = shadowMapCamera.directionWC;
                var up = shadowMapCamera.upWC;
                this._shadowMapCullingVolume = shadowMapCamera.frustum.computeCullingVolume(position, direction, up);

                if (this._passes.length === 1) {
                    // Since there is only one pass, use the shadow map camera as the pass camera.
                    this._passes[0].camera.clone(shadowMapCamera);
                }
            } else {
                this._shadowMapCullingVolume = CullingVolume.fromBoundingSphere(this._boundingSphere);
            }
        }

        if (this._passes.length === 1) {
            // Transforms from eye space to shadow texture space.
            // Always requires an update since the scene camera constantly changes.
            var inverseView = this._sceneCamera.inverseViewMatrix;
            Matrix4.multiply(this._shadowMapCamera.getViewProjection(), inverseView, this._shadowMapMatrix);
        }

        if (this.debugShow) {
            applyDebugSettings(this, frameState);
        }
    };

    /**
     * @private
     */
    ShadowMap.prototype.updatePass = function(context, shadowPass) {
        clearFramebuffer(this, context, shadowPass);
    };

    var scratchTexelStepSize = new Cartesian2();

    function combineUniforms(shadowMap, uniforms, isTerrain) {
        var bias = shadowMap._isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias);

        var mapUniforms = {
            shadowMap_texture :function() {
                return shadowMap._shadowMapTexture;
            },
            shadowMap_textureCube : function() {
                return shadowMap._shadowMapTexture;
            },
            shadowMap_matrix : function() {
                return shadowMap._shadowMapMatrix;
            },
            shadowMap_cascadeSplits : function() {
                return shadowMap._cascadeSplits;
            },
            shadowMap_cascadeMatrices : function() {
                return shadowMap._cascadeMatrices;
            },
            shadowMap_lightDirectionEC : function() {
                return shadowMap._lightDirectionEC;
            },
            shadowMap_lightPositionEC : function() {
                return shadowMap._lightPositionEC;
            },
            shadowMap_cascadeDistances : function() {
                return shadowMap._cascadeDistances;
            },
            shadowMap_texelSizeDepthBiasAndNormalShadingSmooth : function() {
                var texelStepSize = scratchTexelStepSize;
                texelStepSize.x = 1.0 / shadowMap._textureSize.x;
                texelStepSize.y = 1.0 / shadowMap._textureSize.y;

                return Cartesian4.fromElements(texelStepSize.x, texelStepSize.y, bias.depthBias, bias.normalShadingSmooth, this.combinedUniforms1);
            },
            shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness : function() {
                return Cartesian4.fromElements(bias.normalOffsetScale, shadowMap._distance, shadowMap.maximumDistance, shadowMap._darkness, this.combinedUniforms2);
            },

            combinedUniforms1 : new Cartesian4(),
            combinedUniforms2 : new Cartesian4()
        };

        return combine(uniforms, mapUniforms, false);
    }

    function createCastDerivedCommand(shadowMap, shadowsDirty, command, context, oldShaderId, result) {
        var castShader;
        var castRenderState;
        var castUniformMap;
        if (defined(result)) {
            castShader = result.shaderProgram;
            castRenderState = result.renderState;
            castUniformMap = result.uniformMap;
        }

        result = DrawCommand.shallowClone(command, result);
        result.castShadows = true;
        result.receiveShadows = false;

        if (!defined(castShader) || oldShaderId !== command.shaderProgram.id || shadowsDirty) {
            if (defined(castShader)) {
                castShader.destroy();
            }

            var shaderProgram = command.shaderProgram;
            var vertexShaderSource = shaderProgram.vertexShaderSource;
            var fragmentShaderSource = shaderProgram.fragmentShaderSource;

            var isTerrain = command.pass === Pass.GLOBE;
            var isOpaque = command.pass !== Pass.TRANSLUCENT;
            var isPointLight = shadowMap._isPointLight;
            var useDepthTexture = shadowMap._usesDepthTexture;

            var castVS = ShadowMapShader.createShadowCastVertexShader(vertexShaderSource, isPointLight, isTerrain);
            var castFS = ShadowMapShader.createShadowCastFragmentShader(fragmentShaderSource, isPointLight, useDepthTexture, isOpaque);

            castShader = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : castVS,
                fragmentShaderSource : castFS,
                attributeLocations : shaderProgram._attributeLocations
            });

            castRenderState = shadowMap._primitiveRenderState;
            if (isPointLight) {
                castRenderState = shadowMap._pointRenderState;
            } else if (isTerrain) {
                castRenderState = shadowMap._terrainRenderState;
            }

            // Modify the render state for commands that do not use back-face culling, e.g. flat textured walls
            var cullEnabled = command.renderState.cull.enabled;
            if (!cullEnabled) {
                castRenderState = clone(castRenderState, false);
                castRenderState.cull.enabled = false;
                castRenderState = RenderState.fromCache(castRenderState);
            }

            castUniformMap = combineUniforms(shadowMap, command.uniformMap, isTerrain);
        }

        result.shaderProgram = castShader;
        result.renderState = castRenderState;
        result.uniformMap = castUniformMap;

        return result;
    }

    ShadowMap.createDerivedCommands = function(shadowMaps, lightShadowMaps, command, shadowsDirty, context, result) {
        if (!defined(result)) {
            result = {};
        }

        var lightShadowMapsEnabled = (lightShadowMaps.length > 0);
        var shaderProgram = command.shaderProgram;
        var vertexShaderSource = shaderProgram.vertexShaderSource;
        var fragmentShaderSource = shaderProgram.fragmentShaderSource;
        var isTerrain = command.pass === Pass.GLOBE;

        var hasTerrainNormal = false;
        if (isTerrain) {
            hasTerrainNormal = command.owner.data.pickTerrain.mesh.encoding.hasVertexNormals;
        }

        if (command.castShadows) {
            var castCommands = result.castCommands;
            if (!defined(castCommands)) {
                castCommands = result.castCommands = [];
            }

            var oldShaderId = result.castShaderProgramId;

            var shadowMapLength = shadowMaps.length;
            castCommands.length = shadowMapLength;

            for (var i = 0; i < shadowMapLength; ++i) {
                castCommands[i] = createCastDerivedCommand(shadowMaps[i], shadowsDirty, command, context, oldShaderId, castCommands[i]);
            }

            result.castShaderProgramId = command.shaderProgram.id;
        }

        if (command.receiveShadows && lightShadowMapsEnabled) {
            // Only generate a receiveCommand if there is a shadow map originating from a light source.
            var receiveShader;
            var receiveUniformMap;
            if (defined(result.receiveCommand)) {
                receiveShader = result.receiveCommand.shaderProgram;
                receiveUniformMap = result.receiveCommand.uniformMap;
            }

            result.receiveCommand = DrawCommand.shallowClone(command, result.receiveCommand);
            result.castShadows = false;
            result.receiveShadows = true;

            // If castShadows changed, recompile the receive shadows shader. The normal shading technique simulates
            // self-shadowing so it should be turned off if castShadows is false.
            var castShadowsDirty = result.receiveShaderCastShadows !== command.castShadows;
            var shaderDirty = result.receiveShaderProgramId !== command.shaderProgram.id;

            if (!defined(receiveShader) || shaderDirty || shadowsDirty || castShadowsDirty) {
                if (defined(receiveShader)) {
                    receiveShader.destroy();
                }

                var receiveVS = ShadowMapShader.createShadowReceiveVertexShader(vertexShaderSource, isTerrain, hasTerrainNormal);
                var receiveFS = ShadowMapShader.createShadowReceiveFragmentShader(fragmentShaderSource, lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal);

                receiveShader = ShaderProgram.fromCache({
                    context : context,
                    vertexShaderSource : receiveVS,
                    fragmentShaderSource : receiveFS,
                    attributeLocations : shaderProgram._attributeLocations
                });

                receiveUniformMap = combineUniforms(lightShadowMaps[0], command.uniformMap, isTerrain);
            }

            result.receiveCommand.shaderProgram = receiveShader;
            result.receiveCommand.uniformMap = receiveUniformMap;
            result.receiveShaderProgramId = command.shaderProgram.id;
            result.receiveShaderCastShadows = command.castShadows;
        }

        return result;
    };

    /**
     * @private
     */
    ShadowMap.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * @private
     */
    ShadowMap.prototype.destroy = function() {
        destroyFramebuffer(this);

        this._debugLightFrustum = this._debugLightFrustum && this._debugLightFrustum.destroy();
        this._debugCameraFrustum = this._debugCameraFrustum && this._debugCameraFrustum.destroy();
        this._debugShadowViewCommand = this._debugShadowViewCommand && this._debugShadowViewCommand.shaderProgram && this._debugShadowViewCommand.shaderProgram.destroy();

        for (var i = 0; i < this._numberOfCascades; ++i) {
            this._debugCascadeFrustums[i] = this._debugCascadeFrustums[i] && this._debugCascadeFrustums[i].destroy();
        }

        return destroyObject(this);
    };

    return ShadowMap;
});