Ver Fonte

Recover and clean up WebGLDeferredRenderer (#9772)

Takahiro há 8 anos atrás
pai
commit
c75213d59b

+ 1 - 0
examples/files.js

@@ -10,6 +10,7 @@ var files = {
 		"webgl_clipping",
 		"webgl_clipping_advanced",
 		"webgl_decals",
+		"webgl_deferred_animation",
 		"webgl_depth_texture",
 		"webgl_effects_anaglyph",
 		"webgl_effects_parallaxbarrier",

+ 1574 - 0
examples/js/renderers/WebGLDeferredRenderer.js

@@ -0,0 +1,1574 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author MPanknin / http://www.redplant.de/
+ * @author takahiro / https://github.com/takahirox
+ *
+ * Dependencies
+ *  - THREE.CopyShader
+ *  - THREE.RenderPass
+ *  - THREE.ShaderPass
+ *  - THREE.EffectComposer
+ *  - THREE.FXAAShader
+ *
+ * TODO
+ *  - reuse existing glsl
+ *  - shared material
+ *  - shadow
+ *  - optimization
+ *  - MRT (when it's available on Three.js)
+ *  - AmbientLight
+ *  - HemisphereLight
+ *  - PointLight (distance < 0)
+ *  - morphNormals
+ *  - BumpMap
+ *  - ToneMap
+ *  - envMap
+ *  - wrapAround
+ *  - addEffect
+ */
+
+THREE.WebGLDeferredRenderer = function ( parameters ) {
+
+	parameters = parameters || {};
+
+	// private properties
+
+	var _this = this;
+
+	var _gl;
+
+	var _width, _height;
+
+	var _compColor, _compNormalDepth, _compLight, _compFinal;
+	var _passColor, _passNormalDepth, _passLight, _passLightFullscreen, _passFinal, _passForward, _passCopy, _passFXAA;
+
+	var _lightScene, _lightFullscreenScene;
+
+	var _antialias = false, _hasTransparentObject = false;
+
+	var _invisibleMaterial = new THREE.ShaderMaterial( { visible: false } );
+
+	var _tmpVector3 = new THREE.Vector3();
+
+	// external properties
+
+	this.renderer;
+	this.domElement;
+
+	this.forwardRendering = false;  // for debug
+
+	// private methods
+
+	var init = function ( parameters ) {
+
+		_this.renderer = parameters.renderer !== undefined ? parameters.renderer : new THREE.WebGLRenderer( { antialias: false } );
+		_this.domElement = _this.renderer.domElement;
+
+		_gl = _this.renderer.context;
+
+		_width = parameters.width !== undefined ? parameters.width : _this.renderer.getSize().width;
+		_height = parameters.height !== undefined ? parameters.height : _this.renderer.getSize().height;
+
+		var antialias = parameters.antialias !== undefined ? parameters.antialias : false;
+
+		initPassFXAA();
+		initPassNormalDepth();
+		initPassColor();
+		initPassLight();
+		initPassFinal();
+
+		_this.setSize( _width, _height );
+		_this.setAntialias( antialias );
+
+	};
+
+	var initPassFXAA = function () {
+
+		_passFXAA = new THREE.ShaderPass( THREE.FXAAShader );
+
+	};
+
+	var initPassNormalDepth = function () {
+
+		_passNormalDepth = new THREE.RenderPass();
+		_passNormalDepth.clear = true;
+
+		var rt = new THREE.WebGLRenderTarget( _width, _height, {
+			minFilter: THREE.NearestFilter,
+			magFilter: THREE.NearestFilter,
+			format: THREE.RGBAFormat,
+			type: THREE.FloatType,
+			stencilBuffer: true,
+			depthTexture: new THREE.DepthTexture(
+				_width,
+				_height,
+				THREE.UnsignedInt248Type,
+				undefined,
+				undefined,
+				undefined,
+				undefined,
+				undefined,
+				undefined,
+				THREE.DepthStencilFormat
+			)
+		} );
+
+		rt.texture.generateMipamps = false;
+
+		_compNormalDepth = new THREE.EffectComposer( _this.renderer, rt );
+		_compNormalDepth.addPass( _passNormalDepth );
+
+	};
+
+	var initPassColor = function () {
+
+		_passColor = new THREE.RenderPass();
+		_passColor.clear = true;
+
+		var rt = new THREE.WebGLRenderTarget( _width, _height, {
+			minFilter: THREE.NearestFilter,
+			magFilter: THREE.NearestFilter,
+			format: THREE.RGBAFormat,
+			type: THREE.FloatType,
+			depthTexture: _compNormalDepth.renderTarget2.depthTexture
+		} );
+
+		rt.texture.generateMipamps = false;
+
+		_compColor = new THREE.EffectComposer( _this.renderer, rt );
+		_compColor.addPass( _passColor );
+
+	};
+
+	var initPassLight = function () {
+
+		_passLightFullscreen = new THREE.RenderPass();
+		_passLightFullscreen.clear = true;
+		_passLightFullscreen.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 );
+
+		_passLight = new THREE.RenderPass();
+		_passLight.clear = false;
+
+		var rt = new THREE.WebGLRenderTarget( _width, _height, {
+			minFilter: THREE.NearestFilter,
+			magFilter: THREE.NearestFilter,
+			format: THREE.RGBAFormat,
+			type: THREE.FloatType,
+			depthTexture: _compNormalDepth.renderTarget2.depthTexture
+		} );
+
+		rt.texture.generateMipamps = false;
+
+		_compLight = new THREE.EffectComposer( _this.renderer, rt );
+		_compLight.addPass( _passLightFullscreen );
+		_compLight.addPass( _passLight );
+
+	};
+
+	var initPassFinal = function () {
+
+		_passFinal = new THREE.ShaderPass( THREE.ShaderDeferred[ 'composite' ] );
+		_passFinal.clear = true;
+		_passFinal.uniforms.samplerLight.value = _compLight.renderTarget2.texture;
+		_passFinal.material.blending = THREE.NoBlending;
+		_passFinal.material.depthWrite = false;
+		_passFinal.material.depthTest = false;
+
+		_passForward = new THREE.RenderPass();
+		_passForward.clear = false;
+
+		_passCopy = new THREE.ShaderPass( THREE.CopyShader );
+
+		var rt = new THREE.WebGLRenderTarget( _width, _height, {
+			minFilter: THREE.NearestFilter,
+			magFilter: THREE.LinearFilter,
+			format: THREE.RGBFormat,
+			type: THREE.UnsignedByteType,
+			depthTexture: _compNormalDepth.renderTarget2.depthTexture
+		} );
+
+		rt.texture.generateMipamps = false;
+
+		_compFinal = new THREE.EffectComposer( _this.renderer, rt );
+		_compFinal.addPass( _passFinal );
+		_compFinal.addPass( _passForward );
+		_compFinal.addPass( _passCopy );
+		_compFinal.addPass( _passFXAA );
+
+	};
+
+	var initLightScene = function ( scene ) {
+
+		if ( scene.userData.lightScene === undefined ) {
+
+			var lightScene = new THREE.Scene();
+			lightScene.userData.lights = {};
+
+			scene.userData.lightScene = lightScene;
+
+		}
+
+		if ( scene.userData.lightFullscreenScene === undefined ) {
+
+			var lightScene = new THREE.Scene();
+			lightScene.userData.lights = {};
+
+			lightScene.userData.emissiveLight = createDeferredEmissiveLight();
+			lightScene.add( lightScene.userData.emissiveLight );
+
+			scene.userData.lightFullscreenScene = lightScene;
+
+		}
+
+		_lightScene = scene.userData.lightScene;
+		_lightFullscreenScene = scene.userData.lightFullscreenScene;
+
+	};
+
+	var initDeferredProperties = function ( object ) {
+
+		if ( object.userData.deferredInitialized === true ) return;
+
+		if ( object.material ) initDeferredMaterials( object );
+
+		if ( object instanceof THREE.Light ) initDeferredLight( object );
+
+		object.userData.deferredInitialized = true;
+
+	};
+
+	var initDeferredMaterials = function ( object ) {
+
+		if ( object.material instanceof THREE.MultiMaterial ) {
+
+			var normalDepthMaterials = [];
+			var colorMaterials = [];
+			var forwardMaterials = [];
+
+			var materials = object.material.materials;
+
+			for ( var i = 0, il = materials.length; i < il; i ++ ) {
+
+				normalDepthMaterials.push( createDeferredNormalDepthMaterial( materials[ i ] ) );
+				colorMaterials.push( createDeferredColorMaterial( materials[ i ] ) );
+				forwardMaterials.push( _invisibleMaterial );
+
+			}
+
+			object.userData.normalDepthMaterial = new THREE.MultiMaterial( normalDepthMaterials );
+			object.userData.colorMaterial = new THREE.MultiMaterial( colorMaterials );
+			object.userData.forwardMaterial = new THREE.MultiMaterial( forwardMaterials );
+
+		} else {
+
+			object.userData.normalDepthMaterial = createDeferredNormalDepthMaterial( object.material );
+			object.userData.colorMaterial = createDeferredColorMaterial( object.material );
+
+		}
+
+	};
+
+	var createDeferredNormalDepthMaterial = function ( originalMaterial ) {
+
+		var shader = THREE.ShaderDeferred[ 'normalDepth' ];
+
+		var material = new THREE.ShaderMaterial( {
+			uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
+			fragmentShader: shader.fragmentShader,
+			vertexShader: shader.vertexShader,
+			blending: THREE.NoBlending
+		} );
+
+		return material;
+
+	};
+
+	var updateDeferredNormalDepthMaterial = function ( material, originalMaterial ) {
+
+		if ( originalMaterial.skinning !== undefined ) material.skinning = originalMaterial.skinning;
+		if ( originalMaterial.morphTargets !== undefined ) material.morphTargets = originalMaterial.morphTargets;
+
+		if ( originalMaterial.visible === true ) {
+
+			material.visible = ! originalMaterial.transparent;
+
+		} else {
+
+			material.visible = false;
+
+		}
+
+	};
+
+	var createDeferredColorMaterial = function ( originalMaterial ) {
+
+		var shader = THREE.ShaderDeferred[ 'color' ];
+
+		var material = new THREE.ShaderMaterial( {
+			uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
+			fragmentShader: shader.fragmentShader,
+			vertexShader: shader.vertexShader,
+			blending: THREE.NoBlending
+		} );
+
+		if ( originalMaterial.map !== undefined ) material.map = originalMaterial.map;
+
+		return material;
+
+	};
+
+	var updateDeferredColorMaterial = function ( material, originalMaterial ) {
+
+		var diffuse, emissive;
+
+		if ( originalMaterial instanceof THREE.MeshBasicMaterial ) {
+
+			emissive = originalMaterial.color;
+
+		} else {
+
+			diffuse = originalMaterial.color;
+			emissive = originalMaterial.emissive;
+
+		}
+
+		var specular = originalMaterial.specular;
+		var shininess = originalMaterial.shininess;
+		var map = originalMaterial.map;
+
+		if ( ( originalMaterial instanceof THREE.ShaderMaterial ) && originalMaterial.uniforms !== undefined ) {
+
+			if ( diffuse === undefined && originalMaterial.uniforms.diffuse !== undefined ) diffuse = originalMaterial.uniforms.diffuse.value;
+			if ( emissive === undefined && originalMaterial.uniforms.emissive !== undefined ) emissive = originalMaterial.uniforms.emissive.value;
+			if ( specular === undefined && originalMaterial.uniforms.specular !== undefined ) specular = originalMaterial.uniforms.specular.value;
+			if ( shininess === undefined && originalMaterial.uniforms.shininess !== undefined ) shininess = originalMaterial.uniforms.shininess.value;
+
+		}
+
+		if ( diffuse !== undefined ) material.uniforms.diffuse.value.copy( diffuse );
+		if ( emissive !== undefined ) material.uniforms.emissive.value.copy( emissive );
+		if ( specular !== undefined ) material.uniforms.specular.value.copy( specular );
+		if ( shininess !== undefined ) material.uniforms.shininess.value = shininess;
+
+		if ( map !== undefined ) {
+
+			material.map = map;
+			material.uniforms.map.value = map;
+
+		}
+
+		if ( originalMaterial.skinning !== undefined ) material.skinning = originalMaterial.skinning;
+		if ( originalMaterial.morphTargets !== undefined ) material.morphTargets = originalMaterial.morphTargets;
+
+		if ( originalMaterial.visible === true ) {
+
+			material.visible = ! originalMaterial.transparent;
+
+		} else {
+
+			material.visible = false;
+
+		}
+
+	};
+
+	var getForwardRenderingMaterial = function ( originalMaterial ) {
+
+		if ( originalMaterial.transparent === true && originalMaterial.visible === true ) {
+
+			return originalMaterial
+
+		} else {
+
+			return _invisibleMaterial;
+
+		}
+
+	};
+
+	var initDeferredLight = function ( light ) {
+
+		var deferredLight;
+
+		if ( light instanceof THREE.PointLight ) {
+
+			deferredLight = createDeferredPointLight( light );
+
+		} else if ( light instanceof THREE.SpotLight ) {
+
+			deferredLight = createDeferredSpotLight( light );
+
+		} else if ( light instanceof THREE.DirectionalLight ) {
+
+			deferredLight = createDeferredDirectionalLight( light );
+
+		}
+
+		light.userData.deferredLight = deferredLight;
+
+	};
+
+	var updateDeferredLight = function ( light, camera ) {
+
+		var originalLight = light.userData.originalLight;
+
+		if ( originalLight instanceof THREE.PointLight ) {
+
+			updateDeferredPointLight( light, camera );
+
+		} else if ( originalLight instanceof THREE.SpotLight ) {
+
+			updateDeferredSpotLight( light, camera );
+
+		} else if ( originalLight instanceof THREE.DirectionalLight ) {
+
+			updateDeferredDirectionalLight( light, camera );
+
+		}
+
+	};
+
+	var createDeferredEmissiveLight = function () {
+
+		var shader = THREE.ShaderDeferred[ 'emissiveLight' ];
+
+		var material = new THREE.ShaderMaterial( {
+			uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
+			vertexShader: shader.vertexShader,
+			fragmentShader: shader.fragmentShader,
+			blending: THREE.NoBlending,
+			depthWrite: false
+		} );
+
+		material.uniforms.samplerColor.value = _compColor.renderTarget2.texture;
+
+		var geometry = new THREE.PlaneBufferGeometry( 2, 2 );
+		var mesh = new THREE.Mesh( geometry, material );
+
+		return mesh;
+
+	};
+
+	var updateDeferredEmissiveLight = function ( light, camera ) {
+
+		var uniforms = light.material.uniforms;
+
+		uniforms.viewWidth.value = _width;
+		uniforms.viewHeight.value = _height;
+
+	};
+
+	var createDeferredPointLight = function ( light ) {
+
+		var shader = THREE.ShaderDeferred[ 'pointLight' ];
+
+		var material = new THREE.ShaderMaterial( {
+			uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
+			vertexShader: shader.vertexShader,
+			fragmentShader: shader.fragmentShader,
+			transparent: true,
+			side: THREE.BackSide,
+			blending: THREE.AdditiveBlending,
+			depthWrite: false,
+			depthFunc: THREE.GreaterEqualDepth
+		} );
+
+		material.uniforms.samplerNormalDepth.value = _compNormalDepth.renderTarget2.texture;
+		material.uniforms.samplerColor.value = _compColor.renderTarget2.texture;
+
+		var geometry = new THREE.SphereGeometry( 1, 16, 8 );
+		var mesh = new THREE.Mesh( geometry, material );
+
+		mesh.userData.originalLight = light;
+
+		return mesh;
+
+	};
+
+	var updateDeferredPointLight = function ( light, camera ) {
+
+		var originalLight = light.userData.originalLight;
+		var distance = originalLight.distance;
+		var uniforms = light.material.uniforms;
+
+		uniforms.matProjInverse.value.getInverse( camera.projectionMatrix );
+		uniforms.viewWidth.value = _width;
+		uniforms.viewHeight.value = _height;
+		uniforms.lightColor.value.copy( originalLight.color );
+
+		if ( distance > 0 ) {
+
+			light.scale.set( 1, 1, 1 ).multiplyScalar( distance );
+			uniforms.lightRadius.value = distance;
+			uniforms.lightIntensity.value = originalLight.intensity;
+			uniforms.lightPositionVS.value.setFromMatrixPosition( originalLight.matrixWorld ).applyMatrix4( camera.matrixWorldInverse );
+			light.position.setFromMatrixPosition( originalLight.matrixWorld );
+
+		} else {
+
+			uniforms.lightRadius.value = Infinity;
+
+		}
+
+	};
+
+	var createDeferredSpotLight = function ( light ) {
+
+		var shader = THREE.ShaderDeferred[ 'spotLight' ];
+
+		var material = new THREE.ShaderMaterial( {
+			uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
+			vertexShader: shader.vertexShader,
+			fragmentShader: shader.fragmentShader,
+			transparent: true,
+			blending: THREE.AdditiveBlending,
+			depthWrite: false,
+			depthTest: false
+		} );
+
+		material.uniforms.samplerNormalDepth.value = _compNormalDepth.renderTarget2.texture;
+		material.uniforms.samplerColor.value = _compColor.renderTarget2.texture;
+
+		var geometry = new THREE.PlaneBufferGeometry( 2, 2 );
+		var mesh = new THREE.Mesh( geometry, material );
+
+		mesh.userData.originalLight = light;
+
+		return mesh;
+
+	};
+
+	var updateDeferredSpotLight = function ( light, camera ) {
+
+		var originalLight = light.userData.originalLight;
+		var uniforms = light.material.uniforms;
+
+		uniforms.matProjInverse.value.getInverse( camera.projectionMatrix );
+		uniforms.viewWidth.value = _width;
+		uniforms.viewHeight.value = _height;
+		uniforms.lightAngle.value = originalLight.angle;
+		uniforms.lightColor.value.copy( originalLight.color );
+		uniforms.lightIntensity.value = originalLight.intensity;
+		uniforms.lightPositionVS.value.setFromMatrixPosition( originalLight.matrixWorld ).applyMatrix4( camera.matrixWorldInverse );
+
+		var vec = uniforms.lightDirectionVS.value;
+		var vec2 = _tmpVector3;
+
+		vec.setFromMatrixPosition( originalLight.matrixWorld );
+		vec2.setFromMatrixPosition( originalLight.target.matrixWorld );
+		vec.sub( vec2 ).normalize().transformDirection( camera.matrixWorldInverse );
+
+	};
+
+	var createDeferredDirectionalLight = function ( light ) {
+
+		var shader = THREE.ShaderDeferred[ 'directionalLight' ];
+
+		var material = new THREE.ShaderMaterial( {
+			uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
+			vertexShader: shader.vertexShader,
+			fragmentShader: shader.fragmentShader,
+			transparent: true,
+			blending: THREE.AdditiveBlending,
+			depthWrite: false,
+			depthTest: false
+		} );
+
+		material.uniforms.samplerNormalDepth.value = _compNormalDepth.renderTarget2.texture;
+		material.uniforms.samplerColor.value = _compColor.renderTarget2.texture;
+
+		var geometry = new THREE.PlaneBufferGeometry( 2, 2 );
+		var mesh = new THREE.Mesh( geometry, material );
+
+		mesh.userData.originalLight = light;
+
+		return mesh;
+
+	};
+
+	var updateDeferredDirectionalLight = function ( light, camera ) {
+
+		var originalLight = light.userData.originalLight;
+		var uniforms = light.material.uniforms;
+
+		uniforms.matProjInverse.value.getInverse( camera.projectionMatrix );
+		uniforms.viewWidth.value = _width;
+		uniforms.viewHeight.value = _height;
+		uniforms.lightColor.value.copy( originalLight.color );
+		uniforms.lightIntensity.value = originalLight.intensity;
+
+		var vec = uniforms.lightDirectionVS.value;
+		var vec2 = _tmpVector3;
+
+		vec.setFromMatrixPosition( originalLight.matrixWorld );
+		vec2.setFromMatrixPosition( originalLight.target.matrixWorld );
+		vec.sub( vec2 ).normalize().transformDirection( camera.matrixWorldInverse );
+
+	};
+
+	var setMaterialNormalDepth = function ( object ) {
+
+		if ( object.material === undefined ) return;
+
+		if ( object.userData.normalDepthMaterial !== undefined ) {
+
+			object.material = object.userData.normalDepthMaterial;
+
+			if ( object.userData.originalMaterial instanceof THREE.MultiMaterial ) {
+
+				for ( var i = 0, il = object.userData.originalMaterial.materials.length; i < il; i ++ ) {
+
+					updateDeferredNormalDepthMaterial( object.material.materials[ i ], object.userData.originalMaterial.materials[ i ] );
+
+				}
+
+			} else {
+
+				updateDeferredNormalDepthMaterial( object.material, object.userData.originalMaterial );
+
+			}
+
+		}
+
+	};
+
+	var setMaterialColor = function ( object ) {
+
+		if ( object.material === undefined ) return;
+
+		if ( object.userData.colorMaterial !== undefined ) {
+
+			object.material = object.userData.colorMaterial;
+
+			if ( object.userData.originalMaterial instanceof THREE.MultiMaterial ) {
+
+				for ( var i = 0, il = object.userData.originalMaterial.materials.length; i < il; i ++ ) {
+
+					updateDeferredColorMaterial( object.material.materials[ i ], object.userData.originalMaterial.materials[ i ] );
+
+				}
+
+			} else {
+
+				updateDeferredColorMaterial( object.material, object.userData.originalMaterial );
+
+			}
+
+		}
+
+	};
+
+	var setMaterialForwardRendering = function ( object ) {
+
+		if ( object.material === undefined ) return;
+
+		if ( object.userData.originalMaterial instanceof THREE.MultiMaterial ) {
+
+			object.material = object.userData.forwardMaterial;
+
+			for ( var i = 0, il = object.userData.originalMaterial.materials.length; i < il; i ++ ) {
+
+				object.material.materials[ i ] = getForwardRenderingMaterial( object.userData.originalMaterial.materials[ i ] );
+
+			}
+
+		} else {
+
+			object.material = getForwardRenderingMaterial( object.userData.originalMaterial );
+
+		}
+
+	};
+
+	var saveOriginalMaterialAndCheckTransparency = function ( object ) {
+
+		if ( object.material !== undefined ) {
+
+			object.userData.originalMaterial = object.material;
+
+			if ( object.material instanceof THREE.MultiMaterial ) {
+
+				for ( var i = 0, il = object.material.materials.length; i < il; i ++ ) {
+
+					if ( object.material.materials[ i ].transparent === true ) _hasTransparentObject = true;
+
+				}
+
+			} else {
+
+				if ( object.material.transparent === true ) _hasTransparentObject = true;
+
+			}
+
+		}
+
+	};
+
+	var restoreOriginalMaterial = function ( object ) {
+
+		if ( object.userData.originalMaterial !== undefined ) object.material = object.userData.originalMaterial;
+
+	};
+
+	var enableCompositePasses = function () {
+
+		if ( _hasTransparentObject ) {
+
+			if ( _antialias ) {
+
+				_passFinal.renderToScreen = false;
+
+				_passForward.renderToScreen = false;
+				_passForward.enabled = true;
+
+				_passCopy.renderToScreen = false;
+				_passCopy.enabled = false;
+
+				_passFXAA.renderToScreen = true;
+				_passFXAA.enabled = true;
+
+
+			} else {
+
+				_passFinal.renderToScreen = false;
+
+				_passForward.renderToScreen = false;
+				_passForward.enabled = true;
+
+				_passCopy.renderToScreen = true;
+				_passCopy.enabled = true;
+
+				_passFXAA.renderToScreen = false;
+				_passFXAA.enabled = false;
+
+			}
+
+		} else {
+
+			if ( _antialias ) {
+
+				_passFinal.renderToScreen = false;
+
+				_passForward.renderToScreen = false;
+				_passForward.enabled = false;
+
+				_passCopy.renderToScreen = false;
+				_passCopy.enabled = false;
+
+				_passFXAA.renderToScreen = true;
+				_passFXAA.enabled = true;
+
+			} else {
+
+				_passFinal.renderToScreen = true;
+
+				_passForward.renderToScreen = false;
+				_passForward.enabled = false;
+
+				_passCopy.renderToScreen = false;
+				_passCopy.enabled = false;
+
+				_passFXAA.renderToScreen = false;
+				_passFXAA.enabled = false;
+
+			}
+
+		}
+
+	};
+
+	var addDeferredLightsToLightScene = function ( object ) {
+
+		var light = object.userData.deferredLight;
+
+		if ( light !== undefined ) {
+
+			var originalLight = light.userData.originalLight;
+			var scene;
+
+			if ( originalLight instanceof THREE.PointLight ) {
+
+				scene = _lightScene;
+
+			} else {
+
+				scene = _lightFullscreenScene;
+
+			}
+
+			var lights = scene.userData.lights;
+
+			if ( lights[ light.uuid ] === undefined ) {
+
+				scene.add( light );
+
+				lights[ light.uuid ] = {
+					light: light,
+					found: true
+				};
+
+			}
+
+			lights[ light.uuid ].found = true;
+
+
+		}
+
+	};
+
+	var updateDeferredLightsInLightScene = function ( scene, camera ) {
+
+		var lights = scene.userData.lights;
+		var keys = Object.keys( lights );
+
+		for ( var i = 0, il = keys.length; i < il; i ++ ) {
+
+			var key = keys[ i ];
+
+			if ( lights[ key ].found === false ) {
+
+				scene.remove( lights[ key ].light );
+				delete lights[ key ];
+
+			} else {
+
+				updateDeferredLight( lights[ key ].light, camera );
+				lights[ key ].found = false;
+
+			}
+
+		}
+
+	};
+
+	/*
+	 * 1) g-buffer normal + depth pass
+	 *
+	 * RGB: normal
+	 *   A: depth
+	 */
+
+	var renderNormalDepth = function ( scene, camera ) {
+
+		scene.traverse( setMaterialNormalDepth );
+
+		_passNormalDepth.scene = scene;
+		_passNormalDepth.camera = camera;
+
+		_this.renderer.autoClearDepth = true;
+		_this.renderer.autoClearStencil = true;
+
+		_gl.enable( _gl.STENCIL_TEST );
+		_gl.stencilFunc( _gl.ALWAYS, 1, 0xffffffff );
+		_gl.stencilOp( _gl.REPLACE, _gl.REPLACE, _gl.REPLACE );
+
+		_compNormalDepth.render();
+
+	};
+
+	/*
+	 * 2) g-buffer color pass
+	 *
+	 * R: diffuse
+	 * G: emissive
+	 * B: specular
+	 * A: shininess
+	 */
+
+	var renderColor = function ( scene, camera ) {
+
+		scene.traverse( setMaterialColor );
+
+		_passColor.scene = scene;
+		_passColor.camera = camera;
+
+		_this.renderer.autoClearDepth = false;
+		_this.renderer.autoClearStencil = false;
+
+		_gl.stencilFunc( _gl.EQUAL, 1, 0xffffffff );
+		_gl.stencilOp( _gl.KEEP, _gl.KEEP, _gl.KEEP );
+
+		_compColor.render();
+
+	};
+
+	/*
+	 * 3) light pass
+	 *
+	 * optimization:
+	 * Here renders PointLight only back face with stencil test.
+	 */
+
+	var renderLight = function ( scene, camera ) {
+
+		updateDeferredEmissiveLight( _lightFullscreenScene.userData.emissiveLight, camera );
+
+		scene.traverse( addDeferredLightsToLightScene );
+
+		updateDeferredLightsInLightScene( _lightScene, camera );
+		updateDeferredLightsInLightScene( _lightFullscreenScene, camera );
+
+		_passLight.scene = _lightScene;
+		_passLight.camera = camera;
+
+		_passLightFullscreen.scene = _lightFullscreenScene;
+
+		_this.renderer.autoClearDepth = false;
+		_this.renderer.autoClearStencil = false;
+
+		_compLight.render();
+
+		_gl.disable( _gl.STENCIL_TEST );
+
+	};
+
+	/*
+	 * 4) composite pass
+	 *
+	 * transparency handling:
+	 * If there's any transparent objects, here renders them on the deferred rendering result
+	 * with forward rendering. This may be the easist way but heavy.
+	 * We should consider any better ways someday.
+	 *
+	 * antialias handling:
+	 * Here uses postprocessing FXAA for antialias.
+	 */
+
+	var renderComposite = function ( scene, camera ) {
+
+		if ( _hasTransparentObject ) {
+
+			scene.traverse( setMaterialForwardRendering );
+
+			_passForward.scene = scene;
+			_passForward.camera = camera;
+
+		}
+
+		enableCompositePasses();
+
+		_this.renderer.autoClearDepth = false;
+		_this.renderer.autoClearStencil = false;
+
+		_compFinal.render();
+
+	};
+
+	// external APIs
+
+	this.setSize = function ( width, height ) {
+
+		_width = width;
+		_height = height;
+
+		this.renderer.setSize( _width, _height );
+
+		_compNormalDepth.setSize( _width, _height );
+		_compColor.setSize( _width, _height );
+		_compLight.setSize( _width, _height );
+		_compFinal.setSize( _width, _height );
+
+		_compNormalDepth.renderTarget2.depthTexture.image.width = _width;
+		_compNormalDepth.renderTarget2.depthTexture.image.height = _height;
+		_compNormalDepth.renderTarget2.depthTexture.needsUpdate = true;
+
+		_passFXAA.uniforms.resolution.value.set( 1 / _width, 1 / _height );
+
+	};
+
+	this.setAntialias = function ( enabled ) {
+
+		_antialias = enabled;
+
+	};
+
+	this.render = function ( scene, camera ) {
+
+		// for debug to compare with normal forward rendering
+
+		if ( this.forwardRendering ) {
+
+			this.renderer.render( scene, camera );
+			return;
+
+		}
+
+		var tmpSceneAutoUpdate = scene.autoUpdate;
+		var tmpAutoClearColor = this.renderer.autoClearColor;
+		var tmpAutoClearDepth = this.renderer.autoClearDepth;
+		var tmpAutoClearStencil = this.renderer.autoClearStencil;
+
+		initLightScene( scene );
+
+		scene.autoUpdate = false;
+		scene.updateMatrixWorld();
+
+		_hasTransparentObject = false;
+
+		scene.traverse( initDeferredProperties );
+		scene.traverse( saveOriginalMaterialAndCheckTransparency );
+
+		renderNormalDepth( scene, camera );
+		renderColor( scene, camera );
+		renderLight( scene, camera );
+		renderComposite( scene, camera );
+
+		scene.traverse( restoreOriginalMaterial );
+
+		scene.autoUpdate = tmpSceneAutoUpdate;
+		this.renderer.autoClearColor = tmpAutoClearColor;
+		this.renderer.autoClearDepth = tmpAutoClearDepth;
+		this.renderer.autoClearStencil = tmpAutoClearStencil;
+
+	};
+
+	// initialize
+
+	init( parameters );
+
+};
+
+THREE.DeferredShaderChunk = {
+
+	packVector3: [
+
+		"float vec3_to_float( vec3 data ) {",
+
+			"const float unit = 255.0/256.0;",
+			"highp float compressed = fract( data.x * unit ) + floor( data.y * unit * 255.0 ) + floor( data.z * unit * 255.0 ) * 255.0;",
+			"return compressed;",
+
+		"}"
+
+	].join( "\n" ),
+
+	unpackFloat: [
+
+		"vec3 float_to_vec3( float data ) {",
+
+			"const float unit = 255.0;",
+			"vec3 uncompressed;",
+			"uncompressed.x = fract( data );",
+			"float zInt = floor( data / unit );",
+			"uncompressed.z = fract( zInt / unit );",
+			"uncompressed.y = fract( floor( data - ( zInt * unit ) ) / unit );",
+			"return uncompressed;",
+
+		"}"
+
+	].join( "\n" ),
+
+	computeTextureCoord: [
+
+		"vec2 texCoord = gl_FragCoord.xy / vec2( viewWidth, viewHeight );"
+
+	].join( "\n" ),
+
+	packNormalDepth: [
+
+		"vec4 packedNormalDepth;",
+		"packedNormalDepth.xyz = normal * 0.5 + 0.5;",
+		"packedNormalDepth.w = position.z / position.w;",
+
+	].join( "\n" ),
+
+	unpackNormalDepth: [
+
+		"vec4 normalDepthMap = texture2D( samplerNormalDepth, texCoord );",
+		"float depth = normalDepthMap.w;",
+
+		"if ( depth == 0.0 ) discard;",
+
+		"vec3 normal = normalDepthMap.xyz * 2.0 - 1.0;"
+
+	].join( "\n" ),
+
+	packColor: [
+
+		"vec4 packedColor;",
+		"packedColor.x = vec3_to_float( diffuseColor );",
+		"packedColor.y = vec3_to_float( emissiveColor );",
+		"packedColor.z = vec3_to_float( specularColor );",
+		"packedColor.w = shininess;"
+
+	].join( "\n" ),
+
+	unpackColor: [
+
+		"vec4 colorMap = texture2D( samplerColor, texCoord );",
+		"vec3 diffuseColor = float_to_vec3( colorMap.x );",
+		"vec3 emissiveColor = float_to_vec3( colorMap.y );",
+		"vec3 specularColor = float_to_vec3( colorMap.z );",
+		"float shininess = colorMap.w;",
+
+	].join( "\n" ),
+
+	computeVertexPositionVS: [
+
+		"vec2 xy = texCoord * 2.0 - 1.0;",
+		"vec4 vertexPositionProjected = vec4( xy, depth, 1.0 );",
+		"vec4 vertexPositionVS = matProjInverse * vertexPositionProjected;",
+		"vertexPositionVS.xyz /= vertexPositionVS.w;",
+		"vertexPositionVS.w = 1.0;"
+
+	].join( "\n" ),
+
+	computeSpecular: [
+
+		"vec3 halfVector = normalize( lightVector - normalize( vertexPositionVS.xyz ) );",
+		"float dotNormalHalf = max( dot( normal, halfVector ), 0.0 );",
+
+		"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+		"vec3 schlick = specularColor + vec3( 1.0 - specularColor ) * pow( 1.0 - dot( lightVector, halfVector ), 5.0 );",
+		"vec3 specular = schlick * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuseColor * max( dot( normal, lightVector ), 0.0 ) * specularNormalization;"
+
+	].join( "\n" ),
+
+	combine: [
+
+		"gl_FragColor = vec4( lightIntensity * lightColor * ( diffuseColor * max( dot( normal, lightVector ), 0.0 ) + specular ), attenuation );"
+
+	].join( "\n" )
+
+};
+
+THREE.ShaderDeferred = {
+
+	normalDepth: {
+
+		uniforms: {},
+
+		vertexShader: [
+
+			"varying vec3 vNormal;",
+			"varying vec4 vPosition;",
+
+			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+			THREE.ShaderChunk[ "skinning_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "begin_vertex" ],
+				THREE.ShaderChunk[ "beginnormal_vertex" ],
+				THREE.ShaderChunk[ "skinbase_vertex" ],
+				THREE.ShaderChunk[ "skinnormal_vertex" ],
+				THREE.ShaderChunk[ "defaultnormal_vertex" ],
+				THREE.ShaderChunk[ "morphtarget_vertex" ],
+				THREE.ShaderChunk[ "skinning_vertex" ],
+				THREE.ShaderChunk[ "project_vertex" ],
+
+				"vNormal = normalize( normalMatrix * objectNormal );",
+				"vPosition = gl_Position;",
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"varying vec3 vNormal;",
+			"varying vec4 vPosition;",
+
+			"void main() {",
+
+				"vec3 normal = vNormal;",
+				"vec4 position = vPosition;",
+
+				THREE.DeferredShaderChunk[ "packNormalDepth" ],
+
+				"gl_FragColor = packedNormalDepth;",
+
+			"}"
+
+		].join( "\n" )
+
+	},
+
+	color: {
+
+		uniforms: {
+
+				map: { type: "t", value: null },
+				offsetRepeat: { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) },
+
+				diffuse: { type: "c", value: new THREE.Color( 0x000000 ) },
+				emissive: { type: "c", value: new THREE.Color( 0x000000 ) },
+				specular: { type: "c", value: new THREE.Color( 0x000000 ) },
+				shininess: { type: "f", value: 30.0 }
+
+		},
+
+		vertexShader: [
+
+			THREE.ShaderChunk[ "uv_pars_vertex" ],
+			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+			THREE.ShaderChunk[ "skinning_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "uv_vertex" ],
+				THREE.ShaderChunk[ "begin_vertex" ],
+				THREE.ShaderChunk[ "beginnormal_vertex" ],
+				THREE.ShaderChunk[ "skinbase_vertex" ],
+				THREE.ShaderChunk[ "skinnormal_vertex" ],
+				THREE.ShaderChunk[ "defaultnormal_vertex" ],
+				THREE.ShaderChunk[ "morphtarget_vertex" ],
+				THREE.ShaderChunk[ "skinning_vertex" ],
+				THREE.ShaderChunk[ "project_vertex" ],
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"uniform vec3 diffuse;",
+			"uniform vec3 emissive;",
+			"uniform vec3 specular;",
+			"uniform float shininess;",
+
+			THREE.ShaderChunk[ "uv_pars_fragment" ],
+			THREE.ShaderChunk[ "map_pars_fragment" ],
+			THREE.DeferredShaderChunk[ "packVector3" ],
+
+			"void main() {",
+
+				"vec3 diffuseColor = diffuse;",
+				"vec3 emissiveColor = emissive;",
+				"vec3 specularColor = specular;",
+
+				THREE.ShaderChunk[ "map_fragment" ],
+				THREE.DeferredShaderChunk[ "packColor" ],
+
+				"gl_FragColor = packedColor;",
+
+			"}"
+
+		].join( "\n" )
+
+	},
+
+	emissiveLight: {
+
+		uniforms: {
+
+			samplerColor: { type: "t", value: null },
+			viewWidth: { type: "f", value: 800 },
+			viewHeight: { type: "f", value: 600 }
+
+		},
+
+		vertexShader: [
+
+			"void main() { ",
+
+				"gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
+
+			"}"
+
+		].join( '\n' ),
+
+		fragmentShader: [
+
+			"uniform sampler2D samplerColor;",
+
+			"uniform float viewHeight;",
+			"uniform float viewWidth;",
+
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
+
+			"void main() {",
+
+				THREE.DeferredShaderChunk[ "computeTextureCoord" ],
+				THREE.DeferredShaderChunk[ "unpackColor" ],
+
+				"gl_FragColor = vec4( emissiveColor, 1.0 );",
+
+			"}"
+
+		].join( '\n' )
+
+	},
+
+	pointLight: {
+
+		uniforms: {
+
+			samplerNormalDepth: { type: "t", value: null },
+			samplerColor: { type: "t", value: null },
+
+			matProjInverse: { type: "m4", value: new THREE.Matrix4() },
+
+			viewWidth: { type: "f", value: 800 },
+			viewHeight: { type: "f", value: 600 },
+
+			lightColor: { type: "c", value: new THREE.Color( 0x000000 ) },
+			lightPositionVS: { type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+			lightIntensity: { type: "f", value: 1.0 },
+			lightRadius: { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"void main() {",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"uniform sampler2D samplerNormalDepth;",
+			"uniform sampler2D samplerColor;",
+
+			"uniform float viewHeight;",
+			"uniform float viewWidth;",
+
+			"uniform vec3 lightColor;",
+			"uniform vec3 lightPositionVS;",
+			"uniform float lightIntensity;",
+			"uniform float lightRadius;",
+
+			"uniform mat4 matProjInverse;",
+
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
+
+			"void main() {",
+
+				THREE.DeferredShaderChunk[ "computeTextureCoord" ],
+				THREE.DeferredShaderChunk[ "unpackNormalDepth" ],
+				THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
+
+				"vec3 lightVector = lightPositionVS - vertexPositionVS.xyz;",
+				"float distance = length( lightVector );",
+
+				"if ( distance > lightRadius ) discard;",
+
+				"lightVector = normalize( lightVector );",
+
+				THREE.DeferredShaderChunk[ "unpackColor" ],
+				THREE.DeferredShaderChunk[ "computeSpecular" ],
+
+				"//float cutoff = 0.3;",
+				"//float denom = distance / lightRadius + 1.0;",
+				"//float attenuation = 1.0 / ( denom * denom );",
+				"//attenuation = ( attenuation - cutoff ) / ( 1.0 - cutoff );",
+				"//attenuation = max( attenuation, 0.0 );",
+				"//attenuation *= attenuation;",
+
+				"//diffuseColor *= saturate( -distance / lightRadius + 1.0 );",
+				"//float attenuation = 1.0;",
+
+				"float attenuation = saturate( -distance / lightRadius + 1.0 );",
+
+				THREE.DeferredShaderChunk[ "combine" ],
+
+			"}"
+
+		].join( "\n" )
+
+	},
+
+	spotLight: {
+
+		uniforms: {
+
+			samplerNormalDepth: { type: "t", value: null },
+			samplerColor: { type: "t", value: null },
+
+			matProjInverse: { type: "m4", value: new THREE.Matrix4() },
+
+			viewWidth: { type: "f", value: 800 },
+			viewHeight: { type: "f", value: 600 },
+
+			lightColor: { type: "c", value: new THREE.Color( 0x000000 ) },
+			lightDirectionVS: { type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+			lightPositionVS: { type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+			lightAngle: { type: "f", value: 1.0 },
+			lightIntensity: { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"void main() { ",
+
+				"gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"uniform sampler2D samplerNormalDepth;",
+			"uniform sampler2D samplerColor;",
+
+			"uniform float viewHeight;",
+			"uniform float viewWidth;",
+
+			"uniform vec3 lightColor;",
+			"uniform vec3 lightPositionVS;",
+			"uniform vec3 lightDirectionVS;",
+			"uniform float lightAngle;",
+			"uniform float lightIntensity;",
+
+			"uniform mat4 matProjInverse;",
+
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
+
+			"void main() {",
+
+				THREE.DeferredShaderChunk[ "computeTextureCoord" ],
+				THREE.DeferredShaderChunk[ "unpackNormalDepth" ],
+				THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
+				THREE.DeferredShaderChunk[ "unpackColor" ],
+
+				"vec3 lightVector = normalize( lightPositionVS.xyz - vertexPositionVS.xyz );",
+
+				"float rho = dot( lightDirectionVS, lightVector );",
+				"float rhoMax = cos( lightAngle * 0.5 );",
+
+				"if ( rho <= rhoMax ) discard;",
+
+				"float theta = rhoMax + 0.0001;",
+				"float phi = rhoMax + 0.05;",
+				"float falloff = 4.0;",
+
+				"float spot = 0.0;",
+
+				"if ( rho >= phi ) {",
+
+					"spot = 1.0;",
+
+				"} else if ( rho <= theta ) {",
+
+					"spot = 0.0;",
+
+				"} else { ",
+
+					"spot = pow( ( rho - theta ) / ( phi - theta ), falloff );",
+
+				"}",
+
+				"diffuseColor *= spot;",
+
+				THREE.DeferredShaderChunk[ "computeSpecular" ],
+
+				"const float attenuation = 1.0;",
+
+				THREE.DeferredShaderChunk[ "combine" ],
+
+			"}"
+
+		].join( "\n" )
+
+	},
+
+	directionalLight: {
+
+		uniforms: {
+
+			samplerNormalDepth: { type: "t", value: null },
+			samplerColor: { type: "t", value: null },
+
+			matProjInverse: { type: "m4", value: new THREE.Matrix4() },
+
+			viewWidth: { type: "f", value: 800 },
+			viewHeight: { type: "f", value: 600 },
+
+			lightColor: { type: "c", value: new THREE.Color( 0x000000 ) },
+			lightDirectionVS : { type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+			lightIntensity: { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"void main() { ",
+
+				"gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
+
+			"}"
+
+		].join( '\n' ),
+
+		fragmentShader: [
+
+			"uniform sampler2D samplerNormalDepth;",
+			"uniform sampler2D samplerColor;",
+
+			"uniform float viewHeight;",
+			"uniform float viewWidth;",
+
+			"uniform vec3 lightColor;",
+			"uniform vec3 lightDirectionVS;",
+			"uniform float lightIntensity;",
+
+			"uniform mat4 matProjInverse;",
+
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
+
+			"void main() {",
+
+				THREE.DeferredShaderChunk[ "computeTextureCoord" ],
+				THREE.DeferredShaderChunk[ "unpackNormalDepth" ],
+				THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
+				THREE.DeferredShaderChunk[ "unpackColor" ],
+
+				"vec3 lightVector = normalize( lightDirectionVS );",
+
+				THREE.DeferredShaderChunk[ "computeSpecular" ],
+
+				"const float attenuation = 1.0;",
+
+				THREE.DeferredShaderChunk[ "combine" ],
+
+			"}"
+
+		].join( '\n' ),
+
+	},
+
+	composite: {
+
+		uniforms: {
+
+			samplerLight: { type: "t", value: null }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 texCoord;",
+
+			"void main() {",
+
+				"vec4 pos = vec4( sign( position.xy ), 0.0, 1.0 );",
+				"texCoord = pos.xy * vec2( 0.5 ) + 0.5;",
+				"gl_Position = pos;",
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"varying vec2 texCoord;",
+			"uniform sampler2D samplerLight;",
+
+			"void main() {",
+
+				"gl_FragColor = texture2D( samplerLight, texCoord );",
+
+			"}"
+
+		].join( "\n" )
+
+	}
+
+};

+ 294 - 0
examples/webgl_deferred_animation.html

@@ -0,0 +1,294 @@
+<!DOCTYPE HTML>
+<html lang="en">
+	<head>
+		<title>three.js webgl - deferred rendering [morphing + skinning]</title>
+		<meta charset="utf-8" />
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+			#info {
+				position: absolute;
+				top: 20px; width: 100%;
+				color: #ffffff;
+				padding: 5px;
+				font-family: Monospace;
+				font-size: 13px;
+				text-align: center;
+			}
+			a {
+				color: #ff0080;
+				text-decoration: none;
+			}
+			a:hover {
+				color: #0080ff;
+			}
+			#stats { position: absolute; top:10px; left: 5px }
+			#stats #fps { background: transparent !important }
+			#stats #fps #fpsText { color: #aaa !important }
+			#stats #fps #fpsGraph { display: none }
+		</style>
+	</head>
+
+	<body>
+		<div id="container"></div>
+
+		<script src="js/libs/stats.min.js"></script>
+		<script src="../build/three.js"></script>
+		<script src="js/Detector.js"></script>
+
+		<script src="js/shaders/CopyShader.js"></script>
+		<script src="js/shaders/FXAAShader.js"></script>
+
+		<script src="js/postprocessing/EffectComposer.js"></script>
+		<script src="js/postprocessing/RenderPass.js"></script>
+		<script src="js/postprocessing/ShaderPass.js"></script>
+		<script src="js/renderers/WebGLDeferredRenderer.js"></script>
+
+		<script>
+
+			var WIDTH = window.innerWidth;
+			var HEIGHT = window.innerHeight;
+
+			var windowHalfX = WIDTH / 2;
+			var windowHalfY = HEIGHT / 2;
+
+			var mouseX = 0, mouseY = 0;
+
+			var NEAR = 1.0, FAR = 350.0;
+			var VIEW_ANGLE = 45;
+
+			var renderer, camera, scene, stats, clock, mixer;
+
+			var numLights = 40;
+			var lights = [];
+
+			var ready = false;
+
+			init();
+			animate();
+
+			function init() {
+
+				renderer = new THREE.WebGLDeferredRenderer();
+				renderer.setSize( WIDTH, HEIGHT );
+
+				camera = new THREE.PerspectiveCamera( VIEW_ANGLE, WIDTH / HEIGHT, NEAR, FAR );
+				camera.position.z = 150;
+
+				scene = new THREE.Scene();
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '8px';
+				stats.domElement.style.zIndex = 100;
+
+				var container = document.getElementById( 'container' );
+				container.appendChild( renderer.domElement );
+				container.appendChild( stats.domElement );
+
+				initKnight();
+				initRoom();
+				initLights();
+
+				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				clock = new THREE.Clock();
+
+			}
+
+			function initKnight() {
+
+				var loader = new THREE.JSONLoader();
+
+				loader.load( "models/skinned/knight.js", function( geometry, materials ) {
+
+					var material = materials[ 0 ];
+					material.emissive.set( 0x101010 );
+					material.skinning = true;
+					material.morphTargets = true;
+
+					var mesh = new THREE.SkinnedMesh( geometry, material );
+					mesh.position.y = -30;
+					mesh.scale.multiplyScalar( 5 );
+
+					mixer = new THREE.AnimationMixer( mesh );
+
+					for ( var i = 0; i < mesh.geometry.animations.length; i ++ ) {
+
+						var action = mixer.clipAction( mesh.geometry.animations[ i ] );
+
+						if ( i === 1 ) action.timeScale = 0.25;
+
+						action.play();
+
+					}
+
+					scene.add( mesh );
+
+					ready = true;
+
+				} );
+
+			}
+
+			function initLights() {
+
+				var distance = 20;
+
+				var c = new THREE.Vector3();
+				var geometry = new THREE.SphereGeometry( 1, 1, 1 );
+
+				for ( var i = 0; i < numLights; i ++ ) {
+
+					var light = new THREE.PointLight( 0xffffff, 2.0, distance );
+					c.set( Math.random(), Math.random(), Math.random() ).normalize();
+					light.color.setRGB( c.x, c.y, c.z );
+					scene.add( light );
+					lights.push( light );
+
+					var material = new THREE.MeshBasicMaterial( { color: light.color } );
+					var emitter = new THREE.Mesh( geometry, material );
+					light.add( emitter );
+
+				}
+
+				var directionalLight = new THREE.DirectionalLight( 0x101010 );
+				directionalLight.position.set( -1, 1, 1 ).normalize();
+				scene.add( directionalLight );
+
+				var spotLight = new THREE.SpotLight( 0x404040 );
+				spotLight.position.set( 0, 50, 0 );
+				scene.add( spotLight );
+
+			}
+
+			function initRoom() {
+
+				var size = 100;
+				var geometry = new THREE.PlaneBufferGeometry( size, size );
+				var material = new THREE.MeshPhongMaterial( { color: 0x222222, specular: 0x222222, shininess: 75 } );
+				var transparentMaterial = new THREE.MeshPhongMaterial( { color: 0x222222, emissive: 0x88888888, specular: 0x222222, shininess: 75, opacity: 0.3, transparent: true } );
+
+				var room = new THREE.Object3D();
+				room.position.y = size / 2 - 30;
+
+				// top
+				mesh = new THREE.Mesh( geometry, material );
+				mesh.rotation.x = Math.PI/2;
+				mesh.position.y = size / 2;
+				room.add( mesh );
+
+				// bottom
+				mesh = new THREE.Mesh( geometry, material );
+				mesh.rotation.x = -Math.PI/2;
+				mesh.position.y = -size / 2;
+				room.add( mesh );
+
+				// left
+				mesh = new THREE.Mesh( geometry, material );
+				mesh.position.x = -size / 2;
+				mesh.rotation.y = Math.PI/2;
+				room.add( mesh );
+
+				// right
+				mesh = new THREE.Mesh( geometry, material );
+				mesh.position.x = size / 2;
+				mesh.rotation.y = -Math.PI/2;
+				room.add( mesh );
+
+				// back
+				mesh = new THREE.Mesh( geometry, material );
+				mesh.position.z = -size / 2;
+				room.add( mesh );
+
+				// front, to check if transparency works
+				mesh = new THREE.Mesh( geometry, transparentMaterial );
+				mesh.position.z = size / 2;
+				mesh.scale.multiplyScalar( 0.33 );
+				//room.add( mesh );
+
+				// back2, to check if transparency works
+				mesh = new THREE.Mesh( geometry, transparentMaterial );
+				mesh.position.z = -size / 4;
+				mesh.scale.multiplyScalar( 0.75 );
+				//room.add( mesh );
+
+				scene.add( room );
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = ( event.clientX - windowHalfX ) / 10.0;
+				mouseY = ( event.clientY - windowHalfY ) / 10.0;
+
+			}
+
+			function onWindowResize( event ) {
+
+				WIDTH = window.innerWidth;
+				HEIGHT = window.innerHeight;
+
+				windowHalfX = WIDTH / 2;
+				windowHalfY = HEIGHT / 2;
+
+				renderer.setSize( WIDTH, HEIGHT );
+
+				camera.aspect = WIDTH / HEIGHT;
+				camera.updateProjectionMatrix();
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				if ( ready ) {
+
+					update();
+					render();
+
+				}
+
+				stats.update();
+
+			}
+
+			function update() {
+
+				var delta = clock.getDelta();
+				var time = Date.now() * 0.0005;
+
+				mixer.update( delta / 2.0 );
+
+				for ( var i = 0, il = lights.length; i < il; i ++ ) {
+
+					var light = lights[ i ];
+					var x = Math.sin( time + i * 7.0 ) * 45;
+					var y = Math.cos( time + i * 5.0 ) * 45 + 20;
+					var z = Math.cos( time + i * 3.0 ) * 45;
+					light.position.set( x, y, z );
+
+				}
+
+			}
+
+			function render() {
+
+				camera.position.x += ( - mouseX - camera.position.x ) * .05;
+				camera.position.y += ( - mouseY - camera.position.y ) * .05;
+				camera.lookAt( scene.position );
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+
+</html>