소스 검색

Lots of changes - add MJP computations

Popov72 5 년 전
부모
커밋
0ada494f2a
1개의 변경된 파일418개의 추가작업 그리고 188개의 파일을 삭제
  1. 418 188
      src/Lights/Shadows/cascadedShadowGenerator.ts

+ 418 - 188
src/Lights/Shadows/cascadedShadowGenerator.ts

@@ -26,15 +26,38 @@ import { EffectFallbacks } from '../../Materials/effectFallbacks';
 import { IShadowGenerator } from './shadowGenerator';
 import { DirectionalLight } from '../directionalLight';
 
-import { BoundingSphere } from "../../Culling/boundingSphere";
 import { BoundingInfo } from '../../Culling/boundingInfo';
 
+interface ICascade {
+    prevBreakDistance: number;
+    breakDistance: number;
+}
+
+const UpDir = Vector3.Up();
+const ZeroVec = Vector3.Zero();
+
+let tmpv1 = new Vector3(),
+    tmpv2 = new Vector3(),
+    matrix = new Matrix();
+
 /**
  * A CSM implementation allowing casting shadows on large scenes.
  * Documentation : https://doc.babylonjs.com/babylon101/cascadedShadows
  * Based on: https://johanmedestrom.wordpress.com/2016/03/18/opengl-cascaded-shadow-maps/
  */
 export class CascadedShadowGenerator implements IShadowGenerator {
+
+    private static readonly frustumCornersNDCSpace = [
+        new Vector3(-1.0, +1.0, -1.0),
+        new Vector3(+1.0, +1.0, -1.0),
+        new Vector3(+1.0, -1.0, -1.0),
+        new Vector3(-1.0, -1.0, -1.0),
+        new Vector3(-1.0, +1.0, +1.0),
+        new Vector3(+1.0, +1.0, +1.0),
+        new Vector3(+1.0, -1.0, +1.0),
+        new Vector3(-1.0, -1.0, +1.0),
+    ];
+
     /**
      * Defines the default number of cascades used by the CSM.
      */
@@ -248,12 +271,6 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         this._contactHardeningLightSizeUVRatio = contactHardeningLightSizeUVRatio;
     }
 
-    public lightSizeCorrection: boolean = true;
-
-    public depthCorrection: boolean = true;
-
-    public penumbraDarkness: number = 1.0;
-
     private _darkness = 0;
 
     /** Gets or sets the actual darkness of a shadow */
@@ -291,6 +308,11 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         return this;
     }
 
+    /**
+     * Gets or sets the actual darkness of the soft shadows while using PCSS filtering (value between 0. and 1.)
+     */
+    public penumbraDarkness: number = 1.0;
+
     private _transparencyShadow = false;
 
     /** Gets or sets the ability to have transparent shadow  */
@@ -312,24 +334,31 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         return this;
     }
 
-    private _cascades = CascadedShadowGenerator.DEFAULT_CASCADES_COUNT;
+    private _numCascades = CascadedShadowGenerator.DEFAULT_CASCADES_COUNT;
 
     /**
      * Gets or set the number of cascades used by the CSM.
      */
-    public get cascades(): number {
-        return this._cascades;
+    public get numCascades(): number {
+        return this._numCascades;
     }
-    public set cascades(value: number) {
+
+    public set numCascades(value: number) {
         value = Math.min(Math.max(value, CascadedShadowGenerator.MIN_CASCADES_COUNT), CascadedShadowGenerator.MAX_CASCADES_COUNT);
-        if (value === this._cascades) {
+        if (value === this._numCascades) {
             return;
         }
 
-        this._cascades = value;
+        this._numCascades = value;
         this.recreateShadowMap();
     }
 
+    /**
+     * Sets this to true if you want that the edges of the shadows don't "swimm" / "shimmer" when rotating the camera.
+     * The trade off is that you loose some precision in the shadow rendering when enabling this setting.
+     */
+    public stabilizeCascades: boolean = false;
+
     private _shadowMap: Nullable<RenderTargetTexture>;
     /**
      * Gets the main RTT containing the shadow map (usually storing depth from the light point of view).
@@ -342,6 +371,11 @@ export class CascadedShadowGenerator implements IShadowGenerator {
     protected _freezeShadowCastersBoundingInfo: boolean = false;
     private _freezeShadowCastersBoundingInfoObservable: Nullable<Observer<Scene>> = null;
 
+    /**
+     * Enables or disables the shadow casters bounding info computation.
+     * If your shadow casters don't move, you can disable this feature.
+     * If it is enabled, the bounding box computation is done every frame.
+     */
     public get freezeShadowCastersBoundingInfo(): boolean {
         return this._freezeShadowCastersBoundingInfo;
     }
@@ -363,9 +397,12 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         }
     }
 
+    private _scbiMin = new Vector3(0, 0, 0);
+    private _scbiMax = new Vector3(0, 0, 0);
+
     protected _computeShadowCastersBoundingInfo(): void {
-        const min = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE),
-              max = new Vector3(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE);
+        this._scbiMin.copyFromFloats(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
+        this._scbiMax.copyFromFloats(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE);
 
         if (this._shadowMap && this._shadowMap.renderList) {
             const renderList = this._shadowMap.renderList;
@@ -377,18 +414,23 @@ export class CascadedShadowGenerator implements IShadowGenerator {
                 }
 
                 const boundingInfo = mesh.getBoundingInfo(),
-                    boundingBox = boundingInfo.boundingBox;
+                      boundingBox = boundingInfo.boundingBox;
 
-                min.minimizeInPlace(boundingBox.minimumWorld);
-                max.maximizeInPlace(boundingBox.maximumWorld);
+                this._scbiMin.minimizeInPlace(boundingBox.minimumWorld);
+                this._scbiMax.maximizeInPlace(boundingBox.maximumWorld);
             }
         }
 
-        this._shadowCastersBoundingInfo.reConstruct(min, max);
+        this._shadowCastersBoundingInfo.reConstruct(this._scbiMin, this._scbiMax);
     }
 
     protected _shadowCastersBoundingInfo: BoundingInfo;
 
