Browse Source

Merge pull request #7153 from mkkellogg/dev

Added shadow functionality for point lights.
Mr.doob 9 years ago
parent
commit
ffaa6bf06c

+ 1 - 0
examples/index.html

@@ -362,6 +362,7 @@
 				"webgl_shadowmap",
 				"webgl_shadowmap_performance",
 				"webgl_shadowmap_viewer",
+				"webgl_shadowmap_omnidirectional",		
 				"webgl_shadowmesh",
 				"webgl_skinning_simple",
 				"webgl_sprites",

+ 281 - 0
examples/webgl_shadowmap_omnidirectional.html

@@ -0,0 +1,281 @@
+
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - Omni-directional Shadow map viewer 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> - Omni-directional Shadow map viewer example by <a href="https://github.com/mkkellogg">mkkellogg</a>
+		</div>
+
+		<script src="../build/three.min.js"></script>
+		<script src="js/controls/OrbitControls.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, pointLight;
+			var pointLightParent;
+			var torusKnot, cube, cube2, cube3, cube4;
+			var cubeMaterial;
+			var wallMaterial;
+			var ground;
+
+			init();
+			animate();
+
+			function init() {
+
+				initScene();
+				initMisc();
+
+				document.body.appendChild( renderer.domElement );
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function initScene() {
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( 0, 15, 35 );
+
+				scene = new THREE.Scene();
+
+				// Lights
+				var ambient = new THREE.AmbientLight( 0x404040 );
+				scene.add( ambient );
+
+				pointLight = new THREE.PointLight( 0xffffff );
+				pointLight.position.set( 0, 11, 4 );
+				pointLight.castShadow = true;
+				pointLight.shadowCameraNear = 1;
+				pointLight.shadowCameraFar = 30;
+				pointLight.shadowDarkness = 0.5;
+				pointLight.shadowCameraVisible = true;
+				pointLight.shadowMapWidth = 2048;
+				pointLight.shadowMapHeight = 1024;
+				pointLight.shadowBias = 0.1;
+				pointLight.name = 'Point Light';
+				scene.add( pointLight );
+
+				/*dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
+				dirLight.position.set( 0, 50, 0 );
+				dirLight.castShadow = true;
+				dirLight.shadowCameraNear = 1;
+				dirLight.shadowCameraFar = 100;
+				dirLight.shadowCameraRight = 15;
+				dirLight.shadowCameraLeft = -15;
+				dirLight.shadowCameraTop = 15;
+				dirLight.shadowCameraBottom = -15;
+				dirLight.shadowDarkness = 0.5;
+				dirLight.shadowCameraVisible = true;
+				dirLight.shadowMapWidth = 1024;
+				dirLight.shadowMapHeight = 1024;
+				dirLight.name = 'Dir. Light';
+				scene.add( dirLight );*/			
+
+				cubeMaterial = new THREE.MeshPhongMaterial( {
+					color: 0xff0000,
+					shininess: 150,
+					specular: 0x222222,
+					shading: THREE.SmoothShading,
+				} );
+
+				var cubeGeometry = new THREE.BoxGeometry( 3, 3, 3 );
+				cube = new THREE.Mesh( cubeGeometry, cubeMaterial );
+				cube.name = "cube 1";
+				cube.position.set( 8, 3, 6 );
+				cube.castShadow = true;
+				cube.receiveShadow = true;
+				scene.add( cube );
+
+				cube2 = new THREE.Mesh( cubeGeometry, cubeMaterial );
+				cube2.name = "cube 2";
+				cube2.position.set( -8, 3, 4 );
+				cube2.castShadow = true;
+				cube2.receiveShadow = true;
+				scene.add( cube2 );
+
+				cube3 = new THREE.Mesh( cubeGeometry, cubeMaterial );
+				cube3.name = "cube 3";
+				cube3.position.set( -4, 15, -6 );
+				cube3.castShadow = true;
+				cube3.receiveShadow = true;
+				scene.add( cube3 );
+
+				var torusGeometry =  new THREE.TorusKnotGeometry( 25, 8, 75, 20 );
+				torusKnot = new THREE.Mesh( torusGeometry, cubeMaterial );
+				torusKnot.scale.multiplyScalar( 1 / 18 );
+				torusKnot.position.set( -1, 3, -4 );
+				torusKnot.castShadow = true;
+				torusKnot.receiveShadow = true;
+				scene.add( torusKnot );
+			
+				wallMaterial = new THREE.MeshPhongMaterial( {
+					color: 0xa0adaf,
+					shininess: 10,
+					specular: 0x111111,
+					shading: THREE.SmoothShading
+				} );				
+
+				var wallGeometry = new THREE.BoxGeometry( 10, 0.15, 10 );
+				ground = new THREE.Mesh( wallGeometry, wallMaterial );
+				ground.name = "ground";
+				ground.scale.multiplyScalar( 3 );
+				ground.castShadow = false;
+				ground.receiveShadow = true;
+				scene.add( ground );
+				ground.position.set( 0, -5, 0 );
+
+				var ceiling = new THREE.Mesh( wallGeometry, wallMaterial );
+				ceiling.name = "ceiling";
+				ceiling.scale.multiplyScalar( 3 );
+				ceiling.castShadow = false;
+				ceiling.receiveShadow = true;
+				scene.add( ceiling );
+				ceiling.position.set( 0, 24, 0 );
+
+				var wall = new THREE.Mesh( wallGeometry, wallMaterial );
+				wall.name = "left wall";
+				wall.scale.multiplyScalar( 3 );
+				wall.castShadow = false;
+				wall.receiveShadow = true;				
+				scene.add( wall );
+				wall.position.set( -14, 10, 0 );
+				wall.rotation.z = Math.PI / 2;
+
+				wall = new THREE.Mesh( wallGeometry, wallMaterial );
+				wall.name = "right wall";
+				wall.scale.multiplyScalar( 3 );
+				wall.castShadow = false;
+				wall.receiveShadow = true;				
+				scene.add( wall );
+				wall.position.set(14,10,0);
+				wall.rotation.z = Math.PI / 2;
+
+				wall = new THREE.Mesh( wallGeometry, wallMaterial );
+				wall.name = "back wall";
+				wall.scale.multiplyScalar( 3 );
+				wall.castShadow = false;
+				wall.receiveShadow = true;				
+				scene.add( wall );
+				wall.position.set( 0, 10, -14 );
+				wall.rotation.y = Math.PI / 2;
+				wall.rotation.z = Math.PI / 2;
+
+				/*wall = new THREE.Mesh( wallGeometry, wallMaterial );
+				wall.name = "front wall";
+				wall.scale.multiplyScalar( 3 );
+				wall.castShadow = false;
+				wall.receiveShadow = true;				
+				scene.add( wall );
+				wall.position.set( 0, 10, 14 );
+				wall.rotation.y = Math.PI / 2;
+				wall.rotation.z = Math.PI / 2;*/
+
+				var sphereGeometry = new THREE.SphereGeometry( 1, 32, 32 );
+				var material = new THREE.MeshBasicMaterial( { color: 0xffffff } );
+				var sphere = new THREE.Mesh( sphereGeometry, material );
+				sphere.castShadow = false;
+				sphere.receiveShadow = false;	
+				sphere.position.set( 0, 11, 4 );
+				scene.add( sphere );
+
+				pointLightParent = new THREE.Object3D();
+				pointLightParent.add( pointLight );
+				pointLightParent.add( sphere );
+				scene.add( pointLightParent );	
+
+			}
+
+			function initMisc() {
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setClearColor( 0x000000 );				
+				renderer.shadowMap.enabled = true;
+				renderer.shadowMap.type = THREE.BasicShadowMap;
+
+				// Mouse control
+				controls = new THREE.OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 2, 0 );
+				controls.update();
+
+				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 );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+				render();
+				stats.update();
+
+			}
+
+			function renderScene() {
+
+				renderer.render( scene, camera );
+			}
+
+			function render() {
+
+				var delta = clock.getDelta();
+
+				pointLightParent.rotation.y += delta * 2;
+				renderScene();
+
+				cube.rotation.x += 0.25 * delta;
+				cube.rotation.y += 2 * delta;
+				cube.rotation.z += 1 * delta;
+
+				cube2.rotation.x += 0.25 * delta;
+				cube2.rotation.y += 2 * delta;
+				cube2.rotation.z += 1 * delta;
+
+				cube3.rotation.x += 0.25 * delta;
+				cube3.rotation.y += 2 * delta;
+				cube3.rotation.z += 1 * delta;
+
+			}
+
+		</script>
+	</body>
+</html>

