/** * @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 scaledWidth = Math.floor( currentScale * pixelWidth ); var scaledHeight = Math.floor( currentScale * pixelHeight ); 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 } ); this.renderer.setSize( pixelWidth, pixelHeight ); 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 * pixelWidth ); scaledHeight = Math.floor( currentScale * pixelHeight ); 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 / pixelWidth, 1 / pixelHeight ); }; this.setSize = function ( width, height ) { pixelWidth = width; pixelHeight = height; this.renderer.setSize( pixelWidth, pixelHeight ); 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 / pixelWidth, 1 / pixelHeight ); 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;