+    /**
+     * Gets or sets the shadow casters bounding info.
+     * If you provide your own shadow casters bounding info, first enable freezeShadowCastersBoundingInfo
+     * so that the system won't overwrite the bounds you provide
+     */
     public get shadowCastersBoundingInfo(): BoundingInfo {
         return this._shadowCastersBoundingInfo;
     }
@@ -397,6 +439,42 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         this._shadowCastersBoundingInfo = boundingInfo;
     }
 
+    protected _breaksAreDirty: boolean = true;
+
+    protected _minDistance: number = 0;
+    protected _maxDistance: number = 1;
+
+    /**
+     * Sets the minimal and maximal distances to use when computing the cascade breaks.
+     *
+     * The values of min / max are typically the depth zmin and zmax values of your scene, for a given frame.
+     * If you don't know these values, simply leave them to their defaults and don't call this function.
+     * @param min minimal distance for the breaks (default to 0.)
+     * @param max maximal distance for the breaks (default to 1.)
+     */
+    public setMinMaxDistance(min: number, max: number): void {
+        if (this._minDistance === min && this._maxDistance === max) {
+            return;
+        }
+
+        if (min > max) {
+            min = 0;
+            max = 1;
+        }
+
+        if (min < 0) {
+            min = 0;
+        }
+
+        if (max > 1) {
+            max = 1;
+        }
+
+        this._minDistance = min;
+        this._maxDistance = max;
+        this._breaksAreDirty = true;
+    }
+
     /**
      * Gets the class name of that object
      * @returns "ShadowGenerator"
@@ -457,7 +535,6 @@ export class CascadedShadowGenerator implements IShadowGenerator {
 
     /**
      * Controls the extent to which the shadows fade out at the edge of the frustum
-     * Used only by directionals and spots
      */
     public frustumEdgeFalloff = 0;
 