+ 2 - 2
examples/webgl_shadowmap_viewer.html

@@ -65,7 +65,7 @@
 
 			function initScene() {
 
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.01, 1000 );
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
 				camera.position.set( 0, 15, 35 );
 
 				scene = new THREE.Scene();
@@ -89,7 +89,7 @@
 				dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
 				dirLight.position.set( 0, 10, 0 );
 				dirLight.castShadow = true;
-				dirLight.shadowCameraNear = 0.01;
+				dirLight.shadowCameraNear = 1;
 				dirLight.shadowCameraFar = 10;
 				dirLight.shadowCameraRight = 15;
 				dirLight.shadowCameraLeft = -15;

+ 40 - 0
src/lights/PointLight.js

@@ -2,6 +2,7 @@
  * @author mrdoob / http://mrdoob.com/
  */
 
+
 THREE.PointLight = function ( color, intensity, distance, decay ) {
 
 	THREE.Light.call( this, color );
@@ -12,6 +13,30 @@ THREE.PointLight = function ( color, intensity, distance, decay ) {
 	this.distance = ( distance !== undefined ) ? distance : 0;
 	this.decay = ( decay !== undefined ) ? decay : 1;	// for physically correct lights, should be 2.
 
+	this.castShadow = false;
+	this.onlyShadow = false;
+
+	//
+
+	this.shadowCameraNear = 1;
+	this.shadowCameraFar = 500;
+	this.shadowCameraFov = 90;
+
+	this.shadowCameraVisible = false;
+
+	this.shadowBias = 0;
+	this.shadowDarkness = 0.5;
+
+	this.shadowMapWidth = 512;
+	this.shadowMapHeight = 512;
+
+	//
+
+	this.shadowMap = null;
+	this.shadowMapSize = null;
+	this.shadowCamera = null;
+	this.shadowMatrix = null;
+
 };
 
 THREE.PointLight.prototype = Object.create( THREE.Light.prototype );
@@ -25,6 +50,21 @@ THREE.PointLight.prototype.copy = function ( source ) {
 	this.distance = source.distance;
 	this.decay = source.decay;
 
+	this.castShadow = source.castShadow;
+	this.onlyShadow = source.onlyShadow;
+
+	this.shadowCameraNear = source.shadowCameraNear;
+	this.shadowCameraFar = source.shadowCameraFar;
+	this.shadowCameraFov = source.shadowCameraFov;
+
+	this.shadowCameraVisible = source.shadowCameraVisible;
+
+	this.shadowBias = source.shadowBias;
+	this.shadowDarkness = source.shadowDarkness;
+
+	this.shadowMapWidth = source.shadowMapWidth;
+	this.shadowMapHeight = source.shadowMapHeight;
+
 	return this;
 
 };

+ 49 - 10
src/renderers/WebGLRenderer.js

@@ -374,6 +374,16 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	};
 
