소스 검색

Merge pull request #18722 from gkjohnson/orthographic-csm

CSM: Add support for Orthographic Camera
Mr.doob 5 년 전
부모
커밋
afc703be33
3개의 변경된 파일115개의 추가작업 그리고 64개의 파일을 삭제
  1. 18 21
      examples/jsm/csm/CSM.js
  2. 40 18
      examples/jsm/csm/Frustum.js
  3. 57 25
      examples/webgl_shadowmap_csm.html

+ 18 - 21
examples/jsm/csm/CSM.js

@@ -26,11 +26,8 @@ export default class CSM {
 
 		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.maxFar = data.maxFar || 100000;
 		this.mode = data.mode || 'practical';
 		this.shadowMapSize = data.shadowMapSize || 2048;
 		this.shadowBias = data.shadowBias || 0.000001;
@@ -75,11 +72,13 @@ export default class CSM {
 
 	initCascades() {
 
+		const camera = this.camera;
+		camera.updateProjectionMatrix();
 		this.mainFrustum = new Frustum( {
-			fov: this.fov,
-			near: this.near,
-			far: this.far,
-			aspect: this.aspect
+
+			maxFar: this.maxFar,
+			projectionMatrix: camera.projectionMatrix
+
 		} );
 
 		this.mainFrustum.getViewSpaceVertices();
@@ -90,22 +89,24 @@ export default class CSM {
 
 	getBreaks() {
 
+		const camera = this.camera;
+		const far = Math.min(camera.far, this.maxFar);
 		this.breaks = [];
 
 		switch ( this.mode ) {
 
 			case 'uniform':
-				this.breaks = uniformSplit( this.cascades, this.near, this.far );
+				this.breaks = uniformSplit( this.cascades, camera.near, far );
 				break;
 			case 'logarithmic':
-				this.breaks = logarithmicSplit( this.cascades, this.near, this.far );
+				this.breaks = logarithmicSplit( this.cascades, camera.near, far );
 				break;
 			case 'practical':
-				this.breaks = practicalSplit( this.cascades, this.near, this.far, 0.5 );
+				this.breaks = practicalSplit( this.cascades, camera.near, 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 );
+				this.breaks = this.customSplitsCallback( this.cascades, camera.near, far );
 				break;
 
 		}
@@ -221,12 +222,13 @@ export default class CSM {
 		}
 
 		const self = this;
+		const far = Math.min(this.camera.far, this.maxFar);
 
 		material.onBeforeCompile = function ( shader ) {
 
 			shader.uniforms.CSM_cascades = { value: breaksVec2 };
 			shader.uniforms.cameraNear = { value: self.camera.near };
-			shader.uniforms.shadowFar = { value: self.far };
+			shader.uniforms.shadowFar = { value: far };
 
 			self.materials.push( shader );
 
@@ -236,11 +238,13 @@ export default class CSM {
 
 	updateUniforms() {
 
+		const far = Math.min(this.camera.far, this.maxFar);
+
 		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;
+			this.materials[ i ].uniforms.shadowFar.value = far;
 
 		}
 
@@ -262,13 +266,6 @@ export default class CSM {
 
 	}
 
-	setAspect( aspect ) {
-
-		this.aspect = aspect;
-		this.initCascades();
-
-	}
-
 	updateFrustums() {
 
 		this.getBreaks();

+ 40 - 18
examples/jsm/csm/Frustum.js

@@ -2,7 +2,9 @@
  * @author vHawk / https://github.com/vHawk/
  */
 
-import { MathUtils, Vector3 } from '../../../build/three.module.js';
+import { Vector3, Matrix4 } from '../../../build/three.module.js';
+
+const inverseProjectionMatrix = new Matrix4();
 
 export default class Frustum {
 
@@ -10,10 +12,8 @@ export default class Frustum {
 
 		data = data || {};
 
-		this.fov = data.fov || 70;
-		this.near = data.near || 0.1;
-		this.far = data.far || 1000;
-		this.aspect = data.aspect || 1;
+		this.projectionMatrix = data.projectionMatrix;
+		this.maxFar = data.maxFar || 10000;
 
 		this.vertices = {
 			near: [],
@@ -24,29 +24,51 @@ export default class Frustum {
 
 	getViewSpaceVertices() {
 
-		this.nearPlaneY = this.near * Math.tan( MathUtils.degToRad( this.fov / 2 ) );
-		this.nearPlaneX = this.aspect * this.nearPlaneY;
+		const maxFar = this.maxFar;
+		const projectionMatrix = this.projectionMatrix;
+		const isOrthographic = projectionMatrix.elements[ 2 * 4 + 3 ] === 0;
 
-		this.farPlaneY = this.far * Math.tan( MathUtils.degToRad( this.fov / 2 ) );
-		this.farPlaneX = this.aspect * this.farPlaneY;
+		inverseProjectionMatrix.getInverse( this.projectionMatrix );
 
 		// 3 --- 0  vertices.near/far order
 		// |     |
 		// 2 --- 1
+		// clip space spans from [-1, 1]
 
 		this.vertices.near.push(
-			new Vector3( this.nearPlaneX, this.nearPlaneY, - this.near ),
-			new Vector3( this.nearPlaneX, - this.nearPlaneY, - this.near ),
-			new Vector3( - this.nearPlaneX, - this.nearPlaneY, - this.near ),
-			new Vector3( - this.nearPlaneX, this.nearPlaneY, - this.near )
+			new Vector3( 1, 1, - 1 ),
+			new Vector3( 1, - 1, - 1 ),
+			new Vector3( - 1, - 1, - 1 ),
+			new Vector3( - 1, 1, - 1 )
 		);
+		this.vertices.near.forEach( function( v ) {
+
+			v.applyMatrix4( inverseProjectionMatrix );
+
+		} );
 
 		this.vertices.far.push(
-			new Vector3( this.farPlaneX, this.farPlaneY, - this.far ),
-			new Vector3( this.farPlaneX, - this.farPlaneY, - this.far ),
-			new Vector3( - this.farPlaneX, - this.farPlaneY, - this.far ),
-			new Vector3( - this.farPlaneX, this.farPlaneY, - this.far )
-		);
+			new Vector3( 1, 1, 1 ),
+			new Vector3( 1, - 1, 1 ),
+			new Vector3( - 1, - 1, 1 ),
+			new Vector3( - 1, 1, 1 )
+		)
+		this.vertices.far.forEach( function( v ) {
+
+			v.applyMatrix4( inverseProjectionMatrix );
+
+			const absZ = Math.abs( v.z );
+			if ( isOrthographic ) {
+
+				v.z *= Math.min( maxFar / absZ, 1.0 );
+
+			} else {
+
+				v.multiplyScalar( Math.min( maxFar / absZ, 1.0 ) );
+
+			}
+
+		} );
 
 		return this.vertices;
 

+ 57 - 25
examples/webgl_shadowmap_csm.html

@@ -22,15 +22,50 @@
 			import { GUI } from './jsm/libs/dat.gui.module.js';
 			import * as CSM from './jsm/csm/CSM.js';
 
-			var renderer, scene, camera, controls, csm;
+			var renderer, scene, camera, orthoCamera, controls, csm;
+
+			var params = {
+				orthographic: false,
+				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 );
+
+				}
+			};
 
 			init();
 			animate();
 
+			function updateOrthoCamera() {
+
+				var size = controls.target.distanceTo( camera.position );
+				var aspect = camera.aspect;
+
+				orthoCamera.left = size * aspect / - 2;
+				orthoCamera.right = size * aspect / 2;
+
+				orthoCamera.top = size / 2;
+				orthoCamera.bottom = size / - 2;
+				orthoCamera.position.copy( camera.position );
+				orthoCamera.rotation.copy( camera.rotation );
+				orthoCamera.updateProjectionMatrix();
+
+			}
+
 			function init() {
 				scene = new THREE.Scene();
 				scene.background = new THREE.Color( '#454e61' );
 				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 5000 );
+				orthoCamera = new THREE.OrthographicCamera();
 
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer.setSize( window.innerWidth, window.innerHeight );
@@ -47,28 +82,8 @@
 				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,
+					maxFar: params.far,
 					cascades: 4,
 					mode: params.mode,
 					parent: scene,
@@ -114,9 +129,16 @@
 
 				var gui = new GUI();
 
+				gui.add( params, 'orthographic' ).onChange( function ( value ) {
+
+					csm.camera = value ? orthoCamera : camera;
+					csm.updateFrustums();
+
+				} );
+
 				gui.add( params, 'far', 1, 5000 ).step( 1 ).name( 'shadow far' ).onChange( function ( value ) {
 
-					csm.far = value;
+					csm.maxFar = value;
 					csm.updateFrustums();
 
 				} );
@@ -181,7 +203,7 @@
 					camera.aspect = window.innerWidth / window.innerHeight;
 					camera.updateProjectionMatrix();
 
-					csm.setAspect( camera.aspect );
+					updateOrthoCamera();
 
 					renderer.setSize( window.innerWidth, window.innerHeight );
 
@@ -195,7 +217,17 @@
 				csm.update( camera.matrix );
 				controls.update();
 
-				renderer.render( scene, camera );
+				if ( params.orthographic ) {
+
+					updateOrthoCamera();
+					csm.updateFrustums();
+					renderer.render( scene, orthoCamera );
+
+				} else {
+	
+					renderer.render( scene, camera );
+
+				}
 
 			}