/**
* @author alteredq / http://alteredqualia.com/
* @author MPanknin / http://www.redplant.de/
* @author takahiro / https://github.com/takahirox
*
* WebGLDeferredRenderer supports two types of Deferred Renderings.
* One is Classic Deferred Rendering and the other one is
* Light Pre-Pass (Deferred Lighting).
* Classic Deferred Rendering is default. You can use Light Pre-Pass
* by calling .enableLightPrePass( true ) method.
*
* Dependencies
* - THREE.CopyShader
* - THREE.RenderPass
* - THREE.ShaderPass
* - THREE.EffectComposer
* - THREE.FXAAShader
*
* TODO
* - reuse existing glsl
* - 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;
// for Classic Deferred Rendering
var _compColor;
var _passColor, _passForward, _passCopy;
// for Light Pre-Pass
var _compReconstruction;
var _passReconstruction;
// for Common
var _compNormalDepth, _compLight, _compFinal;
var _passNormalDepth, _passLight, _passLightFullscreen, _passFinal, _passFXAA;
var _depthTexture;
var _currentCamera;
var _lightScene, _lightFullscreenScene;
var _antialias = false;
var _hasTransparentObject = false;
var _lightPrePass = false;
var _cacheKeepAlive = false;
var _tmpMaterial = new THREE.ShaderMaterial( { visible: false } );
var _tmpVector3 = new THREE.Vector3();
// scene/material/light cache for deferred rendering.
// save them at the creation and release
// if they're unused removeThresholdCount frames
// unless _cacheKeepAlive is true.
// scene.uuid -> lightScene, lightFullscreenScene
var _lightScenesCache = {};
var _lightFullscreenScenesCache = {};
// object.material.uuid -> deferredMaterial or
// object.material[ n ].uuid -> deferredMaterial
var _normalDepthMaterialsCache = {};
var _normalDepthShininessMaterialsCache = {};
var _colorMaterialsCache = {};
var _reconstructionMaterialsCache = {};
// originalLight.uuid -> deferredLight
var _deferredLightsCache = {};
// deferredLight.uuid -> deferredLightMaterial
var _classicDeferredLightMaterialsCache = {};
var _lightPrePassMaterialsCache = {};
var _removeThresholdCount = 60;
// deferredMaterials.uuid -> object.material or
// deferredMaterials.uuid -> object.material[ n ]
// save before render and release after render.
var _originalMaterialsTable = {};
// object.uuid -> originalOnBeforeRender
// save before render and release after render.
var _originalOnBeforeRendersTable = {};
// object.material.uuid -> object.material.visible or
// object.material[ i ].uuid -> object.material[ i ].visible or
// save before render and release after render.
var _originalVisibleTable = {};
// external properties
this.renderer = undefined;
this.domElement = undefined;
this.forwardRendering = false; // for debug
// private methods
function init( 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;
if ( parameters.cacheKeepAlive !== undefined ) _cacheKeepAlive = parameters.cacheKeepAlive;
initDepthTexture();
initPassNormalDepth();
initPassColor();
initPassLight();
initPassReconstruction();
initPassFinal();
_this.setSize( _width, _height );
_this.setAntialias( antialias );
_this.enableLightPrePass( false );
}
function initDepthTexture() {
_depthTexture = new THREE.DepthTexture(
_width,
_height,
THREE.UnsignedInt248Type,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
THREE.DepthStencilFormat
);
}
function initPassNormalDepth() {
_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: _depthTexture
} );
rt.texture.generateMipamps = false;
_compNormalDepth = new THREE.EffectComposer( _this.renderer, rt );
_compNormalDepth.addPass( _passNormalDepth );
}
function initPassColor() {
_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: _depthTexture
} );
rt.texture.generateMipamps = false;
_compColor = new THREE.EffectComposer( _this.renderer, rt );
_compColor.addPass( _passColor );
}
function initPassLight() {
_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: _depthTexture
} );
rt.texture.generateMipamps = false;
_compLight = new THREE.EffectComposer( _this.renderer, rt );
_compLight.addPass( _passLightFullscreen );
_compLight.addPass( _passLight );
}
function initPassReconstruction() {
_passReconstruction = new THREE.RenderPass();
_passReconstruction.clear = true;
var rt = new THREE.WebGLRenderTarget( _width, _height, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
type: THREE.FloatType,
depthTexture: _depthTexture
} );
rt.texture.generateMipamps = false;
_compReconstruction = new THREE.EffectComposer( _this.renderer, rt );
_compReconstruction.addPass( _passReconstruction );
}
function initPassFinal() {
_passFinal = new THREE.ShaderPass( THREE.ShaderDeferred[ 'final' ] );
_passFinal.clear = true;
_passFinal.uniforms.samplerResult.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 );
_passFXAA = new THREE.ShaderPass( THREE.FXAAShader );
var rt = new THREE.WebGLRenderTarget( _width, _height, {
minFilter: THREE.NearestFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBFormat,
type: THREE.UnsignedByteType,
depthTexture: _depthTexture
} );
rt.texture.generateMipamps = false;
_compFinal = new THREE.EffectComposer( _this.renderer, rt );
_compFinal.addPass( _passFinal );
_compFinal.addPass( _passForward );
_compFinal.addPass( _passCopy );
_compFinal.addPass( _passFXAA );
}
function initLightScene( scene ) {
var lightSceneData = _lightScenesCache[ scene.uuid ];
var lightFullscreenSceneData = _lightFullscreenScenesCache[ scene.uuid ];
if ( lightSceneData === undefined ) {
var s = new THREE.Scene();
s.userData.lights = {};
lightSceneData = createCacheData();
lightSceneData.scene = s;
_lightScenesCache[ scene.uuid ] = lightSceneData;
}
if ( lightFullscreenSceneData === undefined ) {
var s = new THREE.Scene();
s.userData.lights = {};
var emissiveLight = createDeferredEmissiveLight();
s.userData.emissiveLight = emissiveLight;
s.add( emissiveLight );
lightFullscreenSceneData = createCacheData();
lightFullscreenSceneData.scene = s;
_lightFullscreenScenesCache[ scene.uuid ] = lightFullscreenSceneData;
}
lightSceneData.used = true;
lightFullscreenSceneData.used = true;
var lightScene = lightSceneData.scene;
var lightFullscreenScene = lightFullscreenSceneData.scene;
// emissiveLight is only for Classic Deferred Rendering
lightFullscreenScene.userData.emissiveLight.visible = ! _lightPrePass;
_lightScene = lightScene;
_lightFullscreenScene = lightFullscreenScene;
}
function getMaterialFromCacheOrCreate( originalMaterial, cache, createFunc, updateFunc ) {
var data = cache[ originalMaterial.uuid ];
if ( data === undefined ) {
data = createCacheData();
data.material = createFunc( originalMaterial );
cache[ originalMaterial.uuid ] = data;
}
data.used = true;
updateFunc( data.material, originalMaterial );
_originalMaterialsTable[ data.material.uuid ] = originalMaterial;
return data.material;
}
function overrideMaterialAndOnBeforeRender( object, getMaterialFunc, onBeforeRender ) {
if ( object.material === undefined ) return;
if ( Array.isArray( object.material ) ) {
for ( var i = 0, il = object.material.length; i < il; i ++ ) {
object.material[ i ] = getMaterialFunc( object.material[ i ] );
}
} else {
object.material = getMaterialFunc( object.material );
}
object.onBeforeRender = onBeforeRender;
}
function restoreOriginalMaterial( object ) {
if ( object.material === undefined ) return;
if ( Array.isArray( object.material ) ) {
for ( var i = 0, il = object.material.length; i < il; i ++ ) {
object.material[ i ] = _originalMaterialsTable[ object.material[ i ].uuid ];
}
} else {
object.material = _originalMaterialsTable[ object.material.uuid ];
}
}
function setMaterialNormalDepth( object ) {
overrideMaterialAndOnBeforeRender( object, getNormalDepthMaterial, updateDeferredNormalDepthUniforms );
}
function getNormalDepthMaterial( originalMaterial ) {
return getMaterialFromCacheOrCreate(
originalMaterial,
( _lightPrePass ) ? _normalDepthShininessMaterialsCache : _normalDepthMaterialsCache,
createDeferredNormalDepthMaterial,
updateDeferredNormalDepthMaterial
);
}
function createDeferredNormalDepthMaterial( originalMaterial ) {
var shader = ( _lightPrePass ) ? THREE.ShaderDeferred[ 'normalDepthShininess' ] : THREE.ShaderDeferred[ 'normalDepth' ];
return new THREE.ShaderMaterial( {
uniforms: Object.assign( {}, shader.uniforms ),
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
blending: THREE.NoBlending
} );
}
function updateDeferredNormalDepthMaterial( 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;
}
}
function updateDeferredNormalDepthUniforms( renderer, scene, camera, geometry, material, group ) {
if ( ! _lightPrePass ) return;
var originalMaterial = _originalMaterialsTable[ material.uuid ];
if ( originalMaterial === undefined || originalMaterial.shininess === undefined ) return;
material.uniforms.shininess.value = originalMaterial.shininess;
}
function setMaterialColor( object ) {
overrideMaterialAndOnBeforeRender( object, getColorMaterial, updateDeferredColorUniforms );
}
function getColorMaterial( originalMaterial ) {
return getMaterialFromCacheOrCreate(
originalMaterial,
_colorMaterialsCache,
createDeferredColorMaterial,
updateDeferredColorMaterial
);
}
function createDeferredColorMaterial( originalMaterial ) {
var shader = THREE.ShaderDeferred[ 'color' ];
var material = new THREE.ShaderMaterial( {
uniforms: Object.assign( {}, shader.uniforms ),
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
blending: THREE.NoBlending
} );
if ( originalMaterial.map !== undefined ) material.map = originalMaterial.map;
return material;
}
function updateDeferredColorMaterial( material, originalMaterial ) {
if ( originalMaterial.map !== undefined ) material.map = originalMaterial.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;
}
}
function updateDeferredColorUniforms( renderer, scene, camera, geometry, material, group ) {
var originalMaterial = _originalMaterialsTable[ material.uuid ];
var uniforms = material.uniforms;
var diffuse, emissive;
if ( originalMaterial.isMeshBasicMaterial === true ) {
emissive = originalMaterial.color;
} else {
diffuse = originalMaterial.color;
emissive = originalMaterial.emissive;
}
var specular = originalMaterial.specular;
var shininess = originalMaterial.shininess;
var map = originalMaterial.map;
if ( diffuse !== undefined ) uniforms.diffuse.value.copy( diffuse );
if ( emissive !== undefined ) uniforms.emissive.value.copy( emissive );
if ( specular !== undefined ) uniforms.specular.value.copy( specular );
if ( shininess !== undefined && uniforms.shininess !== undefined ) uniforms.shininess.value = shininess;
if ( map !== undefined ) uniforms.map.value = map;
}
function setMaterialReconstruction( object ) {
overrideMaterialAndOnBeforeRender( object, getReconstructionMaterial, updateDeferredReconstructionUniforms );
}
function getReconstructionMaterial( originalMaterial ) {
if ( originalMaterial.transparent === true ) {
_originalMaterialsTable[ originalMaterial.uuid ] = originalMaterial;
return originalMaterial;
}
return getMaterialFromCacheOrCreate(
originalMaterial,
_reconstructionMaterialsCache,
createDeferredReconstructionMaterial,
updateDeferredReconstructionMaterial
);
}
function createDeferredReconstructionMaterial( originalMaterial ) {
var shader = THREE.ShaderDeferred[ 'reconstruction' ];
var material = new THREE.ShaderMaterial( {
uniforms: Object.assign( {}, shader.uniforms ),
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
blending: THREE.NoBlending
} );
if ( originalMaterial.map !== undefined ) material.map = originalMaterial.map;
return material;
}
function updateDeferredReconstructionMaterial( material, originalMaterial ) {
updateDeferredColorMaterial( material, originalMaterial );
}
function updateDeferredReconstructionUniforms( renderer, scene, camera, geometry, material, group ) {
if ( material.transparent === true ) {
// 'this' is object here because this method is set as object.onBefore()
var onBeforeRender = _originalOnBeforeRendersTable[ this.uuid ];
if ( onBeforeRender ) {
onBeforeRender.call( this, renderer, scene, camera, geometry, material, group );
}
return;
}
updateDeferredColorUniforms( renderer, scene, camera, geometry, material, group );
material.uniforms.samplerLight.value = _compLight.renderTarget2.texture;
}
function setVisibleForForwardRendering( object ) {
if ( object.material === undefined ) return;
if ( Array.isArray( object.material ) ) {
for ( var i = 0, il = object.material.length; i < il; i ++ ) {
if ( _originalVisibleTable[ object.material[ i ].uuid ] === undefined ) {
_originalVisibleTable[ object.material[ i ].uuid ] = object.material[ i ].visible;
object.material[ i ].visible = object.material[ i ].transparent && object.material[ i ].visible;
}
}
} else {
if ( _originalVisibleTable[ object.material.uuid ] === undefined ) {
_originalVisibleTable[ object.material.uuid ] = object.material.visible;
object.material.visible = object.material.transparent && object.material.visible;
}
}
}
function restoreVisible( object ) {
if ( object.material === undefined ) return;
if ( Array.isArray( object.material ) ) {
for ( var i = 0, il = object.material.length; i < il; i ++ ) {
object.material[ i ].visible = _originalVisibleTable[ object.material[ i ].uuid ];
}
} else {
object.material.visible = _originalVisibleTable[ object.material.uuid ];
}
}
function createDeferredEmissiveLight() {
var shader = THREE.ShaderDeferred[ 'emissiveLight' ];
var material = new THREE.ShaderMaterial( {
uniforms: Object.assign( {}, shader.uniforms ),
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader,
blending: THREE.NoBlending,
depthWrite: false
} );
var geometry = new THREE.PlaneBufferGeometry( 2, 2 );
var mesh = new THREE.Mesh( geometry, material );
mesh.onBeforeRender = function ( renderer, scene, camera, geometry, material, group ) {
material.uniforms.samplerColor.value = _compColor.renderTarget2.texture;
};
return mesh;
}
function createDeferredLight( originalLight ) {
if ( originalLight.isPointLight ) {
return createDeferredPointLight( originalLight );
} else if ( originalLight.isSpotLight ) {
return createDeferredSpotLight( originalLight );
} else if ( originalLight.isDirectionalLight ) {
return createDeferredDirectionalLight( originalLight );
}
return null;
}
function createDeferredLightMaterial( originalLight ) {
if ( originalLight.isPointLight ) {
return createDeferredPointLightMaterial();
} else if ( originalLight.isSpotLight ) {
return createDeferredSpotLightMaterial();
} else if ( originalLight.isDirectionalLight ) {
return createDeferredDirectionalLightMaterial();
}
return null;
}
function getDeferredLightMaterial( light ) {
var cache = ( _lightPrePass ) ? _lightPrePassMaterialsCache : _classicDeferredLightMaterialsCache;
var data = cache[ light.uuid ];
if ( data === undefined ) {
data = createCacheData();
data.material = createDeferredLightMaterial( light.userData.originalLight );
cache[ light.uuid ] = data;
}
data.used = true;
return data.material;
}
function updateDeferredLight( light ) {
var originalLight = light.userData.originalLight;
if ( originalLight.isPointLight ) {
updateDeferredPointLight( light );
}
}
function createDeferredLightMesh( light, geometry ) {
var mesh = new THREE.Mesh( geometry, _tmpMaterial );
mesh.userData.originalLight = light;
return mesh;
}
function createDeferredLightShaderMaterial( shader ) {
var material = new THREE.ShaderMaterial( {
uniforms: Object.assign( {}, shader.uniforms ),
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
} );
if ( _lightPrePass ) material.premultipliedAlpha = true;
return material;
}
function updateDeferredLightCommonUniforms( uniforms ) {
if ( _lightPrePass ) {
uniforms.samplerNormalDepthShininess.value = _compNormalDepth.renderTarget2.texture;
} else {
uniforms.samplerNormalDepth.value = _compNormalDepth.renderTarget2.texture;
uniforms.samplerColor.value = _compColor.renderTarget2.texture;
}
}
function createDeferredPointLight( light ) {
var mesh = createDeferredLightMesh( light, new THREE.SphereGeometry( 1, 16, 8 ) );
mesh.onBeforeRender = updateDeferredPointLightUniforms;
return mesh;
}
/*
* optimization:
* Renders PointLight only back face with stencil test.
*/
function createDeferredPointLightMaterial() {
var shader = ( _lightPrePass ) ? THREE.ShaderDeferred[ 'pointLightPre' ] : THREE.ShaderDeferred[ 'pointLight' ];
var material = createDeferredLightShaderMaterial( shader );
material.side = THREE.BackSide;
material.depthFunc = THREE.GreaterEqualDepth;
return material;
}
function updateDeferredPointLight( light ) {
var originalLight = light.userData.originalLight;
var distance = originalLight.distance;
if ( distance > 0 ) {
light.scale.set( 1, 1, 1 ).multiplyScalar( distance );
light.position.setFromMatrixPosition( originalLight.matrixWorld );
}
}
function updateDeferredPointLightUniforms( renderer, scene, camera, geometry, material, group ) {
var light = this;
var originalLight = light.userData.originalLight;
var distance = originalLight.distance;
var uniforms = material.uniforms;
uniforms.lightColor.value.copy( originalLight.color );
if ( distance > 0 ) {
uniforms.lightRadius.value = distance;
uniforms.lightIntensity.value = originalLight.intensity;
uniforms.lightPositionVS.value.setFromMatrixPosition( originalLight.matrixWorld ).applyMatrix4( _currentCamera.matrixWorldInverse );
} else {
uniforms.lightRadius.value = Infinity;
}
updateDeferredLightCommonUniforms( uniforms );
}
function createDeferredSpotLight( light ) {
var mesh = createDeferredLightMesh( light, new THREE.PlaneBufferGeometry( 2, 2 ) );
mesh.onBeforeRender = updateDeferredSpotLightUniforms;
return mesh;
}
function createDeferredSpotLightMaterial() {
var shader = ( _lightPrePass ) ? THREE.ShaderDeferred[ 'spotLightPre' ] : THREE.ShaderDeferred[ 'spotLight' ];
var material = createDeferredLightShaderMaterial( shader );
material.depthTest = false;
return material;
}
function updateDeferredSpotLightUniforms( renderer, scene, camera, geometry, material, group ) {
var light = this;
var originalLight = light.userData.originalLight;
var uniforms = light.material.uniforms;
uniforms.lightAngle.value = originalLight.angle;
uniforms.lightColor.value.copy( originalLight.color );
uniforms.lightIntensity.value = originalLight.intensity;
uniforms.lightPositionVS.value.setFromMatrixPosition( originalLight.matrixWorld ).applyMatrix4( _currentCamera.matrixWorldInverse );
var vec = uniforms.lightDirectionVS.value;
var vec2 = _tmpVector3;
vec.setFromMatrixPosition( originalLight.matrixWorld );
vec2.setFromMatrixPosition( originalLight.target.matrixWorld );
vec.sub( vec2 ).normalize().transformDirection( _currentCamera.matrixWorldInverse );
updateDeferredLightCommonUniforms( uniforms );
}
function createDeferredDirectionalLight( light ) {
var mesh = createDeferredLightMesh( light, new THREE.PlaneBufferGeometry( 2, 2 ) );
mesh.onBeforeRender = updateDeferredDirectionalLightUniforms;
return mesh;
}
function createDeferredDirectionalLightMaterial() {
var shader = ( _lightPrePass ) ? THREE.ShaderDeferred[ 'directionalLightPre' ] : THREE.ShaderDeferred[ 'directionalLight' ];
var material = createDeferredLightShaderMaterial( shader );
material.depthTest = false;
return material;
}
function updateDeferredDirectionalLightUniforms( renderer, scene, camera, geometry, material, group ) {
var light = this;
var originalLight = light.userData.originalLight;
var uniforms = light.material.uniforms;
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( _currentCamera.matrixWorldInverse );
updateDeferredLightCommonUniforms( uniforms );
}
function saveOriginalOnBeforeRenderAndCheckTransparency( object ) {
if ( object.material === undefined ) return;
_originalOnBeforeRendersTable[ object.uuid ] = object.onBeforeRender;
// _hasTransparentObject is used only for Classic Deferred Rendering
if ( _hasTransparentObject || _lightPrePass ) return;
if ( ! object.visible ) return;
if ( Array.isArray( object.material ) ) {
for ( var i = 0, il = object.material.length; i < il; i ++ ) {
if ( object.material[ i ].visible === true && object.material[ i ].transparent === true ) {
_hasTransparentObject = true;
break;
}
}
} else {
if ( object.material.visible === true && object.material.transparent === true ) _hasTransparentObject = true;
}
}
function restoreOriginalOnBeforeRender( object ) {
if ( object.material === undefined ) return;
object.onBeforeRender = _originalOnBeforeRendersTable[ object.uuid ];
}
function addDeferredLightsToLightScene( object ) {
if ( object.isLight !== true ) return;
var data = _deferredLightsCache[ object.uuid ];
if ( data === undefined ) {
data = createCacheData();
data.light = createDeferredLight( object );
_deferredLightsCache[ object.uuid ] = data;
}
data.used = true;
var light = data.light;
if ( light === null ) return;
var scene = ( object.isPointLight === true ) ? _lightScene : _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;
}
function updateDeferredLightsInLightScene( scene ) {
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 {
var light = lights[ key ].light;
light.material = getDeferredLightMaterial( light );
updateDeferredLight( light );
lights[ key ].found = false;
}
}
}
function updateDeferredCommonUniforms( camera ) {
var uniforms = THREE.ShaderDeferredCommon[ 'commonUniforms' ];
uniforms.viewWidth.value = _width;
uniforms.viewHeight.value = _height;
uniforms.matProjInverse.value.getInverse( camera.projectionMatrix );
}
function enableFinalPasses() {
if ( _lightPrePass ) {
_passForward.renderToScreen = false;
_passForward.enabled = false;
_passCopy.renderToScreen = false;
_passCopy.enabled = false;
if ( _antialias ) {
_passFinal.renderToScreen = false;
_passFXAA.renderToScreen = true;
_passFXAA.enabled = true;
} else {
_passFinal.renderToScreen = true;
_passFXAA.renderToScreen = false;
_passFXAA.enabled = false;
}
} else {
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;
}
}
}
}
function createCacheData() {
return {
used: true,
keepAlive: _cacheKeepAlive,
count: 0
};
}
function cleanupCache( cache ) {
var keys = Object.keys( cache );
for ( var i = 0, il = keys.length; i < il; i ++ ) {
var key = keys[ i ];
if ( cache[ key ].used === false ) {
cache[ key ].count++;
if ( cache[ key ].keepAlive === false && cache[ key ].count > _removeThresholdCount ) {
delete cache[ key ];
}
} else {
cache[ key ].used = false;
cache[ key ].count = 0;
}
}
}
function cleanupTable( table ) {
var keys = Object.keys( table );
for ( var i = 0, il = keys.length; i < il; i ++ ) {
var key = keys[ i ];
table[ key ] = undefined;
}
}
function cleanupCaches() {
cleanupCache( _lightScenesCache );
cleanupCache( _lightFullscreenScenesCache );
cleanupCache( _normalDepthMaterialsCache );
cleanupCache( _normalDepthShininessMaterialsCache );
cleanupCache( _colorMaterialsCache );
cleanupCache( _reconstructionMaterialsCache );
cleanupCache( _classicDeferredLightMaterialsCache );
cleanupCache( _lightPrePassMaterialsCache );
cleanupCache( _deferredLightsCache );
cleanupTable( _originalMaterialsTable );
cleanupTable( _originalOnBeforeRendersTable );
cleanupTable( _originalVisibleTable );
}
/*
* Classic Deferred Rendering
*
* 1) g-buffer normal + depth pass
*
* RGB: normal
* A: depth
*
*
* Light Pre-Pass Rendering
*
* 1') g-buffer normal + depth pass + shininess
*
* RG: normal
* B: shininess
* A: depth
*/
function renderNormalDepth( 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();
scene.traverse( restoreOriginalMaterial );
}
/*
* Classic Deferred Rendering
*
* 2) g-buffer color pass
*
* R: diffuse
* G: emissive
* B: specular
* A: shininess
*/
function renderColor( 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();
scene.traverse( restoreOriginalMaterial );
}
/*
* Classic Deferred Rendering
*
* 3) light pass
*/
function renderLight( scene, camera ) {
scene.traverse( addDeferredLightsToLightScene );
updateDeferredLightsInLightScene( _lightScene );
updateDeferredLightsInLightScene( _lightFullscreenScene );
_passLight.scene = _lightScene;
_passLight.camera = camera;
_passLightFullscreen.scene = _lightFullscreenScene;
_this.renderer.autoClearDepth = false;
_this.renderer.autoClearStencil = false;
_compLight.render();
_gl.disable( _gl.STENCIL_TEST );
}
/*
* Light Pre-Pass Rendering
*
* 2') Light pre pass
*/
function renderLightPre( scene, camera ) {
scene.traverse( addDeferredLightsToLightScene );
updateDeferredLightsInLightScene( _lightScene );
updateDeferredLightsInLightScene( _lightFullscreenScene );
_passLight.scene = _lightScene;
_passLight.camera = camera;
_passLightFullscreen.scene = _lightFullscreenScene;
_this.renderer.autoClearDepth = false;
_this.renderer.autoClearStencil = false;
_gl.stencilFunc( _gl.EQUAL, 1, 0xffffffff );
_gl.stencilOp( _gl.KEEP, _gl.KEEP, _gl.KEEP );
_compLight.render();
}
/*
* Light Pre-Pass Rendering
*
* 3') Reconstruction pass
*
* Transprency handling:
* Here renders transparent objects with normal forward rendering.
*/
function renderReconstruction( scene, camera ) {
scene.traverse( setMaterialReconstruction );
_passReconstruction.scene = scene;
_passReconstruction.camera = camera;
_this.renderer.autoClearDepth = false;
_this.renderer.autoClearStencil = false;
_compReconstruction.render();
_gl.disable( _gl.STENCIL_TEST );
scene.traverse( restoreOriginalMaterial );
}
/*
* Classic Deferred Rendering
*
* 4) Final pass
*
* transparency handling:
* If there's any transparent objects, here renders them on the deferred rendering result
* with normal forward rendering. This may be the easist way but heavy.
* We should consider any better ways someday.
*
*
* Light Pre-Pass Rendering
*
* 4') Final pass
*
*
* Common
*
* antialias handling:
* Here uses postprocessing FXAA for antialias.
*
*/
function renderFinal( scene, camera ) {
if ( ! _lightPrePass && _hasTransparentObject ) {
scene.traverse( setVisibleForForwardRendering );
scene.traverse( restoreOriginalOnBeforeRender );
_passForward.scene = scene;
_passForward.camera = camera;
}
enableFinalPasses();
_this.renderer.autoClearDepth = false;
_this.renderer.autoClearStencil = false;
_compFinal.render();
if ( ! _lightPrePass && _hasTransparentObject ) {
scene.traverse( restoreVisible );
}
}
// 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 );
_compReconstruction.setSize( _width, _height );
_compFinal.setSize( _width, _height );
_depthTexture.image.width = _width;
_depthTexture.image.height = _height;
_depthTexture.needsUpdate = true;
_passFXAA.uniforms.resolution.value.set( 1 / _width, 1 / _height );
};
this.setAntialias = function ( enabled ) {
_antialias = enabled;
};
this.enableLightPrePass = function ( enabled ) {
_lightPrePass = enabled;
_passFinal.uniforms.samplerResult.value = ( _lightPrePass ) ? _compReconstruction.renderTarget2.texture : _compLight.renderTarget2.texture;
};
this.render = function ( scene, camera ) {
// for debug to compare with normal forward rendering
if ( this.forwardRendering ) {
this.renderer.render( scene, camera );
return;
}
var currentSceneAutoUpdate = scene.autoUpdate;
var currentAutoClearColor = this.renderer.autoClearColor;
var currentAutoClearDepth = this.renderer.autoClearDepth;
var currentAutoClearStencil = this.renderer.autoClearStencil;
_currentCamera = camera;
initLightScene( scene );
scene.autoUpdate = false;
scene.updateMatrixWorld();
_hasTransparentObject = false;
scene.traverse( saveOriginalOnBeforeRenderAndCheckTransparency );
updateDeferredCommonUniforms( camera );
renderNormalDepth( scene, camera );
if ( _lightPrePass ) {
renderLightPre( scene, camera );
renderReconstruction( scene, camera );
} else {
renderColor( scene, camera );
renderLight( scene, camera );
}
renderFinal( scene, camera );
scene.traverse( restoreOriginalOnBeforeRender );
cleanupCaches();
scene.autoUpdate = currentSceneAutoUpdate;
this.renderer.autoClearColor = currentAutoClearColor;
this.renderer.autoClearDepth = currentAutoClearDepth;
this.renderer.autoClearStencil = currentAutoClearStencil;
};
// 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" ),
// Refer to http://aras-p.info/texts/CompactNormalStorage.html
packNormal: [
"vec2 normal_to_vec2( vec3 normal ) {",
" return normal.xy / sqrt( normal.z * 8.0 + 8.0 ) + 0.5;",
"}"
].join( "\n" ),
unpackVector2: [
"vec3 vec2_to_normal( vec2 data ) {",
" vec2 fenc = data * 4.0 - 2.0;",
" float f = dot( fenc, fenc );",
" float g = sqrt( 1.0 - f / 4.0 );",
" vec3 normal;",
" normal.xy = fenc * g;",
" normal.z = 1.0 - f / 2.0;",
" return normal;",
"}"
].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" ),
packNormalDepthShininess: [
"vec4 packedNormalDepthShininess;",
"packedNormalDepthShininess.xy = normal_to_vec2( normal );",
"packedNormalDepthShininess.z = shininess;",
"packedNormalDepthShininess.w = position.z / position.w;"
].join( "\n" ),
unpackNormalDepthShininess: [
"vec4 normalDepthMap = texture2D( samplerNormalDepthShininess, texCoord );",
"float depth = normalDepthMap.w;",
"if ( depth == 0.0 ) discard;",
"vec3 normal = vec2_to_normal( normalDepthMap.xy );",
"float shininess = normalDepthMap.z;"
].join( "\n" ),
packColor: [
"vec4 packedColor;",
"packedColor.x = vec3_to_float( diffuseColor.rgb );",
"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" ),
packLight: [
"vec4 packedLight;",
"packedLight.xyz = lightIntensity * lightColor * max( dot( lightVector, normal ), 0.0 ) * attenuation;",
"packedLight.w = lightIntensity * specular * max( dot( lightVector, normal ), 0.0 ) * attenuation;"
].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" ),
// TODO: calculate schlick
computeSpecular: [
"vec3 halfVector = normalize( lightVector - normalize( vertexPositionVS.xyz ) );",
"float dotNormalHalf = max( dot( normal, halfVector ), 0.0 );",
"float specular = 0.31830988618 * ( shininess * 0.5 + 1.0 ) * pow( dotNormalHalf, shininess );"
].join( "\n" ),
combine: [
"gl_FragColor = vec4( lightIntensity * lightColor * max( dot( lightVector, normal ), 0.0 ) * ( diffuseColor + specular * specularColor ) * attenuation, 1.0 );"
].join( "\n" )
};
THREE.ShaderDeferredCommon = {
commonUniforms: {
matProjInverse: new THREE.Uniform( new THREE.Matrix4() ),
viewWidth: new THREE.Uniform( 800 ),
viewHeight: new THREE.Uniform( 600 )
}
};
THREE.ShaderDeferred = {
normalDepth: {
uniforms: {},
vertexShader: [
"varying vec3 vNormal;",
"varying vec4 vPosition;",
"#include <morphtarget_pars_vertex>",
"#include <skinning_pars_vertex>",
"void main() {",
"#include <begin_vertex>",
"#include <beginnormal_vertex>",
"#include <skinbase_vertex>",
"#include <skinnormal_vertex>",
"#include <defaultnormal_vertex>",
"#include <morphtarget_vertex>",
"#include <skinning_vertex>",
"#include <project_vertex>",
" vNormal = normalize( transformedNormal );",
" 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: new THREE.Uniform( null ),
offsetRepeat: new THREE.Uniform( new THREE.Vector4( 0, 0, 1, 1 ) ),
diffuse: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
emissive: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
specular: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
shininess: new THREE.Uniform( 30.0 )
},
vertexShader: [
"#include <uv_pars_vertex>",
"#include <morphtarget_pars_vertex>",
"#include <skinning_pars_vertex>",
"void main() {",
"#include <uv_vertex>",
"#include <begin_vertex>",
"#include <beginnormal_vertex>",
"#include <skinbase_vertex>",
"#include <skinnormal_vertex>",
"#include <defaultnormal_vertex>",
"#include <morphtarget_vertex>",
"#include <skinning_vertex>",
"#include <project_vertex>",
"}"
].join( "\n" ),
fragmentShader: [
"uniform vec3 diffuse;",
"uniform vec3 emissive;",
"uniform vec3 specular;",
"uniform float shininess;",
"#include <uv_pars_fragment>",
"#include <map_pars_fragment>",
THREE.DeferredShaderChunk[ "packVector3" ],
"void main() {",
" vec4 diffuseColor = vec4( diffuse, 1.0 );",
" vec3 emissiveColor = emissive;",
" vec3 specularColor = specular;",
"#include <map_fragment>",
THREE.DeferredShaderChunk[ "packColor" ],
" gl_FragColor = packedColor;",
"}"
].join( "\n" )
},
emissiveLight: {
uniforms: Object.assign(
{
samplerColor: new THREE.Uniform( null )
},
THREE.ShaderDeferredCommon[ 'commonUniforms' ]
),
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: Object.assign(
{
samplerNormalDepth: new THREE.Uniform( null ),
samplerColor: new THREE.Uniform( null ),
lightColor: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
lightPositionVS: new THREE.Uniform( new THREE.Vector3( 0, 1, 0 ) ),
lightIntensity: new THREE.Uniform( 1.0 ),
lightRadius: new THREE.Uniform( 1.0 )
},
THREE.ShaderDeferredCommon[ 'commonUniforms' ]
),
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: Object.assign(
{
samplerNormalDepth: new THREE.Uniform( null ),
samplerColor: new THREE.Uniform( null ),
lightColor: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
lightDirectionVS: new THREE.Uniform( new THREE.Vector3( 0, 1, 0 ) ),
lightPositionVS: new THREE.Uniform( new THREE.Vector3( 0, 1, 0 ) ),
lightAngle: new THREE.Uniform( 1.0 ),
lightIntensity: new THREE.Uniform( 1.0 )
},
THREE.ShaderDeferredCommon[ 'commonUniforms' ]
),
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 );",
" 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: Object.assign(
{
samplerNormalDepth: new THREE.Uniform( null ),
samplerColor: new THREE.Uniform( null ),
lightColor: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
lightDirectionVS: new THREE.Uniform( new THREE.Vector3( 0, 1, 0 ) ),
lightIntensity: new THREE.Uniform( 1.0 )
},
THREE.ShaderDeferredCommon[ 'commonUniforms' ]
),
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' )
},
normalDepthShininess: {
uniforms: {
shininess: new THREE.Uniform( 30.0 )
},
vertexShader: [
"varying vec3 vNormal;",
"varying vec4 vPosition;",
"#include <morphtarget_pars_vertex>",
"#include <skinning_pars_vertex>",
"void main() {",
"#include <begin_vertex>",
"#include <beginnormal_vertex>",
"#include <skinbase_vertex>",
"#include <skinnormal_vertex>",
"#include <defaultnormal_vertex>",
"#include <morphtarget_vertex>",
"#include <skinning_vertex>",
"#include <project_vertex>",
" vNormal = normalize( transformedNormal );",
" vPosition = gl_Position;",
"}"
].join( "\n" ),
fragmentShader: [
"varying vec3 vNormal;",
"varying vec4 vPosition;",
"uniform float shininess;",
THREE.DeferredShaderChunk[ "packNormal" ],
"void main() {",
" vec3 normal = vNormal;",
" vec4 position = vPosition;",
THREE.DeferredShaderChunk[ "packNormalDepthShininess" ],
" gl_FragColor = packedNormalDepthShininess;",
"}"
].join( "\n" )
},
pointLightPre: {
uniforms: Object.assign(
{
samplerNormalDepthShininess: new THREE.Uniform( null ),
lightColor: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
lightPositionVS: new THREE.Uniform( new THREE.Vector3( 0, 1, 0 ) ),
lightIntensity: new THREE.Uniform( 1.0 ),
lightRadius: new THREE.Uniform( 1.0 )
},
THREE.ShaderDeferredCommon[ 'commonUniforms' ]
),
vertexShader: [
"void main() {",
" gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join( "\n" ),
fragmentShader: [
"uniform sampler2D samplerNormalDepthShininess;",
"uniform float viewHeight;",
"uniform float viewWidth;",
"uniform vec3 lightColor;",
"uniform vec3 lightPositionVS;",
"uniform float lightIntensity;",
"uniform float lightRadius;",
"uniform mat4 matProjInverse;",
THREE.DeferredShaderChunk[ "unpackFloat" ],
THREE.DeferredShaderChunk[ "unpackVector2" ],
"void main() {",
THREE.DeferredShaderChunk[ "computeTextureCoord" ],
THREE.DeferredShaderChunk[ "unpackNormalDepthShininess" ],
THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
" vec3 lightVector = lightPositionVS - vertexPositionVS.xyz;",
" float distance = length( lightVector );",
" if ( distance > lightRadius ) discard;",
" lightVector = normalize( lightVector );",
THREE.DeferredShaderChunk[ "computeSpecular" ],
" float attenuation = saturate( -distance / lightRadius + 1.0 );",
THREE.DeferredShaderChunk[ "packLight" ],
" gl_FragColor = packedLight;",
"}"
].join( "\n" )
},
spotLightPre: {
uniforms: Object.assign(
{
samplerNormalDepthShininess: new THREE.Uniform( null ),
lightColor: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
lightDirectionVS: new THREE.Uniform( new THREE.Vector3( 0, 1, 0 ) ),
lightPositionVS: new THREE.Uniform( new THREE.Vector3( 0, 1, 0 ) ),
lightAngle: new THREE.Uniform( 1.0 ),
lightIntensity: new THREE.Uniform( 1.0 )
},
THREE.ShaderDeferredCommon[ 'commonUniforms' ]
),
vertexShader: [
"void main() { ",
" gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
"}"
].join( "\n" ),
fragmentShader: [
"uniform sampler2D samplerNormalDepthShininess;",
"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" ],
THREE.DeferredShaderChunk[ "unpackVector2" ],
"void main() {",
THREE.DeferredShaderChunk[ "computeTextureCoord" ],
THREE.DeferredShaderChunk[ "unpackNormalDepthShininess" ],
THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
" vec3 lightVector = normalize( lightPositionVS.xyz - vertexPositionVS.xyz );",
" float rho = dot( lightDirectionVS, lightVector );",
" float rhoMax = cos( lightAngle );",
" 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 );",
" }",
THREE.DeferredShaderChunk[ "computeSpecular" ],
" const float attenuation = 1.0;",
THREE.DeferredShaderChunk[ "packLight" ],
" gl_FragColor = spot * packedLight;",
"}"
].join( "\n" )
},
directionalLightPre: {
uniforms: Object.assign(
{
samplerNormalDepthShininess: new THREE.Uniform( null ),
lightColor: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
lightDirectionVS: new THREE.Uniform( new THREE.Vector3( 0, 1, 0 ) ),
lightIntensity: new THREE.Uniform( 1.0 )
},
THREE.ShaderDeferredCommon[ 'commonUniforms' ]
),
vertexShader: [
"void main() { ",
" gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
"}"
].join( '\n' ),
fragmentShader: [
"uniform sampler2D samplerNormalDepthShininess;",
"uniform float viewHeight;",
"uniform float viewWidth;",
"uniform vec3 lightColor;",
"uniform vec3 lightDirectionVS;",
"uniform float lightIntensity;",
"uniform mat4 matProjInverse;",
THREE.DeferredShaderChunk[ "unpackFloat" ],
THREE.DeferredShaderChunk[ "unpackVector2" ],
"void main() {",
THREE.DeferredShaderChunk[ "computeTextureCoord" ],
THREE.DeferredShaderChunk[ "unpackNormalDepthShininess" ],
THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
" vec3 lightVector = normalize( lightDirectionVS );",
THREE.DeferredShaderChunk[ "computeSpecular" ],
" const float attenuation = 1.0;",
THREE.DeferredShaderChunk[ "packLight" ],
" gl_FragColor = packedLight;",
"}"
].join( '\n' )
},
reconstruction: {
uniforms: Object.assign(
{
samplerLight: new THREE.Uniform( null ),
map: new THREE.Uniform( null ),
offsetRepeat: new THREE.Uniform( new THREE.Vector4( 0, 0, 1, 1 ) ),
diffuse: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
emissive: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
specular: new THREE.Uniform( new THREE.Color( 0x000000 ) ),
shininess: new THREE.Uniform( 30.0 )
},
THREE.ShaderDeferredCommon[ 'commonUniforms' ]
),
vertexShader: [
"#include <uv_pars_vertex>",
"#include <morphtarget_pars_vertex>",
"#include <skinning_pars_vertex>",
"void main() {",
"#include <uv_vertex>",
"#include <begin_vertex>",
"#include <beginnormal_vertex>",
"#include <skinbase_vertex>",
"#include <skinnormal_vertex>",
"#include <defaultnormal_vertex>",
"#include <morphtarget_vertex>",
"#include <skinning_vertex>",
"#include <project_vertex>",
"}"
].join( "\n" ),
fragmentShader: [
"uniform sampler2D samplerLight;",
"uniform vec3 diffuse;",
"uniform vec3 emissive;",
"uniform vec3 specular;",
"uniform float shininess;",
"uniform float viewHeight;",
"uniform float viewWidth;",
"#include <uv_pars_fragment>",
"#include <map_pars_fragment>",
THREE.DeferredShaderChunk[ "unpackFloat" ],
"void main() {",
" vec4 diffuseColor = vec4( diffuse, 1.0 );",
" vec3 emissiveColor = emissive;",
" vec3 specularColor = specular;",
THREE.DeferredShaderChunk[ "computeTextureCoord" ],
" vec4 light = texture2D( samplerLight, texCoord );",
"#include <map_fragment>",
" vec3 diffuseFinal = diffuseColor.rgb * light.rgb;",
" vec3 emissiveFinal = emissiveColor;",
" vec3 specularFinal = specularColor * light.rgb * light.a;",
" gl_FragColor = vec4( diffuseFinal + emissiveFinal + specularFinal, 1.0 );",
"}"
].join( "\n" )
},
// TODO: implement tone mapping
final: {
uniforms: {
samplerResult: new THREE.Uniform( 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 samplerResult;",
"void main() {",
" gl_FragColor = texture2D( samplerResult, texCoord );",
"}"
].join( "\n" )
}
};