+	this.getViewport = function ( dimensions ) {
+
+		dimensions.x = _viewportX;
+		dimensions.y = _viewportY;
+
+		dimensions.z = _viewportWidth;
+		dimensions.w = _viewportHeight;
+
+	};
+
 	this.setScissor = function ( x, y, width, height ) {
 
 		_gl.scissor(
@@ -1701,7 +1711,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 			if ( object.receiveShadow && ! material._shadowPass ) {
 
-				refreshUniformsShadow( m_uniforms, lights );
+				refreshUniformsShadow( m_uniforms, lights, camera );
 
 			}
 
@@ -1953,7 +1963,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	}
 
-	function refreshUniformsShadow ( uniforms, lights ) {
+	function refreshUniformsShadow ( uniforms, lights, camera ) {
 
 		if ( uniforms.shadowMatrix ) {
 
@@ -1965,14 +1975,22 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 				if ( ! light.castShadow ) continue;
 
-				if ( light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight ) ) {
+				if ( light instanceof THREE.PointLight || light instanceof THREE.SpotLight || light instanceof THREE.DirectionalLight ) {
 
-					uniforms.shadowMap.value[ j ] = light.shadowMap;
-					uniforms.shadowMapSize.value[ j ] = light.shadowMapSize;
+					if ( light instanceof THREE.PointLight ) {
 
-					uniforms.shadowMatrix.value[ j ] = light.shadowMatrix;
+						// for point lights we set the sign of the shadowDarkness uniform to be negative
+						uniforms.shadowDarkness.value[ j ] = - light.shadowDarkness;
 
-					uniforms.shadowDarkness.value[ j ] = light.shadowDarkness;
+					} else {
+						
+						uniforms.shadowDarkness.value[ j ] = light.shadowDarkness;
+
+					}
+
+					uniforms.shadowMatrix.value[ j ] = light.shadowMatrix;
+					uniforms.shadowMap.value[ j ] =  light.shadowMap;
+					uniforms.shadowMapSize.value[ j ] = light.shadowMapSize;
 					uniforms.shadowBias.value[ j ] = light.shadowBias;
 
 					j ++;
@@ -2308,7 +2326,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 				case 'tv':
 
-					// array of THREE.Texture (2d)
+					// array of THREE.Texture (2d or cube)
 
 					if ( uniform._array === undefined ) {
 
@@ -2331,7 +2349,22 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 						if ( ! texture ) continue;
 
-						_this.setTexture( texture, textureUnit );
+						if ( texture instanceof THREE.CubeTexture ||
+						   ( texture.image instanceof Array && texture.image.length === 6 ) ) {
+
+							// CompressedTexture can have Array in image :/
+
+							setCubeTexture( texture, textureUnit );
+
+						} else if ( texture instanceof THREE.WebGLRenderTargetCube ) {
+
+							setCubeTextureDynamic( texture, textureUnit );
+
+						} else {
+
+							_this.setTexture( texture, textureUnit );
+
+						}
 
 					}
 
@@ -3011,7 +3044,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
 					renderTargetProperties.__webglRenderbuffer[ i ] = _gl.createRenderbuffer();
-
 					state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
 
 					setupFrameBuffer( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );
@@ -3124,6 +3156,13 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		}
 
+		if ( isCube ) {
+
+			var renderTargetProperties = properties.get( renderTarget );
+			_gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0,  _gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderTarget.activeCubeFace, renderTargetProperties.__webglTexture, 0 );
+
+		}
+
 		_currentWidth = width;
 		_currentHeight = height;
 

+ 6 - 0
src/renderers/shaders/ShaderChunk/lights_phong_pars_vertex.glsl

@@ -3,3 +3,9 @@
 	varying vec3 vWorldPosition;
 
 #endif
+
+#if MAX_POINT_LIGHTS > 0
+
+	uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];
+
+#endif

+ 208 - 89
src/renderers/shaders/ShaderChunk/shadowmap_fragment.glsl

@@ -14,10 +14,25 @@
 
 	for( int i = 0; i < MAX_SHADOWS; i ++ ) {
 
+		// to save on uniform space, we use the sign of @shadowDarkness[ i ] to determine
+		// whether or not this light is a point light ( shadowDarkness[ i ] < 0 == point light)
+		bool isPointLight = shadowDarkness[ i ] < 0.0;
+
+		// get the real shadow darkness
+		float realShadowDarkness = abs( shadowDarkness[ i ] );
+
+		// for point lights, the uniform @vShadowCoord is re-purposed to hold
+		// the distance from the light to the world-space position of the fragment.
+		vec3 lightToPosition = vShadowCoord[ i ].xyz;
+
+		float texelSizeX =  1.0 / shadowMapSize[ i ].x;
+		float texelSizeY =  1.0 / shadowMapSize[ i ].y;
+
 		vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;
+		float shadow = 0.0;
 
-				// if ( something && something ) breaks ATI OpenGL shader compiler
-				// if ( all( something, something ) ) using this instead
+		// if ( something && something ) breaks ATI OpenGL shader compiler
+		// if ( all( something, something ) ) using this instead
 
 		bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );
 		bool inFrustum = all( inFrustumVec );
@@ -26,148 +41,252 @@
 
 		bool frustumTest = all( frustumTestVec );
 
-		if ( frustumTest ) {
-
-			shadowCoord.z += shadowBias[ i ];
+		if ( frustumTest || isPointLight ) {			
 
 			#if defined( SHADOWMAP_TYPE_PCF )
 
+				#if defined(POINT_LIGHT_SHADOWS)
+
+					if( isPointLight ) {
+
+						float cubeTexelSize = 1.0 / ( shadowMapSize[ i ].x * 0.25 );
+						vec3 baseDirection3D = normalize( lightToPosition );
+						vec2 baseDirection2D = cubeToUV( baseDirection3D, texelSizeX, texelSizeY );
+
+						initGridSamplingDisk();
+
+						float diskRadius = 1.25;
+						float numSamples = 1.0;
+						shadow = 0.0;
+
+						vec3 baseDirection = normalize( lightToPosition );
+						float curDistance = length( lightToPosition );
+
+						float dist = unpack1K( texture2D( shadowMap[ i ],  baseDirection2D ) ) + 0.1;
+						if ( curDistance >= dist )
+							shadow += 1.0;
+						
+						// evaluate each sampling direction
+						for( int s = 0; s < 20; s++ ) {
+						 
+							vec3 offset = gridSamplingDisk[ s ] * diskRadius * cubeTexelSize;
+							vec3 adjustedBaseDirection3D = baseDirection3D + offset;
+							vec2 adjustedBaseDirection2D = cubeToUV( adjustedBaseDirection3D, texelSizeX, texelSizeY );
+							dist = unpack1K( texture2D( shadowMap[ i ],  adjustedBaseDirection2D ) ) + shadowBias[ i ];
+							if ( curDistance >= dist )
+								shadow += 1.0;
+							numSamples += 1.0;
+
+						}
+
+						shadow /= numSamples;
+
+					} else {
+
+				#endif
+
 						// Percentage-close filtering
 						// (9 pixel kernel)
 						// http://fabiensanglard.net/shadowmappingPCF/
+						
+						/*
+								// nested loops breaks shader compiler / validator on some ATI cards when using OpenGL
+								// must enroll loop manually
+							for ( float y = -1.25; y <= 1.25; y += 1.25 )
+								for ( float x = -1.25; x <= 1.25; x += 1.25 ) {
+									vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );
+											// doesn't seem to produce any noticeable visual difference compared to simple texture2D lookup
+											//vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );
+									float fDepth = unpackDepth( rgbaDepth );
+									if ( fDepth < shadowCoord.z )
+										shadow += 1.0;
+							}
+							shadow /= 9.0;
+						*/
 
-				float shadow = 0.0;
+						shadowCoord.z += shadowBias[ i ];
 
-		/*
-						// nested loops breaks shader compiler / validator on some ATI cards when using OpenGL
-						// must enroll loop manually
+						const float shadowDelta = 1.0 / 9.0;
 
-				for ( float y = -1.25; y <= 1.25; y += 1.25 )
-					for ( float x = -1.25; x <= 1.25; x += 1.25 ) {
+						float xPixelOffset = texelSizeX;
+						float yPixelOffset = texelSizeY;
 
-						vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );
+						float dx0 = -1.25 * xPixelOffset;
+						float dy0 = -1.25 * yPixelOffset;
+						float dx1 = 1.25 * xPixelOffset;
+						float dy1 = 1.25 * yPixelOffset;
 
-								// doesn't seem to produce any noticeable visual difference compared to simple texture2D lookup
-								//vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
 
-						float fDepth = unpackDepth( rgbaDepth );
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
 
-						if ( fDepth < shadowCoord.z )
-							shadow += 1.0;
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
 
-				}
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
 
-				shadow /= 9.0;
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
 
-		*/
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
 
-				const float shadowDelta = 1.0 / 9.0;
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
 
-				float xPixelOffset = 1.0 / shadowMapSize[ i ].x;
-				float yPixelOffset = 1.0 / shadowMapSize[ i ].y;
+						fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );
+						if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
 
-				float dx0 = -1.25 * xPixelOffset;
-				float dy0 = -1.25 * yPixelOffset;
-				float dx1 = 1.25 * xPixelOffset;
-				float dy1 = 1.25 * yPixelOffset;
+				#if defined(POINT_LIGHT_SHADOWS)
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+					}
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+				#endif
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+				shadowColor = shadowColor * vec3( ( 1.0 - realShadowDarkness * shadow ) );
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+			#elif defined( SHADOWMAP_TYPE_PCF_SOFT )
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+				#if defined(POINT_LIGHT_SHADOWS)
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+					if( isPointLight ) {
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+						float cubeTexelSize = 1.0 / ( shadowMapSize[ i ].x * 0.25 );
+						vec3 baseDirection3D = normalize( lightToPosition );
+						vec2 baseDirection2D = cubeToUV( baseDirection3D, texelSizeX, texelSizeY );
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+						initGridSamplingDisk();
 
-				fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );
-				if ( fDepth < shadowCoord.z ) shadow += shadowDelta;
+						float diskRadius = 2.25;
+						float numSamples = 1.0;
+						shadow = 0.0;
 
-				shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );
+						vec3 baseDirection = normalize( lightToPosition );
+						float curDistance = length( lightToPosition );
 
-			#elif defined( SHADOWMAP_TYPE_PCF_SOFT )
+						float dist = unpack1K( texture2D( shadowMap[ i ],  baseDirection2D ) ) + 0.1;
+						if ( curDistance >= dist )
+							shadow += 1.0;
+
+						// evaluate each sampling direction
+						for( int s = 0; s < 20; s++ ) {
+
+							vec3 offset = gridSamplingDisk[ s ] * diskRadius * cubeTexelSize;
+							vec3 adjustedBaseDirection3D = baseDirection3D + offset;
+							vec2 adjustedBaseDirection2D = cubeToUV( adjustedBaseDirection3D, texelSizeX, texelSizeY );
+							dist = unpack1K( texture2D( shadowMap[ i ],  adjustedBaseDirection2D ) ) + shadowBias[ i ];
+							if ( curDistance >= dist )
+								shadow += 1.0;
+							numSamples += 1.0;
+
+						}
+
+						shadow /= numSamples;
+
+					} else {
+
+				#endif
 
 						// Percentage-close filtering
 						// (9 pixel kernel)
 						// http://fabiensanglard.net/shadowmappingPCF/
 
-				float shadow = 0.0;
+						shadowCoord.z += shadowBias[ i ];
+
+						float xPixelOffset = texelSizeX;
+						float yPixelOffset = texelSizeY;
+
+						float dx0 = -1.0 * xPixelOffset;
+						float dy0 = -1.0 * yPixelOffset;
+						float dx1 = 1.0 * xPixelOffset;
+						float dy1 = 1.0 * yPixelOffset;
 
-				float xPixelOffset = 1.0 / shadowMapSize[ i ].x;
-				float yPixelOffset = 1.0 / shadowMapSize[ i ].y;
+						mat3 shadowKernel;
+						mat3 depthKernel;
 
-				float dx0 = -1.0 * xPixelOffset;
-				float dy0 = -1.0 * yPixelOffset;
-				float dx1 = 1.0 * xPixelOffset;
-				float dy1 = 1.0 * yPixelOffset;
+						depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );
+						depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );
+						depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );
+						depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );
+						depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );
+						depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );
+						depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );
+						depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );
+						depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );
 
