Selaa lähdekoodia

VSM shadow maps

supereggbert 6 vuotta sitten
vanhempi
commit
6b1867e3e1

+ 3 - 1
docs/api/en/constants/Renderer.html

@@ -39,13 +39,15 @@
 		THREE.BasicShadowMap
 		THREE.PCFShadowMap
 		THREE.PCFSoftShadowMap
+		THREE.VSMShadowMap
 		</code>
 		<p>
 		These define the WebGLRenderer's [page:WebGLRenderer.shadowMap.type shadowMap.type] property.<br /><br />
 
 		[page:constant BasicShadowMap] gives unfiltered shadow maps - fastest, but lowest quality.<br />
 		[page:constant PCFShadowMap] filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm (default).<br />
-		[page:constant PCFSoftShadowMap] filters shadow maps using the Percentage-Closer Soft Shadows (PCSS) algorithm.
+		[page:constant PCFSoftShadowMap] filters shadow maps using the Percentage-Closer Soft Shadows (PCSS) algorithm.<br />
+		[page:constant VSMShadowMap] filters shadow maps using the Variance Shadow Map (VSM) algorithm.
 		</p>
 
 		<h2>Tone Mapping</h2>

+ 8 - 5
docs/api/en/lights/shadows/LightShadow.html

@@ -22,7 +22,7 @@
 		<p>
 		[page:Camera camera] - the light's view of the world.<br /><br />
 
-		Create a new [name]. This is not intended to be called directly - it is used as a base class by
+		Create a new [name]. This is not intended to be called directly - it is used as a base class by 
 		other light shadows.
 		</p>
 
@@ -46,6 +46,12 @@
 			in shadow. Computed internally during rendering.
 		</p>
 
+		<h3>[property:WebGLRenderTarget mapPass]</h3>
+		<p>
+			The distribution map generated using the internal camera; an occlusion is calculated based 
+			on the distribution of depths. Computed internally during rendering.
+		</p>
+
 		<h3>[property:Vector2 mapSize]</h3>
 		<p>
 			A [Page:Vector2] defining the width and height of the shadow map.<br /><br />
@@ -56,7 +62,6 @@
 			The default is *( 512, 512 )*.
 		</p>
 
-
 		<h3>[property:Matrix4 matrix]</h3>
 		<p>
 			Model to shadow camera space, to compute location and depth in shadow map. Stored
@@ -118,8 +123,6 @@
 
 		<h2>Source</h2>
 
