Ver código fonte

Merge pull request #18481 from vHawk/dev

Examples: Add CSM implementation
Mr.doob 5 anos atrás
pai
commit
28c68f2b2d

+ 1 - 0
examples/files.js

@@ -303,6 +303,7 @@ var files = {
 		"webgl_lightshafts",
 		"webgl_materials_modified",
 		"webgl_raymarching_reflect",
+		"webgl_shadowmap_csm",
 		"webgl_shadowmap_pcss",
 		"webgl_simple_gi",
 		"webgl_tiled_forward",

+ 338 - 0
examples/jsm/csm/CSM.js

@@ -0,0 +1,338 @@
+import * as THREE from '../../../build/three.module.js';
+import Frustum from './Frustum.js';
+import FrustumBoundingBox from './FrustumBoundingBox.js';
+import Shader from './Shader.js';
+
+export default class CSM {
+
+	constructor( data ) {
+
+		data = data || {};
+
+		this.camera = data.camera;
+		this.parent = data.parent;
+		this.fov = data.fov || this.camera.fov;
+		this.near = this.camera.near;
+		this.far = data.far || this.camera.far;
+		this.aspect = data.aspect || this.camera.aspect;
+		this.cascades = data.cascades || 3;
+		this.mode = data.mode || 'practical';
+		this.shadowMapSize = data.shadowMapSize || 2048;
+		this.shadowBias = data.shadowBias || 0.000001;
+		this.lightDirection = data.lightDirection || new THREE.Vector3( 1, - 1, 1 ).normalize();
+		this.lightIntensity = data.lightIntensity || 1;
+		this.lightNear = data.lightNear || 1;
+		this.lightFar = data.lightFar || 2000;
+		this.lightMargin = data.lightMargin || 200;
+		this.customSplitsCallback = data.customSplitsCallback;
+
+		this.lights = [];
+		this.materials = [];
+		this.createLights();
+
+		this.getBreaks();
+		this.initCascades();
+
+		this.injectInclude();
+
+	}
+
+	createLights() {
+
+		for ( let i = 0; i < this.cascades; i ++ ) {
+
+			const light = new THREE.DirectionalLight( 0xffffff, this.lightIntensity );
+			light.castShadow = true;
+			light.shadow.mapSize.width = this.shadowMapSize;
+			light.shadow.mapSize.height = this.shadowMapSize;
+
+			light.shadow.camera.near = this.lightNear;
+			light.shadow.camera.far = this.lightFar;
+			light.shadow.bias = this.shadowBias;
+
+			this.parent.add( light );
+			this.parent.add( light.target );
+			this.lights.push( light );
+
+		}
+
+	}
+
+	initCascades() {
+
+		this.mainFrustum = new Frustum( {
+			fov: this.fov,
+			near: this.near,
+			far: this.far,
+			aspect: this.aspect
+		} );
+
+		this.mainFrustum.getViewSpaceVertices();
+
+		this.frustums = this.mainFrustum.split( this.breaks );
+
+	}
+
+	getBreaks() {
+
+		this.breaks = [];
+
+		switch ( this.mode ) {
+
+			case 'uniform':
+				this.breaks = uniformSplit( this.cascades, this.near, this.far );
+				break;
+			case 'logarithmic':
+				this.breaks = logarithmicSplit( this.cascades, this.near, this.far );
+				break;
+			case 'practical':
+				this.breaks = practicalSplit( this.cascades, this.near, this.far, 0.5 );
+				break;
+			case 'custom':
+				if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' );
+				this.breaks = this.customSplitsCallback( this.cascades, this.near, this.far );
+				break;
+
+		}
+
+		function uniformSplit( amount, near, far ) {
+
+			const r = [];
+
+			for ( let i = 1; i < amount; i ++ ) {
+
+				r.push( ( near + ( far - near ) * i / amount ) / far );
+
+			}
+
+			r.push( 1 );
+			return r;
+
+		}
+
+		function logarithmicSplit( amount, near, far ) {
+
+			const r = [];
+
+			for ( let i = 1; i < amount; i ++ ) {
+
+				r.push( ( near * ( far / near ) ** ( i / amount ) ) / far );
+
+			}
+
+			r.push( 1 );
+			return r;
+
+		}
+
+		function practicalSplit( amount, near, far, lambda ) {
+
+			const log = logarithmicSplit( amount, near, far );
+			const uni = uniformSplit( amount, near, far );
+			const r = [];
+
+			for ( let i = 1; i < amount; i ++ ) {
+
+				r.push( THREE.MathUtils.lerp( uni[ i - 1 ], log[ i - 1 ], lambda ) );
+
+			}
+
+			r.push( 1 );
+			return r;
+
+		}
+
+	}
+
+	update( cameraMatrix ) {
+
+		for ( let i = 0; i < this.frustums.length; i ++ ) {
+
+			const worldSpaceFrustum = this.frustums[ i ].toSpace( cameraMatrix );
+			const light = this.lights[ i ];
+			const lightSpaceFrustum = worldSpaceFrustum.toSpace( light.shadow.camera.matrixWorldInverse );
+
+			light.shadow.camera.updateMatrixWorld( true );
+
+			const bbox = new FrustumBoundingBox().fromFrustum( lightSpaceFrustum );
+			bbox.getSize();
+			bbox.getCenter( this.lightMargin );
+
+			const squaredBBWidth = Math.max( bbox.size.x, bbox.size.y );
+
+			let center = new THREE.Vector3( bbox.center.x, bbox.center.y, bbox.center.z );
+			center.applyMatrix4( light.shadow.camera.matrixWorld );
+
+			light.shadow.camera.left = - squaredBBWidth / 2;
+			light.shadow.camera.right = squaredBBWidth / 2;
+			light.shadow.camera.top = squaredBBWidth / 2;
+			light.shadow.camera.bottom = - squaredBBWidth / 2;
+
+			light.position.copy( center );
+			light.target.position.copy( center );
+
+			light.target.position.x += this.lightDirection.x;
+			light.target.position.y += this.lightDirection.y;
+			light.target.position.z += this.lightDirection.z;
+
+			light.shadow.camera.updateProjectionMatrix();
+			light.shadow.camera.updateMatrixWorld();
+
+		}
+
+	}
+
+	injectInclude() {
+
+		THREE.ShaderChunk.lights_fragment_begin = Shader.lights_fragment_begin;
+		THREE.ShaderChunk.lights_pars_begin = Shader.lights_pars_begin;
+
+	}
+
+	setupMaterial( material ) {
+
+		material.defines = material.defines || {};
+		material.defines.USE_CSM = 1;
+		material.defines.CSM_CASCADES = this.cascades;
+
+		const breaksVec2 = [];
+
+		for ( let i = 0; i < this.cascades; i ++ ) {
+
+			let amount = this.breaks[ i ];
+			let prev = this.breaks[ i - 1 ] || 0;
+			breaksVec2.push( new THREE.Vector2( prev, amount ) );
+
+		}
+
+		const self = this;
+
+		material.onBeforeCompile = function ( shader ) {
+
+			shader.uniforms.CSM_cascades = { value: breaksVec2 };
+			shader.uniforms.cameraNear = { value: self.camera.near };
+			shader.uniforms.shadowFar = { value: self.far };
+
+			self.materials.push( shader );
+
+		};
+
+	}
+
+	updateUniforms() {
+
+		for ( let i = 0; i < this.materials.length; i ++ ) {
+
+			this.materials[ i ].uniforms.CSM_cascades.value = this.getExtendedBreaks();
+			this.materials[ i ].uniforms.cameraNear.value = this.camera.near;
+			this.materials[ i ].uniforms.shadowFar.value = this.far;
+
+		}
+
+	}
+
+	getExtendedBreaks() {
+
+		let breaksVec2 = [];
+
+		for ( let i = 0; i < this.cascades; i ++ ) {
+
+			let amount = this.breaks[ i ];
+			let prev = this.breaks[ i - 1 ] || 0;
+			breaksVec2.push( new THREE.Vector2( prev, amount ) );
+
+		}
+
+		return breaksVec2;
+
+	}
+
+	setAspect( aspect ) {
+
+		this.aspect = aspect;
+		this.initCascades();
+
+	}
+
+	updateFrustums() {
+
+		this.getBreaks();
+		this.initCascades();
+		this.updateUniforms();
+
+	}
+
+	helper( cameraMatrix ) {
+
+		let frustum;
+		let geometry, vertices;
+		const material = new THREE.LineBasicMaterial( { color: 0xffffff } );
+		const object = new THREE.Object3D();
+
+		for ( let i = 0; i < this.frustums.length; i ++ ) {
+
+			frustum = this.frustums[ i ].toSpace( cameraMatrix );
+
+			geometry = new THREE.BufferGeometry();
+			vertices = [];
+			
+
+			for ( let i = 0; i < 5; i ++ ) {
+
+				const point = frustum.vertices.near[ i === 4 ? 0 : i ];
+				vertices.push( point.x, point.y, point.z );
+
+			}
+			
+			geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( vertices ), 3 ) );
+
+			object.add( new THREE.Line( geometry, material ) );
+
+			geometry = new THREE.BufferGeometry();
+			vertices = [];
+
+			for ( let i = 0; i < 5; i ++ ) {
+
+				const point = frustum.vertices.far[ i === 4 ? 0 : i ];
+				vertices.push( point.x, point.y, point.z );
+
+			}
+			
+			geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( vertices ), 3 ) );
+
+			object.add( new THREE.Line( geometry, material ) );
+
+			for ( let i = 0; i < 4; i ++ ) {
+
+				geometry = new THREE.BufferGeometry();
+				vertices = [];
+
+				const near = frustum.vertices.near[ i ];
+				const far = frustum.vertices.far[ i ];
+
+				vertices.push( near.x, near.y, near.z );
+				vertices.push( far.x, far.y, far.z );
+				
+				geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( vertices ), 3 ) );
+
+				object.add( new THREE.Line( geometry, material ) );
+
+			}
+
+		}
+
+		return object;
+
+	}
+
+	remove() {
+
+		for ( let i = 0; i < this.lights.length; i ++ ) {
+
+			this.parent.remove( this.lights[ i ] );
+
+		}
+
+	}
+
+}