-				mat3 shadowKernel;
-				mat3 depthKernel;
+						vec3 shadowZ = vec3( shadowCoord.z );
+						shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));
+						shadowKernel[0] *= vec3(0.25);
 
-				depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );
-				depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );
-				depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );
-				depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );
-				depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );
-				depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );
-				depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );
-				depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );
-				depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );
+						shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));
+						shadowKernel[1] *= vec3(0.25);
 
-				vec3 shadowZ = vec3( shadowCoord.z );
-				shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));
-				shadowKernel[0] *= vec3(0.25);
+						shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));
+						shadowKernel[2] *= vec3(0.25);
 
-				shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));
-				shadowKernel[1] *= vec3(0.25);
+						vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );
 
-				shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));
-				shadowKernel[2] *= vec3(0.25);
+						shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );
+						shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );
 
-				vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );
+						vec4 shadowValues;
+						shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );
+						shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );
+						shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );
+						shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );
 
-				shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );
-				shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );
+						shadow = dot( shadowValues, vec4( 1.0 ) );
 
-				vec4 shadowValues;
-				shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );
-				shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );
-				shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );
-				shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );
+				#if defined(POINT_LIGHT_SHADOWS)
+					
+					}
 
-				shadow = dot( shadowValues, vec4( 1.0 ) );
+				#endif
 
-				shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );
+				shadowColor = shadowColor * vec3( ( 1.0 - realShadowDarkness * shadow ) );
 
 			#else
 
-				vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );
-				float fDepth = unpackDepth( rgbaDepth );
+				#if defined(POINT_LIGHT_SHADOWS)
+
+					if( isPointLight ) {
+
+						vec3 baseDirection3D = normalize( lightToPosition );
+						vec2 baseDirection2D = cubeToUV( baseDirection3D, texelSizeX, texelSizeY );
+						vec4 data = texture2D( shadowMap[ i ],  baseDirection2D );
+						float dist = unpack1K( data ) + shadowBias[ i ];
+						if ( length( lightToPosition ) >= dist)
+							shadowColor = shadowColor * vec3( 1.0 - realShadowDarkness );
+
+					} else {
+
+				#endif
+						shadowCoord.z += shadowBias[ i ];
+
+						vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );
+						float fDepth = unpackDepth( rgbaDepth );
+
+						if ( fDepth < shadowCoord.z )
+
+						// spot with multiple shadows is darker
+
+						shadowColor = shadowColor * vec3( 1.0 - realShadowDarkness );
 
-				if ( fDepth < shadowCoord.z )
+						// spot with multiple shadows has the same color as single shadow spot
 
-		// spot with multiple shadows is darker
+						// 	shadowColor = min( shadowColor, vec3( realShadowDarkness ) );
 
-					shadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );
+				#if defined(POINT_LIGHT_SHADOWS)
 
