Browse Source

Merge pull request #5935 from arya-s/feature_shadowmapviewer

Feature: THREE.ShadowMapViewer
Mr.doob 10 years ago
parent
commit
641639b537
2 changed files with 431 additions and 0 deletions
  1. 190 0
      examples/js/utils/ShadowMapViewer.js
  2. 241 0
      examples/webgl_shadowmapviewer.html

+ 190 - 0
examples/js/utils/ShadowMapViewer.js

@@ -0,0 +1,190 @@
+/**
+ * @author arya-s / https://github.com/arya-s
+ *
+ * This is a helper for visualising a given light's shadow map.
+ * It works for shadow casting lights: THREE.DirectionalLight and THREE.SpotLight.
+ * It renders out the shadow map and displays it on a HUD.
+ *
+ * Example usage:
+ *	1) Include <script src='examples/js/utils/ShadowMapViewer.js'><script> in your html file
+ *
+ *	2) Create a shadow casting light and name it optionally:
+ *		var light = new THREE.DirectionalLight( 0xffffff, 1 );
+ *		light.castShadow = true;
+ *		light.name = 'Sun';
+ *
+ *	3) Create a shadow map viewer for that light and set its size and position optionally:
+ *		var shadowMapViewer = THREE.ShadowMapViewer( light );
+ *		shadowMapViewer.size.set( 128, 128 );	//width, height  default: 256, 256
+ *		shadowMapViewer.position.set( 10, 10 );	//x, y in pixel	 default: 0, 0 (top left corner)
+ *
+ *	4) Render the shadow map viewer in your render loop:
+ *		shadowMapViewer.render( renderer );
+ *
+ *	5) Optionally: Update the shadow map viewer on window resize:
+ *		shadowMapViewer.updateForWindowResize();
+ *
+ *	6) If you set the position or size members directly, you need to call shadowMapViewer.update();
+ */
+
+THREE.ShadowMapViewer = function ( light ) {
+
+	//- Internals
+	var scope = this;
+	var doRenderLabel = ( light.name !== undefined && light.name !== '' );
+	var userAutoClearSetting;
+
+	//Holds the initial position and dimension of the HUD
+	var frame = { 
+		x: 10,
+		y: 10,
+		width: 256,
+		height: 256,
+	};
+
+	var camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 10 );
+	camera.position.set( 0, 0, 2 );
+	var scene = new THREE.Scene();
+
+	//HUD for shadow map
+	var shader = THREE.UnpackDepthRGBAShader;
+
+	var uniforms = new THREE.UniformsUtils.clone( shader.uniforms );
+	var material = new THREE.ShaderMaterial( {
+		uniforms: uniforms,
+		vertexShader: shader.vertexShader,
+		fragmentShader: shader.fragmentShader
+	} );
+	var plane = new THREE.PlaneBufferGeometry( frame.width, frame.height );
+	var mesh = new THREE.Mesh( plane, material );
+
+	scene.add( mesh );
+
+
+	//Label for light's name
+	var labelCanvas, labelMesh;
+
+	if ( doRenderLabel ) {
+
+		labelCanvas = document.createElement( 'canvas' );
+
+		var context = labelCanvas.getContext( '2d' );
+		context.font = 'Bold 20px Arial';
+
+		var labelWidth = context.measureText( light.name ).width;
+		labelCanvas.width = labelWidth;
+		labelCanvas.height = 25;	//25 to account for g, p, etc.
+
+		context.font = 'Bold 20px Arial';
+		context.fillStyle = 'rgba( 255, 0, 0, 1 )';
+		context.fillText( light.name, 0, 20 );
+
+		var labelTexture = new THREE.Texture( labelCanvas );
+		labelTexture.magFilter = THREE.LinearFilter;
+		labelTexture.minFilter = THREE.LinearFilter;
+		labelTexture.needsUpdate = true;
+
+		var labelMaterial = new THREE.MeshBasicMaterial( { map: labelTexture, side: THREE.DoubleSide } );
+		labelMaterial.transparent = true;
+
+		var labelPlane = new THREE.PlaneBufferGeometry( labelCanvas.width, labelCanvas.height );
+		labelMesh = new THREE.Mesh( labelPlane, labelMaterial );
+
+		scene.add( labelMesh );
+
+	}
+
+
+	function resetPosition () {
+
+		scope.position.set( scope.position.x, scope.position.y );
+
+	}
+
+	//- API
+	// Set to false to disable displaying this shadow map
+	this.enabled = true; 
+
+	// Set the size of the displayed shadow map on the HUD
+	this.size = {
+		width: frame.width,
+		height: frame.height,
+		set: function ( width, height ) {
+
+			this.width = width;
+			this.height = height;
+
+			mesh.scale.set( this.width / frame.width, this.height / frame.height, 1 );
+
+			//Reset the position as it is off when we scale stuff
+			resetPosition();
+
+		}
+	};
+
+	// Set the position of the displayed shadow map on the HUD
+	this.position = {
+		x: frame.x,
+		y: frame.y,
+		set: function ( x, y ) {
+
+			this.x = x;
+			this.y = y;
+
+			var width = scope.size.width;
+			var height = scope.size.height;
+
+			mesh.position.set( -window.innerWidth / 2 + width / 2 + this.x, window.innerHeight / 2 - height / 2 - this.y, 0 );
+
+			if ( doRenderLabel ) labelMesh.position.set( mesh.position.x, mesh.position.y - scope.size.height / 2 + labelCanvas.height / 2, 0 );
+
+		}
+	};
+
+	this.render = function ( renderer ) {
+
+		if ( this.enabled ) {
+
+			//Because a light's .shadowMap is only initialised after the first render pass
+			//we have to make sure the correct map is sent into the shader, otherwise we
+			//always end up with the scene's first added shadow casting light's shadowMap
+			//in the shader
+			//See: https://github.com/mrdoob/three.js/issues/5932
+			uniforms.tDiffuse.value = light.shadowMap;
+
+			userAutoClearSetting = renderer.autoClear;
+			renderer.autoClear = false; // To allow render overlay
+			renderer.clearDepth()
+			renderer.render( scene, camera );
+			renderer.autoClear = userAutoClearSetting;	//Restore user's setting
+
+		}
+
+	};
+
+	this.updateForWindowResize = function () {
+
+		if ( this.enabled ) {
+
+			 camera.left = window.innerWidth / - 2;
+			 camera.right = window.innerWidth / 2;
+			 camera.top = window.innerHeight / 2;
+			 camera.bottom = window.innerHeight / - 2;
+
+		}
+
+	};
+
+	this.update = function () {
+
+		this.position.set( this.position.x, this.position.y );
+		this.size.set( this.size.width, this.size.height );
+
+	};
+
+	//Force an update to set position/size
+	this.update();
+
+}
+
+THREE.ShadowMapViewer.prototype.constructor = THREE.ShadowMapViewer;