+ 118 - 0
examples/jsm/csm/Frustum.js

@@ -0,0 +1,118 @@
+import * as THREE from '../../../build/three.module.js';
+import FrustumVertex from './FrustumVertex.js';
+import { toRad } from './Utils.js';
+
+export default class Frustum {
+
+	constructor( data ) {
+
+		data = data || {};
+
+		this.fov = data.fov || 70;
+		this.near = data.near || 0.1;
+		this.far = data.far || 1000;
+		this.aspect = data.aspect || 1;
+
+		this.vertices = {
+			near: [],
+			far: []
+		};
+
+	}
+
+	getViewSpaceVertices() {
+
+		this.nearPlaneY = this.near * Math.tan( toRad( this.fov / 2 ) );
+		this.nearPlaneX = this.aspect * this.nearPlaneY;
+
+		this.farPlaneY = this.far * Math.tan( toRad( this.fov / 2 ) );
+		this.farPlaneX = this.aspect * this.farPlaneY;
+
+		// 3 --- 0  vertices.near/far order
+		// |     |
+		// 2 --- 1
+
+		this.vertices.near.push(
+			new FrustumVertex( this.nearPlaneX, this.nearPlaneY, - this.near ),
+			new FrustumVertex( this.nearPlaneX, - this.nearPlaneY, - this.near ),
+			new FrustumVertex( - this.nearPlaneX, - this.nearPlaneY, - this.near ),
+			new FrustumVertex( - this.nearPlaneX, this.nearPlaneY, - this.near )
+		);
+
+		this.vertices.far.push(
+			new FrustumVertex( this.farPlaneX, this.farPlaneY, - this.far ),
+			new FrustumVertex( this.farPlaneX, - this.farPlaneY, - this.far ),
+			new FrustumVertex( - this.farPlaneX, - this.farPlaneY, - this.far ),
+			new FrustumVertex( - this.farPlaneX, this.farPlaneY, - this.far )
+		);
+
+		return this.vertices;
+
+	}
+
+	split( breaks ) {
+
+		const result = [];
+
+		for ( let i = 0; i < breaks.length; i ++ ) {
+
+			const cascade = new Frustum();
+
+			if ( i === 0 ) {
+
+				cascade.vertices.near = this.vertices.near;
+
+			} else {
+
+				for ( let j = 0; j < 4; j ++ ) {
+
+					cascade.vertices.near.push( new FrustumVertex().fromLerp( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i - 1 ] ) );
+
+				}
+
+			}
+
+			if ( i === breaks - 1 ) {
+
+				cascade.vertices.far = this.vertices.far;
+
+			} else {
+
+				for ( let j = 0; j < 4; j ++ ) {
+
+					cascade.vertices.far.push( new FrustumVertex().fromLerp( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i ] ) );
+
+				}
+
+			}
+
+			result.push( cascade );
+
+		}
+
+		return result;
+
+	}
+
+	toSpace( cameraMatrix ) {
+
+		const result = new Frustum();
+		const point = new THREE.Vector3();
+
+		for ( var i = 0; i < 4; i ++ ) {
+
+			point.set( this.vertices.near[ i ].x, this.vertices.near[ i ].y, this.vertices.near[ i ].z );
+			point.applyMatrix4( cameraMatrix );
+			result.vertices.near.push( new FrustumVertex( point.x, point.y, point.z ) );
+
+			point.set( this.vertices.far[ i ].x, this.vertices.far[ i ].y, this.vertices.far[ i ].z );
+			point.applyMatrix4( cameraMatrix );
+			result.vertices.far.push( new FrustumVertex( point.x, point.y, point.z ) );
+
+		}
+
+		return result;
+
+	}
+
+}