-		// spot with multiple shadows has the same color as single shadow spot
+					}
 
-		// 					shadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );
+				#endif
 
 			#endif
 

+ 121 - 1
src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl

@@ -1,5 +1,5 @@
 #ifdef USE_SHADOWMAP
-
+	
 	uniform sampler2D shadowMap[ MAX_SHADOWS ];
 	uniform vec2 shadowMapSize[ MAX_SHADOWS ];
 
@@ -16,4 +16,124 @@
 
 	}
 
+	#if defined(POINT_LIGHT_SHADOWS)
+
+		float unpack1K ( vec4 color ) {
+		
+			const vec4 bitSh = vec4( 1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0 );
+			return dot( color, bitSh ) * 1000.0;
+			
+		}
+
+		/**
+		*  cubeToUV() maps a 3D direction vector suitable for cube texture mapping to a 2D
+		*  vector suitable for 2D texture mapping. This code uses the following layout for the
+		*  2D texture:		
+		*  
+		*  xzXZ
+		*   y Y
+		*
+		*  Y - Positive y direction
+		*  y - Negative y direction
+		*  X - Positive x direction
+		*  x - Negative x direction
+		*  Z - Positive z direction
+		*  z - Negative z direction
+		*
+		*  Alternate code for a horizontal cross layout can be found here:
+		*  https://gist.github.com/tschw/da10c43c467ce8afd0c4
+		*/
+
+		vec2 cubeToUV( vec3 v, float texelSizeX, float texelSizeY ) {
+
+			// Number of texels to avoid at the edge of each square
+
+			vec3 absV = abs( v );
+
+			// Intersect unit cube
+
+			float scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );
+			absV *= scaleToCube;
+
+			// Apply scale to avoid seams
+
+			// two texels less per square (one texel will do for NEAREST)
+			v *= scaleToCube * ( 1.0 - 4.0 * texelSizeY );
+
+			// Unwrap
+
+			// space: -1 ... 1 range for each square
+			//
+			// #X##		dim    := ( 4 , 2 )
+			//  # #		center := ( 1 , 1 )
+
+			vec2 planar = v.xy;
+
+			float almostATexel = 1.5 * texelSizeY;
+			float almostOne = 1.0 - almostATexel;
+
+			if ( absV.z >= almostOne ) {
+
+				if ( v.z > 0.0 )
+					planar.x = 4.0 - v.x;
+
+			} else if ( absV.x >= almostOne ) {
+
+				float signX = sign( v.x );
+				planar.x = v.z * signX + 2.0 * signX;
+
+			} else if ( absV.y >= almostOne ) {
+
+				float signY = sign( v.y );
+				planar.x = v.x + 2.0 * signY + 2.0;
+				planar.y = v.z * signY - 2.0;
+
+			}
+
+			// Transform to UV space
+
+			// scale := 0.5 / dim
+			// translate := ( center + 0.5 ) / dim
+			return vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );
+			
+		}
+
+		vec3 gridSamplingDisk[ 20 ];
+		bool gridSamplingInitialized = false;
+
+		void initGridSamplingDisk(){
+
+			if( gridSamplingInitialized ){
+
+				return;
+
+			}
+
+			gridSamplingDisk[0] = vec3(1, 1, 1);
+			gridSamplingDisk[1] = vec3(1, -1, 1);
+			gridSamplingDisk[2] = vec3(-1, -1, 1);
+			gridSamplingDisk[3] = vec3(-1, 1, 1);
+			gridSamplingDisk[4] = vec3(1, 1, -1);
+			gridSamplingDisk[5] = vec3(1, -1, -1);
+			gridSamplingDisk[6] = vec3(-1, -1, -1);
+			gridSamplingDisk[7] = vec3(-1, 1, -1);
+			gridSamplingDisk[8] = vec3(1, 1, 0);
+			gridSamplingDisk[9] = vec3(1, -1, 0);
+			gridSamplingDisk[10] = vec3(-1, -1, 0);
+			gridSamplingDisk[11] = vec3(-1, 1, 0);
+			gridSamplingDisk[12] = vec3(1, 0, 1);
+			gridSamplingDisk[13] = vec3(-1, 0, 1);
+			gridSamplingDisk[14] = vec3(1, 0, -1);
+			gridSamplingDisk[15] = vec3(-1, 0, -1);
+			gridSamplingDisk[16] = vec3(0, 1, 1);
+			gridSamplingDisk[17] = vec3(0, -1, 1);
+			gridSamplingDisk[18] = vec3(0, -1, -1);
+			gridSamplingDisk[19] = vec3(0, 1, -1);
+
+			gridSamplingInitialized = true;
+			
+		}
+
+	#endif
+
 #endif

+ 2 - 1
src/renderers/shaders/ShaderChunk/shadowmap_pars_vertex.glsl

@@ -1,6 +1,7 @@
 #ifdef USE_SHADOWMAP
 
-	varying vec4 vShadowCoord[ MAX_SHADOWS ];
+	uniform float shadowDarkness[ MAX_SHADOWS ];
 	uniform mat4 shadowMatrix[ MAX_SHADOWS ];
+	varying vec4 vShadowCoord[ MAX_SHADOWS ];
 
 #endif

+ 33 - 1
src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl

@@ -2,7 +2,39 @@
 
 	for( int i = 0; i < MAX_SHADOWS; i ++ ) {
 
-		vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;
+		#if defined(POINT_LIGHT_SHADOWS)
+
+			// if shadowDarkness[ i ] < 0.0, that means we have a point light with a cube
+			// shadow map
+			if( shadowDarkness[ i ] < 0.0 ) {
+
+				// calculate vector from light to vertex in view space
+
+				vec3 fromLight = mvPosition.xyz - pointLightPosition[ i ];
+
+				// Transform 'fromLight' into world space by multiplying it on the left
+				// side of 'viewMatrix'. This is equivalent to multiplying it on the right
+				// side of the transpose of 'viewMatrix'. Since 'viewMatrix' is orthogonal, 
+				// its transpose is the same as its inverse.
+
+				fromLight = fromLight * mat3( viewMatrix );
+
+				// We repurpose vShadowCoord to hold the distance in world space from the
+				// light to the vertex. This value will be interpolated correctly in the fragment shader.
+
+				vShadowCoord[ i ] = vec4( fromLight, 1.0 );
+
+			} else {
+
+				vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;
+
+			}
+
+		#else
+
+			vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;
+
+		#endif
 
 	}
 

+ 68 - 0
src/renderers/shaders/ShaderLib.js

@@ -835,6 +835,74 @@ THREE.ShaderLib = {
 
 		].join( "\n" )
 