+ 241 - 0
examples/webgl_shadowmapviewer.html

@@ -0,0 +1,241 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>three.js webgl - ShadowMapViewer example </title>
+    <meta charset="utf-8">
+    <style>
+      body {
+        font-family: Monospace;
+        background-color: #000;
+        color: #fff;
+        margin: 0px;
+        overflow: hidden;
+      }
+      #info {
+        position: absolute;
+        top: 10px;
+        width: 100%;
+        text-align: center;
+        z-index: 100;
+        display:block;
+      }
+      #info a { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
+    </style>
+  </head>
+
+  <body>
+
+    <div id="info">
+    <a href="http://threejs.org" target="_blank">three.js</a> - ShadowMapViewer example by <a href="https://github.com/arya-s">arya-s</a>
+    </div>
+
+    <script src="../build/three.min.js"></script>
+
+    <script src="js/controls/OrbitControls.js"></script>
+
+    <script src="js/shaders/UnpackDepthRGBAShader.js"></script>
+
+    <script src="js/utils/ShadowMapViewer.js"></script>
+
+    <script src="js/Detector.js"></script>
+    
+    <script src="js/libs/stats.min.js"></script>
+
+    <script>
+    
+      if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+      var camera, scene, renderer, clock, stats;
+      var dirLight, spotLight;
+      var torusKnot, cube;
+      var dirLightShadowMapViewer, spotLightShadowMapViewer;
+
+      init();
+      animate();
+
+
+      function init() {
+
+        initScene();
+        initShadowMapViewers();
+        initMisc();
+
+        document.body.appendChild( renderer.domElement );
+        window.addEventListener( 'resize', onWindowResize, false );
+
+      }
+
+      function initScene() {
+
+        camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.01, 1000 );
+        camera.position.set( 0, 15, 35 );
+
+        scene = new THREE.Scene();
+
+        // Lights
+        var ambient = new THREE.AmbientLight( 0x404040 );
+        scene.add( ambient );
+
+        spotLight = new THREE.SpotLight( 0xffffff );
+        spotLight.position.set( 10, 10, 5 );
+        spotLight.castShadow = true;
+        spotLight.shadowCameraNear = 8;
+        spotLight.shadowCameraFar = 30;
+        spotLight.shadowDarkness = 0.5;
+        spotLight.shadowCameraVisible = true;
+        spotLight.shadowMapWidth = 1024;
+        spotLight.shadowMapHeight = 1024;
+        spotLight.name = 'Spot Light';
+        scene.add( spotLight );
+
+        dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
+        dirLight.position.set( 0, 10, 0 );
+        dirLight.castShadow = true;
+        dirLight.shadowCameraNear = 0.01;
+        dirLight.shadowCameraFar = 10;
+        dirLight.shadowCameraRight = 15;
+        dirLight.shadowCameraLeft = -15;
+        dirLight.shadowCameraTop  = 15;
+        dirLight.shadowCameraBottom = -15;
+        spotLight.shadowDarkness = 0.5;
+        dirLight.shadowCameraVisible = true;
+        dirLight.shadowMapWidth = 1024;
+        dirLight.shadowMapHeight = 1024;
+        dirLight.name = 'Dir. Light';
+        scene.add( dirLight );
+
+
+        // Geometry
+        var geometry = new THREE.TorusKnotGeometry( 25, 8, 75, 20 );
+        var material = new THREE.MeshPhongMaterial( {
+          ambient: 0x404040,
+          color: 0xff0000,
+          shininess: 150, 
+          specular: 0xffffff,
+          shading: THREE.SmoothShading,
+        } );
+
+        torusKnot = new THREE.Mesh( geometry, material );
+        torusKnot.scale.multiplyScalar( 1 / 18 );
+        torusKnot.position.y = 3;
+        torusKnot.castShadow = true;
+        torusKnot.receiveShadow = true;
+        scene.add( torusKnot );
+
+        var geometry = new THREE.BoxGeometry( 3, 3, 3 );
+        cube = new THREE.Mesh( geometry, material );
+        cube.position.set( 10, 3, 10 );
+        cube.castShadow = true;
+        cube.receiveShadow = true;
+        scene.add( cube );
+
+        var geometry = new THREE.BoxGeometry( 10, 0.15, 10 );
+        var material = new THREE.MeshPhongMaterial( {
+          ambient: 0x404040,
+          color: 0xa0adaf,
+          shininess: 150, 
+          specular: 0xffffff,
+          shading: THREE.SmoothShading
+        } );
+
+        var ground = new THREE.Mesh( geometry, material );
+        ground.scale.multiplyScalar( 3 );
+        ground.castShadow = false;
+        ground.receiveShadow = true;
+        scene.add( ground );
+
+      }
+
+      function initShadowMapViewers() {
+
+        dirLightShadowMapViewer = new THREE.ShadowMapViewer( dirLight );
+        dirLightShadowMapViewer.position.x = 10;
+        dirLightShadowMapViewer.position.y = 10;
+        dirLightShadowMapViewer.size.width = 256;
+        dirLightShadowMapViewer.size.height = 256;
+        dirLightShadowMapViewer.update(); //Required when setting position or size directly
+
+        spotLightShadowMapViewer = new THREE.ShadowMapViewer( spotLight );
+        spotLightShadowMapViewer.size.set( 256, 256 );
+        spotLightShadowMapViewer.position.set( 276, 10 );
+        // spotLightShadowMapViewer.update();  //NOT required because .set updates automatically
+
+      }
+
+      function initMisc() {
+
+        renderer = new THREE.WebGLRenderer( { antialias: true } );
+        renderer.setSize( window.innerWidth, window.innerHeight );
+        renderer.setClearColor( 0x000000, 1 );
+        renderer.shadowMapEnabled = true;
+        renderer.shadowMapType = THREE.BasicShadowMap;
+
+        //Mouse control
+        controls = new THREE.OrbitControls( camera, renderer.domElement );
+
+        clock = new THREE.Clock();
+
+        stats = new Stats();
+        stats.domElement.style.position = 'absolute';
+        stats.domElement.style.right = '0px';
+        stats.domElement.style.top = '0px';
+        document.body.appendChild( stats.domElement );
+
+      }
+
+      function onWindowResize() {
+
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+
+        renderer.setSize( window.innerWidth, window.innerHeight );
+
+        dirLightShadowMapViewer.updateForWindowResize();
+        spotLightShadowMapViewer.updateForWindowResize();
+
+      }
+
+      function animate() {
+
+        requestAnimationFrame( animate );
+        render();
+
+        stats.update();
+
+      }
+
+      function renderScene() {
+
+        renderer.render( scene, camera );
+
+      }
+
+      function renderShadowMapViewers() {
+
+        dirLightShadowMapViewer.render( renderer );
+        spotLightShadowMapViewer.render( renderer );
+
+      }
+
+      function render() {
+
+        var delta = clock.getDelta();
+
+        renderScene();
+        renderShadowMapViewers();
+
+        torusKnot.rotation.x += 0.5 * delta;
+        torusKnot.rotation.y += 3 * delta;
+        torusKnot.rotation.z += 2 * delta;
+
+        cube.rotation.x += 0.5 * delta;
+        cube.rotation.y += 3 * delta;
+        cube.rotation.z += 2 * delta;
+
+      }
+
+    </script>
+
+  </body>
+
+</html>