+ 79 - 0
examples/jsm/csm/FrustumBoundingBox.js

@@ -0,0 +1,79 @@
+export default class FrustumBoundingBox {
+
+	constructor() {
+
+		this.min = {
+			x: 0,
+			y: 0,
+			z: 0
+		};
+		this.max = {
+			x: 0,
+			y: 0,
+			z: 0
+		};
+
+	}
+
+	fromFrustum( frustum ) {
+
+		const vertices = [];
+
+		for ( let i = 0; i < 4; i ++ ) {
+
+			vertices.push( frustum.vertices.near[ i ] );
+			vertices.push( frustum.vertices.far[ i ] );
+
+		}
+
+		this.min = {
+			x: vertices[ 0 ].x,
+			y: vertices[ 0 ].y,
+			z: vertices[ 0 ].z
+		};
+		this.max = {
+			x: vertices[ 0 ].x,
+			y: vertices[ 0 ].y,
+			z: vertices[ 0 ].z
+		};
+
+		for ( let i = 1; i < 8; i ++ ) {
+
+			this.min.x = Math.min( this.min.x, vertices[ i ].x );
+			this.min.y = Math.min( this.min.y, vertices[ i ].y );
+			this.min.z = Math.min( this.min.z, vertices[ i ].z );
+			this.max.x = Math.max( this.max.x, vertices[ i ].x );
+			this.max.y = Math.max( this.max.y, vertices[ i ].y );
+			this.max.z = Math.max( this.max.z, vertices[ i ].z );
+
+		}
+
+		return this;
+
+	}
+
+	getSize() {
+
+		this.size = {
+			x: this.max.x - this.min.x,
+			y: this.max.y - this.min.y,
+			z: this.max.z - this.min.z
+		};
+
+		return this.size;
+
+	}
+
+	getCenter( margin ) {
+
+		this.center = {
+			x: ( this.max.x + this.min.x ) / 2,
+			y: ( this.max.y + this.min.y ) / 2,
+			z: this.max.z + margin
+		};
+
+		return this.center;
+
+	}
+
+}

