Browse Source

Experimenting with cascaded shadow maps.

Work-in-progress. Don't merge, it's still very rough and full of debug things.
alteredq 13 years ago
parent
commit
aa803fd2b4

+ 205 - 9
src/extras/plugins/ShadowMapPlugin.js

@@ -11,6 +11,9 @@ THREE.ShadowMapPlugin = function ( ) {
 	_frustum = new THREE.Frustum(),
 	_projScreenMatrix = new THREE.Matrix4();
 
+	_min = new THREE.Vector3(),
+	_max = new THREE.Vector3();
+
 	this.init = function ( renderer ) {
 
 		_gl = renderer.context;
@@ -37,14 +40,16 @@ THREE.ShadowMapPlugin = function ( ) {
 
 	this.update = function ( scene, camera ) {
 
-		var i, il, j, jl,
+		var i, il, j, jl, n,
 
 		shadowMap, shadowMatrix, shadowCamera,
 		program, buffer, material,
 		webglObject, object, light,
 		renderList,
 
-		lights = scene.lights,
+		lights = [],
+		k = 0,
+
 		fog = null;
 
 		// set GL state for depth map
@@ -55,14 +60,67 @@ THREE.ShadowMapPlugin = function ( ) {
 
 		_renderer.setDepthTest( true );
 
+		// preprocess lights
+		// 	- skip lights that are not casting shadows
+		//	- create virtual lights for cascaded shadow maps
+
+		for ( i = 0, il = scene.lights.length; i < il; i ++ ) {
+
+			light = scene.lights[ i ];
+
+			if ( ! light.castShadow ) continue;
+
+			if ( ( light instanceof THREE.DirectionalLight ) && light.shadowCascade ) {
+
+				for ( n = 0; n < light.shadowCascadeCount; n ++ ) {
+
+					var virtualLight;
+
+					if ( ! light.shadowCascadeArray[ n ] ) {
+
+						virtualLight = createVirtualLight( light, n );
+						virtualLight.originalCamera = camera;
+
+						var gyro = new THREE.Gyroscope();
+						gyro.position = light.shadowCascadeOffset;
+
+						gyro.add( virtualLight );
+						gyro.add( virtualLight.target );
+
+						camera.add( gyro );
+
+						light.shadowCascadeArray[ n ] = virtualLight;
+
+						console.log( "Created virtualLight", virtualLight );
+
+					} else {
+
+						virtualLight = light.shadowCascadeArray[ n ];
+
+					}
+
+					updateVirtualLight( light, n );
+
+					lights[ k ] = virtualLight;
+					k ++;
+
+				}
+
+			} else {
+
+				lights[ k ] = light;
+				k ++;
+
+			}
+
+		}
+
 		// render depth map
 
 		for ( i = 0, il = lights.length; i < il; i ++ ) {
 
 			light = lights[ i ];
 
-			if ( ! light.castShadow ) continue;
-
 			if ( ! light.shadowMap ) {
 
 				var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat };
@@ -97,7 +155,6 @@ THREE.ShadowMapPlugin = function ( ) {
 
 			}
 
-
 			if ( light.shadowCameraVisible && ! light.cameraHelper ) {
 
 				light.cameraHelper = new THREE.CameraHelper( light.shadowCamera );
@@ -105,6 +162,12 @@ THREE.ShadowMapPlugin = function ( ) {
 
 			}
 
+			if ( light.isVirtual && virtualLight.originalCamera == camera ) {
+
+				updateShadowCamera( camera, light );
+
+			}
+
 			shadowMap = light.shadowMap;
 			shadowMatrix = light.shadowMatrix;
 			shadowCamera = light.shadowCamera;
@@ -197,11 +260,11 @@ THREE.ShadowMapPlugin = function ( ) {
 
 					if ( buffer instanceof THREE.BufferGeometry ) {
 
-						_renderer.renderBufferDirect( shadowCamera, lights, fog, material, buffer, object );
+						_renderer.renderBufferDirect( shadowCamera, scene.lights, fog, material, buffer, object );
 
 					} else {
 
-						_renderer.renderBuffer( shadowCamera, lights, fog, material, buffer, object );
+						_renderer.renderBuffer( shadowCamera, scene.lights, fog, material, buffer, object );
 
 					}
 
@@ -228,7 +291,7 @@ THREE.ShadowMapPlugin = function ( ) {
 
 					object._modelViewMatrix.multiplyToArray( shadowCamera.matrixWorldInverse, object.matrixWorld, object._modelViewMatrixArray );
 
-					_renderer.renderImmediateObject( shadowCamera, lights, fog, _depthMaterial, object );
+					_renderer.renderImmediateObject( shadowCamera, scene.lights, fog, _depthMaterial, object );
 
 				}
 
@@ -247,4 +310,137 @@ THREE.ShadowMapPlugin = function ( ) {
 
 	};
 
-};
+	function createVirtualLight( light, cascade ) {
+
+		var virtualLight = new THREE.DirectionalLight();
+
+		virtualLight.isVirtual = true;
+
+		virtualLight.onlyShadow = true;
+		virtualLight.castShadow = true;
+
+		virtualLight.shadowCameraNear = light.shadowCameraNear;
+		virtualLight.shadowCameraFar = light.shadowCameraFar;
+
+		virtualLight.shadowCameraLeft = light.shadowCameraLeft;
+		virtualLight.shadowCameraRight = light.shadowCameraRight;
+		virtualLight.shadowCameraBottom = light.shadowCameraBottom;
+		virtualLight.shadowCameraTop = light.shadowCameraTop;
+
+		virtualLight.shadowCameraVisible = light.shadowCameraVisible;
+
+		virtualLight.shadowDarkness = light.shadowDarkness;
+
+		virtualLight.shadowBias = light.shadowCascadeBias[ cascade ];
+		virtualLight.shadowMapWidth = light.shadowCascadeWidth[ cascade ];
+		virtualLight.shadowMapHeight = light.shadowCascadeHeight[ cascade ];
+
+		virtualLight.pointsWorld = [];
+		virtualLight.pointsFrustum = [];
+
+		var pointsWorld = virtualLight.pointsWorld,
+			pointsFrustum = virtualLight.pointsFrustum;
+
+		for ( var i = 0; i < 8; i ++ ) {
+
+			pointsWorld[ i ] = new THREE.Vector3();
+			pointsFrustum[ i ] = new THREE.Vector3();
+
+		}
+
+		var nearZ = light.shadowCascadeNearZ[ cascade ];
+		var farZ = light.shadowCascadeFarZ[ cascade ];
+
+		pointsFrustum[ 0 ].set( -1, -1, nearZ );
+		pointsFrustum[ 1 ].set(  1, -1, nearZ );
+		pointsFrustum[ 2 ].set( -1,  1, nearZ );
+		pointsFrustum[ 3 ].set(  1,  1, nearZ );
+
+		pointsFrustum[ 4 ].set( -1, -1, farZ );
+		pointsFrustum[ 5 ].set(  1, -1, farZ );
+		pointsFrustum[ 6 ].set( -1,  1, farZ );
+		pointsFrustum[ 7 ].set(  1,  1, farZ );
+
+		return virtualLight;
+
+	}
+
+	// Synchronize virtual light with the original light
+
+	function updateVirtualLight( light, cascade ) {
+
+		var virtualLight = light.shadowCascadeArray[ cascade ];
+
+		virtualLight.position.copy( light.position );
+		virtualLight.target.position.copy( light.target.position );
+		virtualLight.lookAt( virtualLight.target );
+
+		virtualLight.shadowCameraVisible = light.shadowCameraVisible;
+		virtualLight.shadowDarkness = light.shadowDarkness;
+
+		virtualLight.shadowBias = light.shadowCascadeBias[ cascade ];
+
+		var nearZ = light.shadowCascadeNearZ[ cascade ];
+		var farZ = light.shadowCascadeFarZ[ cascade ];
+
+		var pointsFrustum = virtualLight.pointsFrustum;
+
+		pointsFrustum[ 0 ].z = nearZ;
+		pointsFrustum[ 1 ].z = nearZ;
+		pointsFrustum[ 2 ].z = nearZ;
+		pointsFrustum[ 3 ].z = nearZ;
+
+		pointsFrustum[ 4 ].z = farZ;
+		pointsFrustum[ 5 ].z = farZ;
+		pointsFrustum[ 6 ].z = farZ;
+		pointsFrustum[ 7 ].z = farZ;
+
+	}
+
+	// Fit shadow camera's ortho frustum to camera frustum
+
+	function updateShadowCamera( camera, light ) {
+
+		var shadowCamera = light.shadowCamera,
+			pointsFrustum = light.pointsFrustum,
+			pointsWorld = light.pointsWorld;
+
+		_min.set( Infinity, Infinity, Infinity );
+		_max.set( -Infinity, -Infinity, -Infinity );
+
+		for ( var i = 0; i < 8; i ++ ) {
+
+			var p = pointsWorld[ i ];
+
+			p.copy( pointsFrustum[ i ] );
+			THREE.ShadowMapPlugin.__projector.unprojectVector( p, camera );
+
+			shadowCamera.matrixWorldInverse.multiplyVector3( p );
+
+			if ( p.x < _min.x ) _min.x = p.x;
+			if ( p.x > _max.x ) _max.x = p.x;
+
+			if ( p.y < _min.y ) _min.y = p.y;
+			if ( p.y > _max.y ) _max.y = p.y;
+
+			if ( p.z < _min.z ) _min.z = p.z;
+			if ( p.z > _max.z ) _max.z = p.z;
+
+		}
+
+		shadowCamera.left = _min.x;
+		shadowCamera.right = _max.x;
+		shadowCamera.top = _max.y;
+		shadowCamera.bottom = _min.y;
+
+		// can't really fit near/far
+		//shadowCamera.near = _min.z;
+		//shadowCamera.far = _max.z;
+
+		shadowCamera.updateProjectionMatrix();
+
+	}
+
+};
+
+THREE.ShadowMapPlugin.__projector = new THREE.Projector();

+ 16 - 0
src/lights/DirectionalLight.js

@@ -36,6 +36,22 @@ THREE.DirectionalLight = function ( hex, intensity, distance ) {
 
 	//
 
+	this.shadowCascade = false;
+
+	this.shadowCascadeOffset = new THREE.Vector3( 0, 0, -1000 );
+	this.shadowCascadeCount = 2;
+
+	this.shadowCascadeBias = [];
+	this.shadowCascadeWidth = [];
+	this.shadowCascadeHeight = [];
+
+	this.shadowCascadeNearZ = [ -1.000, 0.990, 0.998 ];
+	this.shadowCascadeFarZ  = [  0.990, 0.998, 1.000 ];
+
+	this.shadowCascadeArray = [];
+
+	//
+
 	this.shadowMap = null;
 	this.shadowMapSize = null;
 	this.shadowCamera = null;

+ 7 - 2
src/renderers/WebGLRenderer.js

@@ -4453,7 +4453,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 				if ( ! light.castShadow ) continue;
 
-				if ( light instanceof THREE.SpotLight || light instanceof THREE.DirectionalLight ) {
+				if ( light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) ) {
 
 					uniforms.shadowMap.texture[ j ] = light.shadowMap;
 					uniforms.shadowMapSize.value[ j ] = light.shadowMapSize;
@@ -5774,6 +5774,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		}
 
+		console.log( "maxDirLights", maxDirLights );
+
 		return { 'directional' : maxDirLights, 'point' : maxPointLights };
 
 	};
@@ -5788,10 +5790,13 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 			if ( ! light.castShadow ) continue;
 
-			if ( light instanceof THREE.SpotLight || light instanceof THREE.DirectionalLight ) maxShadows ++;
+			if ( light instanceof THREE.SpotLight ) maxShadows ++;
+			if ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) maxShadows ++;
 
 		}
 
+		console.log( "maxShadows", maxShadows );
+
 		return maxShadows;
 
 	};

+ 20 - 1
src/renderers/WebGLShaders.js

@@ -757,13 +757,26 @@ THREE.ShaderChunk = {
 			"vec3 shadowColor = vec3( 1.0 );",
 			"float fDepth;",
 
+			"vec3 colors[3];",
+			"colors[0] = vec3( 1.0, 0.5, 0.0 );",
+			"colors[1] = vec3( 0.0, 1.0, 0.8 );",
+			"colors[2] = vec3( 0.0, 0.5, 1.0 );",
+
+			"float inShadowFrustum = 0.0;",
+
 			"for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
 
 				"vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;",
 
+				"if ( ( shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0 ) ) {",
+
+					"inShadowFrustum += 1.0;",
+
+				"}",
+
 				// don't shadow pixels behind far plane of light frustum
 
-				"if ( shadowCoord.z <= 1.0 ) {",
+				"if ( shadowCoord.z <= 1.0  && inShadowFrustum == 1.0 ) {",
 
 					"shadowCoord.z += shadowBias[ i ];",
 
@@ -872,6 +885,12 @@ THREE.ShaderChunk = {
 				//"if ( !( shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0 ) )",
 				//	"gl_FragColor.xyz =  gl_FragColor.xyz * vec3( 1.0, 0.0, 0.0 );",
 
+				"if ( inShadowFrustum == 1.0 && ( shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0 ) ) {",
+
+					"gl_FragColor.xyz *= colors[ i ];",
+
+				"}",
+
 			"}",
 
 			"#ifdef GAMMA_OUTPUT",