+	},
+
+
+	'distanceRGBA': {
+
+		uniforms: {
+
+			"lightPos": { type: "v3", value: new THREE.Vector3( 0, 0, 0 ) }
+
+		},
+
+		vertexShader: [
+
+			"varying vec4 vWorldPosition;",
+
+			THREE.ShaderChunk[ "common" ],
+			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+			THREE.ShaderChunk[ "skinning_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "skinbase_vertex" ],
+				THREE.ShaderChunk[ "begin_vertex" ],
+				THREE.ShaderChunk[ "morphtarget_vertex" ],
+				THREE.ShaderChunk[ "skinning_vertex" ],
+				THREE.ShaderChunk[ "project_vertex" ],
+				THREE.ShaderChunk[ "worldpos_vertex" ],
+
+				"vWorldPosition = worldPosition;",
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"uniform vec3 lightPos;",
+			"varying vec4 vWorldPosition;",
+
+			THREE.ShaderChunk[ "common" ],
+
+			"vec4 pack1K ( float depth ) {",
+
+			"   depth /= 1000.0;",
+			"   const vec4 bitSh = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );",
+  			"	const vec4 bitMsk = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );",
+   			"	vec4 res = fract( depth * bitSh );",
+   			"	res -= res.xxyz * bitMsk;",
+   			"	return res; ",
+
+			"}",
+
+			"float unpack1K ( vec4 color ) {",
+
+			"	const vec4 bitSh = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );",
+			"	return dot( color, bitSh ) * 1000.0;",
+
+			"}",
+
+			"void main () {",
+
+			"	gl_FragColor = pack1K( length( vWorldPosition.xyz - lightPos.xyz ) );",
+
+			"}"
+
+		].join( "\n" )
+
 	}
 
 };
+

+ 2 - 0
src/renderers/webgl/WebGLProgram.js