+ 21 - 0
examples/jsm/csm/FrustumVertex.js

@@ -0,0 +1,21 @@
+export default class FrustumVertex {
+
+	constructor( x, y, z ) {
+
+		this.x = x || 0;
+		this.y = y || 0;
+		this.z = z || 0;
+
+	}
+
+	fromLerp( v1, v2, amount ) {
+
+		this.x = ( 1 - amount ) * v1.x + amount * v2.x;
+		this.y = ( 1 - amount ) * v1.y + amount * v2.y;
+		this.z = ( 1 - amount ) * v1.z + amount * v2.z;
+
+		return this;
+
+	}
+
+}

+ 94 - 0
examples/jsm/csm/Shader.js

@@ -0,0 +1,94 @@
+import * as THREE from '../../../build/three.module.js';
+
+export default {
+	lights_fragment_begin: `
+GeometricContext geometry;
+geometry.position = - vViewPosition;
+geometry.normal = normal;
+geometry.viewDir = normalize( vViewPosition );
+#ifdef CLEARCOAT
+	geometry.clearcoatNormal = clearcoatNormal;
+#endif
+IncidentLight directLight;
+#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )
+	PointLight pointLight;
+	#pragma unroll_loop
+	for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {
+		pointLight = pointLights[ i ];
+		getPointDirectLightIrradiance( pointLight, geometry, directLight );
+		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )
+		directLight.color *= all( bvec3( pointLight.shadow, directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;
+		#endif
+		RE_Direct( directLight, geometry, material, reflectedLight );
+	}
+#endif
+#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )
+	SpotLight spotLight;
+	#pragma unroll_loop
+	for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {
+		spotLight = spotLights[ i ];
+		getSpotDirectLightIrradiance( spotLight, geometry, directLight );
+		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
+		directLight.color *= all( bvec3( spotLight.shadow, directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;
+		#endif
+		RE_Direct( directLight, geometry, material, reflectedLight );
+	}
+#endif
+#if ( NUM_DIR_LIGHTS > 0) && defined( RE_Direct ) && defined( USE_CSM ) && defined( CSM_CASCADES )
+	DirectionalLight directionalLight;
+	float linearDepth = (vViewPosition.z) / (shadowFar - cameraNear);
+
+	#pragma unroll_loop
+	for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
+		directionalLight = directionalLights[ i ];
+		getDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );
+		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
+		if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= all( bvec3( directionalLight.shadow, directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
+		#endif
+		if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometry, material, reflectedLight );
+	}
+#endif
+#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && !defined( USE_CSM ) && !defined( CSM_CASCADES )
+	DirectionalLight directionalLight;
+	#pragma unroll_loop
+	for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
+		directionalLight = directionalLights[ i ];
+		getDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );
+		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
+		directLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
+		#endif
+		RE_Direct( directLight, geometry, material, reflectedLight );
+	}
+#endif
+#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )
+	RectAreaLight rectAreaLight;
+	#pragma unroll_loop
+	for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {
+		rectAreaLight = rectAreaLights[ i ];
+		RE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );
+	}
+#endif
+#if defined( RE_IndirectDiffuse )
+	vec3 iblIrradiance = vec3( 0.0 );
+	vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
+	irradiance += getLightProbeIrradiance( lightProbe, geometry );
+	#if ( NUM_HEMI_LIGHTS > 0 )
+		#pragma unroll_loop
+		for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
+			irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );
+		}
+	#endif
+#endif
+#if defined( RE_IndirectSpecular )
+	vec3 radiance = vec3( 0.0 );
+	vec3 clearcoatRadiance = vec3( 0.0 );
+#endif
+`,
+	lights_pars_begin: `
+#if defined( USE_CSM ) && defined( CSM_CASCADES )
+uniform vec2 CSM_cascades[CSM_CASCADES];
+uniform float cameraNear;
+uniform float shadowFar;
+#endif
+	` + THREE.ShaderChunk.lights_pars_begin
+};