@@ -477,14 +554,31 @@ export class CascadedShadowGenerator implements IShadowGenerator {
      */
     public forceBackFacesOnly = false;
 
+    private _cascadeMinExtents: Array<Vector3>;
+    private _cascadeMaxExtents: Array<Vector3>;
+
+    /**
+     * Gets a cascade minimum extents
+     * @param cascadeIndex index of the cascade
+     */
+    public getCascadeMinExtents(cascadeIndex: number): Nullable<Vector3> {
+        return cascadeIndex >= 0 && cascadeIndex < this._numCascades ? this._cascadeMinExtents[cascadeIndex] : null;
+    }
+
+    /**
+     * Gets a cascade maximum extents
+     * @param cascadeIndex index of the cascade
+     */
+    public getCascadeMaxExtents(cascadeIndex: number): Nullable<Vector3> {
+        return cascadeIndex >= 0 && cascadeIndex < this._numCascades ? this._cascadeMaxExtents[cascadeIndex] : null;
+    }
+
     private _scene: Scene;
     private _lightDirection = Vector3.Zero();
 
     private _effect: Effect;
 
-    private _viewMatrix: Array<Matrix>;
-    private _lightMinExtents: Array<Vector3>;
-    private _lightMaxExtents: Array<Vector3>;
+    private _cascades: Array<ICascade>;
     private _cachedPosition: Vector3 = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
     private _cachedDirection: Vector3 = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
     private _cachedDefines: string;
@@ -495,15 +589,22 @@ export class CascadedShadowGenerator implements IShadowGenerator {
     private _defaultTextureMatrix = Matrix.Identity();
     private _storedUniqueId: Nullable<number>;
 
-    private _viewSpaceFrustums: Array<Array<Vector3>>;
     private _viewSpaceFrustumsZ: Array<number>;
-    private _viewSpaceBoundingSpheres: Array<BoundingSphere>;
+    private _viewMatrices: Array<Matrix>;
+    private _projectionMatrices: Array<Matrix>;
     private _transformMatrices: Array<Matrix>;
     private _transformMatricesAsArray: Float32Array;
+    private _frustumLengths: Array<number>;
+    private _lightSizeUVCorrection: Array<number>;
+    private _depthCorrection: Array<number>;
+    private _frustumCornersWorldSpace: Array<Array<Vector3>>;
+    private _frustumCenter: Array<Vector3>;
+    private _shadowCameraPos: Array<Vector3>;
 
     private _shadowMaxZ: number;
     /**
-     * Gets the csmFrustumLength value: furthest range of the frustum for the CSM mode.
+     * Gets the shadow max z distance. It's the limit beyond which shadows are not displayed.
+     * It defaults to camera.maxZ
      */
     public get shadowMaxZ(): number {
         if (!this._scene || !this._scene.activeCamera) {
@@ -512,7 +613,7 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         return this._shadowMaxZ;
     }
     /**
-     * Sets the csmFrustumLength: furthest range of the frustum for the CSM mode.
+     * Sets the shadow max z distance.
      */
     public set shadowMaxZ(value: number) {
         if (!this._scene || !this._scene.activeCamera) {
@@ -523,11 +624,15 @@ export class CascadedShadowGenerator implements IShadowGenerator {
             return;
         }
         this._shadowMaxZ = value;
-        this._initCascades();
+        this._breaksAreDirty = true;
     }
 
     protected _debug = false;
 
+    /**
+     * Gets or sets the debug flag.
+     * When enabled, the cascades are materialized by different colors on the screen.
+     */
     public get debug(): boolean {
         return this._debug;
     }
@@ -537,129 +642,79 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         this._light._markMeshesAsLightDirty();
     }
 
-    public depthClamp: boolean = false;
+    private _depthClamp = true;
 
+    /**
+     * Gets or sets the depth clamping value.
+     *
+     * When enabled, it improves the shadow quality because the near z plane of the light frustum don't need to be adjusted
+     * to account for the shadow casters far away.
+     *
+     * Note that this property is incompatible with PCSS filtering, so it won't be used in that case.
+     */
+    public get depthClamp(): boolean {
+        return this._depthClamp;
+    }
+
+    public set depthClamp(value: boolean) {
+        this._depthClamp = value;
+    }
+
+    /**
+     * Gets or sets the percentage of blending between two cascades (value between 0. and 1.).
+     * It defaults to 0.1 (10% blending).
+     */
     public cascadeBlendPercentage: number = 0.1;
 
     private _lambda = 0.5;
 
     /**
-     * Gets csmLambda: parameter used for calculating the frustum in CSM.
+     * Gets or set the lambda parameter.
+     * This parameter is used to split the camera frustum and create the cascades.
+     * It's a value between 0. and 1.: If 0, the split is a uniform split of the frustum, if 1 it is a logarithmic split.
+     * For all values in-between, it's a linear combination of the uniform and logarithm split algorithm.
      */
     public get lambda(): number {
         return this._lambda;
     }
-    /**
-     * Sets csmLambda: parameter used for calculating the frustum in CSM.
-     */
+
     public set lambda(value: number) {
         const lambda = Math.min(Math.max(value, 0), 1);
         if (this._lambda == lambda) {
             return;
         }
         this._lambda = lambda;
-        this._initCascades();
+        this._breaksAreDirty = true;
     }
 
-    private _initCascades(): void {
-        let camera = this._scene.activeCamera;
-        if (!camera) {
-            return;
-        }
-
-        if (!this._shadowMaxZ) {
-            this._shadowMaxZ = camera.maxZ;
-        }
-
-        this._currentRenderID = new Array(this._cascades);
-
-        // inits and sets all static params related to CSM
-        let engine = this._scene.getEngine();
-        this._viewSpaceFrustums = [];
-        this._viewSpaceFrustumsZ = [];
-        this._viewSpaceBoundingSpheres = [];
-        let nearx = 0;
-        let neary = 0;
-        let farx = 0;
-        let fary = 0;
-
-        // get all internal camera cascaded frustum points
-        let breaks = this._frustumSplit();
-        if (camera.fovMode === 0) {
-            nearx = camera.minZ * Math.tan(camera.fov / 2) * engine.getAspectRatio(camera);
-            neary = camera.minZ * Math.tan(camera.fov / 2);
-            farx = this._shadowMaxZ * Math.tan(camera.fov / 2) * engine.getAspectRatio(camera);
-            fary = this._shadowMaxZ * Math.tan(camera.fov / 2);
-        } else if (camera.fovMode === 1) {
-            nearx = camera.minZ * Math.tan(camera.fov / 2);
-            neary = camera.minZ * Math.tan(camera.fov / 2) * engine.getAspectRatio(camera);
-            farx = this._shadowMaxZ * Math.tan(camera.fov / 2);
-            fary = this._shadowMaxZ * Math.tan(camera.fov / 2) * engine.getAspectRatio(camera);
-        }
-
-        // populate the viewSpaceFrustums array
-        for (let i = 0; i < this.cascades + 1; i++) {
-            this._viewSpaceFrustums[i] = [];
-            this._viewSpaceFrustums[i].push(Vector3.Lerp(new Vector3(nearx, neary, camera.minZ), new Vector3(farx, fary, this._shadowMaxZ), breaks[i]));
-            this._viewSpaceFrustums[i].push(Vector3.Lerp(new Vector3(nearx, -neary, camera.minZ), new Vector3(farx, -fary, this._shadowMaxZ), breaks[i]));
-            this._viewSpaceFrustums[i].push(Vector3.Lerp(new Vector3(-nearx, -neary, camera.minZ), new Vector3(-farx, -fary, this._shadowMaxZ), breaks[i]));
-            this._viewSpaceFrustums[i].push(Vector3.Lerp(new Vector3(-nearx, neary, camera.minZ), new Vector3(-farx, fary, this._shadowMaxZ), breaks[i]));
-        }
-
-        // populate the viewSpaceBoundingSpheres array
-        var minX = Number.MAX_VALUE;
-        var minY = Number.MAX_VALUE;
-        var minZ = Number.MAX_VALUE;
-        var maxX = Number.MIN_VALUE;
-        var maxY = Number.MIN_VALUE;
-        var maxZ = Number.MIN_VALUE;
-        for (let i = 0; i < this.cascades; i++) {
-            for (let j = 0; j < this._viewSpaceFrustums[i].length; j++) {
-                minX = Math.min(minX, this._viewSpaceFrustums[i][j].x);
-                minY = Math.min(minY, this._viewSpaceFrustums[i][j].y);
-                minZ = Math.min(minZ, this._viewSpaceFrustums[i][j].z);
-                maxX = Math.max(maxX, this._viewSpaceFrustums[i][j].x);
-                maxY = Math.max(maxY, this._viewSpaceFrustums[i][j].y);
-                maxZ = Math.max(maxZ, this._viewSpaceFrustums[i][j].z);
-
-                minX = Math.min(minX, this._viewSpaceFrustums[i + 1][j].x);
-                minY = Math.min(minY, this._viewSpaceFrustums[i + 1][j].y);
-                minZ = Math.min(minZ, this._viewSpaceFrustums[i + 1][j].z);
-                maxX = Math.max(maxX, this._viewSpaceFrustums[i + 1][j].x);
-                maxY = Math.max(maxY, this._viewSpaceFrustums[i + 1][j].y);
-                maxZ = Math.max(maxZ, this._viewSpaceFrustums[i + 1][j].z);
-            }
-            let bs = new BoundingSphere(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ));
-            this._viewSpaceBoundingSpheres.push(bs);
-
-            this._viewSpaceFrustumsZ[i] = this._viewSpaceFrustums[i + 1][0].z;
-        }
+    /**
+     * Gets the view matrix corresponding to a given cascade
+     * @param cascadeNum cascade to retrieve the view matrix from
+     */
+    public getCascadeViewMatrix(cascadeNum: number): Nullable<Matrix> {
+        return cascadeNum >= 0 && cascadeNum < this._numCascades ? this._viewMatrices[cascadeNum] : null;
+    }
 
-        // initialize the CSM transformMatrices
-        this._viewMatrix = [];
-        this._lightMinExtents = [];
-        this._lightMaxExtents = [];
-        this._transformMatrices = [];
-        for (let index = 0; index < this.cascades; index++) {
-            this._viewMatrix[index] = Matrix.Zero();
-            this._transformMatrices[index] = Matrix.Zero();
-            this._lightMinExtents[index] = new Vector3();
-            this._lightMaxExtents[index] = new Vector3();
-        }
-        this._transformMatricesAsArray = new Float32Array(this.cascades * 16);
+    /**
+     * Create the cascade breaks according to the lambda, shadowMaxZ and min/max distance properties, as well as the camera near and far planes.
+     * This function is automatically called when updating lambda, shadowMaxZ and min/max distances, however you should call it yourself if
+     * you change the camera near/far planes!
+     */
+    public splitFrustum(): void {
+        this._breaksAreDirty = true;
     }
 
-    private _frustumSplit(): Array<number> {
+    private _splitFrustum(): void {
         let camera = this._scene.activeCamera;
         if (!camera) {
-            return [];
+            return;
         }
 
         const near = camera.minZ,
               far = camera.maxZ,
               cameraRange = far - near,
-              minDistance = 0,
-              maxDistance = this._shadowMaxZ < far && this._shadowMaxZ >= near ? (this._shadowMaxZ - near) / (far - near) : 1;
+              minDistance = this._minDistance,
+              maxDistance = this._shadowMaxZ < far && this._shadowMaxZ >= near ? Math.min((this._shadowMaxZ - near) / (far - near), this._maxDistance) : this._maxDistance;
 
         const minZ = near + minDistance * cameraRange,
               maxZ = near + maxDistance * cameraRange;
@@ -667,39 +722,41 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         const range = maxZ - minZ,
               ratio = maxZ / minZ;
 
-        let r = [];
-        r.push(minDistance);
-        for (let cascadeIndex = 0; cascadeIndex < this._cascades; ++cascadeIndex) {
-            const p = (cascadeIndex + 1) / this._cascades,
+        for (let cascadeIndex = 0; cascadeIndex < this._cascades.length; ++cascadeIndex) {
+            const p = (cascadeIndex + 1) / this._numCascades,
                   log = minZ * (ratio ** p),
                   uniform = minZ + range * p;
 
             const d = this._lambda * (log - uniform) + uniform;
 
-            r.push((d - near) / cameraRange);
+            this._cascades[cascadeIndex].prevBreakDistance = cascadeIndex === 0 ? minDistance : this._cascades[cascadeIndex - 1].breakDistance;
+            this._cascades[cascadeIndex].breakDistance = (d - near) / cameraRange;
+
+            this._viewSpaceFrustumsZ[cascadeIndex] = near + this._cascades[cascadeIndex].breakDistance * cameraRange;
+            this._frustumLengths[cascadeIndex] = (this._cascades[cascadeIndex].breakDistance - this._cascades[cascadeIndex].prevBreakDistance) * cameraRange;
         }
 
-        return r;
+        this._breaksAreDirty = false;
     }
 
     /**
      * Gets the CSM transformation matrix used to project the meshes into the map from the light point of view.
-     * (eq to shadow prjection matrix * light transform matrix)
-	 * @param mapIndex index number of the cascaded shadow map
+     * (eq to view projection * shadow projection matrices)
+	 * @param cascadeIndex index number of the cascaded shadow map
      * @returns The transform matrix used to create the CSM shadow map
      */
-    public getCSMTransformMatrix(mapIndex: number): Matrix {
+    public getCSMTransformMatrix(cascadeIndex: number): Matrix {
         var scene = this._scene;
-        if (this._currentRenderID[mapIndex] === scene.getRenderId()) {
-            return this._transformMatrices[mapIndex];
+        if (this._currentRenderID[cascadeIndex] === scene.getRenderId()) {
+            return this._transformMatrices[cascadeIndex];
         }
 
         let camera = scene.activeCamera;
         if (!camera) {
-            return this._transformMatrices[mapIndex];
+            return this._transformMatrices[cascadeIndex];
         }
 
-        this._currentRenderID[mapIndex] = scene.getRenderId();
+        this._currentRenderID[cascadeIndex] = scene.getRenderId();
 
         var lightPosition = this._light.position;
         if (this._light.computeTransformedInformation()) {
@@ -715,56 +772,141 @@ export class CascadedShadowGenerator implements IShadowGenerator {
             this._cachedPosition.copyFrom(lightPosition);
             this._cachedDirection.copyFrom(this._lightDirection);
 
-            // get bounding sphere of current cascade
-            let bs = new BoundingSphere(this._viewSpaceBoundingSpheres[mapIndex].minimum, this._viewSpaceBoundingSpheres[mapIndex].maximum, camera.getWorldMatrix());
-            // get view matrix
-            let shadowCamPos = bs.centerWorld.subtract(this._lightDirection.scale(bs.radius));
-            Matrix.LookAtLHToRef(shadowCamPos, bs.centerWorld, Vector3.Up(), this._viewMatrix[mapIndex]);
-            // get ortho matrix
-            let minx = -bs.radius, miny = -bs.radius, minz = 0;
-            let maxx = bs.radius, maxy = bs.radius, maxz = 2 * bs.radius;
+            this._computeFrustumInWorldSpace(cascadeIndex);
+            this._computeCascadeFrustum(cascadeIndex);
+
+            this._cascadeMaxExtents[cascadeIndex].subtractToRef(this._cascadeMinExtents[cascadeIndex], tmpv1); // tmpv1 = cascadeExtents
+
+            // Get position of the shadow camera
+            this._frustumCenter[cascadeIndex].addToRef(this._lightDirection.scale(this._cascadeMinExtents[cascadeIndex].z), this._shadowCameraPos[cascadeIndex]);
+
+            // Come up with a new orthographic camera for the shadow caster
+            Matrix.LookAtLHToRef(this._shadowCameraPos[cascadeIndex], this._frustumCenter[cascadeIndex], UpDir, this._viewMatrices[cascadeIndex]);
+
+            let minZ = 0, maxZ = tmpv1.z;
 
             // Try to tighten minZ and maxZ based on the bounding box of the shadow casters
             const boundingInfo = this._shadowCastersBoundingInfo;
 
-            boundingInfo.update(this._viewMatrix[mapIndex]);
+            boundingInfo.update(this._viewMatrices[cascadeIndex]);
 
-            maxz = Math.min(maxz, boundingInfo.boundingBox.maximumWorld.z);
+            maxZ = Math.min(maxZ, boundingInfo.boundingBox.maximumWorld.z);
 
-            if (!this.depthClamp) {
+            if (!this._depthClamp || this.filter === CascadedShadowGenerator.FILTER_PCSS) {
                 // If we don't use depth clamping, we must set minZ so that all shadow casters are in the light frustum
-                minz = Math.min(minz, boundingInfo.boundingBox.minimumWorld.z);
+                minZ = Math.min(minZ, boundingInfo.boundingBox.minimumWorld.z);
             } else {
                 // If using depth clamping, we can adjust minZ to reduce the [minZ, maxZ] range (and get some additional precision in the shadow map)
-                minz = Math.max(minz, boundingInfo.boundingBox.minimumWorld.z);
+                minZ = Math.max(minZ, boundingInfo.boundingBox.minimumWorld.z);
             }
 
-            this._lightMinExtents[mapIndex].copyFromFloats(minx, miny, minz);
-            this._lightMaxExtents[mapIndex].copyFromFloats(maxx, maxy, maxz);
-
-            let OrthoMatrix;
-
             if (this._scene.useRightHandedSystem) {
-                OrthoMatrix = Matrix.OrthoOffCenterRH(minx, maxx, miny, maxy, minz, maxz);
+                Matrix.OrthoOffCenterRHToRef(this._cascadeMinExtents[cascadeIndex].x, this._cascadeMaxExtents[cascadeIndex].x, this._cascadeMinExtents[cascadeIndex].y, this._cascadeMaxExtents[cascadeIndex].y, minZ, maxZ, this._projectionMatrices[cascadeIndex]);
             } else {
-                OrthoMatrix = Matrix.OrthoOffCenterLH(minx, maxx, miny, maxy, minz, maxz);
+                Matrix.OrthoOffCenterLHToRef(this._cascadeMinExtents[cascadeIndex].x, this._cascadeMaxExtents[cascadeIndex].x, this._cascadeMinExtents[cascadeIndex].y, this._cascadeMaxExtents[cascadeIndex].y, minZ, maxZ, this._projectionMatrices[cascadeIndex]);
+            }
+
+            this._cascadeMinExtents[cascadeIndex].z = minZ;
+            this._cascadeMaxExtents[cascadeIndex].z = maxZ;
+
+            this._viewMatrices[cascadeIndex].multiplyToRef(this._projectionMatrices[cascadeIndex], this._transformMatrices[cascadeIndex]);
+
+            // Create the rounding matrix, by projecting the world-space origin and determining
+            // the fractional offset in texel space
+            Vector3.TransformCoordinatesToRef(ZeroVec, this._transformMatrices[cascadeIndex], tmpv1); // tmpv1 = shadowOrigin
+            tmpv1.scaleInPlace(this._mapSize / 2);
+
+            tmpv2.copyFromFloats(Math.round(tmpv1.x), Math.round(tmpv1.y), Math.round(tmpv1.z)); // tmpv2 = roundedOrigin
+            tmpv2.subtractInPlace(tmpv1).scaleInPlace(2 / this._mapSize); // tmpv2 = roundOffset
+
+            Matrix.TranslationToRef(tmpv2.x, tmpv2.y, 0.0, matrix);
+
+            this._projectionMatrices[cascadeIndex].multiplyToRef(matrix, this._projectionMatrices[cascadeIndex]);
+            this._viewMatrices[cascadeIndex].multiplyToRef(this._projectionMatrices[cascadeIndex], this._transformMatrices[cascadeIndex]);
+        }
+
+        this._transformMatrices[cascadeIndex].copyToArray(this._transformMatricesAsArray, cascadeIndex * 16);
+
+        return this._transformMatrices[cascadeIndex];
+    }
+
+    // Get the 8 points of the view frustum in world space
+    private _computeFrustumInWorldSpace(cascadeIndex: number): void {
+        if (!this._scene.activeCamera) {
+            return;
+        }
+
+        const prevSplitDist = this._cascades[cascadeIndex].prevBreakDistance,
+              splitDist = this._cascades[cascadeIndex].breakDistance;
+
+        this._scene.activeCamera.getViewMatrix(); // make sure the transformation matrix we get when calling 'getTransformationMatrix()' is calculated with an up to date view matrix
+
+        const invViewProj = Matrix.Invert(this._scene.activeCamera.getTransformationMatrix());
+        for (let cornerIndex = 0; cornerIndex < CascadedShadowGenerator.frustumCornersNDCSpace.length; ++cornerIndex) {
+            Vector3.TransformCoordinatesToRef(CascadedShadowGenerator.frustumCornersNDCSpace[cornerIndex], invViewProj, this._frustumCornersWorldSpace[cascadeIndex][cornerIndex]);
+        }
+
+        // Get the corners of the current cascade slice of the view frustum
+        for (let cornerIndex = 0; cornerIndex < CascadedShadowGenerator.frustumCornersNDCSpace.length / 2; ++cornerIndex) {
+            tmpv1.copyFrom(this._frustumCornersWorldSpace[cascadeIndex][cornerIndex + 4]).subtractInPlace(this._frustumCornersWorldSpace[cascadeIndex][cornerIndex]);
+            tmpv2.copyFrom(tmpv1).scaleInPlace(prevSplitDist); // near corner ray
+            tmpv1.scaleInPlace(splitDist); // far corner ray
+
+            tmpv1.addInPlace(this._frustumCornersWorldSpace[cascadeIndex][cornerIndex]);
+
+            this._frustumCornersWorldSpace[cascadeIndex][cornerIndex + 4].copyFrom(tmpv1);
+            this._frustumCornersWorldSpace[cascadeIndex][cornerIndex].addInPlace(tmpv2);
+        }
+    }
+
+    private _computeCascadeFrustum(cascadeIndex: number): void {
+        this._cascadeMinExtents[cascadeIndex].copyFromFloats(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
+        this._cascadeMaxExtents[cascadeIndex].copyFromFloats(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE);
+        this._frustumCenter[cascadeIndex].copyFromFloats(0, 0, 0);
+
+        const camera = this._scene.activeCamera;
+
+        if (!camera) {
+            return;
+        }
+
+        // Calculate the centroid of the view frustum slice
+        for (let cornerIndex = 0; cornerIndex < this._frustumCornersWorldSpace[cascadeIndex].length; ++cornerIndex) {
+            this._frustumCenter[cascadeIndex].addInPlace(this._frustumCornersWorldSpace[cascadeIndex][cornerIndex]);
+        }
+
+        this._frustumCenter[cascadeIndex].scaleInPlace(1 / this._frustumCornersWorldSpace[cascadeIndex].length);
+
+        if (this.stabilizeCascades) {
+            // Calculate the radius of a bounding sphere surrounding the frustum corners
+            let sphereRadius = 0;
+            for (let cornerIndex = 0; cornerIndex < this._frustumCornersWorldSpace[cascadeIndex].length; ++cornerIndex) {
+                const dist = this._frustumCornersWorldSpace[cascadeIndex][cornerIndex].subtract(this._frustumCenter[cascadeIndex]).length();
+                sphereRadius = Math.max(sphereRadius, dist);
             }
 
-            // get projection matrix
-            this._viewMatrix[mapIndex].multiplyToRef(OrthoMatrix, this._transformMatrices[mapIndex]);
+            sphereRadius = Math.ceil(sphereRadius * 16) / 16;
+
+            this._cascadeMaxExtents[cascadeIndex].copyFromFloats(sphereRadius, sphereRadius, sphereRadius);
+            this._cascadeMinExtents[cascadeIndex].copyFromFloats(-sphereRadius, -sphereRadius, -sphereRadius);
+        } else {
+            // Create a temporary view matrix for the light
+            const lightCameraPos = this._frustumCenter[cascadeIndex];
+
+            this._frustumCenter[cascadeIndex].addToRef(this._lightDirection, tmpv1); // tmpv1 = look at
+
+            Matrix.LookAtLHToRef(lightCameraPos, tmpv1, UpDir, matrix); // matrix = lightView
+
+            // Calculate an AABB around the frustum corners
+            for (let cornerIndex = 0; cornerIndex < this._frustumCornersWorldSpace[cascadeIndex].length; ++cornerIndex) {
+                Vector3.TransformCoordinatesToRef(this._frustumCornersWorldSpace[cascadeIndex][cornerIndex], matrix, tmpv1);
 
-            // rounding the transform matrix to prevent shimmering artifacts from camera movement
-            let shadowOrigin = Vector3.TransformCoordinates(Vector3.Zero(), this._transformMatrices[mapIndex]);
-            shadowOrigin = shadowOrigin.scale(this._mapSize / 2.0);
-            let roundedOrigin = new Vector3(Math.ceil(shadowOrigin.x), Math.ceil(shadowOrigin.y), Math.ceil(shadowOrigin.z));
-            let roundOffset = roundedOrigin.subtract(shadowOrigin);
-            roundOffset = roundOffset.scale(2.0 / this._mapSize);
-            let roundMatrix = Matrix.Translation(roundOffset.x, roundOffset.y, 0.0);
-            this._transformMatrices[mapIndex].multiplyToRef(roundMatrix, this._transformMatrices[mapIndex]);
+                this._cascadeMinExtents[cascadeIndex].minimizeInPlace(tmpv1);
+                this._cascadeMaxExtents[cascadeIndex].maximizeInPlace(tmpv1);
+            }
         }
 
-        this._transformMatrices[mapIndex].copyToArray(this._transformMatricesAsArray, mapIndex * 16);
-        return this._transformMatrices[mapIndex];
+        return;
     }
 
     /** @hidden */
@@ -831,11 +973,49 @@ export class CascadedShadowGenerator implements IShadowGenerator {
     }
 
     private _initializeShadowMap(): void {
+        // CSM
+        this._transformMatricesAsArray = new Float32Array(this._numCascades * 16);
+        this._viewSpaceFrustumsZ = new Array(this._numCascades);
+        this._frustumLengths = new Array(this._numCascades);
+        this._currentRenderID = new Array(this._numCascades);
+        this._lightSizeUVCorrection = new Array(this._numCascades * 2);
+        this._depthCorrection = new Array(this._numCascades);
+
+        this._cascades = [];
+        this._viewMatrices = [];
+        this._projectionMatrices = [];
+        this._transformMatrices = [];
+        this._cascadeMinExtents = [];
+        this._cascadeMaxExtents = [];
+        this._frustumCenter = [];
+        this._shadowCameraPos = [];
+        this._frustumCornersWorldSpace = [];
+
+        for (let cascadeIndex = 0; cascadeIndex < this._numCascades; ++cascadeIndex) {
+            this._cascades[cascadeIndex] = {
+                prevBreakDistance: 0,
+                breakDistance: 0,
+            };
+
+            this._viewMatrices[cascadeIndex] = Matrix.Zero();
+            this._projectionMatrices[cascadeIndex] = Matrix.Zero();
+            this._transformMatrices[cascadeIndex] = Matrix.Zero();
+            this._cascadeMinExtents[cascadeIndex] = new Vector3();
+            this._cascadeMaxExtents[cascadeIndex] = new Vector3();
+            this._frustumCenter[cascadeIndex] = new Vector3();
+            this._shadowCameraPos[cascadeIndex] = new Vector3();
+            this._frustumCornersWorldSpace[cascadeIndex] = new Array(CascadedShadowGenerator.frustumCornersNDCSpace.length);
+
+            for (let i = 0; i < CascadedShadowGenerator.frustumCornersNDCSpace.length; ++i) {
+                this._frustumCornersWorldSpace[cascadeIndex][i] = new Vector3();
+            }
+        }
+
         // Render target
         let engine = this._scene.getEngine();
 
-        const size = { width: this._mapSize, height: this._mapSize, layers: this.cascades };
-        this._shadowMap = new RenderTargetTexture(this._light.name + "_shadowMap", size, this._scene, false, true, this._textureType, false, undefined, false, false);
+        const size = { width: this._mapSize, height: this._mapSize, layers: this.numCascades };
+        this._shadowMap = new RenderTargetTexture(this._light.name + "_shadowMap", size, this._scene, false, true, this._textureType, false, undefined, false, false, undefined, Constants.TEXTUREFORMAT_RED);
         this._shadowMap.createDepthStencilTexture(Constants.LESS, true);
 
         this._shadowMap.wrapU = Texture.CLAMP_ADDRESSMODE;
@@ -848,6 +1028,12 @@ export class CascadedShadowGenerator implements IShadowGenerator {
             this._shadowMap.uniqueId = this._storedUniqueId;
         }
 
+        this._shadowMap.onBeforeBindObservable.add(() => {
+            if (this._breaksAreDirty) {
+                this._splitFrustum();
+            }
+        });
+
         // Record Face Index before render.
         this._shadowMap.onBeforeRenderObservable.add((layer: number) => {
             this._currentLayer = layer;
@@ -884,7 +1070,7 @@ export class CascadedShadowGenerator implements IShadowGenerator {
             this.recreateShadowMap();
         });
 
-        this._initCascades();
+        this._splitFrustum();
     }
 
     private _renderForShadowMap(opaqueSubMeshes: SmartArray<SubMesh>, alphaTestSubMeshes: SmartArray<SubMesh>, transparentSubMeshes: SmartArray<SubMesh>, depthOnlySubMeshes: SmartArray<SubMesh>): void {
@@ -1108,7 +1294,7 @@ export class CascadedShadowGenerator implements IShadowGenerator {
 
         defines.push("#define DEPTHTEXTURE");
 
-        if (this.depthClamp) {
+        if (this._depthClamp && this._filter !== CascadedShadowGenerator.FILTER_PCSS) {
             defines.push("#define DEPTHCLAMP");
         }
 
@@ -1224,7 +1410,13 @@ export class CascadedShadowGenerator implements IShadowGenerator {
 
         defines["SHADOWCSM" + lightIndex] = true;
         defines["SHADOWCSMDEBUG" + lightIndex] = this.debug;
-        defines["SHADOWCSMNUM_CASCADES" + lightIndex] = this.cascades;
+        defines["SHADOWCSMNUM_CASCADES" + lightIndex] = this.numCascades;
+
+        const camera = scene.activeCamera;
+
+        if (camera && this._shadowMaxZ < camera.maxZ) {
+            defines["SHADOWCSMUSESHADOWMAXZ" + lightIndex] = true;
+        }
 
         if (this.useContactHardeningShadow) {
             defines["SHADOWPCSS" + lightIndex] = true;
@@ -1274,18 +1466,16 @@ export class CascadedShadowGenerator implements IShadowGenerator {
 
         const width = shadowMap.getSize().width;
 
-        const lightSizeUVCorrection: Array<number> = [],
-              depthCorrection: Array<number> = [];
-
-        for (let i = 0; i < this._cascades; ++i) {
-            lightSizeUVCorrection.push(i === 0 || !this.lightSizeCorrection ? 1 : (this._lightMaxExtents[0].x - this._lightMinExtents[0].x) / (this._lightMaxExtents[i].x - this._lightMinExtents[i].x)); // x correction
-            lightSizeUVCorrection.push(i === 0 || !this.lightSizeCorrection ? 1 : (this._lightMaxExtents[0].y - this._lightMinExtents[0].y) / (this._lightMaxExtents[i].y - this._lightMinExtents[i].y)); // y correction
-            depthCorrection.push(i === 0 || !this.depthCorrection ? 1 : (this._lightMaxExtents[i].z - this._lightMinExtents[i].z) / (this._lightMaxExtents[0].z - this._lightMinExtents[0].z));
+        for (let cascadeIndex = 0; cascadeIndex < this._numCascades; ++cascadeIndex) {
+            this._lightSizeUVCorrection[cascadeIndex * 2 + 0] = cascadeIndex === 0 ? 1 : (this._cascadeMaxExtents[0].x - this._cascadeMinExtents[0].x) / (this._cascadeMaxExtents[cascadeIndex].x - this._cascadeMinExtents[cascadeIndex].x); // x correction
+            this._lightSizeUVCorrection[cascadeIndex * 2 + 1] = cascadeIndex === 0 ? 1 : (this._cascadeMaxExtents[0].y - this._cascadeMinExtents[0].y) / (this._cascadeMaxExtents[cascadeIndex].y - this._cascadeMinExtents[cascadeIndex].y); // y correction
+            this._depthCorrection[cascadeIndex] = cascadeIndex === 0 ? 1 : (this._cascadeMaxExtents[cascadeIndex].z - this._cascadeMinExtents[cascadeIndex].z) / (this._cascadeMaxExtents[0].z - this._cascadeMinExtents[0].z);
         }
 
         effect.setMatrices("lightMatrix" + lightIndex, this._transformMatricesAsArray);
         effect.setArray("viewFrustumZ" + lightIndex, this._viewSpaceFrustumsZ);
         effect.setFloat("cascadeBlendFactor" + lightIndex, this.cascadeBlendPercentage === 0 ? 10000 : 1 / this.cascadeBlendPercentage);
+        effect.setArray("frustumLengths" + lightIndex, this._frustumLengths);
 
         // Only PCF uses depth stencil texture.
         if (this._filter === CascadedShadowGenerator.FILTER_PCF) {
@@ -1294,8 +1484,8 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         } else if (this._filter === CascadedShadowGenerator.FILTER_PCSS) {
             effect.setDepthStencilTexture("shadowSampler" + lightIndex, shadowMap);
             effect.setTexture("depthSampler" + lightIndex, shadowMap);
-            effect.setArray2("lightSizeUVCorrection" + lightIndex, lightSizeUVCorrection);
-            effect.setArray("depthCorrection" + lightIndex, depthCorrection);
+            effect.setArray2("lightSizeUVCorrection" + lightIndex, this._lightSizeUVCorrection);
+            effect.setArray("depthCorrection" + lightIndex, this._depthCorrection);
             effect.setFloat("penumbraDarkness" + lightIndex, this.penumbraDarkness);
             light._uniformBuffer.updateFloat4("shadowsInfo", this.getDarkness(), 1 / width, this._contactHardeningLightSizeUVRatio * width, this.frustumEdgeFalloff, lightIndex);
         }
@@ -1308,8 +1498,8 @@ export class CascadedShadowGenerator implements IShadowGenerator {
     }
 
     /**
-     * Gets the transformation matrix used to project the meshes into the map from the light point of view.
-     * (eq to shadow prjection matrix * light transform matrix)
+     * Gets the transformation matrix of the first cascade used to project the meshes into the map from the light point of view.
+     * (eq to view projection * shadow projection matrices)
      * @returns The transform matrix used to create the shadow map
      */
     public getTransformMatrix(): Matrix {
@@ -1388,6 +1578,13 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         serializationObject.darkness = this.getDarkness();
         serializationObject.transparencyShadow = this._transparencyShadow;
         serializationObject.frustumEdgeFalloff = this.frustumEdgeFalloff;
+        serializationObject.numCascades = this._numCascades;
+        serializationObject.stabilizeCascades = this.stabilizeCascades;
+        serializationObject.depthClamp = this._depthClamp;
+        serializationObject.lambda = this._lambda;
+        serializationObject.freezeShadowCastersBoundingInfo = this._freezeShadowCastersBoundingInfo;
+        serializationObject.shadowMaxZ = this._shadowMaxZ;
+        serializationObject.cascadeBlendPercentage = this.cascadeBlendPercentage;
 
         serializationObject.bias = this.bias;
         serializationObject.normalBias = this.normalBias;
@@ -1396,6 +1593,7 @@ export class CascadedShadowGenerator implements IShadowGenerator {
         serializationObject.useContactHardeningShadow = this.useContactHardeningShadow;
         serializationObject.filteringQuality = this.filteringQuality;
         serializationObject.contactHardeningLightSizeUVRatio = this.contactHardeningLightSizeUVRatio;
+        serializationObject.penumbraDarkness = this.penumbraDarkness;
 
         serializationObject.renderList = [];
         if (shadowMap.renderList) {
@@ -1440,11 +1638,11 @@ export class CascadedShadowGenerator implements IShadowGenerator {
             shadowGenerator.useContactHardeningShadow = true;
         }
 
-        if (parsedShadowGenerator.filteringQuality) {
+        if (parsedShadowGenerator.filteringQuality !== undefined) {
             shadowGenerator.filteringQuality = parsedShadowGenerator.filteringQuality;
         }
 
-        if (parsedShadowGenerator.contactHardeningLightSizeUVRatio) {
+        if (parsedShadowGenerator.contactHardeningLightSizeUVRatio !== undefined) {
             shadowGenerator.contactHardeningLightSizeUVRatio = parsedShadowGenerator.contactHardeningLightSizeUVRatio;
         }
 
@@ -1460,7 +1658,7 @@ export class CascadedShadowGenerator implements IShadowGenerator {
             shadowGenerator.frustumEdgeFalloff = parsedShadowGenerator.frustumEdgeFalloff;
         }
 
-        if (parsedShadowGenerator.darkness) {
+        if (parsedShadowGenerator.darkness !== undefined) {
             shadowGenerator.setDarkness(parsedShadowGenerator.darkness);
         }
 
@@ -1468,7 +1666,39 @@ export class CascadedShadowGenerator implements IShadowGenerator {
             shadowGenerator.setTransparencyShadow(true);
         }
 
-        shadowGenerator.forceBackFacesOnly = parsedShadowGenerator.forceBackFacesOnly;
+        shadowGenerator.forceBackFacesOnly = !!parsedShadowGenerator.forceBackFacesOnly;
+
+        if (parsedShadowGenerator.stabilizeCascades !== undefined) {
+            shadowGenerator.stabilizeCascades = parsedShadowGenerator.stabilizeCascades;
+        }
+
+        if (parsedShadowGenerator.depthClamp !== undefined) {
+             shadowGenerator.depthClamp = parsedShadowGenerator.depthClamp;
+        }
+
+        if (parsedShadowGenerator.lambda !== undefined) {
+             shadowGenerator.lambda = parsedShadowGenerator.lambda;
+        }
+
+        if (parsedShadowGenerator.freezeShadowCastersBoundingInfo !== undefined) {
+            shadowGenerator.freezeShadowCastersBoundingInfo = parsedShadowGenerator.freezeShadowCastersBoundingInfo;
+        }
+
+        if (parsedShadowGenerator.shadowMaxZ !== undefined) {
+             shadowGenerator.shadowMaxZ = parsedShadowGenerator.shadowMaxZ;
+        }
+
+        if (parsedShadowGenerator.cascadeBlendPercentage !== undefined) {
+             shadowGenerator.cascadeBlendPercentage = parsedShadowGenerator.cascadeBlendPercentage;
+        }
+
+        if (parsedShadowGenerator.penumbraDarkness !== undefined) {
+            shadowGenerator.penumbraDarkness = parsedShadowGenerator.penumbraDarkness;
+        }
+
+        if (parsedShadowGenerator.numCascades !== undefined) {
+            shadowGenerator.numCascades = parsedShadowGenerator.numCascades;
+        }
 
         return shadowGenerator;
     }