-		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/lights/[name].js src/lights/[name].js]
-		</p>
+		[link:https://github.com/mrdoob/three.js/blob/master/src/lights/[name].js src/lights/[name].js]
 	</body>
 </html>

+ 1 - 1
docs/api/en/renderers/WebGLRenderer.html

@@ -248,7 +248,7 @@
 
 		<h3>[property:Integer shadowMap.type]</h3>
 		<p>Defines shadow map type (unfiltered, percentage close filtering, percentage close filtering with bilinear filtering in shader)</p>
-		<p>Options are THREE.BasicShadowMap, THREE.PCFShadowMap (default) and THREE.PCFSoftShadowMap. See [page:Renderer Renderer constants] for details.</p>
+		<p>Options are THREE.BasicShadowMap, THREE.PCFShadowMap (default), THREE.PCFSoftShadowMap and THREE.VSMShadowMap. See [page:Renderer Renderer constants] for details.</p>
 
 		<h3>[property:Boolean sortObjects]</h3>
 		<p>

+ 1 - 0
examples/files.js

@@ -229,6 +229,7 @@ var files = {
 		"webgl_shaders_vector",
 		"webgl_shading_physical",
 		"webgl_shadowmap",
+		"webgl_shadowmap_vsm",
 		"webgl_shadowmap_performance",
 		"webgl_shadowmap_pointlight",
 		"webgl_shadowmap_viewer",

+ 222 - 0
examples/webgl_shadowmap_vsm.html

@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - VSM Shadows example </title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+		<div id="info">
+		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - VSM Shadows example by <a href="https://github.com/supereggbert">Paul Brunt</a>
+		</div>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import Stats from './jsm/libs/stats.module.js';
+			import { GUI } from './jsm/libs/dat.gui.module.js';
+
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+
+			var camera, scene, renderer, clock, stats;
+			var dirLight, spotLight;
+			var torusKnot, dirGroup;
+
+			init();
+			animate();
+
+			function init() {
+
+				initScene();
+				initMisc();
+				
+				// Init gui
+				var gui = new GUI();
+
+				var config = {
+					'Spotlight Radius': 4,
+					'Directional light Radius': 4,
+				};
+
+				gui.add( config, 'Spotlight Radius' ).min( 2 ).max( 8 ).onChange( function ( value ) {
+
+					spotLight.shadow.radius = value;
+
+				} );
+
+				gui.add( config, 'Directional light Radius' ).min( 2 ).max( 8 ).onChange( function ( value ) {
+
+					dirLight.shadow.radius = value;
+
+				} );
+
+				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, 10, 30 );
+
+				scene = new THREE.Scene();
+				scene.fog = new THREE.Fog( 0xCCCCCC, 50, 100 );
+
+				// Lights
+
+				scene.add( new THREE.AmbientLight( 0x444444 ) );
+
+				spotLight = new THREE.SpotLight( 0x888888 );
+				spotLight.name = 'Spot Light';
+				spotLight.angle = Math.PI / 5;
+				spotLight.penumbra = 0.3;
+				spotLight.position.set( 8, 10, 5 );
+				spotLight.castShadow = true;
+				spotLight.shadow.camera.near = 8;
+				spotLight.shadow.camera.far = 200;
+				spotLight.shadow.mapSize.width = 256;
+				spotLight.shadow.mapSize.height = 256;
+				spotLight.shadow.bias = -0.002;
+				spotLight.shadow.radius = 4;
+				scene.add( spotLight );
+
+
+				dirLight = new THREE.DirectionalLight( 0xFFFFFF, 1 );
+				dirLight.name = 'Dir. Light';
+				dirLight.position.set( 3, 12, 17 );
+				dirLight.castShadow = true;
+				dirLight.shadow.camera.near = 0.1;
+				dirLight.shadow.camera.far = 500;
+				dirLight.shadow.camera.right = 17;
+				dirLight.shadow.camera.left = - 17;
+				dirLight.shadow.camera.top	= 17;
+				dirLight.shadow.camera.bottom = - 17;
+				dirLight.shadow.mapSize.width = 512;
+				dirLight.shadow.mapSize.height = 512;
+				dirLight.shadow.radius = 4;
+				dirLight.shadow.bias = -0.0005;
+				scene.add( dirLight );
+
+				dirGroup = new THREE.Group();
+				dirGroup.add( dirLight );
+				scene.add( dirGroup );
+
+				// Geometry
+
+				var geometry = new THREE.TorusKnotBufferGeometry( 25, 8, 75, 20 );
+				var material = new THREE.MeshPhongMaterial( {
+					color: 0x999999,
+					shininess: 0,
+					specular: 0x222222
+				} );
+
+				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.CylinderBufferGeometry( 0.75, 0.75, 7, 32 );
+
+				var pillar1 = new THREE.Mesh( geometry, material );
+				pillar1.position.set( 10, 3.5, 10 );
+				pillar1.castShadow = true;
+				pillar1.receiveShadow = true;
+
+				var pillar2 = pillar1.clone();
+				pillar2.position.set( 10, 3.5, -10 );
+				var pillar3 = pillar1.clone();
+				pillar3.position.set( -10, 3.5, 10 );
+				var pillar4 = pillar1.clone();
+				pillar4.position.set( -10, 3.5, -10 );
+
+				scene.add( pillar1 );
+				scene.add( pillar2 );
+				scene.add( pillar3 );
+				scene.add( pillar4 );
+
+				var geometry = new THREE.PlaneBufferGeometry( 200, 200 );
+				var material = new THREE.MeshPhongMaterial( {
+					color: 0x999999,
+					shininess: 0,
+					specular: 0x111111
+				} );
+
+				var ground = new THREE.Mesh( geometry, material );
+				ground.rotation.x = -Math.PI/2;
+				ground.scale.multiplyScalar( 3 );
+				ground.castShadow = true;
+				ground.receiveShadow = true;
+				scene.add( ground );
+
+			}
+
+			function initMisc() {
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.shadowMap.enabled = true;
+				renderer.shadowMap.type = THREE.VSMShadowMap;		
+
+				renderer.setClearColor( 0xCCCCCC, 1 );
+
+				// Mouse control
+				var controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 2, 0 );
+				controls.update();
+
+				clock = new THREE.Clock();
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+			}
+
+			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();
+				var time = clock.elapsedTime;
+
+				renderScene();
+
+				torusKnot.rotation.x += 0.25 * delta;
+				torusKnot.rotation.y += 2 * delta;
+				torusKnot.rotation.z += 1 * delta;
+
+				dirGroup.rotation.y += 0.7 * delta;
+				dirLight.position.z = 17 + Math.sin(time*0.001)*5;
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 0
src/constants.d.ts