@@ -219,6 +219,7 @@ THREE.WebGLProgram = ( function () {
 				parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
 				parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
 				parameters.shadowMapDebug ? '#define SHADOWMAP_DEBUG' : '',
+				parameters.pointLightShadows > 0 ? '#define POINT_LIGHT_SHADOWS' : '',
 
 				parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',
 
@@ -330,6 +331,7 @@ THREE.WebGLProgram = ( function () {
 				parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
 				parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
 				parameters.shadowMapDebug ? '#define SHADOWMAP_DEBUG' : '',
+				parameters.pointLightShadows > 0 ? '#define POINT_LIGHT_SHADOWS' : '',
 
 				parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
 				parameters.logarithmicDepthBuffer && renderer.extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '',

+ 14 - 7
src/renderers/webgl/WebGLPrograms.js

@@ -20,7 +20,7 @@ THREE.WebGLPrograms = function ( renderer, capabilities ) {
 		"flatShading", "sizeAttenuation", "logarithmicDepthBuffer", "skinning",
 		"maxBones", "useVertexTexture", "morphTargets", "morphNormals",
 		"maxMorphTargets", "maxMorphNormals", "maxDirLights", "maxPointLights",
-		"maxSpotLights", "maxHemiLights", "maxShadows", "shadowMapEnabled",
+		"maxSpotLights", "maxHemiLights", "maxShadows", "shadowMapEnabled", "pointLightShadows", 
 		"shadowMapType", "shadowMapDebug", "alphaTest", "metal", "doubleSided",
 		"flipSided"
 	];
@@ -91,6 +91,7 @@ THREE.WebGLPrograms = function ( renderer, capabilities ) {
 	function allocateShadows( lights ) {
 
 		var maxShadows = 0;
+		var pointLightShadows = 0;
 
 		for ( var l = 0, ll = lights.length; l < ll; l ++ ) {
 
@@ -98,12 +99,17 @@ THREE.WebGLPrograms = function ( renderer, capabilities ) {
 
 			if ( ! light.castShadow ) continue;
 
-			if ( light instanceof THREE.SpotLight ) maxShadows ++;
-			if ( light instanceof THREE.DirectionalLight ) maxShadows ++;
+			if ( light instanceof THREE.SpotLight || light instanceof THREE.DirectionalLight ) maxShadows ++;
+			if ( light instanceof THREE.PointLight ) {
+
+				maxShadows ++;
+				pointLightShadows ++;
+
+			} 
 
 		}
 
-		return maxShadows;
+		return { 'maxShadows': maxShadows, 'pointLightShadows': pointLightShadows };
 
 	}
 
@@ -114,7 +120,7 @@ THREE.WebGLPrograms = function ( renderer, capabilities ) {
 		// (not to blow over maxLights budget)
 
 		var maxLightCount = allocateLights( lights );
-		var maxShadows = allocateShadows( lights );
+		var allocatedShadows = allocateShadows( lights );
 		var maxBones = allocateBones( object );
 		var precision = renderer.getPrecision();
 
@@ -176,8 +182,9 @@ THREE.WebGLPrograms = function ( renderer, capabilities ) {
 			maxSpotLights: maxLightCount.spot,
 			maxHemiLights: maxLightCount.hemi,
 
-			maxShadows: maxShadows,
-			shadowMapEnabled: renderer.shadowMap.enabled && object.receiveShadow && maxShadows > 0,
+			maxShadows: allocatedShadows.maxShadows,
+			pointLightShadows: allocatedShadows.pointLightShadows,
+			shadowMapEnabled: renderer.shadowMap.enabled && object.receiveShadow && allocatedShadows.maxShadows > 0,
 			shadowMapType: renderer.shadowMap.type,
 			shadowMapDebug: renderer.shadowMap.debug,
 

+ 215 - 62
src/renderers/webgl/WebGLShadowMap.js

@@ -13,36 +13,51 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 	_min = new THREE.Vector3(),
 	_max = new THREE.Vector3(),
 
-	_matrixPosition = new THREE.Vector3(),
+	_lookTarget = new THREE.Vector3(),
+	_lightPositionWorld = new THREE.Vector3(),
 
 	_renderList = [];
 
+	var _depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin,
+	_distanceMaterial, _distanceMaterialMorph, _distanceMaterialSkin, _distanceMaterialMorphSkin;
+
+	var cubeDirections = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( - 1, 0, 0 ), new THREE.Vector3( 0, 0, 1 ), 
+						   new THREE.Vector3( 0, 0, - 1 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, - 1, 0 ) ];
+
+	var cubeUps = [ new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 1, 0 ), 
+					new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ),	new THREE.Vector3( 0, 0, - 1 ) ];
+
+	var cube2DViewPorts = [ new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4(),
+				   			new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4() ];
+
+	var _vector4 = new THREE.Vector4();
+
 	// init
 
 	var depthShader = THREE.ShaderLib[ "depthRGBA" ];
 	var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms );
 
-	var _depthMaterial = new THREE.ShaderMaterial( {
+	_depthMaterial = new THREE.ShaderMaterial( {
 		uniforms: depthUniforms,
 		vertexShader: depthShader.vertexShader,
 		fragmentShader: depthShader.fragmentShader
 	 } );
 
-	var _depthMaterialMorph = new THREE.ShaderMaterial( {
+	_depthMaterialMorph = new THREE.ShaderMaterial( {
 		uniforms: depthUniforms,
 		vertexShader: depthShader.vertexShader,
 		fragmentShader: depthShader.fragmentShader,
 		morphTargets: true
 	} );
 
-	var _depthMaterialSkin = new THREE.ShaderMaterial( {
+	_depthMaterialSkin = new THREE.ShaderMaterial( {
 		uniforms: depthUniforms,
 		vertexShader: depthShader.vertexShader,
 		fragmentShader: depthShader.fragmentShader,
 		skinning: true
 	} );
 
-	var _depthMaterialMorphSkin = new THREE.ShaderMaterial( {
+	_depthMaterialMorphSkin = new THREE.ShaderMaterial( {
 		uniforms: depthUniforms,
 		vertexShader: depthShader.vertexShader,
 		fragmentShader: depthShader.fragmentShader,
@@ -55,6 +70,43 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 	_depthMaterialSkin._shadowPass = true;
 	_depthMaterialMorphSkin._shadowPass = true;
 
+
+	var distanceShader = THREE.ShaderLib[ "distanceRGBA" ];
+	var distanceUniforms = THREE.UniformsUtils.clone( distanceShader.uniforms );
+
+	_distanceMaterial = new THREE.ShaderMaterial( {
+		uniforms: distanceUniforms,
+		vertexShader: distanceShader.vertexShader,
+		fragmentShader: distanceShader.fragmentShader
+	 } );
+
+	_distanceMaterialMorph = new THREE.ShaderMaterial( {
+		uniforms: distanceUniforms,
+		vertexShader: distanceShader.vertexShader,
+		fragmentShader: distanceShader.fragmentShader,
+		morphTargets: true
+	} );
+
+	_distanceMaterialSkin = new THREE.ShaderMaterial( {
+		uniforms: distanceUniforms,
+		vertexShader: distanceShader.vertexShader,
+		fragmentShader: distanceShader.fragmentShader,
+		skinning: true
+	} );
+
+	_distanceMaterialMorphSkin = new THREE.ShaderMaterial( {
+		uniforms: distanceUniforms,
+		vertexShader: distanceShader.vertexShader,
+		fragmentShader: distanceShader.fragmentShader,
+		morphTargets: true,
+		skinning: true
+	} );
+
+	_distanceMaterial._shadowPass = true;
+	_distanceMaterialMorph._shadowPass = true;
+	_distanceMaterialSkin._shadowPass = true;
+	_distanceMaterialMorphSkin._shadowPass = true;
+
 	//
 
 	var scope = this;
@@ -69,6 +121,8 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 	this.render = function ( scene ) {
 
+		var faceCount, isPointLight;
+
 		if ( scope.enabled === false ) return;
 		if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
 
@@ -98,13 +152,58 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 			var light = _lights[ i ];
 
+			if ( light instanceof THREE.PointLight ) {
+
+				faceCount = 6;
+				isPointLight = true;
+
+				var vpWidth = light.shadowMapWidth / 4.0;
+				var vpHeight = light.shadowMapHeight / 2.0;
+
+				/*
+				*
+				* These viewports map a cube-map onto a 2D texture with the
+				* following orientation:
+				*
+				*  xzXZ
+				*   y Y
+				*
+				*  Y - Positive y direction
+				*  y - Negative y direction
+				*  X - Positive x direction
+				*  x - Negative x direction
+				*  Z - Positive z direction
+				*  z - Negative z direction
+				*
+				*/
+
+				// positive X
+				cube2DViewPorts[ 0 ].set( vpWidth * 2, vpHeight, vpWidth, vpHeight );
+				// negative X
+				cube2DViewPorts[ 1 ].set( 0, vpHeight, vpWidth, vpHeight );
+				// positive Z
+				cube2DViewPorts[ 2 ].set( vpWidth * 3, vpHeight, vpWidth, vpHeight );
+				// negative Z
+				cube2DViewPorts[ 3 ].set( vpWidth, vpHeight, vpWidth, vpHeight );
+				// positive Y
+				cube2DViewPorts[ 4 ].set(  vpWidth * 3, 0, vpWidth, vpHeight );
+				// negative Y
+				cube2DViewPorts[ 5 ].set( vpWidth, 0, vpWidth, vpHeight );
+
+			} else {
+
+				faceCount = 1;
+				isPointLight = false;
+
+			}
+
 			if ( ! light.castShadow ) continue;
 
 			if ( ! light.shadowMap ) {
 
 				var shadowFilter = THREE.LinearFilter;
 
-				if ( scope.type === THREE.PCFSoftShadowMap ) {
+				if ( scope.type === THREE.PCFSoftShadowMap) {
 
 					shadowFilter = THREE.NearestFilter;
 
@@ -131,8 +230,7 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 				} else {
 
-					console.error( "THREE.ShadowMapPlugin: Unsupported light type for shadow", light );
-					continue;
+					light.shadowCamera = new THREE.PerspectiveCamera( light.shadowCameraFov, 1.0, light.shadowCameraNear, light.shadowCameraFar );
 
 				}
 
@@ -153,78 +251,99 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 			var shadowMatrix = light.shadowMatrix;
 			var shadowCamera = light.shadowCamera;
 
-			//
+			_lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
+			shadowCamera.position.copy( _lightPositionWorld );
 
-			shadowCamera.position.setFromMatrixPosition( light.matrixWorld );
-			_matrixPosition.setFromMatrixPosition( light.target.matrixWorld );
-			shadowCamera.lookAt( _matrixPosition );
-			shadowCamera.updateMatrixWorld();
+			// save the existing viewport so it can be restored later
+			_renderer.getViewport( _vector4 );
 
-			shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld );
+			_renderer.setRenderTarget( shadowMap );
+			_renderer.clear();		
 
-			//
+			// render shadow map for each cube face (if omni-directional) or
+			// run a single pass if not
 
-			if ( light.cameraHelper ) light.cameraHelper.visible = light.shadowCameraVisible;
-			if ( light.shadowCameraVisible ) light.cameraHelper.update();
+			for ( var face = 0; face < faceCount; face ++ ) {
 
-			// compute shadow matrix
+				if ( isPointLight ) {
 
-			shadowMatrix.set(
-				0.5, 0.0, 0.0, 0.5,
-				0.0, 0.5, 0.0, 0.5,
-				0.0, 0.0, 0.5, 0.5,
-				0.0, 0.0, 0.0, 1.0
-			);
+					_lookTarget.copy( shadowCamera.position );
+					_lookTarget.add( cubeDirections[ face ] );
+					shadowCamera.up.copy( cubeUps[ face ] );
+					shadowCamera.lookAt( _lookTarget );
+					var vpDimensions = cube2DViewPorts[ face ];
+					_renderer.setViewport( vpDimensions.x, vpDimensions.y, vpDimensions.z, vpDimensions.w );
 
-			shadowMatrix.multiply( shadowCamera.projectionMatrix );
-			shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
+				} else {
 
-			// update camera matrices and frustum
+					_lookTarget.setFromMatrixPosition( light.target.matrixWorld );
+					shadowCamera.lookAt( _lookTarget );
 
-			_projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
-			_frustum.setFromMatrix( _projScreenMatrix );
+				}
 
-			// render shadow map
+				shadowCamera.updateMatrixWorld();
+				shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld );
 
-			_renderer.setRenderTarget( shadowMap );
-			_renderer.clear();
+				if ( light.cameraHelper ) light.cameraHelper.visible = light.shadowCameraVisible;
+				if ( light.shadowCameraVisible ) light.cameraHelper.update();
 
-			// set object matrices & frustum culling
+				// compute shadow matrix
 
-			_renderList.length = 0;
+				shadowMatrix.set(
+					0.5, 0.0, 0.0, 0.5,
+					0.0, 0.5, 0.0, 0.5,
+					0.0, 0.0, 0.5, 0.5,
+					0.0, 0.0, 0.0, 1.0
+				);
 
-			projectObject( scene, shadowCamera );
+				shadowMatrix.multiply( shadowCamera.projectionMatrix );
+				shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
 
+				// update camera matrices and frustum
 
-			// render regular objects
+				_projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
+				_frustum.setFromMatrix( _projScreenMatrix );
 
-			for ( var j = 0, jl = _renderList.length; j < jl; j ++ ) {
+				// set object matrices & frustum culling
 
-				var object = _renderList[ j ];
-				var geometry = _objects.update( object );
-				var material = object.material;
+				_renderList.length = 0;
+
+				projectObject( scene, shadowCamera );
+
+				// render shadow map
+				// render regular objects
+
+				for ( var j = 0, jl = _renderList.length; j < jl; j ++ ) {
 
-				if ( material instanceof THREE.MeshFaceMaterial ) {
+					var object = _renderList[ j ];
+					var geometry = _objects.update( object );
+					var material = object.material;
 
-					var groups = geometry.groups;
-					var materials = material.materials;
+					if ( material instanceof THREE.MeshFaceMaterial ) {
 
-					for ( var k = 0, kl = groups.length; k < kl; k ++ ) {
+						var groups = geometry.groups;
+						var materials = material.materials;
 
-						var group = groups[ k ];
-						var groupMaterial = materials[ group.materialIndex ];
+						for ( var k = 0, kl = groups.length; k < kl; k ++ ) {
 
-						if ( groupMaterial.visible === true ) {
+							var group = groups[ k ];
+							var groupMaterial = materials[ group.materialIndex ];
 
-							_renderer.renderBufferDirect( shadowCamera, _lights, null, geometry, getDepthMaterial( object, groupMaterial ), object, group );
+							if ( groupMaterial.visible === true ) {
+
+								var depthMaterial = getDepthMaterial( object, groupMaterial, isPointLight, _lightPositionWorld );
+								_renderer.renderBufferDirect( shadowCamera, _lights, null, geometry, depthMaterial, object, group );
+
+							}
 
 						}
 
-					}
+					} else {
 
-				} else {
+						var depthMaterial = getDepthMaterial( object, material, isPointLight, _lightPositionWorld );
+						_renderer.renderBufferDirect( shadowCamera, _lights, null, geometry, depthMaterial, object, null );
 
-					_renderer.renderBufferDirect( shadowCamera, _lights, null, geometry, getDepthMaterial( object, material ), object, null );
+					}
 
 				}
 
@@ -246,44 +365,78 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 		}
 
+		_renderer.setViewport( _vector4.x, _vector4.y, _vector4.z, _vector4.w );
+
 		_renderer.resetGLState();
 
 		scope.needsUpdate = false;
 
 	};
 
-	function getDepthMaterial( object, material ) {
+	function getDepthMaterial( object, material, isPointLight, lightPositionWorld ) {
 
 		var geometry = object.geometry;
 
 		var useMorphing = geometry.morphTargets !== undefined && geometry.morphTargets.length > 0 && material.morphTargets;
 		var useSkinning = object instanceof THREE.SkinnedMesh && material.skinning;
 
-		var depthMaterial;
+		var newMaterial;
+
+		var depthMaterial = _depthMaterial;
+		var depthMaterialMorph = _depthMaterialMorph;
+		var depthMaterialSkin = _depthMaterialSkin;
+		var depthMaterialMorphSkin = _depthMaterialMorphSkin;
 
-		if ( object.customDepthMaterial ) {
+		if ( isPointLight ) {
+
+			depthMaterial = _distanceMaterial;
+			depthMaterialMorph = _distanceMaterialMorph;
+			depthMaterialSkin = _distanceMaterialSkin;
+			depthMaterialMorphSkin = _distanceMaterialMorphSkin;
+
+		}
 
-			depthMaterial = object.customDepthMaterial;
+		if ( object.customDepthMaterial || object.customDistanceMaterial ) {
+
+			if ( isPointLight ) {
+
+				newMaterial = object.customDistanceMaterial;
+
+			} else {
+
+				newMaterial = object.customDepthMaterial;
+
+			}
 
 		} else if ( useSkinning ) {
 
-			depthMaterial = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin;
+			newMaterial = useMorphing ? depthMaterialMorphSkin : depthMaterialSkin;
 
 		} else if ( useMorphing ) {
 
-			depthMaterial = _depthMaterialMorph;
+			newMaterial = depthMaterialMorph;
 
 		} else {
 
-			depthMaterial = _depthMaterial;
+			newMaterial = depthMaterial;
 
 		}
 
-		depthMaterial.visible = material.visible;
-		depthMaterial.wireframe = material.wireframe;
-		depthMaterial.wireframeLinewidth = material.wireframeLinewidth;
+		newMaterial.visible = material.visible;
+		newMaterial.wireframe = material.wireframe;
+		newMaterial.wireframeLinewidth = material.wireframeLinewidth;
+
+		if ( isPointLight ) {
+
+			if ( newMaterial.uniforms.lightPos ) {
+
+				newMaterial.uniforms.lightPos.value.copy( lightPositionWorld );
+
+			}
+
+		}
 
-		return depthMaterial;
+		return newMaterial;
 
 	}