Browse Source

Merge pull request #18790 from gkjohnson/csm-helper

CSM: Add CSMHelper class with more visualizations
Mr.doob 5 years ago
parent
commit
7af45fadec
3 changed files with 212 additions and 76 deletions
  1. 1 69
      examples/jsm/csm/CSM.js
  2. 163 0
      examples/jsm/csm/CSMHelper.js
  3. 48 7
      examples/webgl_shadowmap_csm.html

+ 1 - 69
examples/jsm/csm/CSM.js

@@ -8,11 +8,6 @@ import {
 	DirectionalLight,
 	MathUtils,
 	ShaderChunk,
-	LineBasicMaterial,
-	Object3D,
-	BufferGeometry,
-	BufferAttribute,
-	Line,
 	Matrix4,
 	Box3
 } from '../../../build/three.module.js';
@@ -21,13 +16,12 @@ import Shader from './Shader.js';
 
 const _cameraToLightMatrix = new Matrix4();
 const _lightSpaceFrustum = new Frustum();
-const _frustum = new Frustum();
 const _center = new Vector3();
 const _bbox = new Box3();
 const _uniformArray = [];
 const _logArray = [];
 
-export default class CSM {
+export class CSM {
 
 	constructor( data ) {
 
@@ -347,68 +341,6 @@ export default class CSM {
 
 	}
 
-	helper( cameraMatrix ) {
-
-		let geometry, vertices;
-		const material = new LineBasicMaterial( { color: 0xffffff } );
-		const object = new Object3D();
-
-		for ( let i = 0; i < this.frustums.length; i ++ ) {
-
-			this.frustums[ i ].toSpace( cameraMatrix, _frustum );
-
-			geometry = new 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 BufferAttribute( new Float32Array( vertices ), 3 ) );
-
-			object.add( new Line( geometry, material ) );
-
-			geometry = new 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 BufferAttribute( new Float32Array( vertices ), 3 ) );
-
-			object.add( new Line( geometry, material ) );
-
-			for ( let i = 0; i < 4; i ++ ) {
-
-				geometry = new 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 BufferAttribute( new Float32Array( vertices ), 3 ) );
-
-				object.add( new Line( geometry, material ) );
-
-			}
-
-		}
-
-		return object;
-
-	}
-
 	remove() {
 
 		for ( let i = 0; i < this.lights.length; i ++ ) {

+ 163 - 0
examples/jsm/csm/CSMHelper.js

@@ -0,0 +1,163 @@
+import {
+	Group,
+	Mesh,
+	LineSegments,
+	BufferGeometry,
+	LineBasicMaterial,
+	Box3Helper,
+	Box3,
+	PlaneBufferGeometry,
+	MeshBasicMaterial,
+	BufferAttribute,
+	DoubleSide
+} from '../../../build/three.module.js';
+
+class CSMHelper extends Group {
+
+	constructor( csm ) {
+
+		super();
+		this.csm = csm;
+		this.displayFrustum = true;
+		this.displayPlanes = true;
+		this.displayShadowBounds = true;
+
+		const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );
+		const positions = new Float32Array( 24 );
+		const frustumGeometry = new BufferGeometry();
+		frustumGeometry.setIndex( new BufferAttribute( indices, 1 ) );
+		frustumGeometry.setAttribute( 'position', new BufferAttribute( positions, 3, false ) );
+		const frustumLines = new LineSegments( frustumGeometry, new LineBasicMaterial() );
+		this.add( frustumLines );
+
+		this.frustumLines = frustumLines;
+		this.cascadeLines = [];
+		this.cascadePlanes = [];
+		this.shadowLines = [];
+
+	}
+
+	updateVisibility() {
+
+		const displayFrustum = this.displayFrustum;
+		const displayPlanes = this.displayPlanes;
+		const displayShadowBounds = this.displayShadowBounds;
+
+		const frustumLines = this.frustumLines;
+		const cascadeLines = this.cascadeLines;
+		const cascadePlanes = this.cascadePlanes;
+		const shadowLines = this.shadowLines;
+		for ( let i = 0, l = cascadeLines.length; i < l; i ++ ) {
+
+			const cascadeLine = cascadeLines[ i ];
+			const cascadePlane = cascadePlanes[ i ];
+			const shadowLineGroup = shadowLines[ i ];
+
+			cascadeLine.visible = displayFrustum;
+			cascadePlane.visible = displayFrustum && displayPlanes;
+			shadowLineGroup.visible = displayShadowBounds;
+
+		}
+
+		frustumLines.visible = displayFrustum;
+
+	}
+
+	update() {
+
+		const csm = this.csm;
+		const camera = csm.camera;
+		const cascades = csm.cascades;
+		const mainFrustum = csm.mainFrustum;
+		const frustums = csm.frustums;
+		const lights = csm.lights;
+
+		const frustumLines = this.frustumLines;
+		const frustumLinePositions = frustumLines.geometry.getAttribute( 'position' );
+		const cascadeLines = this.cascadeLines;
+		const cascadePlanes = this.cascadePlanes;
+		const shadowLines = this.shadowLines;
+
+		this.position.copy( camera.position );
+		this.quaternion.copy( camera.quaternion );
+		this.scale.copy( camera.scale );
+		this.updateMatrixWorld( true );
+
+		while( cascadeLines.length > cascades ) {
+
+			this.remove( cascadeLines.pop() );
+			this.remove( cascadePlanes.pop() );
+			this.remove( shadowLines.pop() );
+
+		}
+
+		while( cascadeLines.length < cascades ) {
+
+			const cascadeLine = new Box3Helper( new Box3(), 0xffffff );
+			const planeMat = new MeshBasicMaterial( { transparent: true, opacity: 0.1, depthWrite: false, side: DoubleSide } );
+			const cascadePlane = new Mesh( new PlaneBufferGeometry(), planeMat );
+			const shadowLineGroup = new Group();
+			const shadowLine = new Box3Helper( new Box3(), 0xffff00 );
+			shadowLineGroup.add( shadowLine );
+
+			this.add( cascadeLine );
+			this.add( cascadePlane );
+			this.add( shadowLineGroup );
+
+			cascadeLines.push( cascadeLine );
+			cascadePlanes.push( cascadePlane );
+			shadowLines.push( shadowLineGroup );
+
+		}
+
+		for ( let i = 0; i < cascades; i ++ ) {
+
+			const frustum = frustums[ i ];
+			const light = lights[ i ];
+			const shadowCam = light.shadow.camera;
+			const farVerts = frustum.vertices.far;
+
+			const cascadeLine = cascadeLines[ i ];
+			const cascadePlane = cascadePlanes[ i ];
+			const shadowLineGroup = shadowLines[ i ];
+			const shadowLine = shadowLineGroup.children[ 0 ];
+
+			cascadeLine.box.min.copy( farVerts[ 2 ] );
+			cascadeLine.box.max.copy( farVerts[ 0 ] );
+			cascadeLine.box.max.z += 1e-4;
+
+			cascadePlane.position.addVectors( farVerts[ 0 ], farVerts[ 2 ] );
+			cascadePlane.position.multiplyScalar( 0.5 );
+			cascadePlane.scale.subVectors( farVerts[ 0 ], farVerts[ 2 ] );
+			cascadePlane.scale.z = 1e-4;
+
+			this.remove( shadowLineGroup );
+			shadowLineGroup.position.copy( shadowCam.position );
+			shadowLineGroup.quaternion.copy( shadowCam.quaternion );
+			shadowLineGroup.scale.copy( shadowCam.scale );
+			shadowLineGroup.updateMatrixWorld( true );
+			this.attach( shadowLineGroup );
+
+			shadowLine.box.min.set( shadowCam.bottom, shadowCam.left, - shadowCam.far );
+			shadowLine.box.max.set( shadowCam.top, shadowCam.right, - shadowCam.near );
+
+		}
+
+		const nearVerts = mainFrustum.vertices.near;
+		const farVerts = mainFrustum.vertices.far;
+		frustumLinePositions.setXYZ( 0, farVerts[ 0 ].x, farVerts[ 0 ].y, farVerts[ 0 ].z );
+		frustumLinePositions.setXYZ( 1, farVerts[ 3 ].x, farVerts[ 3 ].y, farVerts[ 3 ].z );
+		frustumLinePositions.setXYZ( 2, farVerts[ 2 ].x, farVerts[ 2 ].y, farVerts[ 2 ].z );
+		frustumLinePositions.setXYZ( 3, farVerts[ 1 ].x, farVerts[ 1 ].y, farVerts[ 1 ].z );
+
+		frustumLinePositions.setXYZ( 4, nearVerts[ 0 ].x, nearVerts[ 0 ].y, nearVerts[ 0 ].z );
+		frustumLinePositions.setXYZ( 5, nearVerts[ 3 ].x, nearVerts[ 3 ].y, nearVerts[ 3 ].z );
+		frustumLinePositions.setXYZ( 6, nearVerts[ 2 ].x, nearVerts[ 2 ].y, nearVerts[ 2 ].z );
+		frustumLinePositions.setXYZ( 7, nearVerts[ 1 ].x, nearVerts[ 1 ].y, nearVerts[ 1 ].z );
+		frustumLinePositions.needsUpdate = true;
+
+	}
+
+}
+
+export { CSMHelper };

+ 48 - 7
examples/webgl_shadowmap_csm.html

@@ -20,9 +20,10 @@
 
 			import { OrbitControls } from './jsm/controls/OrbitControls.js';
 			import { GUI } from './jsm/libs/dat.gui.module.js';
-			import * as CSM from './jsm/csm/CSM.js';
+			import { CSM } from './jsm/csm/CSM.js';
+			import { CSMHelper } from './jsm/csm/CSMHelper.js';
 
-			var renderer, scene, camera, orthoCamera, controls, csm;
+			var renderer, scene, camera, orthoCamera, controls, csm, csmHelper;
 
 			var params = {
 				orthographic: false,
@@ -35,10 +36,10 @@
 				margin: 100,
 				lightFar: 5000,
 				lightNear: 1,
-				helper: function () {
+				autoUpdateHelper: true,
+				updateHelper: function () {
 
-					var helper = csm.helper( camera.matrix );
-					scene.add( helper );
+					csmHelper.update();
 
 				}
 			};
@@ -83,7 +84,7 @@
 				var ambientLight = new THREE.AmbientLight( 0xffffff, 0.5 );
 				scene.add( ambientLight );
 
-				csm = new CSM.default({
+				csm = new CSM({
 					maxFar: params.far,
 					cascades: 4,
 					mode: params.mode,
@@ -93,6 +94,10 @@
 					camera: camera
 				} );
 
+				csmHelper = new CSMHelper( csm );
+				csmHelper.visible = false;
+				scene.add( csmHelper );
+
 				var floorMaterial = new THREE.MeshPhongMaterial( { color: '#252a34' } );
 				csm.setupMaterial( floorMaterial );
 
@@ -204,7 +209,33 @@
 
 				} );
 
-				gui.add( params, 'helper' ).name( 'add frustum helper' );
+				const helperFolder = gui.addFolder( 'helper' );
+				
+				helperFolder.add( csmHelper, 'visible' );
+
+				helperFolder.add( csmHelper, 'displayFrustum' ).onChange( function () { 
+
+					csmHelper.updateVisibility();
+
+				} );
+
+				helperFolder.add( csmHelper, 'displayPlanes' ).onChange( function () { 
+
+					csmHelper.updateVisibility();
+
+				} );
+
+				helperFolder.add( csmHelper, 'displayShadowBounds' ).onChange( function () { 
+
+					csmHelper.updateVisibility();
+
+				} );
+
+				helperFolder.add( params, 'autoUpdateHelper' ).name( 'auto update' );
+
+				helperFolder.add( params, 'updateHelper' ).name( 'update' );
+				
+				helperFolder.open();
 
 				window.addEventListener( 'resize', function () {
 
@@ -231,10 +262,20 @@
 
 					updateOrthoCamera();
 					csm.updateFrustums();
+					if ( params.autoUpdateHelper ) {
+						
+						csmHelper.update();
+					
+					}
 					renderer.render( scene, orthoCamera );
 
 				} else {
 	
+					if ( params.autoUpdateHelper ) {
+						
+						csmHelper.update();
+					
+					}
 					renderer.render( scene, camera );
 
 				}