@@ -33,6 +33,7 @@ export enum ShadowMapType {}
 export const BasicShadowMap: ShadowMapType;
 export const PCFShadowMap: ShadowMapType;
 export const PCFSoftShadowMap: ShadowMapType;
+export const VSMShadowMap: ShadowMapType;
 
 // MATERIAL CONSTANTS
 

+ 1 - 0
src/constants.js

@@ -10,6 +10,7 @@ export var FrontFaceDirectionCCW = 1;
 export var BasicShadowMap = 0;
 export var PCFShadowMap = 1;
 export var PCFSoftShadowMap = 2;
+export var VSMShadowMap = 3;
 export var FrontSide = 0;
 export var BackSide = 1;
 export var DoubleSide = 2;

+ 1 - 0
src/lights/LightShadow.d.ts

@@ -14,6 +14,7 @@ export class LightShadow {
 	radius: number;
 	mapSize: Vector2;
 	map: RenderTarget;
+	mapPass: RenderTarget;
 	matrix: Matrix4;
 
 	copy( source: LightShadow ): this;

+ 1 - 0
src/lights/LightShadow.js

@@ -18,6 +18,7 @@ function LightShadow( camera ) {
 	this.mapSize = new Vector2( 512, 512 );
 
 	this.map = null;
+	this.mapPass = null;
 	this.matrix = new Matrix4();
 
 	this._frustum = new Frustum();

+ 5 - 1
src/renderers/shaders/ShaderChunk.js

@@ -122,6 +122,8 @@ import shadow_frag from './ShaderLib/shadow_frag.glsl.js';
 import shadow_vert from './ShaderLib/shadow_vert.glsl.js';
 import sprite_frag from './ShaderLib/sprite_frag.glsl.js';
 import sprite_vert from './ShaderLib/sprite_vert.glsl.js';
+import vsm_frag from './ShaderLib/vsm_frag.glsl.js';
+import vsm_vert from './ShaderLib/vsm_vert.glsl.js';
 
 export var ShaderChunk = {
 	alphamap_fragment: alphamap_fragment,
@@ -247,5 +249,7 @@ export var ShaderChunk = {
 	shadow_frag: shadow_frag,
 	shadow_vert: shadow_vert,
 	sprite_frag: sprite_frag,
-	sprite_vert: sprite_vert
+	sprite_vert: sprite_vert,
+	vsm_frag: vsm_frag,
+	vsm_vert: vsm_vert
 };

+ 17 - 0
src/renderers/shaders/ShaderChunk/packing.glsl.js

@@ -25,6 +25,23 @@ float unpackRGBAToDepth( const in vec4 v ) {
 	return dot( v, UnpackFactors );
 }
 
+vec4 encodeHalfRGBA ( vec2 v ) {
+	vec4 encoded = vec4( 0.0 );
+	const vec2 offset = vec2( 1.0 / 255.0, 0.0 );
+
+	encoded.xy = vec2( v.x, fract( v.x * 255.0 ) );
+	encoded.xy = encoded.xy - ( encoded.yy * offset );
+
+	encoded.zw = vec2( v.y, fract( v.y * 255.0 ) );
+	encoded.zw = encoded.zw - ( encoded.ww * offset );
+
+	return encoded;
+}
+
+vec2 decodeHalfRGBA( vec4 v ) {
+	return vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );
+}
+
 // NOTE: viewZ/eyeZ is < 0 when in front of the camera per OpenGL conventions
 
 float viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {

+ 32 - 1
src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js

@@ -36,6 +36,33 @@ export default /* glsl */`
 
 	}
 
+	vec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {
+
+		return decodeHalfRGBA( texture2D( shadow, uv ) );
+
+	}
+
+	float VSMShadow (sampler2D shadow, vec2 uv, float compare ){
+
+		float occlusion = 1.0;
+
+		vec2 distribution = texture2DDistribution( shadow, uv );
+
+		float hard_shadow = step( compare , distribution.x ); // Hard Shadow
+
+		if (hard_shadow != 1.0 ) {
+
+			float distance = compare - distribution.x ;
+			float variance = max( 0.00000, distribution.y * distribution.y );
+			float softness_probability = variance / (variance + distance * distance ); // Chebeyshevs inequality
+			softness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 ); // 0.3 reduces light bleed
+			occlusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );
+
+		}
+		return occlusion;
+
+	}
+
 	float texture2DShadowLerp( sampler2D depths, vec2 size, vec2 uv, float compare ) {
 
 		const vec2 offset = vec2( 0.0, 1.0 );
@@ -131,6 +158,10 @@ export default /* glsl */`
 				texture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )
 			) * ( 1.0 / 9.0 );
 
+		#elif defined( SHADOWMAP_TYPE_VSM )
+
+			shadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );
+
 		#else // no percentage-closer filtering:
 
 			shadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );
@@ -229,7 +260,7 @@ export default /* glsl */`
 		// bd3D = base direction 3D
 		vec3 bd3D = normalize( lightToPosition );
 
-		#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT )
+		#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )
 
 			vec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;
 

+ 42 - 0
src/renderers/shaders/ShaderLib/vsm_frag.glsl.js

@@ -0,0 +1,42 @@
+export default /* glsl */`
+uniform sampler2D shadow_pass;
+uniform vec2 resolution;
+uniform float radius;
+
+#include <packing>
+
+void main() {
+
+  float mean = 0.0;
+  float squared_mean = 0.0;
+  
+	// This seems totally useless but it's a crazy work around for a Adreno compiler bug
+	float depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy  ) / resolution ) );
+
+  for ( float i = -1.0; i < 1.0 ; i += SAMPLE_RATE) {
+
+    #ifdef HORIZONAL_PASS
+
+      vec2 distribution = decodeHalfRGBA ( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( i, 0.0 ) * radius ) / resolution ) );
+      mean += distribution.x;
+      squared_mean += distribution.y * distribution.y + distribution.x * distribution.x;
+
+    #else
+
+      float depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0,  i )  * radius ) / resolution ) );
+      mean += depth;
+      squared_mean += depth * depth;
+
+    #endif
+
+  }
+
+  mean = mean * HALF_SAMPLE_RATE;
+  squared_mean = squared_mean * HALF_SAMPLE_RATE;
+
+  float std_dev = pow( squared_mean - mean * mean, 0.5 );
+
+  gl_FragColor = encodeHalfRGBA( vec2( mean, std_dev ) );
+
+}
+`;

+ 9 - 0
src/renderers/shaders/ShaderLib/vsm_vert.glsl.js

@@ -0,0 +1,9 @@
+export default /* glsl */`
+
+void main() {
+
+	gl_Position = vec4( position, 1.0 );
+
+}
+
+`;

+ 5 - 1
src/renderers/webgl/WebGLProgram.js

@@ -5,7 +5,7 @@
 import { WebGLUniforms } from './WebGLUniforms.js';
 import { WebGLShader } from './WebGLShader.js';
 import { ShaderChunk } from '../shaders/ShaderChunk.js';
-import { NoToneMapping, AddOperation, MixOperation, MultiplyOperation, EquirectangularRefractionMapping, CubeRefractionMapping, SphericalReflectionMapping, EquirectangularReflectionMapping, CubeUVRefractionMapping, CubeUVReflectionMapping, CubeReflectionMapping, PCFSoftShadowMap, PCFShadowMap, ACESFilmicToneMapping, CineonToneMapping, Uncharted2ToneMapping, ReinhardToneMapping, LinearToneMapping, GammaEncoding, RGBDEncoding, RGBM16Encoding, RGBM7Encoding, RGBEEncoding, sRGBEncoding, LinearEncoding, LogLuvEncoding } from '../../constants.js';
+import { NoToneMapping, AddOperation, MixOperation, MultiplyOperation, EquirectangularRefractionMapping, CubeRefractionMapping, SphericalReflectionMapping, EquirectangularReflectionMapping, CubeUVRefractionMapping, CubeUVReflectionMapping, CubeReflectionMapping, PCFSoftShadowMap, PCFShadowMap, VSMShadowMap, ACESFilmicToneMapping, CineonToneMapping, Uncharted2ToneMapping, ReinhardToneMapping, LinearToneMapping, GammaEncoding, RGBDEncoding, RGBM16Encoding, RGBM7Encoding, RGBEEncoding, sRGBEncoding, LinearEncoding, LogLuvEncoding } from '../../constants.js';
 
 var programIdCount = 0;
 
@@ -262,6 +262,10 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters,
 
 		shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';
 
+	} else if ( parameters.shadowMapType === VSMShadowMap ) {
+
+		shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM';
+
 	}
 
 	var envMapTypeDefine = 'ENVMAP_TYPE_CUBE';

+ 102 - 9
src/renderers/webgl/WebGLShadowMap.js

@@ -3,10 +3,15 @@
  * @author mrdoob / http://mrdoob.com/
  */
 
-import { FrontSide, BackSide, DoubleSide, RGBAFormat, NearestFilter, PCFShadowMap, RGBADepthPacking, NoBlending } from '../../constants.js';
+import { FrontSide, BackSide, DoubleSide, RGBAFormat, NearestFilter, LinearFilter, PCFShadowMap, VSMShadowMap, RGBADepthPacking, NoBlending } from '../../constants.js';
 import { WebGLRenderTarget } from '../WebGLRenderTarget.js';
 import { MeshDepthMaterial } from '../../materials/MeshDepthMaterial.js';
 import { MeshDistanceMaterial } from '../../materials/MeshDistanceMaterial.js';
+import { ShaderMaterial } from '../../materials/ShaderMaterial.js';
+import { BufferAttribute } from '../../core/BufferAttribute.js';
+import { BufferGeometry } from '../../core/BufferGeometry.js';
+import { Mesh } from '../../objects/Mesh.js';
+import { ShaderChunk } from '../shaders/ShaderChunk.js';
 import { Vector4 } from '../../math/Vector4.js';
 import { Vector2 } from '../../math/Vector2.js';
 import { Frustum } from '../../math/Frustum.js';
@@ -32,6 +37,39 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 	var shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };
 
+	var shadowMaterialVertical = new ShaderMaterial( {
+
+		defines: {
+			SAMPLE_RATE: 2.0 / 8.0,
+			HALF_SAMPLE_RATE: 1.0 / 8.0
+		},
+
+		uniforms: {
+			shadow_pass: { value: null },
+			resolution: { value: new Vector2() },
+			radius: { value: 4.0 }
+		},
+
+		vertexShader: ShaderChunk.vsm_vert,
+
+		fragmentShader: ShaderChunk.vsm_frag
+
+	} );
+
+	var shadowMaterialHorizonal = shadowMaterialVertical.clone();
+	shadowMaterialHorizonal.defines.HORIZONAL_PASS = 1;
+
+	var fullScreenTri = new BufferGeometry();
+	fullScreenTri.addAttribute(
+		"position",
+		new BufferAttribute(
+			new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ),
+			3
+		)
+	);
+
+	var fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical );
+
 	// init
 
 	for ( var i = 0; i !== _NumberOfMaterialVariants; ++ i ) {
@@ -133,6 +171,19 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 			}
 
+			if ( shadow.map === null && ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+
+				var pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
+
+				shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+				shadow.map.texture.name = light.name + ".shadowMap";
+
+				shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+
+				shadow.camera.updateProjectionMatrix();
+
+			}
+
 			if ( shadow.map === null ) {
 
 				var pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };
@@ -166,7 +217,15 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 				_frustum = shadow.getFrustum();
 
-				renderObject( scene, camera, shadow.camera, light );
+				renderObject( scene, camera, shadow.camera, light, this.type );
+
+			}
+
+			// do blur pass for VSM
+
+			if ( ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+
+				VSMPass( shadow, camera );
 
 			}
 
@@ -178,7 +237,31 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 	};
 
-	function getDepthMaterial( object, material, light, shadowCameraNear, shadowCameraFar ) {
+	function VSMPass( shadow, camera ) {
+
+		var geometry = _objects.update( fullScreenMesh );
+
+		// vertical pass
+
+		shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture;
+		shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize;
+		shadowMaterialVertical.uniforms.radius.value = shadow.radius;
+		_renderer.setRenderTarget( shadow.mapPass );
+		_renderer.clear();
+		_renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null );
+
+		// horizonal pass
+
+		shadowMaterialHorizonal.uniforms.shadow_pass.value = shadow.mapPass.texture;
+		shadowMaterialHorizonal.uniforms.resolution.value = shadow.mapSize;
+		shadowMaterialHorizonal.uniforms.radius.value = shadow.radius;
+		_renderer.setRenderTarget( shadow.map );
+		_renderer.clear();
+		_renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizonal, fullScreenMesh, null );
+
+	}
+
+	function getDepthMaterial( object, material, light, shadowCameraNear, shadowCameraFar, type ) {
 
 		var geometry = object.geometry;
 
@@ -267,7 +350,15 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 		result.visible = material.visible;
 		result.wireframe = material.wireframe;
 
-		result.side = ( material.shadowSide != null ) ? material.shadowSide : shadowSide[ material.side ];
+		if ( type === VSMShadowMap ) {
+
+			result.side = ( material.shadowSide != null ) ? material.shadowSide : material.side;
+
+		} else {
+
+			result.side = ( material.shadowSide != null ) ? material.shadowSide : shadowSide[ material.side ];
+
+		}
 
 		result.clipShadows = material.clipShadows;
 		result.clippingPlanes = material.clippingPlanes;
@@ -288,7 +379,7 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 	}
 
-	function renderObject( object, camera, shadowCamera, light ) {
+	function renderObject( object, camera, shadowCamera, light, type ) {
 
 		if ( object.visible === false ) return;
 
@@ -296,7 +387,7 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 		if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {
 
-			if ( object.castShadow && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
+			if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
 
 				object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
 
@@ -314,7 +405,8 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 						if ( groupMaterial && groupMaterial.visible ) {
 
-							var depthMaterial = getDepthMaterial( object, groupMaterial, light, shadowCamera.near, shadowCamera.far );
+							var depthMaterial = getDepthMaterial( object, groupMaterial, light, shadowCamera.near, shadowCamera.far, type );
+
 							_renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );
 
 						}
@@ -323,7 +415,8 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 				} else if ( material.visible ) {
 
-					var depthMaterial = getDepthMaterial( object, material, light, shadowCamera.near, shadowCamera.far );
+					var depthMaterial = getDepthMaterial( object, material, light, shadowCamera.near, shadowCamera.far, type );
+
 					_renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );
 
 				}
@@ -336,7 +429,7 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 		for ( var i = 0, l = children.length; i < l; i ++ ) {
 
-			renderObject( children[ i ], camera, shadowCamera, light );
+			renderObject( children[ i ], camera, shadowCamera, light, type );
 
 		}