text: /**
   * @author alteredq / http://alteredqualia.com/
   * @author MPanknin / http://www.redplant.de/
   */
  
  THREE.WebGLDeferredRenderer = function ( parameters ) {
  
  	var _this = this;
  
  	var pixelWidth = parameters.width !== undefined ? parameters.width : 800;
  	var pixelHeight = parameters.height !== undefined ? parameters.height : 600;
  	var currentScale = parameters.scale !== undefined ? parameters.scale : 1;
      
  	var devicePixelRatio = parameters.devicePixelRatio !== undefined
  		? parameters.devicePixelRatio
  			: self.devicePixelRatio !== undefined
  				? self.devicePixelRatio
  				: 1;
  
  	var fullWidth = pixelWidth * devicePixelRatio;
  	var fullHeight = pixelHeight * devicePixelRatio;
  	
  	var scaledWidth = Math.floor( currentScale * fullWidth );
  	var scaledHeight = Math.floor( currentScale * fullHeight );
  	
  	var brightness = parameters.brightness !== undefined ? parameters.brightness : 1;
  	var tonemapping = parameters.tonemapping !== undefined ? parameters.tonemapping : THREE.SimpleOperator;
  	var antialias = parameters.antialias !== undefined ? parameters.antialias : false;
  
  	this.renderer = parameters.renderer;
  
  	if ( this.renderer === undefined ) {
  
  		this.renderer = new THREE.WebGLRenderer( { antialias: false, devicePixelRatio : devicePixelRatio } );
  		this.renderer.setSize( fullWidth, fullHeight );
  		this.renderer.setClearColor( 0x000000, 0 );
  
  		this.renderer.autoClear = false;
  
  	}
  
  	this.domElement = this.renderer.domElement;
  
  	//
  
  	var gl = this.renderer.context;
  
  	//
  
  	var currentCamera = null;
  	var projectionMatrixInverse = new THREE.Matrix4();
  
  	var positionVS = new THREE.Vector3();
  	var directionVS = new THREE.Vector3();
  	var tempVS = new THREE.Vector3();
  
  	var rightVS = new THREE.Vector3();
  	var normalVS = new THREE.Vector3();
  	var upVS = new THREE.Vector3();
  
  	//
  
  	var geometryLightSphere = new THREE.SphereGeometry( 1, 16, 8 );
  	var geometryLightPlane = new THREE.PlaneBufferGeometry( 2, 2 );
  
  	var black = new THREE.Color( 0x000000 );
  
  	var colorShader = THREE.ShaderDeferred[ "color" ];
  	var normalDepthShader = THREE.ShaderDeferred[ "normalDepth" ];
  
  	//
  
  	var emissiveLightShader = THREE.ShaderDeferred[ "emissiveLight" ];
  	var pointLightShader = THREE.ShaderDeferred[ "pointLight" ];
  	var spotLightShader = THREE.ShaderDeferred[ "spotLight" ];
  	var directionalLightShader = THREE.ShaderDeferred[ "directionalLight" ];
  	var hemisphereLightShader = THREE.ShaderDeferred[ "hemisphereLight" ];
  	var areaLightShader = THREE.ShaderDeferred[ "areaLight" ];
  
  	var compositeShader = THREE.ShaderDeferred[ "composite" ];
  
  	//
  
  	var compColor, compNormal, compDepth, compLight, compFinal;
  	var passColor, passNormal, passDepth, passLightFullscreen, passLightProxy, compositePass;
  
  	var effectFXAA;
  
  	//
  
  	var lightSceneFullscreen, lightSceneProxy;
  
  	//
  
  	var resizableMaterials = [];
  
  	//
  
  	var invisibleMaterial = new THREE.ShaderMaterial();
  	invisibleMaterial.visible = false;
  
  
  	var defaultNormalDepthMaterial = new THREE.ShaderMaterial( {
  
  		uniforms:       THREE.UniformsUtils.clone( normalDepthShader.uniforms ),
  		vertexShader:   normalDepthShader.vertexShader,
  		fragmentShader: normalDepthShader.fragmentShader,
  		blending:		THREE.NoBlending
  
  	} );
  
  	//
  
  	var initDeferredMaterials = function ( object ) {
  
  		if ( object.material instanceof THREE.MeshFaceMaterial ) {
  
  			var colorMaterials = [];
  			var normalDepthMaterials = [];
  
  			var materials = object.material.materials;
  
  			for ( var i = 0, il = materials.length; i < il; i ++ ) {
  
  				var deferredMaterials = createDeferredMaterials( materials[ i ] );
  
  				if ( deferredMaterials.transparent ) {
  
  					colorMaterials.push( invisibleMaterial );
  					normalDepthMaterials.push( invisibleMaterial );
  
  				} else {
  
  					colorMaterials.push( deferredMaterials.colorMaterial );
  					normalDepthMaterials.push( deferredMaterials.normalDepthMaterial );
  
  				}
  
  			}
  
  			object.userData.colorMaterial = new THREE.MeshFaceMaterial( colorMaterials );
  			object.userData.normalDepthMaterial = new THREE.MeshFaceMaterial( normalDepthMaterials );
  
  		} else {
  
  			var deferredMaterials = createDeferredMaterials( object.material );
  
  			object.userData.colorMaterial = deferredMaterials.colorMaterial;
  			object.userData.normalDepthMaterial = deferredMaterials.normalDepthMaterial;
  			object.userData.transparent = deferredMaterials.transparent;
  
  		}
  
  	};
  
  	var createDeferredMaterials = function ( originalMaterial ) {
  
  		var deferredMaterials = {};
  
  		// color material
  		// -----------------
  		// 	diffuse color
  		//	specular color
  		//	shininess
  		//	diffuse map
  		//	vertex colors
  		//	alphaTest
  		// 	morphs
  
  		var uniforms = THREE.UniformsUtils.clone( colorShader.uniforms );
  		var defines = { "USE_MAP": !! originalMaterial.map, "USE_ENVMAP": !! originalMaterial.envMap, "GAMMA_INPUT": true };
  
  		var material = new THREE.ShaderMaterial( {
  
  			fragmentShader: colorShader.fragmentShader,
  			vertexShader: 	colorShader.vertexShader,
  			uniforms: 		uniforms,
  			defines: 		defines,
  			shading:		originalMaterial.shading
  
  		} );
  
  		if ( originalMaterial instanceof THREE.MeshBasicMaterial ) {
  
  			var diffuse = black;
  			var emissive = originalMaterial.color;
  
  		} else {
  
  			var diffuse = originalMaterial.color;
  			var emissive = originalMaterial.emissive !== undefined ? originalMaterial.emissive : black;
  
  		}
  
  		var specular = originalMaterial.specular !== undefined ? originalMaterial.specular : black;
  		var shininess = originalMaterial.shininess !== undefined ? originalMaterial.shininess : 1;
  		var wrapAround = originalMaterial.wrapAround !== undefined ? ( originalMaterial.wrapAround ? -1 : 1 ) : 1;
  		var additiveSpecular = originalMaterial.metal !== undefined ? ( originalMaterial.metal ? 1 : -1 ) : -1;
  
  		uniforms.emissive.value.copyGammaToLinear( emissive );
  		uniforms.diffuse.value.copyGammaToLinear( diffuse );
  		uniforms.specular.value.copyGammaToLinear( specular );
  		uniforms.shininess.value = shininess;
  		uniforms.wrapAround.value = wrapAround;
  		uniforms.additiveSpecular.value = additiveSpecular;
  
  		uniforms.map.value = originalMaterial.map;
  
  		if ( originalMaterial.envMap ) {
  
  			uniforms.envMap.value = originalMaterial.envMap;
  			uniforms.useRefract.value = originalMaterial.envMap.mapping instanceof THREE.CubeRefractionMapping;
  			uniforms.refractionRatio.value = originalMaterial.refractionRatio;
  			uniforms.combine.value = originalMaterial.combine;
  			uniforms.reflectivity.value = originalMaterial.reflectivity;
  			uniforms.flipEnvMap.value = ( originalMaterial.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : -1;
  
  			uniforms.samplerNormalDepth.value = compNormalDepth.renderTarget2;
  			uniforms.viewWidth.value = scaledWidth;
  			uniforms.viewHeight.value = scaledHeight;
  
  			resizableMaterials.push( { "material": material } );
  
  		}
  
  		material.vertexColors = originalMaterial.vertexColors;
  		material.morphTargets = originalMaterial.morphTargets;
  		material.morphNormals = originalMaterial.morphNormals;
  		material.skinning = originalMaterial.skinning;
  
  		material.alphaTest = originalMaterial.alphaTest;
  		material.wireframe = originalMaterial.wireframe;
  
  		// uv repeat and offset setting priorities
  		//	1. color map
  		//	2. specular map
  		//	3. normal map
  		//	4. bump map
  
  		var uvScaleMap;
  
  		if ( originalMaterial.map ) {
  
  			uvScaleMap = originalMaterial.map;
  
  		} else if ( originalMaterial.specularMap ) {
  
  			uvScaleMap = originalMaterial.specularMap;
  
  		} else if ( originalMaterial.normalMap ) {
  
  			uvScaleMap = originalMaterial.normalMap;
  
  		} else if ( originalMaterial.bumpMap ) {
  
  			uvScaleMap = originalMaterial.bumpMap;
  
  		}
  
  		if ( uvScaleMap !== undefined ) {
  
  			var offset = uvScaleMap.offset;
  			var repeat = uvScaleMap.repeat;
  
  			uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
  
  		}
  
  		deferredMaterials.colorMaterial = material;
  
  		// normal + depth material
  		// -----------------
  		//	vertex normals
  		//	morph normals
  		//	bump map
  		//	bump scale
  		//  clip depth
  
  		if ( originalMaterial.morphTargets || originalMaterial.skinning || originalMaterial.bumpMap ) {
  
  			var uniforms = THREE.UniformsUtils.clone( normalDepthShader.uniforms );
  			var defines = { "USE_BUMPMAP": !!originalMaterial.bumpMap };
  
  			var normalDepthMaterial = new THREE.ShaderMaterial( {
  
  				uniforms:       uniforms,
  				vertexShader:   normalDepthShader.vertexShader,
  				fragmentShader: normalDepthShader.fragmentShader,
  				shading:		originalMaterial.shading,
  				defines:		defines,
  				blending:		THREE.NoBlending
  
  			} );
  
  			normalDepthMaterial.morphTargets = originalMaterial.morphTargets;
  			normalDepthMaterial.morphNormals = originalMaterial.morphNormals;
  			normalDepthMaterial.skinning = originalMaterial.skinning;
  
  			if ( originalMaterial.bumpMap ) {
  
  				uniforms.bumpMap.value = originalMaterial.bumpMap;
  				uniforms.bumpScale.value = originalMaterial.bumpScale;
  
  				var offset = originalMaterial.bumpMap.offset;
  				var repeat = originalMaterial.bumpMap.repeat;
  
  				uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
  
  			}
  
  		} else {
  
  			var normalDepthMaterial = defaultNormalDepthMaterial.clone();
  
  		}
  
  		normalDepthMaterial.wireframe = originalMaterial.wireframe;
  		normalDepthMaterial.vertexColors = originalMaterial.vertexColors;
  
  		deferredMaterials.normalDepthMaterial = normalDepthMaterial;
  
  		//
  
  		deferredMaterials.transparent = originalMaterial.transparent;
  
  		return deferredMaterials;
  
  	};
  
  	var updatePointLightProxy = function ( lightProxy ) {
  
  		var light = lightProxy.userData.originalLight;
  		var uniforms = lightProxy.material.uniforms;
  
  		// skip infinite pointlights
  		// right now you can't switch between infinite and finite pointlights
  		// it's just too messy as they use different proxies
  
  		var distance = light.distance;
  
  		if ( distance > 0 ) {
  
  			lightProxy.scale.set( 1, 1, 1 ).multiplyScalar( distance );
  			uniforms[ "lightRadius" ].value = distance;
  
  			positionVS.setFromMatrixPosition( light.matrixWorld );
  			positionVS.applyMatrix4( currentCamera.matrixWorldInverse );
  
  			uniforms[ "lightPositionVS" ].value.copy( positionVS );
  
  			lightProxy.position.setFromMatrixPosition( light.matrixWorld );
  
  		} else {
  
  			uniforms[ "lightRadius" ].value = Infinity;
  
  		}
  
  		// linear space colors
  
  		var intensity = light.intensity * light.intensity;
  
  		uniforms[ "lightIntensity" ].value = intensity;
  		uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
  
  	};
  
  	var createDeferredPointLight = function ( light ) {
  
  		// setup light material
  
  		var materialLight = new THREE.ShaderMaterial( {
  
  			uniforms:       THREE.UniformsUtils.clone( pointLightShader.uniforms ),
  			vertexShader:   pointLightShader.vertexShader,
  			fragmentShader: pointLightShader.fragmentShader,
  
  			blending:		THREE.AdditiveBlending,
  			depthWrite:		false,
  			transparent:	true,
  
  			side: THREE.BackSide
  
  		} );
  
  		// infinite pointlights use full-screen quad proxy
  		// regular pointlights use sphere proxy
  
  		var  geometry;
  
  		if ( light.distance > 0 ) {
  
  			geometry = geometryLightSphere;
  
  		} else {
  
  			geometry = geometryLightPlane;
  
  			materialLight.depthTest = false;
  			materialLight.side = THREE.FrontSide;
  
  		}
  
  		materialLight.uniforms[ "viewWidth" ].value = scaledWidth;
  		materialLight.uniforms[ "viewHeight" ].value = scaledHeight;
  
  		materialLight.uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
  		materialLight.uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
  
  		// create light proxy mesh
  
  		var meshLight = new THREE.Mesh( geometry, materialLight );
  
  		// keep reference for color and intensity updates
  
  		meshLight.userData.originalLight = light;
  
  		// keep reference for size reset
  
  		resizableMaterials.push( { "material": materialLight } );
  
  		// sync proxy uniforms to the original light
  
  		updatePointLightProxy( meshLight );
  
  		return meshLight;
  
  	};
  
  	var updateSpotLightProxy = function ( lightProxy ) {
  
  		var light = lightProxy.userData.originalLight;
  		var uniforms = lightProxy.material.uniforms;
  
  		var viewMatrix = currentCamera.matrixWorldInverse;
  		var modelMatrix = light.matrixWorld;
  
  		positionVS.setFromMatrixPosition( modelMatrix );
  		positionVS.applyMatrix4( viewMatrix );
  
  		directionVS.setFromMatrixPosition( modelMatrix );
  		tempVS.setFromMatrixPosition( light.target.matrixWorld );
  		directionVS.sub( tempVS );
  		directionVS.normalize();
  		directionVS.transformDirection( viewMatrix );
  
  		uniforms[ "lightPositionVS" ].value.copy( positionVS );
  		uniforms[ "lightDirectionVS" ].value.copy( directionVS );
  
  		uniforms[ "lightAngle" ].value = light.angle;
  		uniforms[ "lightDistance" ].value = light.distance;
  
  		// linear space colors
  
  		var intensity = light.intensity * light.intensity;
  
  		uniforms[ "lightIntensity" ].value = intensity;
  		uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
  
  	};
  
  	var createDeferredSpotLight = function ( light ) {
  
  		// setup light material
  
  		var uniforms = THREE.UniformsUtils.clone( spotLightShader.uniforms );
  
  		var materialLight = new THREE.ShaderMaterial( {
  
  			uniforms:       uniforms,
  			vertexShader:   spotLightShader.vertexShader,
  			fragmentShader: spotLightShader.fragmentShader,
  
  			blending:		THREE.AdditiveBlending,
  			depthWrite:		false,
  			depthTest:		false,
  			transparent:	true
  
  		} );
  
  		uniforms[ "viewWidth" ].value = scaledWidth;
  		uniforms[ "viewHeight" ].value = scaledHeight;
  
  		uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
  		uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
  
  		// create light proxy mesh
  
  		var meshLight = new THREE.Mesh( geometryLightPlane, materialLight );
  
  		// keep reference for color and intensity updates
  
  		meshLight.userData.originalLight = light;
  
  		// keep reference for size reset
  
  		resizableMaterials.push( { "material": materialLight } );
  
  		// sync proxy uniforms to the original light
  
  		updateSpotLightProxy( meshLight );
  
  		return meshLight;
  
  	};
  
  	var updateDirectionalLightProxy = function ( lightProxy ) {
  
  		var light = lightProxy.userData.originalLight;
  		var uniforms = lightProxy.material.uniforms;
  
  		directionVS.setFromMatrixPosition( light.matrixWorld );
  		tempVS.setFromMatrixPosition( light.target.matrixWorld );
  		directionVS.sub( tempVS );
  		directionVS.normalize();
  		directionVS.transformDirection( currentCamera.matrixWorldInverse );
  
  		uniforms[ "lightDirectionVS" ].value.copy( directionVS );
  
  		// linear space colors
  
  		var intensity = light.intensity * light.intensity;
  
  		uniforms[ "lightIntensity" ].value = intensity;
  		uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
  
  	};
  
  	var createDeferredDirectionalLight = function ( light ) {
  
  		// setup light material
  
  		var uniforms = THREE.UniformsUtils.clone( directionalLightShader.uniforms );
  
  		var materialLight = new THREE.ShaderMaterial( {
  
  			uniforms:       uniforms,
  			vertexShader:   directionalLightShader.vertexShader,
  			fragmentShader: directionalLightShader.fragmentShader,
  
  			blending:		THREE.AdditiveBlending,
  			depthWrite:		false,
  			depthTest:		false,
  			transparent:	true
  
  		} );
  
  		uniforms[ "viewWidth" ].value = scaledWidth;
  		uniforms[ "viewHeight" ].value = scaledHeight;
  
  		uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
  		uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
  
  		// create light proxy mesh
  
  		var meshLight = new THREE.Mesh( geometryLightPlane, materialLight );
  
  		// keep reference for color and intensity updates
  
  		meshLight.userData.originalLight = light;
  
  		// keep reference for size reset
  
  		resizableMaterials.push( { "material": materialLight } );
  
  		// sync proxy uniforms to the original light
  
  		updateDirectionalLightProxy( meshLight );
  
  		return meshLight;
  
  	};
  
  	var updateHemisphereLightProxy = function ( lightProxy ) {
  
  		var light = lightProxy.userData.originalLight;
  		var uniforms = lightProxy.material.uniforms;
  
  		directionVS.setFromMatrixPosition( light.matrixWorld );
  		directionVS.normalize();
  		directionVS.transformDirection( currentCamera.matrixWorldInverse );
  
  		uniforms[ "lightDirectionVS" ].value.copy( directionVS );
  
  		// linear space colors
  
  		var intensity = light.intensity * light.intensity;
  
  		uniforms[ "lightIntensity" ].value = intensity;
  		uniforms[ "lightColorSky" ].value.copyGammaToLinear( light.color );
  		uniforms[ "lightColorGround" ].value.copyGammaToLinear( light.groundColor );
  
  	};
  
  	var createDeferredHemisphereLight = function ( light ) {
  
  		// setup light material
  
  		var uniforms = THREE.UniformsUtils.clone( hemisphereLightShader.uniforms );
  
  		var materialLight = new THREE.ShaderMaterial( {
  
  			uniforms:       uniforms,
  			vertexShader:   hemisphereLightShader.vertexShader,
  			fragmentShader: hemisphereLightShader.fragmentShader,
  
  			blending:		THREE.AdditiveBlending,
  			depthWrite:		false,
  			depthTest:		false,
  			transparent:	true
  
  		} );
  
  		uniforms[ "viewWidth" ].value = scaledWidth;
  		uniforms[ "viewHeight" ].value = scaledHeight;
  
  		uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
  		uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
  
  		// create light proxy mesh
  
  		var meshLight = new THREE.Mesh( geometryLightPlane, materialLight );
  
  		// keep reference for color and intensity updates
  
  		meshLight.userData.originalLight = light;
  
  		// keep reference for size reset
  
  		resizableMaterials.push( { "material": materialLight } );
  
  		// sync proxy uniforms to the original light
  
  		updateHemisphereLightProxy( meshLight );
  
  		return meshLight;
  
  	};
  
  	var updateAreaLightProxy = function ( lightProxy ) {
  
  		var light = lightProxy.userData.originalLight;
  		var uniforms = lightProxy.material.uniforms;
  
  		var modelMatrix = light.matrixWorld;
  		var viewMatrix = currentCamera.matrixWorldInverse;
  
  		positionVS.setFromMatrixPosition( modelMatrix );
  		positionVS.applyMatrix4( viewMatrix );
  
  		uniforms[ "lightPositionVS" ].value.copy( positionVS );
  
  		rightVS.copy( light.right );
  		rightVS.transformDirection( modelMatrix );
  		rightVS.transformDirection( viewMatrix );
  
  		normalVS.copy( light.normal );
  		normalVS.transformDirection( modelMatrix );
  		normalVS.transformDirection( viewMatrix );
  
  		upVS.crossVectors( rightVS, normalVS );
  		upVS.normalize();
  
  		uniforms[ "lightRightVS" ].value.copy( rightVS );
  		uniforms[ "lightNormalVS" ].value.copy( normalVS );
  		uniforms[ "lightUpVS" ].value.copy( upVS );
  
  		uniforms[ "lightWidth" ].value = light.width;
  		uniforms[ "lightHeight" ].value = light.height;
  
  		uniforms[ "constantAttenuation" ].value = light.constantAttenuation;
  		uniforms[ "linearAttenuation" ].value = light.linearAttenuation;
  		uniforms[ "quadraticAttenuation" ].value = light.quadraticAttenuation;
  
  		// linear space colors
  
  		var intensity = light.intensity * light.intensity;
  
  		uniforms[ "lightIntensity" ].value = intensity;
  		uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
  
  	};
  
  	var createDeferredAreaLight = function ( light ) {
  
  		// setup light material
  
  		var uniforms = THREE.UniformsUtils.clone( areaLightShader.uniforms );
  
  		var materialLight = new THREE.ShaderMaterial( {
  
  			uniforms:       uniforms,
  			vertexShader:   areaLightShader.vertexShader,
  			fragmentShader: areaLightShader.fragmentShader,
  
  			blending:		THREE.AdditiveBlending,
  			depthWrite:		false,
  			depthTest:		false,
  			transparent:	true
  
  		} );
  
  		uniforms[ "viewWidth" ].value = scaledWidth;
  		uniforms[ "viewHeight" ].value = scaledHeight;
  
  		uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
  		uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
  
  		// create light proxy mesh
  
  		var meshLight = new THREE.Mesh( geometryLightPlane, materialLight );
  
  		// keep reference for color and intensity updates
  
  		meshLight.userData.originalLight = light;
  
  		// keep reference for size reset
  
  		resizableMaterials.push( { "material": materialLight } );
  
  		// sync proxy uniforms to the original light
  
  		updateAreaLightProxy( meshLight );
  
  		return meshLight;
  
  	};
  
  	var createDeferredEmissiveLight = function () {
  
  		// setup light material
  
  		var materialLight = new THREE.ShaderMaterial( {
  
  			uniforms:       THREE.UniformsUtils.clone( emissiveLightShader.uniforms ),
  			vertexShader:   emissiveLightShader.vertexShader,
  			fragmentShader: emissiveLightShader.fragmentShader,
  			depthTest:		false,
  			depthWrite:		false,
  			blending:		THREE.NoBlending
  
  		} );
  
  		materialLight.uniforms[ "viewWidth" ].value = scaledWidth;
  		materialLight.uniforms[ "viewHeight" ].value = scaledHeight;
  
  		materialLight.uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
  
  		// create light proxy mesh
  
  		var meshLight = new THREE.Mesh( geometryLightPlane, materialLight );
  
  		// keep reference for size reset
  
  		resizableMaterials.push( { "material": materialLight } );
  
  		return meshLight;
  
  	};
  
  	var initDeferredProperties = function ( object ) {
  
  		if ( object.userData.deferredInitialized ) return;
  
  		if ( object.material ) initDeferredMaterials( object );
  
  		if ( object instanceof THREE.PointLight ) {
  
  			var proxy = createDeferredPointLight( object );
  
  			if ( object.distance > 0 ) {
  
  				lightSceneProxy.add( proxy );
  
  			} else {
  
  				lightSceneFullscreen.add( proxy );
  
  			}
  
  		} else if ( object instanceof THREE.SpotLight ) {
  
  			var proxy = createDeferredSpotLight( object );
  			lightSceneFullscreen.add( proxy );
  
  		} else if ( object instanceof THREE.DirectionalLight ) {
  
  			var proxy = createDeferredDirectionalLight( object );
  			lightSceneFullscreen.add( proxy );
  
  		} else if ( object instanceof THREE.HemisphereLight ) {
  
  			var proxy = createDeferredHemisphereLight( object );
  			lightSceneFullscreen.add( proxy );
  
  		} else if ( object instanceof THREE.AreaLight ) {
  
  			var proxy = createDeferredAreaLight( object );
  			lightSceneFullscreen.add( proxy );
  
  		}
  
  		object.userData.deferredInitialized = true;
  
  	};
  
  	//
  
  	var setMaterialColor = function ( object ) {
  
  		if ( object.material ) {
  
  			if ( object.userData.transparent ) {
  
  				object.material = invisibleMaterial;
  
  			} else {
  
  				object.material = object.userData.colorMaterial;
  
  			}
  
  		}
  
  	};
  
  	var setMaterialNormalDepth = function ( object ) {
  
  		if ( object.material ) {
  
  			if ( object.userData.transparent ) {
  
  				object.material = invisibleMaterial;
  
  			} else {
  
  				object.material = object.userData.normalDepthMaterial;
  
  			}
  
  		}
  
  	};
  
  	// external API
  
  	this.setAntialias = function ( enabled ) {
  
  		antialias = enabled;
  
  		if ( antialias ) {
  
  			effectFXAA.enabled = true;
  			compositePass.renderToScreen = false;
  
  		} else {
  
  			effectFXAA.enabled = false;
  			compositePass.renderToScreen = true;
  		}
  
  	};
  
  	this.getAntialias = function () {
  
  		return antialias;
  
  	};
  
  	this.addEffect = function ( effect, normalDepthUniform, colorUniform ) {
  
  		if ( effect.material && effect.uniforms ) {
  
  			if ( normalDepthUniform ) effect.uniforms[ normalDepthUniform ].value = compNormalDepth.renderTarget2;
  			if ( colorUniform )    	  effect.uniforms[ colorUniform ].value = compColor.renderTarget2;
  
  			if ( normalDepthUniform || colorUniform ) {
  
  				resizableMaterials.push( { "material": effect.material, "normalDepth": normalDepthUniform, "color": colorUniform } );
  
  			}
  
  		}
  
  		compFinal.insertPass( effect, -1 );
  
  	};
  
  	this.setScale = function ( scale ) {
  
  		currentScale = scale;
  
  		scaledWidth = Math.floor( currentScale * fullWidth );
  		scaledHeight = Math.floor( currentScale * fullHeight );
  
  		compNormalDepth.setSize( scaledWidth, scaledHeight );
  		compColor.setSize( scaledWidth, scaledHeight );
  		compLight.setSize( scaledWidth, scaledHeight );
  		compFinal.setSize( scaledWidth, scaledHeight );
  
  		compColor.renderTarget2.shareDepthFrom = compNormalDepth.renderTarget2;
  		compLight.renderTarget2.shareDepthFrom = compNormalDepth.renderTarget2;
  
  		for ( var i = 0, il = resizableMaterials.length; i < il; i ++ ) {
  
  			var materialEntry = resizableMaterials[ i ];
  
  			var material = materialEntry.material;
  			var uniforms = material.uniforms;
  
  			var colorLabel = materialEntry.color !== undefined ? materialEntry.color : 'samplerColor';
  			var normalDepthLabel = materialEntry.normalDepth !== undefined ? materialEntry.normalDepth : 'samplerNormalDepth';
  
  			if ( uniforms[ colorLabel ] ) uniforms[ colorLabel ].value = compColor.renderTarget2;
  			if ( uniforms[ normalDepthLabel ] ) uniforms[ normalDepthLabel ].value = compNormalDepth.renderTarget2;
  
  			if ( uniforms[ 'viewWidth' ] ) uniforms[ "viewWidth" ].value = scaledWidth;
  			if ( uniforms[ 'viewHeight' ] ) uniforms[ "viewHeight" ].value = scaledHeight;
  
  		}
  
  		compositePass.uniforms[ 'samplerLight' ].value = compLight.renderTarget2;
  
  		effectFXAA.uniforms[ 'resolution' ].value.set( 1 / fullWidth, 1 / fullHeight );
  
  	};
  
  	this.setSize = function ( width, height ) {
  
  		fullWidth = width;
  		fullHeight = height;
  
  		this.renderer.setSize( fullWidth, fullHeight );
  
  		this.setScale( currentScale );
  
  	};
  
  	//
  
  	function updateLightProxy ( proxy ) {
  
  		var uniforms = proxy.material.uniforms;
  
  		if ( uniforms[ "matProjInverse" ] ) uniforms[ "matProjInverse" ].value = projectionMatrixInverse;
  		if ( uniforms[ "matView" ] ) uniforms[ "matView" ].value = currentCamera.matrixWorldInverse;
  
  		var originalLight = proxy.userData.originalLight;
  
  		if ( originalLight ) {
  
  			proxy.visible = originalLight.visible;
  
  			if ( originalLight instanceof THREE.PointLight ) {
  
  				updatePointLightProxy( proxy );
  
  			} else if ( originalLight instanceof THREE.SpotLight ) {
  
  				updateSpotLightProxy( proxy );
  
  			} else if ( originalLight instanceof THREE.DirectionalLight ) {
  
  				updateDirectionalLightProxy( proxy );
  
  			} else if ( originalLight instanceof THREE.HemisphereLight ) {
  
  				updateHemisphereLightProxy( proxy );
  
  			} else if ( originalLight instanceof THREE.AreaLight ) {
  
  				updateAreaLightProxy( proxy );
  
  			}
  
  		}
  
  	};
  
  	this.render = function ( scene, camera ) {
  
  		// setup deferred properties
  
  		if ( ! scene.userData.lightSceneProxy ) {
  
  			scene.userData.lightSceneProxy = new THREE.Scene();
  			scene.userData.lightSceneFullscreen = new THREE.Scene();
  
  			var meshLight = createDeferredEmissiveLight();
  			scene.userData.lightSceneFullscreen.add( meshLight );
  
  		}
  
  		currentCamera = camera;
  
  		lightSceneProxy = scene.userData.lightSceneProxy;
  		lightSceneFullscreen = scene.userData.lightSceneFullscreen;
  
  		passColor.camera = currentCamera;
  		passNormalDepth.camera = currentCamera;
  		passLightProxy.camera = currentCamera;
  		passLightFullscreen.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 );
  
  		passColor.scene = scene;
  		passNormalDepth.scene = scene;
  		passLightFullscreen.scene = lightSceneFullscreen;
  		passLightProxy.scene = lightSceneProxy;
  
  		scene.traverse( initDeferredProperties );
  
  		// update scene graph only once per frame
  		// (both color and normalDepth passes use exactly the same scene state)
  
  		scene.autoUpdate = false;
  		scene.updateMatrixWorld();
  
  		// 1) g-buffer normals + depth pass
  
  		scene.traverse( setMaterialNormalDepth );
  
  		// clear shared depth buffer
  
  		this.renderer.autoClearDepth = true;
  		this.renderer.autoClearStencil = true;
  
  		// write 1 to shared stencil buffer
  		// for non-background pixels
  
  		//gl.enable( gl.STENCIL_TEST );
  		gl.stencilOp( gl.REPLACE, gl.REPLACE, gl.REPLACE );
  		gl.stencilFunc( gl.ALWAYS, 1, 0xffffffff );
  		gl.clearStencil( 0 );
  
  		compNormalDepth.render();
  
  		// just touch foreground pixels (stencil == 1)
  		// both in color and light passes
  
  		gl.stencilFunc( gl.EQUAL, 1, 0xffffffff );
  		gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP );
  
  		// 2) g-buffer color pass
  
  		scene.traverse( setMaterialColor );
  
  		// must use clean slate depth buffer
  		// otherwise there are z-fighting glitches
  		// not enough precision between two geometry passes
  		// just to use EQUAL depth test
  
  		this.renderer.autoClearDepth = true;
  		this.renderer.autoClearStencil = false;
  
  		compColor.render();
  
  		// 3) light pass
  
  		// do not clear depth buffer in this pass
  		// depth from geometry pass is used for light culling
  		// (write light proxy color pixel if behind scene pixel)
  
  		this.renderer.autoClearDepth = false;
  
  		scene.autoUpdate = true;
  
  		gl.depthFunc( gl.GEQUAL );
  
  		projectionMatrixInverse.getInverse( currentCamera.projectionMatrix );
  
  		for ( var i = 0, il = lightSceneProxy.children.length; i < il; i ++ ) {
  
  			var proxy = lightSceneProxy.children[ i ];
  			updateLightProxy( proxy );
  
  		}
  
  		for ( var i = 0, il = lightSceneFullscreen.children.length; i < il; i ++ ) {
  
  			var proxy = lightSceneFullscreen.children[ i ];
  			updateLightProxy( proxy );
  
  		}
  
  		compLight.render();
  
  		// 4) composite pass
  
  		// return back to usual depth and stencil handling state
  
  		this.renderer.autoClearDepth = true;
  		this.renderer.autoClearStencil = true;
  		gl.depthFunc( gl.LEQUAL );
  		gl.disable( gl.STENCIL_TEST );
  
  		compFinal.render( 0.1 );
  
  	};
  
  	//
  
  	var createRenderTargets = function ( ) {
  
  		var rtParamsFloatLinear = { minFilter: THREE.NearestFilter, magFilter: THREE.LinearFilter, stencilBuffer: true,
  									format: THREE.RGBAFormat, type: THREE.FloatType };
  
  		var rtParamsFloatNearest = { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, stencilBuffer: true,
  									 format: THREE.RGBAFormat, type: THREE.FloatType };
  
  		var rtParamsUByte = { minFilter: THREE.NearestFilter, magFilter: THREE.LinearFilter, stencilBuffer: false,
  							  format: THREE.RGBFormat, type: THREE.UnsignedByteType };
  
  		// g-buffers
  
  		var rtColor   = new THREE.WebGLRenderTarget( scaledWidth, scaledHeight, rtParamsFloatNearest );
  		var rtNormalDepth = new THREE.WebGLRenderTarget( scaledWidth, scaledHeight, rtParamsFloatNearest );
  		var rtLight   = new THREE.WebGLRenderTarget( scaledWidth, scaledHeight, rtParamsFloatLinear );
  		var rtFinal   = new THREE.WebGLRenderTarget( scaledWidth, scaledHeight, rtParamsUByte );
  
  		rtColor.generateMipmaps = false;
  		rtNormalDepth.generateMipmaps = false;
  		rtLight.generateMipmaps = false;
  		rtFinal.generateMipmaps = false;
  
  		// normal + depth composer
  
  		passNormalDepth = new THREE.RenderPass();
  		passNormalDepth.clear = true;
  
  		compNormalDepth = new THREE.EffectComposer( _this.renderer, rtNormalDepth );
  		compNormalDepth.addPass( passNormalDepth );
  
  		// color composer
  
  		passColor = new THREE.RenderPass();
  		passColor.clear = true;
  
  		compColor = new THREE.EffectComposer( _this.renderer, rtColor );
  		compColor.addPass( passColor );
  
  		compColor.renderTarget2.shareDepthFrom = compNormalDepth.renderTarget2;
  
  		// light composer
  
  		passLightFullscreen = new THREE.RenderPass();
  		passLightFullscreen.clear = true;
  
  		passLightProxy = new THREE.RenderPass();
  		passLightProxy.clear = false;
  
  		compLight = new THREE.EffectComposer( _this.renderer, rtLight );
  		compLight.addPass( passLightFullscreen );
  		compLight.addPass( passLightProxy );
  
  		compLight.renderTarget2.shareDepthFrom = compNormalDepth.renderTarget2;
  
  		// final composer
  
  		compositePass = new THREE.ShaderPass( compositeShader );
  		compositePass.uniforms[ 'samplerLight' ].value = compLight.renderTarget2;
  		compositePass.uniforms[ 'brightness' ].value = brightness;
  		compositePass.material.blending = THREE.NoBlending;
  		compositePass.clear = true;
  
  		var defines;
  
  		switch ( tonemapping ) {
  
  			case THREE.SimpleOperator:    defines = { "TONEMAP_SIMPLE": true };    break;
  			case THREE.LinearOperator:    defines = { "TONEMAP_LINEAR": true };    break;
  			case THREE.ReinhardOperator:  defines = { "TONEMAP_REINHARD": true };  break;
  			case THREE.FilmicOperator:    defines = { "TONEMAP_FILMIC": true };    break;
  			case THREE.UnchartedOperator: defines = { "TONEMAP_UNCHARTED": true }; break;
  
  		}
  
  		compositePass.material.defines = defines;
  
  		// FXAA
  
  		effectFXAA = new THREE.ShaderPass( THREE.FXAAShader );
  		effectFXAA.uniforms[ 'resolution' ].value.set( 1 / fullWidth, 1 / fullHeight );
  		effectFXAA.renderToScreen = true;
  
  		//
  
  		compFinal = new THREE.EffectComposer( _this.renderer, rtFinal );
  		compFinal.addPass( compositePass );
  		compFinal.addPass( effectFXAA );
  
  		if ( antialias ) {
  
  			effectFXAA.enabled = true;
  			compositePass.renderToScreen = false;
  
  		} else {
  
  			effectFXAA.enabled = false;
  			compositePass.renderToScreen = true;
  
  		}
  
  	};
  
  	// init
  
  	createRenderTargets();
  
  };
  
  // tonemapping operator types
  
  THREE.NoOperator = 0;
  THREE.SimpleOperator = 1;
  THREE.LinearOperator = 2;
  THREE.ReinhardOperator = 3;
  THREE.FilmicOperator = 4;
  THREE.UnchartedOperator = 5;
  