+ 5 - 0
examples/jsm/csm/Utils.js

@@ -0,0 +1,5 @@
+export function toRad( degrees ) {
+
+	return degrees * Math.PI / 180;
+
+}

+ 205 - 0
examples/webgl_shadowmap_csm.html

@@ -0,0 +1,205 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - cascaded shadow maps</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - cascaded shadow maps<br>
+			by <a href="https://github.com/vHawk" target="_blank" rel="noopener">vHawk</a> (<a href="https://github.com/vHawk/three-csm" target="_blank" rel="noopener">original repository</a>)
+		</div>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { GUI } from './jsm/libs/dat.gui.module.js';
+			import * as CSM from './jsm/csm/CSM.js';
+
+			var renderer, scene, camera, controls, csm;
+
+			init();
+			animate();
+
+			function init() {
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( '#454e61' );
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 5000 );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+				renderer.shadowMap.enabled = true;
+				renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.maxPolarAngle = Math.PI / 2;
+				camera.position.set( 60, 60, 0 );
+				controls.target = new THREE.Vector3( -100, 10, 0 );
+				controls.update();
+
+				var ambientLight = new THREE.AmbientLight( 0xffffff, 0.5 );
+				scene.add( ambientLight );
+
+				var params = {
+					far: 1000,
+					mode: 'practical',
+					lightX: - 1,
+					lightY: - 1,
+					lightZ: - 1,
+					margin: 100,
+					lightFar: 5000,
+					lightNear: 1,
+					helper: function () {
+
+						var helper = csm.helper( camera.matrix );
+						scene.add( helper );
+
+					}
+				};
+
+				csm = new CSM.default({
+					fov: camera.fov,
+					near: camera.near,
+					far: params.far,
+					aspect: camera.aspect,
+					cascades: 4,
+					mode: params.mode,
+					parent: scene,
+					shadowMapSize: 1024,
+					lightDirection: new THREE.Vector3( params.lightX, params.lightY, params.lightZ ).normalize(),
+					camera: camera
+				} );
+
+				var floorMaterial = new THREE.MeshPhongMaterial( { color: '#252a34' } );
+				csm.setupMaterial( floorMaterial );
+
+				var floor = new THREE.Mesh( new THREE.PlaneBufferGeometry( 10000, 10000, 8, 8 ), floorMaterial );
+				floor.rotation.x = - Math.PI / 2;
+				floor.castShadow = true;
+				floor.receiveShadow = true;
+				scene.add( floor );
+
+				var material1 = new THREE.MeshPhongMaterial( { color: '#08d9d6' } );
+				csm.setupMaterial( material1 );
+
+				var material2 = new THREE.MeshPhongMaterial( { color: '#ff2e63' } );
+				csm.setupMaterial( material2 );
+
+				var geometry = new THREE.BoxBufferGeometry( 10, 10, 10 );
+
+				for ( var i = 0; i < 40; i ++ ) {
+
+					var cube1 = new THREE.Mesh( geometry, i % 2 === 0 ? material1 : material2 );
+					cube1.castShadow = true;
+					cube1.receiveShadow = true;
+					scene.add( cube1 );
+					cube1.position.set( - i * 25, 20, 30 );
+					cube1.scale.y = Math.random() * 2 + 6;
+
+					var cube2 = new THREE.Mesh( geometry, i % 2 === 0 ? material2 : material1 );
+					cube2.castShadow = true;
+					cube2.receiveShadow = true;
+					scene.add( cube2 );
+					cube2.position.set( - i * 25, 20, - 30 );
+					cube2.scale.y = Math.random() * 2 + 6;
+
+				}
+
+				var gui = new GUI();
+
+				gui.add( params, 'far', 1, 5000 ).step( 1 ).name( 'shadow far' ).onChange( function ( value ) {
+
+					csm.far = value;
+					csm.updateFrustums();
+
+				} );
+
+				gui.add( params, 'mode', [ 'uniform', 'logarithmic', 'practical' ] ).name( 'frustum split mode' ).onChange( function ( value ) {
+
+					csm.mode = value;
+					csm.updateFrustums();
+
+				} );
+
+				gui.add( params, 'lightX', - 1, 1 ).name( 'light direction x' ).onChange( function ( value ) {
+
+					csm.lightDirection.x = value;
+
+				} );
+
+				gui.add( params, 'lightY', - 1, 1 ).name( 'light direction y' ).onChange( function ( value ) {
+
+					csm.lightDirection.y = value;
+
+				} );
+
+				gui.add( params, 'lightZ', - 1, 1 ).name( 'light direction z' ).onChange( function ( value ) {
+
+					csm.lightDirection.z = value;
+
+				} );
+
+				gui.add( params, 'margin', 0, 200 ).name( 'light margin' ).onChange( function ( value ) {
+
+					csm.lightMargin = value;
+
+				} );
+
+				gui.add( params, 'lightNear', 1, 10000 ).name( 'light near' ).onChange( function ( value ) {
+
+					for ( var i = 0; i < csm.lights.length; i ++ ) {
+
+						csm.lights[ i ].shadow.camera.near = value;
+						csm.lights[ i ].shadow.camera.updateProjectionMatrix();
+
+					}
+
+				} );
+
+				gui.add( params, 'lightFar', 1, 10000 ).name( 'light far' ).onChange( function ( value ) {
+
+					for ( var i = 0; i < csm.lights.length; i ++ ) {
+
+						csm.lights[ i ].shadow.camera.far = value;
+						csm.lights[ i ].shadow.camera.updateProjectionMatrix();
+
+					}
+
+				} );
+
+				gui.add( params, 'helper' ).name( 'add frustum helper' );
+
+				window.addEventListener( 'resize', function () {
+
+					camera.aspect = window.innerWidth / window.innerHeight;
+					camera.updateProjectionMatrix();
+
+					csm.setAspect( camera.aspect );
+
+					renderer.setSize( window.innerWidth, window.innerHeight );
+
+				}, false);
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				csm.update( camera.matrix );
+				controls.update();
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>