diff --git a/examples/jsm/postprocessing/AfterimagePass.js b/examples/jsm/postprocessing/AfterimagePass.js index e3939ed4aa2737..c5ff12b0ea22ed 100644 --- a/examples/jsm/postprocessing/AfterimagePass.js +++ b/examples/jsm/postprocessing/AfterimagePass.js @@ -10,67 +10,109 @@ import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; import { AfterimageShader } from '../shaders/AfterimageShader.js'; +/** + * Pass for a basic after image effect. + * + * ```js + * const afterimagePass = new AfterimagePass( 0.9 ); + * composer.addPass( afterimagePass ); + * ``` + * + * @augments Pass + */ class AfterimagePass extends Pass { + /** + * Constructs a new after image pass. + * + * @param {number} [damp=0.96] - The damping intensity. A higher value means a stronger after image effect. + */ constructor( damp = 0.96 ) { super(); - this.shader = AfterimageShader; - - this.uniforms = UniformsUtils.clone( this.shader.uniforms ); + /** + * The pass uniforms. Use this object if you want to update the + * `damp` value at runtime. + * ```js + * pass.uniforms.damp.value = 0.9; + * ``` + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( AfterimageShader.uniforms ); this.uniforms[ 'damp' ].value = damp; - this.textureComp = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { - magFilter: NearestFilter, - type: HalfFloatType - } ); - - this.textureOld = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { - magFilter: NearestFilter, - type: HalfFloatType - } ); - + /** + * The composition material. + * + * @type {ShaderMaterial} + */ this.compFsMaterial = new ShaderMaterial( { uniforms: this.uniforms, - vertexShader: this.shader.vertexShader, - fragmentShader: this.shader.fragmentShader + vertexShader: AfterimageShader.vertexShader, + fragmentShader: AfterimageShader.fragmentShader } ); - this.compFsQuad = new FullScreenQuad( this.compFsMaterial ); - - const copyShader = CopyShader; - + /** + * The copy material. + * + * @type {ShaderMaterial} + */ this.copyFsMaterial = new ShaderMaterial( { - uniforms: UniformsUtils.clone( copyShader.uniforms ), - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, blending: NoBlending, depthTest: false, depthWrite: false } ); - this.copyFsQuad = new FullScreenQuad( this.copyFsMaterial ); + // internals + + this._textureComp = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { + magFilter: NearestFilter, + type: HalfFloatType + } ); + + this._textureOld = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { + magFilter: NearestFilter, + type: HalfFloatType + } ); + + this._compFsQuad = new FullScreenQuad( this.compFsMaterial ); + this._copyFsQuad = new FullScreenQuad( this.copyFsMaterial ); } + /** + * Performs the after image pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { - this.uniforms[ 'tOld' ].value = this.textureOld.texture; + this.uniforms[ 'tOld' ].value = this._textureOld.texture; this.uniforms[ 'tNew' ].value = readBuffer.texture; - renderer.setRenderTarget( this.textureComp ); - this.compFsQuad.render( renderer ); + renderer.setRenderTarget( this._textureComp ); + this._compFsQuad.render( renderer ); - this.copyFsQuad.material.uniforms.tDiffuse.value = this.textureComp.texture; + this._copyFsQuad.material.uniforms.tDiffuse.value = this._textureComp.texture; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.copyFsQuad.render( renderer ); + this._copyFsQuad.render( renderer ); } else { @@ -78,35 +120,45 @@ class AfterimagePass extends Pass { if ( this.clear ) renderer.clear(); - this.copyFsQuad.render( renderer ); + this._copyFsQuad.render( renderer ); } // Swap buffers. - const temp = this.textureOld; - this.textureOld = this.textureComp; - this.textureComp = temp; + const temp = this._textureOld; + this._textureOld = this._textureComp; + this._textureComp = temp; // Now textureOld contains the latest image, ready for the next frame. } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { - this.textureComp.setSize( width, height ); - this.textureOld.setSize( width, height ); + this._textureComp.setSize( width, height ); + this._textureOld.setSize( width, height ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.textureComp.dispose(); - this.textureOld.dispose(); + this._textureComp.dispose(); + this._textureOld.dispose(); this.compFsMaterial.dispose(); this.copyFsMaterial.dispose(); - this.compFsQuad.dispose(); - this.copyFsQuad.dispose(); + this._compFsQuad.dispose(); + this._copyFsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/BloomPass.js b/examples/jsm/postprocessing/BloomPass.js index 5649f8de901918..ea41b9c34e90b7 100644 --- a/examples/jsm/postprocessing/BloomPass.js +++ b/examples/jsm/postprocessing/BloomPass.js @@ -9,25 +9,47 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { ConvolutionShader } from '../shaders/ConvolutionShader.js'; +/** + * A pass for a basic Bloom effect. + * + * {@link UnrealBloomPass} produces a more advanced Bloom but is also + * more expensive. + * + * ```js + * const effectBloom = new BloomPass( 0.75 ); + * composer.addPass( effectBloom ); + * ``` + * + * @augments Pass + */ class BloomPass extends Pass { + /** + * Constructs a new Bloom pass. + * + * @param {number} [strength=1] - The Bloom strength. + * @param {number} [kernelSize=25] - The kernel size. + * @param {number} [sigma=4] - The sigma. + */ constructor( strength = 1, kernelSize = 25, sigma = 4 ) { super(); - // render targets - - this.renderTargetX = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later - this.renderTargetX.texture.name = 'BloomPass.x'; - this.renderTargetY = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later - this.renderTargetY.texture.name = 'BloomPass.y'; - // combine material + /** + * The combine pass uniforms. + * + * @type {Object} + */ this.combineUniforms = UniformsUtils.clone( CombineShader.uniforms ); - this.combineUniforms[ 'strength' ].value = strength; + /** + * The combine pass material. + * + * @type {ShaderMaterial} + */ this.materialCombine = new ShaderMaterial( { name: CombineShader.name, @@ -43,11 +65,21 @@ class BloomPass extends Pass { const convolutionShader = ConvolutionShader; + /** + * The convolution pass uniforms. + * + * @type {Object} + */ this.convolutionUniforms = UniformsUtils.clone( convolutionShader.uniforms ); this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; this.convolutionUniforms[ 'cKernel' ].value = ConvolutionShader.buildKernel( sigma ); + /** + * The convolution pass material. + * + * @type {ShaderMaterial} + */ this.materialConvolution = new ShaderMaterial( { name: convolutionShader.name, @@ -61,67 +93,101 @@ class BloomPass extends Pass { } ); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.fsQuad = new FullScreenQuad( null ); + // internals + + this._renderTargetX = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later + this._renderTargetX.texture.name = 'BloomPass.x'; + this._renderTargetY = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later + this._renderTargetY.texture.name = 'BloomPass.y'; + + this._fsQuad = new FullScreenQuad( null ); } + /** + * Performs the Bloom pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); // Render quad with blurred scene into texture (convolution pass 1) - this.fsQuad.material = this.materialConvolution; + this._fsQuad.material = this.materialConvolution; this.convolutionUniforms[ 'tDiffuse' ].value = readBuffer.texture; this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; - renderer.setRenderTarget( this.renderTargetX ); + renderer.setRenderTarget( this._renderTargetX ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Render quad with blurred scene into texture (convolution pass 2) - this.convolutionUniforms[ 'tDiffuse' ].value = this.renderTargetX.texture; + this.convolutionUniforms[ 'tDiffuse' ].value = this._renderTargetX.texture; this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurY; - renderer.setRenderTarget( this.renderTargetY ); + renderer.setRenderTarget( this._renderTargetY ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Render original scene with superimposed blur to texture - this.fsQuad.material = this.materialCombine; + this._fsQuad.material = this.materialCombine; - this.combineUniforms[ 'tDiffuse' ].value = this.renderTargetY.texture; + this.combineUniforms[ 'tDiffuse' ].value = this._renderTargetY.texture; if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); renderer.setRenderTarget( readBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { - this.renderTargetX.setSize( width, height ); - this.renderTargetY.setSize( width, height ); + this._renderTargetX.setSize( width, height ); + this._renderTargetY.setSize( width, height ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.renderTargetX.dispose(); - this.renderTargetY.dispose(); + this._renderTargetX.dispose(); + this._renderTargetY.dispose(); this.materialCombine.dispose(); this.materialConvolution.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/BokehPass.js b/examples/jsm/postprocessing/BokehPass.js index 7ee11685e24a4d..ed8f50592bbcd1 100644 --- a/examples/jsm/postprocessing/BokehPass.js +++ b/examples/jsm/postprocessing/BokehPass.js @@ -13,16 +13,44 @@ import { Pass, FullScreenQuad } from './Pass.js'; import { BokehShader } from '../shaders/BokehShader.js'; /** - * Depth-of-field post-process with bokeh shader + * Pass for creating depth of field (DOF) effect. + * + * ```js + * const bokehPass = new BokehPass( scene, camera, { + * focus: 500 + * aperture: 5, + * maxblur: 0.01 + * } ); + * composer.addPass( bokehPass ); + * ``` + * + * @augments Pass */ - class BokehPass extends Pass { + /** + * Constructs a new Bokeh pass. + * + * @param {Scene} scene - The scene to render the DOF for. + * @param {Camera} camera - The camera. + * @param {BokehPass~Options} params - The pass options. + */ constructor( scene, camera, params ) { super(); + /** + * The scene to render the DOF for. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; const focus = ( params.focus !== undefined ) ? params.focus : 1.0; @@ -31,26 +59,25 @@ class BokehPass extends Pass { // render targets - this.renderTargetDepth = new WebGLRenderTarget( 1, 1, { // will be resized later + this._renderTargetDepth = new WebGLRenderTarget( 1, 1, { // will be resized later minFilter: NearestFilter, magFilter: NearestFilter, type: HalfFloatType } ); - this.renderTargetDepth.texture.name = 'BokehPass.depth'; + this._renderTargetDepth.texture.name = 'BokehPass.depth'; // depth material - this.materialDepth = new MeshDepthMaterial(); - this.materialDepth.depthPacking = RGBADepthPacking; - this.materialDepth.blending = NoBlending; + this._materialDepth = new MeshDepthMaterial(); + this._materialDepth.depthPacking = RGBADepthPacking; + this._materialDepth.blending = NoBlending; // bokeh material - const bokehShader = BokehShader; - const bokehUniforms = UniformsUtils.clone( bokehShader.uniforms ); + const bokehUniforms = UniformsUtils.clone( BokehShader.uniforms ); - bokehUniforms[ 'tDepth' ].value = this.renderTargetDepth.texture; + bokehUniforms[ 'tDepth' ].value = this._renderTargetDepth.texture; bokehUniforms[ 'focus' ].value = focus; bokehUniforms[ 'aspect' ].value = camera.aspect; @@ -59,26 +86,56 @@ class BokehPass extends Pass { bokehUniforms[ 'nearClip' ].value = camera.near; bokehUniforms[ 'farClip' ].value = camera.far; + /** + * The pass bokeh material. + * + * @type {ShaderMaterial} + */ this.materialBokeh = new ShaderMaterial( { - defines: Object.assign( {}, bokehShader.defines ), + defines: Object.assign( {}, BokehShader.defines ), uniforms: bokehUniforms, - vertexShader: bokehShader.vertexShader, - fragmentShader: bokehShader.fragmentShader + vertexShader: BokehShader.vertexShader, + fragmentShader: BokehShader.fragmentShader } ); + /** + * The pass uniforms. Use this object if you want to update the + * `focus`, `aperture` or `maxblur` values at runtime. + * + * ```js + * pass.uniforms.focus.value = focus; + * pass.uniforms.aperture.value = aperture; + * pass.uniforms.maxblur.value = maxblur; + * ``` + * + * @type {Object} + */ this.uniforms = bokehUniforms; - this.fsQuad = new FullScreenQuad( this.materialBokeh ); + // internals + + this._fsQuad = new FullScreenQuad( this.materialBokeh ); this._oldClearColor = new Color(); } + /** + * Performs the Bokeh pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { // Render depth into texture - this.scene.overrideMaterial = this.materialDepth; + this.scene.overrideMaterial = this._materialDepth; renderer.getClearColor( this._oldClearColor ); const oldClearAlpha = renderer.getClearAlpha(); @@ -87,7 +144,7 @@ class BokehPass extends Pass { renderer.setClearColor( 0xffffff ); renderer.setClearAlpha( 1.0 ); - renderer.setRenderTarget( this.renderTargetDepth ); + renderer.setRenderTarget( this._renderTargetDepth ); renderer.clear(); renderer.render( this.scene, this.camera ); @@ -100,13 +157,13 @@ class BokehPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } @@ -117,25 +174,44 @@ class BokehPass extends Pass { } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { this.materialBokeh.uniforms[ 'aspect' ].value = width / height; - this.renderTargetDepth.setSize( width, height ); + this._renderTargetDepth.setSize( width, height ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.renderTargetDepth.dispose(); + this._renderTargetDepth.dispose(); - this.materialDepth.dispose(); + this._materialDepth.dispose(); this.materialBokeh.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } } +/** + * Constructor options of `BokehPass`. + * + * @typedef {Object} BokehPass~Options + * @property {number} [focus=1] - Defines the effect's focus which is the distance along the camera's look direction in world units. + * @property {number} [aperture=0.025] - Defines the effect's aperture. + * @property {number} [maxblur=1] - Defines the effect's maximum blur. + **/ + export { BokehPass }; diff --git a/examples/jsm/postprocessing/ClearPass.js b/examples/jsm/postprocessing/ClearPass.js index eac2ec756a034f..db9ec0bb3ad8ec 100644 --- a/examples/jsm/postprocessing/ClearPass.js +++ b/examples/jsm/postprocessing/ClearPass.js @@ -3,20 +3,70 @@ import { } from 'three'; import { Pass } from './Pass.js'; +/** + * This class can be used to force a clear operation for the current read or + * default framebuffer (when rendering to screen). + * + * ```js + * const clearPass = new ClearPass(); + * composer.addPass( clearPass ); + * ``` + * + * @augments Pass + */ class ClearPass extends Pass { - constructor( clearColor, clearAlpha ) { + /** + * Constructs a new clear pass. + * + * @param {(number|Color|string)} [clearColor=0x000000] - The clear color. + * @param {number} [clearAlpha=0] - The clear alpha. + */ + constructor( clearColor = 0x000000, clearAlpha = 0 ) { super(); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.clearColor = ( clearColor !== undefined ) ? clearColor : 0x000000; - this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; + /** + * The clear color. + * + * @type {(number|Color|string)} + * @default 0x000000 + */ + this.clearColor = clearColor; + + /** + * The clear alpha. + * + * @type {number} + * @default 0 + */ + this.clearAlpha = clearAlpha; + + // internals + this._oldClearColor = new Color(); } + /** + * Performs the clear operation. This affects the current read or the default framebuffer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { let oldClearAlpha; diff --git a/examples/jsm/postprocessing/CubeTexturePass.js b/examples/jsm/postprocessing/CubeTexturePass.js index ff9b1f6e481cbc..901665d1f90b70 100644 --- a/examples/jsm/postprocessing/CubeTexturePass.js +++ b/examples/jsm/postprocessing/CubeTexturePass.js @@ -10,30 +10,78 @@ import { } from 'three'; import { Pass } from './Pass.js'; +/** + * This pass can be used to render a cube texture over the entire screen. + * + * ```js + * const cubeMap = new THREE.CubeTextureLoader().load( urls ); + * + * const cubeTexturePass = new CubeTexturePass( camera, cubemap ); + * composer.addPass( cubeTexturePass ); + * ``` + * + * @augments Pass + */ class CubeTexturePass extends Pass { + /** + * Constructs a new cube texture pass. + * + * @param {PerspectiveCamera} camera - The camera. + * @param {CubeTexture} tCube - The cube texture to render. + * @param {number} [opacity=1] - The opacity. + */ constructor( camera, tCube, opacity = 1 ) { super(); + /** + * The camera. + * + * @type {PerspectiveCamera} + */ this.camera = camera; + /** + * The cube texture to render. + * + * @type {CubeTexture} + */ + this.tCube = tCube; + + /** + * The opacity. + * + * @type {number} + * @default 1 + */ + this.opacity = opacity; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.cubeShader = ShaderLib[ 'cube' ]; - this.cubeMesh = new Mesh( + // internals + + const cubeShader = ShaderLib[ 'cube' ]; + + this._cubeMesh = new Mesh( new BoxGeometry( 10, 10, 10 ), new ShaderMaterial( { - uniforms: UniformsUtils.clone( this.cubeShader.uniforms ), - vertexShader: this.cubeShader.vertexShader, - fragmentShader: this.cubeShader.fragmentShader, + uniforms: UniformsUtils.clone( cubeShader.uniforms ), + vertexShader: cubeShader.vertexShader, + fragmentShader: cubeShader.fragmentShader, depthTest: false, depthWrite: false, side: BackSide } ) ); - Object.defineProperty( this.cubeMesh.material, 'envMap', { + Object.defineProperty( this._cubeMesh.material, 'envMap', { get: function () { @@ -43,40 +91,52 @@ class CubeTexturePass extends Pass { } ); - this.tCube = tCube; - this.opacity = opacity; - - this.cubeScene = new Scene(); - this.cubeCamera = new PerspectiveCamera(); - this.cubeScene.add( this.cubeMesh ); + this._cubeScene = new Scene(); + this._cubeCamera = new PerspectiveCamera(); + this._cubeScene.add( this._cubeMesh ); } + /** + * Performs the cube texture pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { const oldAutoClear = renderer.autoClear; renderer.autoClear = false; - this.cubeCamera.projectionMatrix.copy( this.camera.projectionMatrix ); - this.cubeCamera.quaternion.setFromRotationMatrix( this.camera.matrixWorld ); + this._cubeCamera.projectionMatrix.copy( this.camera.projectionMatrix ); + this._cubeCamera.quaternion.setFromRotationMatrix( this.camera.matrixWorld ); - this.cubeMesh.material.uniforms.tCube.value = this.tCube; - this.cubeMesh.material.uniforms.tFlip.value = ( this.tCube.isCubeTexture && this.tCube.isRenderTargetTexture === false ) ? - 1 : 1; - this.cubeMesh.material.uniforms.opacity.value = this.opacity; - this.cubeMesh.material.transparent = ( this.opacity < 1.0 ); + this._cubeMesh.material.uniforms.tCube.value = this.tCube; + this._cubeMesh.material.uniforms.tFlip.value = ( this.tCube.isCubeTexture && this.tCube.isRenderTargetTexture === false ) ? - 1 : 1; + this._cubeMesh.material.uniforms.opacity.value = this.opacity; + this._cubeMesh.material.transparent = ( this.opacity < 1.0 ); renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); if ( this.clear ) renderer.clear(); - renderer.render( this.cubeScene, this.cubeCamera ); + renderer.render( this._cubeScene, this._cubeCamera ); renderer.autoClear = oldAutoClear; } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.cubeMesh.geometry.dispose(); - this.cubeMesh.material.dispose(); + this._cubeMesh.geometry.dispose(); + this._cubeMesh.material.dispose(); } diff --git a/examples/jsm/postprocessing/DotScreenPass.js b/examples/jsm/postprocessing/DotScreenPass.js index fa5fe42ce5675b..9d082fbf424d97 100644 --- a/examples/jsm/postprocessing/DotScreenPass.js +++ b/examples/jsm/postprocessing/DotScreenPass.js @@ -5,33 +5,77 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { DotScreenShader } from '../shaders/DotScreenShader.js'; +/** + * Pass for creating a dot-screen effect. + * + * ```js + * const pass = new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); + * composer.addPass( pass ); + * ``` + * + * @augments Pass + */ class DotScreenPass extends Pass { + /** + * Constructs a new dot screen pass. + * + * @param {Vector2} center - The center point. + * @param {number} angle - The rotation of the effect in radians. + * @param {number} scale - The scale of the effect. A higher value means smaller dots. + */ constructor( center, angle, scale ) { super(); - const shader = DotScreenShader; - - this.uniforms = UniformsUtils.clone( shader.uniforms ); + /** + * The pass uniforms. Use this object if you want to update the + * `center`, `angle` or `scale` values at runtime. + * ```js + * pass.uniforms.center.value.copy( center ); + * pass.uniforms.angle.value = 0; + * pass.uniforms.scale.value = 0.5; + * ``` + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( DotScreenShader.uniforms ); if ( center !== undefined ) this.uniforms[ 'center' ].value.copy( center ); if ( angle !== undefined ) this.uniforms[ 'angle' ].value = angle; if ( scale !== undefined ) this.uniforms[ 'scale' ].value = scale; + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { - name: shader.name, + name: DotScreenShader.name, uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader + vertexShader: DotScreenShader.vertexShader, + fragmentShader: DotScreenShader.fragmentShader } ); - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the dot screen pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; @@ -40,23 +84,27 @@ class DotScreenPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/EffectComposer.js b/examples/jsm/postprocessing/EffectComposer.js index b814654202e9be..2c1b2ad39a4bb3 100644 --- a/examples/jsm/postprocessing/EffectComposer.js +++ b/examples/jsm/postprocessing/EffectComposer.js @@ -9,10 +9,51 @@ import { CopyShader } from '../shaders/CopyShader.js'; import { ShaderPass } from './ShaderPass.js'; import { ClearMaskPass, MaskPass } from './MaskPass.js'; +/** + * Used to implement post-processing effects in three.js. + * The class manages a chain of post-processing passes to produce the final visual result. + * Post-processing passes are executed in order of their addition/insertion. + * The last pass is automatically rendered to screen. + * + * This module can only be used with {@link WebGLRenderer}. + * + * ```js + * const composer = new EffectComposer( renderer ); + * + * // adding some passes + * const renderPass = new RenderPass( scene, camera ); + * composer.addPass( renderPass ); + * + * const glitchPass = new GlitchPass(); + * composer.addPass( glitchPass ); + * + * const outputPass = new OutputPass() + * composer.addPass( outputPass ); + * + * function animate() { + * + * composer.render(); // instead of renderer.render() + * + * } + * ``` + */ class EffectComposer { + /** + * Constructs a new effect composer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will + * be used as the internal read and write buffers. If not given, the composer creates + * the buffers automatically. + */ constructor( renderer, renderTarget ) { + /** + * The renderer. + * + * @type {WebGLRenderer} + */ this.renderer = renderer; this._pixelRatio = renderer.getPixelRatio(); @@ -37,20 +78,59 @@ class EffectComposer { this.renderTarget2 = renderTarget.clone(); this.renderTarget2.texture.name = 'EffectComposer.rt2'; + /** + * A reference to the internal write buffer. Passes usually write + * their result into this buffer. + * + * @type {WebGLRenderTarget} + */ this.writeBuffer = this.renderTarget1; + + /** + * A reference to the internal read buffer. Passes usually read + * the previous render result from this buffer. + * + * @type {WebGLRenderTarget} + */ this.readBuffer = this.renderTarget2; + /** + * Whether the final pass is rendered to the screen (default framebuffer) or not. + * + * @type {boolean} + * @default true + */ this.renderToScreen = true; + /** + * An array representing the (ordered) chain of post-processing passes. + * + * @type {Array} + */ this.passes = []; + /** + * A copy pass used for internal swap operations. + * + * @private + * @type {ShaderPass} + */ this.copyPass = new ShaderPass( CopyShader ); this.copyPass.material.blending = NoBlending; + /** + * The intenral clock for managing time data. + * + * @private + * @type {Clock} + */ this.clock = new Clock(); } + /** + * Swaps the internal read/write buffers. + */ swapBuffers() { const tmp = this.readBuffer; @@ -59,6 +139,11 @@ class EffectComposer { } + /** + * Adds the given pass to the pass chain. + * + * @param {Pass} pass - The pass to add. + */ addPass( pass ) { this.passes.push( pass ); @@ -66,6 +151,12 @@ class EffectComposer { } + /** + * Inserts the given pass at a given index. + * + * @param {Pass} pass - The pass to insert. + * @param {number} index - The index into the pass chain. + */ insertPass( pass, index ) { this.passes.splice( index, 0, pass ); @@ -73,6 +164,11 @@ class EffectComposer { } + /** + * Removes the given pass from the pass chain. + * + * @param {Pass} pass - The pass to remove. + */ removePass( pass ) { const index = this.passes.indexOf( pass ); @@ -85,6 +181,12 @@ class EffectComposer { } + /** + * Returns `true` if the pass for the given index is the last enabled pass in the pass chain. + * + * @param {number} passIndex - The pass index. + * @return {boolean} Whether the the pass for the given index is the last pass in the pass chain. + */ isLastEnabledPass( passIndex ) { for ( let i = passIndex + 1; i < this.passes.length; i ++ ) { @@ -101,6 +203,12 @@ class EffectComposer { } + /** + * Executes all enabled post-processing passes in order to produce the final frame. + * + * @param {number} deltaTime - The delta time in seconds. If not given, the composer computes + * its own time delta value. + */ render( deltaTime ) { // deltaTime value is in seconds @@ -165,6 +273,12 @@ class EffectComposer { } + /** + * Resets the internal state of the EffectComposer. + * + * @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like + * the one from the constructor. If set, it is used to setup the read and write buffers. + */ reset( renderTarget ) { if ( renderTarget === undefined ) { @@ -189,6 +303,13 @@ class EffectComposer { } + /** + * Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize}, + * this method honors the current pixel ration. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + */ setSize( width, height ) { this._width = width; @@ -208,6 +329,12 @@ class EffectComposer { } + /** + * Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output. + * Setting the pixel ratio will automatically resize the composer. + * + * @param {number} pixelRatio - The pixel ratio to set. + */ setPixelRatio( pixelRatio ) { this._pixelRatio = pixelRatio; @@ -216,6 +343,10 @@ class EffectComposer { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the composer is no longer used in your app. + */ dispose() { this.renderTarget1.dispose(); diff --git a/examples/jsm/postprocessing/FilmPass.js b/examples/jsm/postprocessing/FilmPass.js index b2da31c168d663..d109278d59911e 100644 --- a/examples/jsm/postprocessing/FilmPass.js +++ b/examples/jsm/postprocessing/FilmPass.js @@ -5,16 +5,47 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { FilmShader } from '../shaders/FilmShader.js'; +/** + * This pass can be used to create a film grain effect. + * + * ```js + * const filmPass = new FilmPass(); + * composer.addPass( filmPass ); + * ``` + * + * @augments Pass + */ class FilmPass extends Pass { + /** + * Constructs a new film pass. + * + * @param {number} [intensity=0.5] - The grain intensity in the range `[0,1]` (0 = no effect, 1 = full effect). + * @param {boolean} [grayscale=false] - Whether to apply a grayscale effect or not. + */ constructor( intensity = 0.5, grayscale = false ) { super(); const shader = FilmShader; + /** + * The pass uniforms. Use this object if you want to update the + * `intensity` or `grayscale` values at runtime. + * ```js + * pass.uniforms.intensity.value = 1; + * pass.uniforms.grayscale.value = true; + * ``` + * + * @type {Object} + */ this.uniforms = UniformsUtils.clone( shader.uniforms ); + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { name: shader.name, @@ -24,13 +55,26 @@ class FilmPass extends Pass { } ); - this.uniforms.intensity.value = intensity; // (0 = no effect, 1 = full effect) + this.uniforms.intensity.value = intensity; this.uniforms.grayscale.value = grayscale; - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the film pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime /*, maskActive */ ) { this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; @@ -39,23 +83,27 @@ class FilmPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/GTAOPass.js b/examples/jsm/postprocessing/GTAOPass.js index ad0a76214bbcbc..27ad456ef97d4b 100644 --- a/examples/jsm/postprocessing/GTAOPass.js +++ b/examples/jsm/postprocessing/GTAOPass.js @@ -26,28 +26,118 @@ import { generatePdSamplePointInitializer, PoissonDenoiseShader } from '../shade import { CopyShader } from '../shaders/CopyShader.js'; import { SimplexNoise } from '../math/SimplexNoise.js'; +/** + * A pass for an GTAO effect. + * + * `GTAOPass` provides better quality than {@link SSAOPass} but is also more expensive. + * + * ```js + * const gtaoPass = new GTAOPass( scene, camera, width, height ); + * gtaoPass.output = GTAOPass.OUTPUT.Denoise; + * composer.addPass( gtaoPass ); + * ``` + * + * @augments Pass + */ class GTAOPass extends Pass { - constructor( scene, camera, width, height, parameters, aoParameters, pdParameters ) { + /** + * Constructs a new GTAO pass. + * + * @param {Scene} scene - The scene to compute the AO for. + * @param {Camera} camera - The camera. + * @param {number} [width=512] - The width of the effect. + * @param {number} [height=512] - The height of the effect. + * @param {Object} [parameters] - The pass parameters. + * @param {Object} [aoParameters] - The AO parameters. + * @param {Object} [pdParameters] - The denoise parameters. + */ + constructor( scene, camera, width = 512, height = 512, parameters, aoParameters, pdParameters ) { super(); - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + /** + * The width of the effect. + * + * @type {number} + * @default 512 + */ + this.width = width; + + /** + * The height of the effect. + * + * @type {number} + * @default 512 + */ + this.height = height; + + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + + /** + * The scene to render the AO for. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The output configuration. + * + * @type {number} + * @default 0 + */ this.output = 0; this._renderGBuffer = true; this._visibilityCache = new Map(); + + /** + * The AO blend intensity. + * + * @type {number} + * @default 1 + */ this.blendIntensity = 1.; + /** + * The number of Poisson Denoise rings. + * + * @type {number} + * @default 2 + */ this.pdRings = 2.; + + /** + * The Poisson Denoise radius exponent. + * + * @type {number} + * @default 2 + */ this.pdRadiusExponent = 2.; + + /** + * The Poisson Denoise sample count. + * + * @type {number} + * @default 16 + */ this.pdSamples = 16; this.gtaoNoiseTexture = generateMagicSquareNoise(); - this.pdNoiseTexture = this.generateNoise(); + this.pdNoiseTexture = this._generateNoise(); this.gtaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } ); this.pdRenderTarget = this.gtaoRenderTarget.clone(); @@ -127,9 +217,9 @@ class GTAOPass extends Pass { blendEquationAlpha: AddEquation } ); - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); - this.originalClearColor = new Color(); + this._originalClearColor = new Color(); this.setGBuffer( parameters ? parameters.depthTexture : undefined, parameters ? parameters.normalTexture : undefined ); @@ -147,6 +237,34 @@ class GTAOPass extends Pass { } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ + setSize( width, height ) { + + this.width = width; + this.height = height; + + this.gtaoRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.pdRenderTarget.setSize( width, height ); + + this.gtaoMaterial.uniforms.resolution.value.set( width, height ); + this.gtaoMaterial.uniforms.cameraProjectionMatrix.value.copy( this.camera.projectionMatrix ); + this.gtaoMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); + + this.pdMaterial.uniforms.resolution.value.set( width, height ); + this.pdMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.gtaoNoiseTexture.dispose(); @@ -158,16 +276,30 @@ class GTAOPass extends Pass { this.pdMaterial.dispose(); this.copyMaterial.dispose(); this.depthRenderMaterial.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * A texture holding the computed AO. + * + * @type {Texture} + * @readonly + */ get gtaoMap() { return this.pdRenderTarget.texture; } + /** + * Configures the GBuffer of this pass. If no arguments are passed, + * the pass creates an internal render target for holding depth + * and normal data. + * + * @param {DepthTexture} [depthTexture] - The depth texture. + * @param {DepthTexture} [normalTexture] - The normal texture. + */ setGBuffer( depthTexture, normalTexture ) { if ( depthTexture !== undefined ) { @@ -209,6 +341,12 @@ class GTAOPass extends Pass { } + /** + * Configures the clip box of the GTAO shader with the given AABB. + * + * @param {?Box3} box - The AABB enclosing the scene that should receive AO. When passing + * `null`, to clip box is used. + */ setSceneClipBox( box ) { if ( box ) { @@ -227,6 +365,11 @@ class GTAOPass extends Pass { } + /** + * Updates the GTAO material from the given paramter object. + * + * @param {Object} parameters - The GTAO material parameters. + */ updateGtaoMaterial( parameters ) { if ( parameters.radius !== undefined ) { @@ -276,6 +419,11 @@ class GTAOPass extends Pass { } + /** + * Updates the Denoise material from the given paramter object. + * + * @param {Object} parameters - The denoise parameters. + */ updatePdMaterial( parameters ) { let updateShader = false; @@ -335,15 +483,26 @@ class GTAOPass extends Pass { } + /** + * Performs the GTAO pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { // render normals and depth (honor only meshes, points and lines do not contribute to AO) if ( this._renderGBuffer ) { - this.overrideVisibility(); - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); - this.restoreVisibility(); + this._overrideVisibility(); + this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + this._restoreVisibility(); } @@ -354,12 +513,12 @@ class GTAOPass extends Pass { this.gtaoMaterial.uniforms.cameraProjectionMatrix.value.copy( this.camera.projectionMatrix ); this.gtaoMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); this.gtaoMaterial.uniforms.cameraWorldMatrix.value.copy( this.camera.matrixWorld ); - this.renderPass( renderer, this.gtaoMaterial, this.gtaoRenderTarget, 0xffffff, 1.0 ); + this._renderPass( renderer, this.gtaoMaterial, this.gtaoRenderTarget, 0xffffff, 1.0 ); // render poisson denoise this.pdMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); - this.renderPass( renderer, this.pdMaterial, this.pdRenderTarget, 0xffffff, 1.0 ); + this._renderPass( renderer, this.pdMaterial, this.pdRenderTarget, 0xffffff, 1.0 ); // output result to screen @@ -372,7 +531,7 @@ class GTAOPass extends Pass { this.copyMaterial.uniforms.tDiffuse.value = readBuffer.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -380,7 +539,7 @@ class GTAOPass extends Pass { this.copyMaterial.uniforms.tDiffuse.value = this.gtaoRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -388,7 +547,7 @@ class GTAOPass extends Pass { this.copyMaterial.uniforms.tDiffuse.value = this.pdRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -396,7 +555,7 @@ class GTAOPass extends Pass { this.depthRenderMaterial.uniforms.cameraNear.value = this.camera.near; this.depthRenderMaterial.uniforms.cameraFar.value = this.camera.far; - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -404,7 +563,7 @@ class GTAOPass extends Pass { this.copyMaterial.uniforms.tDiffuse.value = this.normalRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -412,11 +571,11 @@ class GTAOPass extends Pass { this.copyMaterial.uniforms.tDiffuse.value = readBuffer.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); this.blendMaterial.uniforms.intensity.value = this.blendIntensity; this.blendMaterial.uniforms.tDiffuse.value = this.pdRenderTarget.texture; - this.renderPass( renderer, this.blendMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.blendMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -427,10 +586,12 @@ class GTAOPass extends Pass { } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + // internals + + _renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { // save original state - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -446,19 +607,19 @@ class GTAOPass extends Pass { } - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + this._fsQuad.material = passMaterial; + this._fsQuad.render( renderer ); // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -481,30 +642,12 @@ class GTAOPass extends Pass { this.scene.overrideMaterial = null; renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - setSize( width, height ) { - - this.width = width; - this.height = height; - - this.gtaoRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.pdRenderTarget.setSize( width, height ); - - this.gtaoMaterial.uniforms.resolution.value.set( width, height ); - this.gtaoMaterial.uniforms.cameraProjectionMatrix.value.copy( this.camera.projectionMatrix ); - this.gtaoMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); - - this.pdMaterial.uniforms.resolution.value.set( width, height ); - this.pdMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); - - } - - overrideVisibility() { + _overrideVisibility() { const scene = this.scene; const cache = this._visibilityCache; @@ -519,7 +662,7 @@ class GTAOPass extends Pass { } - restoreVisibility() { + _restoreVisibility() { const scene = this.scene; const cache = this._visibilityCache; @@ -535,7 +678,7 @@ class GTAOPass extends Pass { } - generateNoise( size = 64 ) { + _generateNoise( size = 64 ) { const simplex = new SimplexNoise(); diff --git a/examples/jsm/postprocessing/GlitchPass.js b/examples/jsm/postprocessing/GlitchPass.js index 3b7c865cfbfdc9..fe33d2a6fff53a 100644 --- a/examples/jsm/postprocessing/GlitchPass.js +++ b/examples/jsm/postprocessing/GlitchPass.js @@ -9,41 +9,86 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { DigitalGlitch } from '../shaders/DigitalGlitch.js'; +/** + * Pass for creating a glitch effect. + * + * ```js + * const glitchPass = new GlitchPass(); + * composer.addPass( glitchPass ); + * ``` + * + * @augments Pass + */ class GlitchPass extends Pass { + /** + * Constructs a new glitch pass. + * + * @param {number} [dt_size=64] - The size of the displacement texture + * for digital glitch squares. + */ constructor( dt_size = 64 ) { super(); - const shader = DigitalGlitch; + /** + * The pass uniforms. + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( DigitalGlitch.uniforms ); + + /** + * The pass material. + * + * @type {ShaderMaterial} + */ + this.material = new ShaderMaterial( { + uniforms: this.uniforms, + vertexShader: DigitalGlitch.vertexShader, + fragmentShader: DigitalGlitch.fragmentShader + } ); - this.uniforms = UniformsUtils.clone( shader.uniforms ); + /** + * Whether to noticeably increase the effect instensity or not. + * + * @type {boolean} + * @default false + */ + this.goWild = false; - this.heightMap = this.generateHeightmap( dt_size ); + // internals + this._heightMap = this._generateHeightmap( dt_size ); this.uniforms[ 'tDisp' ].value = this.heightMap; - this.material = new ShaderMaterial( { - uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader - } ); + this._fsQuad = new FullScreenQuad( this.material ); - this.fsQuad = new FullScreenQuad( this.material ); + this._curF = 0; + this._randX = 0; - this.goWild = false; - this.curF = 0; - this.generateTrigger(); + this._generateTrigger(); } + /** + * Performs the glitch pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.uniforms[ 'seed' ].value = Math.random();//default seeding + this.uniforms[ 'seed' ].value = Math.random(); // default seeding this.uniforms[ 'byp' ].value = 0; - if ( this.curF % this.randX == 0 || this.goWild == true ) { + if ( this._curF % this._randX == 0 || this.goWild == true ) { this.uniforms[ 'amount' ].value = Math.random() / 30; this.uniforms[ 'angle' ].value = MathUtils.randFloat( - Math.PI, Math.PI ); @@ -51,10 +96,10 @@ class GlitchPass extends Pass { this.uniforms[ 'seed_y' ].value = MathUtils.randFloat( - 1, 1 ); this.uniforms[ 'distortion_x' ].value = MathUtils.randFloat( 0, 1 ); this.uniforms[ 'distortion_y' ].value = MathUtils.randFloat( 0, 1 ); - this.curF = 0; - this.generateTrigger(); + this._curF = 0; + this._generateTrigger(); - } else if ( this.curF % this.randX < this.randX / 5 ) { + } else if ( this._curF % this._randX < this._randX / 5 ) { this.uniforms[ 'amount' ].value = Math.random() / 90; this.uniforms[ 'angle' ].value = MathUtils.randFloat( - Math.PI, Math.PI ); @@ -69,30 +114,46 @@ class GlitchPass extends Pass { } - this.curF ++; + this._curF ++; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } - generateTrigger() { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { - this.randX = MathUtils.randInt( 120, 240 ); + this.material.dispose(); + + this.heightMap.dispose(); + + this._fsQuad.dispose(); } - generateHeightmap( dt_size ) { + // internals + + _generateTrigger() { + + this._randX = MathUtils.randInt( 120, 240 ); + + } + + _generateHeightmap( dt_size ) { const data_arr = new Float32Array( dt_size * dt_size ); const length = dt_size * dt_size; @@ -110,16 +171,6 @@ class GlitchPass extends Pass { } - dispose() { - - this.material.dispose(); - - this.heightMap.dispose(); - - this.fsQuad.dispose(); - - } - } export { GlitchPass }; diff --git a/examples/jsm/postprocessing/HalftonePass.js b/examples/jsm/postprocessing/HalftonePass.js index 0727f468463b1d..810122bfe74578 100644 --- a/examples/jsm/postprocessing/HalftonePass.js +++ b/examples/jsm/postprocessing/HalftonePass.js @@ -6,25 +6,56 @@ import { Pass, FullScreenQuad } from './Pass.js'; import { HalftoneShader } from '../shaders/HalftoneShader.js'; /** - * RGB Halftone pass for three.js effects composer. Requires HalftoneShader. + * Pass for creating a RGB halftone effect. + * + * ```js + * const params = { + * shape: 1, + * radius: 4, + * rotateR: Math.PI / 12, + * rotateB: Math.PI / 12 * 2, + * rotateG: Math.PI / 12 * 3, + * scatter: 0, + * blending: 1, + * blendingMode: 1, + * greyscale: false, + * disable: false + * }; + * const halftonePass = new HalftonePass( params ); + * composer.addPass( halftonePass ); + * ``` + * + * @augments Pass */ - class HalftonePass extends Pass { - constructor( width, height, params ) { + /** + * Constructs a new halftone pass. + * + * @param {Object} params - The halftone shader parameter. + */ + constructor( params ) { super(); + /** + * The pass uniforms. + * + * @type {Object} + */ this.uniforms = UniformsUtils.clone( HalftoneShader.uniforms ); + + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { uniforms: this.uniforms, fragmentShader: HalftoneShader.fragmentShader, vertexShader: HalftoneShader.vertexShader } ); - // set params - this.uniforms.width.value = width; - this.uniforms.height.value = height; for ( const key in params ) { @@ -36,10 +67,23 @@ class HalftonePass extends Pass { } - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the halftone pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { this.material.uniforms[ 'tDiffuse' ].value = readBuffer.texture; @@ -47,18 +91,24 @@ class HalftonePass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { this.uniforms.width.value = width; @@ -66,11 +116,15 @@ class HalftonePass extends Pass { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/LUTPass.js b/examples/jsm/postprocessing/LUTPass.js index d3372fd207d315..8ec24c61c98954 100644 --- a/examples/jsm/postprocessing/LUTPass.js +++ b/examples/jsm/postprocessing/LUTPass.js @@ -56,8 +56,45 @@ const LUTShader = { }; +/** + * Pass for color grading via lookup tables. + * + * ```js + * const lutPass = new LUTPass( { lut: lut.texture3D } ); + * composer.addPass( lutPass ); + * ``` + * + * @augments ShaderPass + */ class LUTPass extends ShaderPass { + /** + * Constructs a LUT pass. + * + * @param {{lut:Data3DTexture,intensity:number}} [options={}] - The pass options. + */ + constructor( options = {} ) { + + super( LUTShader ); + + /** + * The LUT as a 3D texture. + * + * @type {?Data3DTexture} + * @default null + */ + this.lut = options.lut || null; + + /** + * The intensity. + * + * @type {?number} + * @default 1 + */ + this.intensity = 'intensity' in options ? options.intensity : 1; + + } + set lut( v ) { const material = this.material; @@ -95,14 +132,6 @@ class LUTPass extends ShaderPass { } - constructor( options = {} ) { - - super( LUTShader ); - this.lut = options.lut || null; - this.intensity = 'intensity' in options ? options.intensity : 1; - - } - } export { LUTPass }; diff --git a/examples/jsm/postprocessing/MaskPass.js b/examples/jsm/postprocessing/MaskPass.js index b30811c8d4ca86..e6e46de0fd26a8 100644 --- a/examples/jsm/postprocessing/MaskPass.js +++ b/examples/jsm/postprocessing/MaskPass.js @@ -1,21 +1,81 @@ import { Pass } from './Pass.js'; +/** + * This pass can be used to define a mask during post processing. + * Meaning only areas of subsequent post processing are affected + * which lie in the masking area of this pass. Internally, the masking + * is implemented with the stencil buffer. + * + * ```js + * const maskPass = new MaskPass( scene, camera ); + * composer.addPass( maskPass ); + * ``` + * + * @augments Pass + */ class MaskPass extends Pass { + /** + * Constructs a new mask pass. + * + * @param {Scene} scene - The 3D objects in this scene will define the mask. + * @param {Camera} camera - The camera. + */ constructor( scene, camera ) { super(); + /** + * The scene that defines the mask. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; + /** + * Whether to inverse the mask or not. + * + * @type {boolean} + * @default false + */ this.inverse = false; } + /** + * Performs a mask pass with the configured scene and camera. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { const context = renderer.getContext(); @@ -82,16 +142,46 @@ class MaskPass extends Pass { } +/** + * This pass can be used to clear a mask previously defined with {@link MaskPass}. + * + * ```js + * const clearPass = new ClearMaskPass(); + * composer.addPass( clearPass ); + * ``` + * + * @augments Pass + */ class ClearMaskPass extends Pass { + /** + * Constructs a new clear mask pass. + */ constructor() { super(); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; } + /** + * Performs the clear of the currently defined mask. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) { renderer.state.buffers.stencil.setLocked( false ); diff --git a/examples/jsm/postprocessing/OutlinePass.js b/examples/jsm/postprocessing/OutlinePass.js index 90c3e4e16a328a..6f563a32505369 100644 --- a/examples/jsm/postprocessing/OutlinePass.js +++ b/examples/jsm/postprocessing/OutlinePass.js @@ -16,27 +16,137 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * A pass for rendering outlines around selected objects. + * + * ```js + * const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); + * const outlinePass = new OutlinePass( resolution, scene, camera ); + * composer.addPass( outlinePass ); + * ``` + * + * @augments Pass + */ class OutlinePass extends Pass { + /** + * Constructs a new outline pass. + * + * @param {Vector2} [resolution] - The effect's resolution. + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {Array} [selectedObjects] - The selected 3D objects that should receive an outline. + * + */ constructor( resolution, scene, camera, selectedObjects ) { super(); + /** + * The scene to render. + * + * @type {Object} + */ this.renderScene = scene; + + /** + * The camera. + * + * @type {Object} + */ this.renderCamera = camera; + + /** + * The selected 3D objects that should receive an outline. + * + * @type {Array} + */ this.selectedObjects = selectedObjects !== undefined ? selectedObjects : []; + + /** + * The visible edge color. + * + * @type {Color} + * @default (1,1,1) + */ this.visibleEdgeColor = new Color( 1, 1, 1 ); + + /** + * The hidden edge color. + * + * @type {Color} + * @default (0.1,0.04,0.02) + */ this.hiddenEdgeColor = new Color( 0.1, 0.04, 0.02 ); + + /** + * Can be used for an animated glow/pulse effect. + * + * @type {number} + * @default 0 + */ this.edgeGlow = 0.0; + + /** + * Whether to use a pattern texture for to highlight selected + * 3D objects or not. + * + * @type {boolean} + * @default false + */ this.usePatternTexture = false; + + /** + * Can be used to highlight selected 3D objects. Requires to set + * {@link OutlinePass#usePatternTexture} to `true`. + * + * @type {?Texture} + * @default null + */ + this.patternTexture = null; + + /** + * The edge thickness. + * + * @type {number} + * @default 1 + */ this.edgeThickness = 1.0; + + /** + * The edge strength. + * + * @type {number} + * @default 3 + */ this.edgeStrength = 3.0; + + /** + * The downsample ratio. The effect can be rendered in a much + * lower resolution than the beauty pass. + * + * @type {number} + * @default 2 + */ this.downSampleRatio = 2; + + /** + * The pulse period. + * + * @type {number} + * @default 0 + */ this.pulsePeriod = 0; this._visibilityCache = new Map(); this._selectionCache = new Set(); + /** + * The effect's resolution. + * + * @type {Vector2} + * @default (256,256) + */ this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); const resx = Math.round( this.resolution.x / this.downSampleRatio ); @@ -51,7 +161,7 @@ class OutlinePass extends Pass { this.depthMaterial.depthPacking = RGBADepthPacking; this.depthMaterial.blending = NoBlending; - this.prepareMaskMaterial = this.getPrepareMaskMaterial(); + this.prepareMaskMaterial = this._getPrepareMaskMaterial(); this.prepareMaskMaterial.side = DoubleSide; this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera ); @@ -70,7 +180,7 @@ class OutlinePass extends Pass { this.renderTargetBlurBuffer2.texture.name = 'OutlinePass.blur2'; this.renderTargetBlurBuffer2.texture.generateMipmaps = false; - this.edgeDetectionMaterial = this.getEdgeDetectionMaterial(); + this.edgeDetectionMaterial = this._getEdgeDetectionMaterial(); this.renderTargetEdgeBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); this.renderTargetEdgeBuffer1.texture.name = 'OutlinePass.edge1'; this.renderTargetEdgeBuffer1.texture.generateMipmaps = false; @@ -81,15 +191,15 @@ class OutlinePass extends Pass { const MAX_EDGE_THICKNESS = 4; const MAX_EDGE_GLOW = 4; - this.separableBlurMaterial1 = this.getSeparableBlurMaterial( MAX_EDGE_THICKNESS ); + this.separableBlurMaterial1 = this._getSeparableBlurMaterial( MAX_EDGE_THICKNESS ); this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = 1; - this.separableBlurMaterial2 = this.getSeparableBlurMaterial( MAX_EDGE_GLOW ); + this.separableBlurMaterial2 = this._getSeparableBlurMaterial( MAX_EDGE_GLOW ); this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( Math.round( resx / 2 ), Math.round( resy / 2 ) ); this.separableBlurMaterial2.uniforms[ 'kernelRadius' ].value = MAX_EDGE_GLOW; // Overlay material - this.overlayMaterial = this.getOverlayMaterial(); + this.overlayMaterial = this._getOverlayMaterial(); // copy material @@ -112,7 +222,7 @@ class OutlinePass extends Pass { this._oldClearColor = new Color(); this.oldClearAlpha = 1; - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); this.tempPulseColor1 = new Color(); this.tempPulseColor2 = new Color(); @@ -128,6 +238,10 @@ class OutlinePass extends Pass { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.renderTargetMaskBuffer.dispose(); @@ -146,10 +260,16 @@ class OutlinePass extends Pass { this.overlayMaterial.dispose(); this.materialCopy.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { this.renderTargetMaskBuffer.setSize( width, height ); @@ -172,108 +292,17 @@ class OutlinePass extends Pass { } - updateSelectionCache() { - - const cache = this._selectionCache; - - function gatherSelectedMeshesCallBack( object ) { - - if ( object.isMesh ) cache.add( object ); - - } - - cache.clear(); - - for ( let i = 0; i < this.selectedObjects.length; i ++ ) { - - const selectedObject = this.selectedObjects[ i ]; - selectedObject.traverse( gatherSelectedMeshesCallBack ); - - } - - } - - changeVisibilityOfSelectedObjects( bVisible ) { - - const cache = this._visibilityCache; - - for ( const mesh of this._selectionCache ) { - - if ( bVisible === true ) { - - mesh.visible = cache.get( mesh ); - - } else { - - cache.set( mesh, mesh.visible ); - mesh.visible = bVisible; - - } - - } - - } - - changeVisibilityOfNonSelectedObjects( bVisible ) { - - const visibilityCache = this._visibilityCache; - const selectionCache = this._selectionCache; - - function VisibilityChangeCallBack( object ) { - - if ( object.isMesh || object.isSprite ) { - - // only meshes and sprites are supported by OutlinePass - - if ( ! selectionCache.has( object ) ) { - - const visibility = object.visible; - - if ( bVisible === false || visibilityCache.get( object ) === true ) { - - object.visible = bVisible; - - } - - visibilityCache.set( object, visibility ); - - } - - } else if ( object.isPoints || object.isLine ) { - - // the visibility of points and lines is always set to false in order to - // not affect the outline computation - - if ( bVisible === true ) { - - object.visible = visibilityCache.get( object ); // restore - - } else { - - visibilityCache.set( object, object.visible ); - object.visible = bVisible; - - } - - } - - } - - this.renderScene.traverse( VisibilityChangeCallBack ); - - } - - updateTextureMatrix() { - - this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 ); - this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); - this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); - - } - + /** + * Performs the Outline pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { if ( this.selectedObjects.length > 0 ) { @@ -288,10 +317,10 @@ class OutlinePass extends Pass { renderer.setClearColor( 0xffffff, 1 ); - this.updateSelectionCache(); + this._updateSelectionCache(); // Make selected objects invisible - this.changeVisibilityOfSelectedObjects( false ); + this._changeVisibilityOfSelectedObjects( false ); const currentBackground = this.renderScene.background; const currentOverrideMaterial = this.renderScene.overrideMaterial; @@ -304,14 +333,14 @@ class OutlinePass extends Pass { renderer.render( this.renderScene, this.renderCamera ); // Make selected objects visible - this.changeVisibilityOfSelectedObjects( true ); + this._changeVisibilityOfSelectedObjects( true ); this._visibilityCache.clear(); // Update Texture Matrix for Depth compare - this.updateTextureMatrix(); + this._updateTextureMatrix(); // Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects - this.changeVisibilityOfNonSelectedObjects( false ); + this._changeVisibilityOfNonSelectedObjects( false ); this.renderScene.overrideMaterial = this.prepareMaskMaterial; this.prepareMaskMaterial.uniforms[ 'cameraNearFar' ].value.set( this.renderCamera.near, this.renderCamera.far ); this.prepareMaskMaterial.uniforms[ 'depthTexture' ].value = this.renderTargetDepthBuffer.texture; @@ -319,7 +348,7 @@ class OutlinePass extends Pass { renderer.setRenderTarget( this.renderTargetMaskBuffer ); renderer.clear(); renderer.render( this.renderScene, this.renderCamera ); - this.changeVisibilityOfNonSelectedObjects( true ); + this._changeVisibilityOfNonSelectedObjects( true ); this._visibilityCache.clear(); this._selectionCache.clear(); @@ -327,11 +356,11 @@ class OutlinePass extends Pass { this.renderScene.overrideMaterial = currentOverrideMaterial; // 2. Downsample to Half resolution - this.fsQuad.material = this.materialCopy; + this._fsQuad.material = this.materialCopy; this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetMaskBuffer.texture; renderer.setRenderTarget( this.renderTargetMaskDownSampleBuffer ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.tempPulseColor1.copy( this.visibleEdgeColor ); this.tempPulseColor2.copy( this.hiddenEdgeColor ); @@ -345,44 +374,44 @@ class OutlinePass extends Pass { } // 3. Apply Edge Detection Pass - this.fsQuad.material = this.edgeDetectionMaterial; + this._fsQuad.material = this.edgeDetectionMaterial; this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture; this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height ); this.edgeDetectionMaterial.uniforms[ 'visibleEdgeColor' ].value = this.tempPulseColor1; this.edgeDetectionMaterial.uniforms[ 'hiddenEdgeColor' ].value = this.tempPulseColor2; renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // 4. Apply Blur on Half res - this.fsQuad.material = this.separableBlurMaterial1; + this._fsQuad.material = this.separableBlurMaterial1; this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = this.edgeThickness; renderer.setRenderTarget( this.renderTargetBlurBuffer1 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer1.texture; this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Apply Blur on quarter res - this.fsQuad.material = this.separableBlurMaterial2; + this._fsQuad.material = this.separableBlurMaterial2; this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; renderer.setRenderTarget( this.renderTargetBlurBuffer2 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer2.texture; this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; renderer.setRenderTarget( this.renderTargetEdgeBuffer2 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Blend it additively over the input texture - this.fsQuad.material = this.overlayMaterial; + this._fsQuad.material = this.overlayMaterial; this.overlayMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskBuffer.texture; this.overlayMaterial.uniforms[ 'edgeTexture1' ].value = this.renderTargetEdgeBuffer1.texture; this.overlayMaterial.uniforms[ 'edgeTexture2' ].value = this.renderTargetEdgeBuffer2.texture; @@ -395,7 +424,7 @@ class OutlinePass extends Pass { if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); renderer.autoClear = oldAutoClear; @@ -404,16 +433,120 @@ class OutlinePass extends Pass { if ( this.renderToScreen ) { - this.fsQuad.material = this.materialCopy; + this._fsQuad.material = this.materialCopy; this.copyUniforms[ 'tDiffuse' ].value = readBuffer.texture; renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); + + } + + } + + // internals + + _updateSelectionCache() { + + const cache = this._selectionCache; + + function gatherSelectedMeshesCallBack( object ) { + + if ( object.isMesh ) cache.add( object ); + + } + + cache.clear(); + + for ( let i = 0; i < this.selectedObjects.length; i ++ ) { + + const selectedObject = this.selectedObjects[ i ]; + selectedObject.traverse( gatherSelectedMeshesCallBack ); + + } + + } + + _changeVisibilityOfSelectedObjects( bVisible ) { + + const cache = this._visibilityCache; + + for ( const mesh of this._selectionCache ) { + + if ( bVisible === true ) { + + mesh.visible = cache.get( mesh ); + + } else { + + cache.set( mesh, mesh.visible ); + mesh.visible = bVisible; + + } } } - getPrepareMaskMaterial() { + _changeVisibilityOfNonSelectedObjects( bVisible ) { + + const visibilityCache = this._visibilityCache; + const selectionCache = this._selectionCache; + + function VisibilityChangeCallBack( object ) { + + if ( object.isMesh || object.isSprite ) { + + // only meshes and sprites are supported by OutlinePass + + if ( ! selectionCache.has( object ) ) { + + const visibility = object.visible; + + if ( bVisible === false || visibilityCache.get( object ) === true ) { + + object.visible = bVisible; + + } + + visibilityCache.set( object, visibility ); + + } + + } else if ( object.isPoints || object.isLine ) { + + // the visibility of points and lines is always set to false in order to + // not affect the outline computation + + if ( bVisible === true ) { + + object.visible = visibilityCache.get( object ); // restore + + } else { + + visibilityCache.set( object, object.visible ); + object.visible = bVisible; + + } + + } + + } + + this.renderScene.traverse( VisibilityChangeCallBack ); + + } + + _updateTextureMatrix() { + + this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 ); + this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); + this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); + + } + + _getPrepareMaskMaterial() { return new ShaderMaterial( { @@ -475,7 +608,7 @@ class OutlinePass extends Pass { } - getEdgeDetectionMaterial() { + _getEdgeDetectionMaterial() { return new ShaderMaterial( { @@ -522,7 +655,7 @@ class OutlinePass extends Pass { } - getSeparableBlurMaterial( maxRadius ) { + _getSeparableBlurMaterial( maxRadius ) { return new ShaderMaterial( { @@ -579,7 +712,7 @@ class OutlinePass extends Pass { } - getOverlayMaterial() { + _getOverlayMaterial() { return new ShaderMaterial( { diff --git a/examples/jsm/postprocessing/OutputPass.js b/examples/jsm/postprocessing/OutputPass.js index a1688dcfa40c17..5323d22c59512e 100644 --- a/examples/jsm/postprocessing/OutputPass.js +++ b/examples/jsm/postprocessing/OutputPass.js @@ -14,34 +14,69 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { OutputShader } from '../shaders/OutputShader.js'; +/** + * This pass is responsible for including tone mapping and color space conversion + * into your pass chain. In most cases, this pass should be included at the end + * of each pass chain. If a pass requires sRGB input (e.g. like FXAA), the pass + * must follow `OutputPass` in the pass chain. + * + * The tone mapping and color space settings are extracted from the renderer. + * + * ```js + * const outputPass = new OutputPass(); + * composer.addPass( outputPass ); + * ``` + * + * @augments Pass + */ class OutputPass extends Pass { + /** + * Constructs a new output pass. + */ constructor() { super(); - // - - const shader = OutputShader; - - this.uniforms = UniformsUtils.clone( shader.uniforms ); - + /** + * The pass uniforms. + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( OutputShader.uniforms ); + + /** + * The pass material. + * + * @type {RawShaderMaterial} + */ this.material = new RawShaderMaterial( { - name: shader.name, + name: OutputShader.name, uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader + vertexShader: OutputShader.vertexShader, + fragmentShader: OutputShader.fragmentShader } ); - this.fsQuad = new FullScreenQuad( this.material ); + // internals - // internal cache + this._fsQuad = new FullScreenQuad( this.material ); this._outputColorSpace = null; this._toneMapping = null; } + /** + * Performs the output pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) { this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; @@ -75,22 +110,26 @@ class OutputPass extends Pass { if ( this.renderToScreen === true ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/Pass.js b/examples/jsm/postprocessing/Pass.js index a81582d13de21a..6312923ae8ed8b 100644 --- a/examples/jsm/postprocessing/Pass.js +++ b/examples/jsm/postprocessing/Pass.js @@ -5,34 +5,97 @@ import { Mesh } from 'three'; +/** + * Abstract base class for all post processing passes. + * + * This module is only relevant for post processing with {@link WebGLRenderer}. + * + * @abstract + */ class Pass { + /** + * Constructs a new pass. + */ constructor() { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPass = true; - // if set to true, the pass is processed by the composer + /** + * If set to `true`, the pass is processed by the composer. + * + * @type {boolean} + * @default true + */ this.enabled = true; - // if set to true, the pass indicates to swap read and write buffer after rendering + /** + * If set to `true`, the pass indicates to swap read and write buffer after rendering. + * + * @type {boolean} + * @default true + */ this.needsSwap = true; - // if set to true, the pass clears its buffer before rendering + /** + * If set to `true`, the pass clears its buffer before rendering + * + * @type {boolean} + * @default false + */ this.clear = false; - // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer. + /** + * If set to `true`, the result of the pass is rendered to screen. The last pass in the composers + * pass chain gets automatically rendered to screen, no matter how this property is configured. + * + * @type {boolean} + * @default false + */ this.renderToScreen = false; } + /** + * Sets the size of the pass. + * + * @abstract + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( /* width, height */ ) {} + /** + * This method holds the render logic of a pass. It must be implemented in all derived classes. + * + * @abstract + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + * + * @abstract + */ dispose() {} } @@ -58,26 +121,57 @@ class FullscreenTriangleGeometry extends BufferGeometry { const _geometry = new FullscreenTriangleGeometry(); + +/** + * This module is a helper for passes which need to render a full + * screen effect which is quite common in context of post processing. + * + * The intended usage is to reuse a single full screen quad for rendering + * subsequent passes by just reassigning the `material` reference. + * + * This module can only be used with {@link WebGLRenderer}. + * + * @augments Mesh + */ class FullScreenQuad { + /** + * Constructs a new full screen quad. + * + * @param {?Material} material - The material to render te full screen quad with. + */ constructor( material ) { this._mesh = new Mesh( _geometry, material ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the instance is no longer used in your app. + */ dispose() { this._mesh.geometry.dispose(); } + /** + * Renders the full screen quad. + * + * @param {WebGLRenderer} renderer - The renderer. + */ render( renderer ) { renderer.render( this._mesh, _camera ); } + /** + * The quad's material. + * + * @type {?Material} + */ get material() { return this._mesh.material; diff --git a/examples/jsm/postprocessing/RenderPass.js b/examples/jsm/postprocessing/RenderPass.js index c60e92d333501e..bbb72ec181beb4 100644 --- a/examples/jsm/postprocessing/RenderPass.js +++ b/examples/jsm/postprocessing/RenderPass.js @@ -3,27 +3,110 @@ import { } from 'three'; import { Pass } from './Pass.js'; +/** + * This class represents a render pass. It takes a camera and a scene and produces + * a beauty pass for subsequent post processing effects. + * + * ```js + * const renderPass = new RenderPass( scene, camera ); + * composer.addPass( renderPass ); + * ``` + * + * @augments Pass + */ class RenderPass extends Pass { + /** + * Constructs a new render pass. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {?Material} [overrideMaterial=null] - The override material. If set, this material is used + * for all objects in the scene. + * @param {?(number|Color|string)} [clearColor=null] - The clear color of the render pass. + * @param {?number} [clearAlpha=null] - The clear alpha of the render pass. + */ constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) { super(); + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * The override material. If set, this material is used + * for all objects in the scene. + * + * @type {?Material} + * @default null + */ this.overrideMaterial = overrideMaterial; + /** + * The clear color of the render pass. + * + * @type {?(number|Color|string)} + * @default null + */ this.clearColor = clearColor; + + /** + * The clear alpha of the render pass. + * + * @type {?number} + * @default null + */ this.clearAlpha = clearAlpha; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * If set to `true`, only the depth can be cleared when `clear` is to `false`. + * + * @type {boolean} + * @default false + */ this.clearDepth = false; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; this._oldClearColor = new Color(); } + /** + * Performs a beauty pass with the configured scene and camera. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { const oldAutoClear = renderer.autoClear; diff --git a/examples/jsm/postprocessing/RenderPixelatedPass.js b/examples/jsm/postprocessing/RenderPixelatedPass.js index 3913e1e73efb80..8a2dd1c6029098 100644 --- a/examples/jsm/postprocessing/RenderPixelatedPass.js +++ b/examples/jsm/postprocessing/RenderPixelatedPass.js @@ -10,87 +10,170 @@ import { } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; +/** + * A special type of render pass that produces a pixelated beauty pass. + * + * ```js + * const renderPixelatedPass = new RenderPixelatedPass( 6, scene, camera ); + * composer.addPass( renderPixelatedPass ); + * ``` + * + * @augments Pass + */ class RenderPixelatedPass extends Pass { + /** + * Constructs a new render pixelated pass. + * + * @param {number} pixelSize - The effect's pixel size. + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {{normalEdgeStrength:number,depthEdgeStrength:number}} options - The pass options. + */ constructor( pixelSize, scene, camera, options = {} ) { super(); + /** + * The effect's pixel size. + * + * @type {number} + */ this.pixelSize = pixelSize; - this.resolution = new Vector2(); - this.renderResolution = new Vector2(); - this.pixelatedMaterial = this.createPixelatedMaterial(); - this.normalMaterial = new MeshNormalMaterial(); - - this.fsQuad = new FullScreenQuad( this.pixelatedMaterial ); + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * The normal edge strength. + * + * @type {number} + * @default 0.3 + */ this.normalEdgeStrength = options.normalEdgeStrength || 0.3; + + /** + * The normal edge strength. + * + * @type {number} + * @default 0.4 + */ this.depthEdgeStrength = options.depthEdgeStrength || 0.4; - this.beautyRenderTarget = new WebGLRenderTarget(); - this.beautyRenderTarget.texture.minFilter = NearestFilter; - this.beautyRenderTarget.texture.magFilter = NearestFilter; - this.beautyRenderTarget.texture.type = HalfFloatType; - this.beautyRenderTarget.depthTexture = new DepthTexture(); + /** + * The pixelated material. + * + * @type {ShaderMaterial} + */ + this.pixelatedMaterial = this._createPixelatedMaterial(); + + // internals + + this._resolution = new Vector2(); + this._renderResolution = new Vector2(); - this.normalRenderTarget = new WebGLRenderTarget(); - this.normalRenderTarget.texture.minFilter = NearestFilter; - this.normalRenderTarget.texture.magFilter = NearestFilter; - this.normalRenderTarget.texture.type = HalfFloatType; + this._normalMaterial = new MeshNormalMaterial(); + + this._beautyRenderTarget = new WebGLRenderTarget(); + this._beautyRenderTarget.texture.minFilter = NearestFilter; + this._beautyRenderTarget.texture.magFilter = NearestFilter; + this._beautyRenderTarget.texture.type = HalfFloatType; + this._beautyRenderTarget.depthTexture = new DepthTexture(); + + this._normalRenderTarget = new WebGLRenderTarget(); + this._normalRenderTarget.texture.minFilter = NearestFilter; + this._normalRenderTarget.texture.magFilter = NearestFilter; + this._normalRenderTarget.texture.type = HalfFloatType; + + this._fsQuad = new FullScreenQuad( this.pixelatedMaterial ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.beautyRenderTarget.dispose(); - this.normalRenderTarget.dispose(); + this._beautyRenderTarget.dispose(); + this._normalRenderTarget.dispose(); this.pixelatedMaterial.dispose(); - this.normalMaterial.dispose(); + this._normalMaterial.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { - this.resolution.set( width, height ); - this.renderResolution.set( ( width / this.pixelSize ) | 0, ( height / this.pixelSize ) | 0 ); - const { x, y } = this.renderResolution; - this.beautyRenderTarget.setSize( x, y ); - this.normalRenderTarget.setSize( x, y ); - this.fsQuad.material.uniforms.resolution.value.set( x, y, 1 / x, 1 / y ); + this._resolution.set( width, height ); + this._renderResolution.set( ( width / this.pixelSize ) | 0, ( height / this.pixelSize ) | 0 ); + const { x, y } = this._renderResolution; + this._beautyRenderTarget.setSize( x, y ); + this._normalRenderTarget.setSize( x, y ); + this._fsQuad.material.uniforms.resolution.value.set( x, y, 1 / x, 1 / y ); } + /** + * Sets the effect's pixel size. + * + * @param {number} pixelSize - The pixel size to set. + */ setPixelSize( pixelSize ) { this.pixelSize = pixelSize; - this.setSize( this.resolution.x, this.resolution.y ); + this.setSize( this._resolution.x, this._resolution.y ); } - render( renderer, writeBuffer ) { - - const uniforms = this.fsQuad.material.uniforms; + /** + * Performs the pixelation pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer/*, readBuffer , deltaTime, maskActive */ ) { + + const uniforms = this._fsQuad.material.uniforms; uniforms.normalEdgeStrength.value = this.normalEdgeStrength; uniforms.depthEdgeStrength.value = this.depthEdgeStrength; - renderer.setRenderTarget( this.beautyRenderTarget ); + renderer.setRenderTarget( this._beautyRenderTarget ); renderer.render( this.scene, this.camera ); const overrideMaterial_old = this.scene.overrideMaterial; - renderer.setRenderTarget( this.normalRenderTarget ); - this.scene.overrideMaterial = this.normalMaterial; + renderer.setRenderTarget( this._normalRenderTarget ); + this.scene.overrideMaterial = this._normalMaterial; renderer.render( this.scene, this.camera ); this.scene.overrideMaterial = overrideMaterial_old; - uniforms.tDiffuse.value = this.beautyRenderTarget.texture; - uniforms.tDepth.value = this.beautyRenderTarget.depthTexture; - uniforms.tNormal.value = this.normalRenderTarget.texture; + uniforms.tDiffuse.value = this._beautyRenderTarget.texture; + uniforms.tDepth.value = this._beautyRenderTarget.depthTexture; + uniforms.tNormal.value = this._normalRenderTarget.texture; if ( this.renderToScreen ) { @@ -104,25 +187,20 @@ class RenderPixelatedPass extends Pass { } - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } - createPixelatedMaterial() { + // internals + + _createPixelatedMaterial() { return new ShaderMaterial( { uniforms: { tDiffuse: { value: null }, tDepth: { value: null }, tNormal: { value: null }, - resolution: { - value: new Vector4( - this.renderResolution.x, - this.renderResolution.y, - 1 / this.renderResolution.x, - 1 / this.renderResolution.y, - ) - }, + resolution: { value: new Vector4() }, normalEdgeStrength: { value: 0 }, depthEdgeStrength: { value: 0 } }, diff --git a/examples/jsm/postprocessing/RenderTransitionPass.js b/examples/jsm/postprocessing/RenderTransitionPass.js index 8fe2ed8b4e9878..1b68d9793b8f0b 100644 --- a/examples/jsm/postprocessing/RenderTransitionPass.js +++ b/examples/jsm/postprocessing/RenderTransitionPass.js @@ -5,68 +5,159 @@ import { } from 'three'; import { FullScreenQuad, Pass } from './Pass.js'; +/** + * A special type of render pass for implementing transition effects. + * When active, the pass will transition from scene A to scene B. + * + * ```js + * const renderTransitionPass = new RenderTransitionPass( fxSceneA.scene, fxSceneA.camera, fxSceneB.scene, fxSceneB.camera ); + * renderTransitionPass.setTexture( textures[ 0 ] ); + * composer.addPass( renderTransitionPass ); + * ``` + * + * @augments Pass + */ class RenderTransitionPass extends Pass { + /** + * Constructs a render transition pass. + * + * @param {Scene} sceneA - The first scene. + * @param {Camera} cameraA - The camera of the first scene. + * @param {Scene} sceneB - The second scene. + * @param {Camera} cameraB - The camera of the second scene. + */ constructor( sceneA, cameraA, sceneB, cameraB ) { super(); - this.material = this.createMaterial(); - this.fsQuad = new FullScreenQuad( this.material ); - + /** + * The first scene. + * + * @type {Scene} + */ this.sceneA = sceneA; + + + /** + * The camera of the first scene. + * + * @type {Camera} + */ this.cameraA = cameraA; + + /** + * The second scene. + * + * @type {Scene} + */ this.sceneB = sceneB; + + /** + * The camera of the second scene. + * + * @type {Camera} + */ this.cameraB = cameraB; - this.renderTargetA = new WebGLRenderTarget(); - this.renderTargetA.texture.type = HalfFloatType; - this.renderTargetB = new WebGLRenderTarget(); - this.renderTargetB.texture.type = HalfFloatType; + /** + * The pass material. + * + * @type {ShaderMaterial} + */ + this.material = this._createMaterial(); + + // internals + + this._renderTargetA = new WebGLRenderTarget(); + this._renderTargetA.texture.type = HalfFloatType; + this._renderTargetB = new WebGLRenderTarget(); + this._renderTargetB.texture.type = HalfFloatType; + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Sets the transition factor. Must be in the range `[0,1]`. + * This value determines to what degree both scenes are mixed. + * + * @param {boolenumberan} value - The transition factor. + */ setTransition( value ) { this.material.uniforms.mixRatio.value = value; } + /** + * Toggles the usage of a texture for the effect. + * + * @param {boolean} value - Whether to use a texture for the transition effect or not. + */ useTexture( value ) { this.material.uniforms.useTexture.value = value ? 1 : 0; } + /** + * Sets the effect texture. + * + * @param {Texture} value - The effect texture. + */ setTexture( value ) { this.material.uniforms.tMixTexture.value = value; } + /** + * Sets the texture threshold. This value defined how strong the texture effects + * the transition. Must be in the range `[0,1]` (0 means full effect, 1 means no effect). + * + * @param {boolenumberan} value - The threshold value. + */ setTextureThreshold( value ) { this.material.uniforms.threshold.value = value; } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { - this.renderTargetA.setSize( width, height ); - this.renderTargetB.setSize( width, height ); + this._renderTargetA.setSize( width, height ); + this._renderTargetB.setSize( width, height ); } - render( renderer, writeBuffer ) { - - renderer.setRenderTarget( this.renderTargetA ); + /** + * Performs the transition pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer/*, readBuffer , deltaTime, maskActive */ ) { + + renderer.setRenderTarget( this._renderTargetA ); renderer.render( this.sceneA, this.cameraA ); - renderer.setRenderTarget( this.renderTargetB ); + renderer.setRenderTarget( this._renderTargetB ); renderer.render( this.sceneB, this.cameraB ); - const uniforms = this.fsQuad.material.uniforms; - uniforms.tDiffuse1.value = this.renderTargetA.texture; - uniforms.tDiffuse2.value = this.renderTargetB.texture; + const uniforms = this._fsQuad.material.uniforms; + uniforms.tDiffuse1.value = this._renderTargetA.texture; + uniforms.tDiffuse2.value = this._renderTargetB.texture; if ( this.renderToScreen ) { @@ -80,20 +171,27 @@ class RenderTransitionPass extends Pass { } - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.renderTargetA.dispose(); - this.renderTargetB.dispose(); this.material.dispose(); - this.fsQuad.dispose(); + + this._renderTargetA.dispose(); + this._renderTargetB.dispose(); + this._fsQuad.dispose(); } - createMaterial() { + // internals + + _createMaterial() { return new ShaderMaterial( { uniforms: { diff --git a/examples/jsm/postprocessing/SAOPass.js b/examples/jsm/postprocessing/SAOPass.js index fab190407acbba..726a30bc9321a6 100644 --- a/examples/jsm/postprocessing/SAOPass.js +++ b/examples/jsm/postprocessing/SAOPass.js @@ -23,25 +23,69 @@ import { BlurShaderUtils, DepthLimitedBlurShader } from '../shaders/DepthLimited import { CopyShader } from '../shaders/CopyShader.js'; /** - * SAO implementation inspired from bhouston previous SAO work + * A SAO implementation inspired from @bhouston previous SAO work. + * + * `SAOPass` provides better quality than {@link SSAOPass} but is also more expensive. + * + * ```js + * const saoPass = new SAOPass( scene, camera ); + * composer.addPass( saoPass ); + * ``` + * + * @augments Pass */ - class SAOPass extends Pass { + /** + * Constructs a new SAO pass. + * + * @param {Scene} scene - The scene to compute the AO for. + * @param {Camera} camera - The camera. + * @param {Vector2} [resolution] - The effect's resolution. + */ constructor( scene, camera, resolution = new Vector2( 256, 256 ) ) { super(); + /** + * The scene to render the AO for. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.originalClearColor = new Color(); + this._originalClearColor = new Color(); this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this._oldClearAlpha = 1; + /** + * The SAO paramter. + * + * @type {Object} + */ this.params = { output: 0, saoBias: 0.5, @@ -55,6 +99,12 @@ class SAOPass extends Pass { saoBlurDepthCutoff: 0.01 }; + /** + * The effect's resolution. + * + * @type {Vector2} + * @default (256,256) + */ this.resolution = new Vector2( resolution.x, resolution.y ); this.saoRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); @@ -135,6 +185,17 @@ class SAOPass extends Pass { } + /** + * Performs the SAO pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { // Rendering readBuffer first when rendering to screen @@ -143,12 +204,12 @@ class SAOPass extends Pass { this.materialCopy.blending = NoBlending; this.materialCopy.uniforms[ 'tDiffuse' ].value = readBuffer.texture; this.materialCopy.needsUpdate = true; - this.renderPass( renderer, this.materialCopy, null ); + this._renderPass( renderer, this.materialCopy, null ); } renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); + this._oldClearAlpha = renderer.getClearAlpha(); const oldAutoClear = renderer.autoClear; renderer.autoClear = false; @@ -181,16 +242,16 @@ class SAOPass extends Pass { } // render normal and depth - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); // Rendering SAO texture - this.renderPass( renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); + this._renderPass( renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); // Blurring SAO texture if ( this.params.saoBlur ) { - this.renderPass( renderer, this.vBlurMaterial, this.blurIntermediateRenderTarget, 0xffffff, 1.0 ); - this.renderPass( renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); + this._renderPass( renderer, this.vBlurMaterial, this.blurIntermediateRenderTarget, 0xffffff, 1.0 ); + this._renderPass( renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); } @@ -221,17 +282,64 @@ class SAOPass extends Pass { } // Rendering SAOPass result on top of previous pass - this.renderPass( renderer, outputMaterial, this.renderToScreen ? null : readBuffer ); + this._renderPass( renderer, outputMaterial, this.renderToScreen ? null : readBuffer ); - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.setClearColor( this._oldClearColor, this._oldClearAlpha ); renderer.autoClear = oldAutoClear; } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ + setSize( width, height ) { + + this.saoRenderTarget.setSize( width, height ); + this.blurIntermediateRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + + this.saoMaterial.uniforms[ 'size' ].value.set( width, height ); + this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; + this.saoMaterial.needsUpdate = true; + + this.vBlurMaterial.uniforms[ 'size' ].value.set( width, height ); + this.vBlurMaterial.needsUpdate = true; + + this.hBlurMaterial.uniforms[ 'size' ].value.set( width, height ); + this.hBlurMaterial.needsUpdate = true; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { + + this.saoRenderTarget.dispose(); + this.blurIntermediateRenderTarget.dispose(); + this.normalRenderTarget.dispose(); + + this.normalMaterial.dispose(); + this.saoMaterial.dispose(); + this.vBlurMaterial.dispose(); + this.hBlurMaterial.dispose(); + this.materialCopy.dispose(); + + this.fsQuad.dispose(); + + } + + // internal + + _renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { // save original state - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -252,14 +360,14 @@ class SAOPass extends Pass { // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -282,46 +390,11 @@ class SAOPass extends Pass { // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - setSize( width, height ) { - - this.saoRenderTarget.setSize( width, height ); - this.blurIntermediateRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - - this.saoMaterial.uniforms[ 'size' ].value.set( width, height ); - this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; - this.saoMaterial.needsUpdate = true; - - this.vBlurMaterial.uniforms[ 'size' ].value.set( width, height ); - this.vBlurMaterial.needsUpdate = true; - - this.hBlurMaterial.uniforms[ 'size' ].value.set( width, height ); - this.hBlurMaterial.needsUpdate = true; - - } - - dispose() { - - this.saoRenderTarget.dispose(); - this.blurIntermediateRenderTarget.dispose(); - this.normalRenderTarget.dispose(); - - this.normalMaterial.dispose(); - this.saoMaterial.dispose(); - this.vBlurMaterial.dispose(); - this.hBlurMaterial.dispose(); - this.materialCopy.dispose(); - - this.fsQuad.dispose(); - - } - } SAOPass.OUTPUT = { diff --git a/examples/jsm/postprocessing/SMAAPass.js b/examples/jsm/postprocessing/SMAAPass.js index 9dd22b823334db..b423c5dc9dd68a 100644 --- a/examples/jsm/postprocessing/SMAAPass.js +++ b/examples/jsm/postprocessing/SMAAPass.js @@ -10,185 +10,217 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { SMAABlendShader, SMAAEdgesShader, SMAAWeightsShader } from '../shaders/SMAAShader.js'; +/** + * A pass for applying SMAA. Unlike {@link FXAAPass}, `SMAAPass` operates in + * `linar-srgb` so this pass must be executed before {@link OutputPass}. + * + * ```js + * const smaaPass = new SMAAPass(); + * composer.addPass( smaaPass ); + * ``` + * + * @augments Pass + */ class SMAAPass extends Pass { - constructor( width, height ) { + /** + * Constructs a new SMAA pass. + */ + constructor( ) { super(); // render targets - this.edgesRT = new WebGLRenderTarget( width, height, { + this._edgesRT = new WebGLRenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } ); - this.edgesRT.texture.name = 'SMAAPass.edges'; + this._edgesRT.texture.name = 'SMAAPass.edges'; - this.weightsRT = new WebGLRenderTarget( width, height, { + this._weightsRT = new WebGLRenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } ); - this.weightsRT.texture.name = 'SMAAPass.weights'; + this._weightsRT.texture.name = 'SMAAPass.weights'; // textures const scope = this; const areaTextureImage = new Image(); - areaTextureImage.src = this.getAreaTexture(); + areaTextureImage.src = this._getAreaTexture(); areaTextureImage.onload = function () { // assigning data to HTMLImageElement.src is asynchronous (see #15162) - scope.areaTexture.needsUpdate = true; + scope._areaTexture.needsUpdate = true; }; - this.areaTexture = new Texture(); - this.areaTexture.name = 'SMAAPass.area'; - this.areaTexture.image = areaTextureImage; - this.areaTexture.minFilter = LinearFilter; - this.areaTexture.generateMipmaps = false; - this.areaTexture.flipY = false; + this._areaTexture = new Texture(); + this._areaTexture.name = 'SMAAPass.area'; + this._areaTexture.image = areaTextureImage; + this._areaTexture.minFilter = LinearFilter; + this._areaTexture.generateMipmaps = false; + this._areaTexture.flipY = false; const searchTextureImage = new Image(); - searchTextureImage.src = this.getSearchTexture(); + searchTextureImage.src = this._getSearchTexture(); searchTextureImage.onload = function () { // assigning data to HTMLImageElement.src is asynchronous (see #15162) - scope.searchTexture.needsUpdate = true; + scope._searchTexture.needsUpdate = true; }; - this.searchTexture = new Texture(); - this.searchTexture.name = 'SMAAPass.search'; - this.searchTexture.image = searchTextureImage; - this.searchTexture.magFilter = NearestFilter; - this.searchTexture.minFilter = NearestFilter; - this.searchTexture.generateMipmaps = false; - this.searchTexture.flipY = false; + this._searchTexture = new Texture(); + this._searchTexture.name = 'SMAAPass.search'; + this._searchTexture.image = searchTextureImage; + this._searchTexture.magFilter = NearestFilter; + this._searchTexture.minFilter = NearestFilter; + this._searchTexture.generateMipmaps = false; + this._searchTexture.flipY = false; // materials - pass 1 - this.uniformsEdges = UniformsUtils.clone( SMAAEdgesShader.uniforms ); + this._uniformsEdges = UniformsUtils.clone( SMAAEdgesShader.uniforms ); - this.uniformsEdges[ 'resolution' ].value.set( 1 / width, 1 / height ); - - this.materialEdges = new ShaderMaterial( { + this._materialEdges = new ShaderMaterial( { defines: Object.assign( {}, SMAAEdgesShader.defines ), - uniforms: this.uniformsEdges, + uniforms: this._uniformsEdges, vertexShader: SMAAEdgesShader.vertexShader, fragmentShader: SMAAEdgesShader.fragmentShader } ); // materials - pass 2 - this.uniformsWeights = UniformsUtils.clone( SMAAWeightsShader.uniforms ); + this._uniformsWeights = UniformsUtils.clone( SMAAWeightsShader.uniforms ); - this.uniformsWeights[ 'resolution' ].value.set( 1 / width, 1 / height ); - this.uniformsWeights[ 'tDiffuse' ].value = this.edgesRT.texture; - this.uniformsWeights[ 'tArea' ].value = this.areaTexture; - this.uniformsWeights[ 'tSearch' ].value = this.searchTexture; + this._uniformsWeights[ 'tDiffuse' ].value = this._edgesRT.texture; + this._uniformsWeights[ 'tArea' ].value = this._areaTexture; + this._uniformsWeights[ 'tSearch' ].value = this._searchTexture; - this.materialWeights = new ShaderMaterial( { + this._materialWeights = new ShaderMaterial( { defines: Object.assign( {}, SMAAWeightsShader.defines ), - uniforms: this.uniformsWeights, + uniforms: this._uniformsWeights, vertexShader: SMAAWeightsShader.vertexShader, fragmentShader: SMAAWeightsShader.fragmentShader } ); // materials - pass 3 - this.uniformsBlend = UniformsUtils.clone( SMAABlendShader.uniforms ); - - this.uniformsBlend[ 'resolution' ].value.set( 1 / width, 1 / height ); - this.uniformsBlend[ 'tDiffuse' ].value = this.weightsRT.texture; + this._uniformsBlend = UniformsUtils.clone( SMAABlendShader.uniforms ); + this._uniformsBlend[ 'tDiffuse' ].value = this._weightsRT.texture; - this.materialBlend = new ShaderMaterial( { - uniforms: this.uniformsBlend, + this._materialBlend = new ShaderMaterial( { + uniforms: this._uniformsBlend, vertexShader: SMAABlendShader.vertexShader, fragmentShader: SMAABlendShader.fragmentShader } ); - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); } + /** + * Performs the SMAA pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { // pass 1 - this.uniformsEdges[ 'tDiffuse' ].value = readBuffer.texture; + this._uniformsEdges[ 'tDiffuse' ].value = readBuffer.texture; - this.fsQuad.material = this.materialEdges; + this._fsQuad.material = this._materialEdges; - renderer.setRenderTarget( this.edgesRT ); + renderer.setRenderTarget( this._edgesRT ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // pass 2 - this.fsQuad.material = this.materialWeights; + this._fsQuad.material = this._materialWeights; - renderer.setRenderTarget( this.weightsRT ); + renderer.setRenderTarget( this._weightsRT ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // pass 3 - this.uniformsBlend[ 'tColor' ].value = readBuffer.texture; + this._uniformsBlend[ 'tColor' ].value = readBuffer.texture; - this.fsQuad.material = this.materialBlend; + this._fsQuad.material = this._materialBlend; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { - this.edgesRT.setSize( width, height ); - this.weightsRT.setSize( width, height ); + this._edgesRT.setSize( width, height ); + this._weightsRT.setSize( width, height ); - this.materialEdges.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); - this.materialWeights.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); - this.materialBlend.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); + this._materialEdges.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); + this._materialWeights.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); + this._materialBlend.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); } - getAreaTexture() { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { - return 'data:image/png;base64,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'; + this._edgesRT.dispose(); + this._weightsRT.dispose(); - } + this._areaTexture.dispose(); + this._searchTexture.dispose(); - getSearchTexture() { + this._materialEdges.dispose(); + this._materialWeights.dispose(); + this._materialBlend.dispose(); - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAAAhCAAAAABIXyLAAAAAOElEQVRIx2NgGAWjYBSMglEwEICREYRgFBZBqDCSLA2MGPUIVQETE9iNUAqLR5gIeoQKRgwXjwAAGn4AtaFeYLEAAAAASUVORK5CYII='; + this._fsQuad.dispose(); } - dispose() { + // internals - this.edgesRT.dispose(); - this.weightsRT.dispose(); + _getAreaTexture() { - this.areaTexture.dispose(); - this.searchTexture.dispose(); + return 'data:image/png;base64,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'; - this.materialEdges.dispose(); - this.materialWeights.dispose(); - this.materialBlend.dispose(); + } - this.fsQuad.dispose(); + _getSearchTexture() { + + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAAAhCAAAAABIXyLAAAAAOElEQVRIx2NgGAWjYBSMglEwEICREYRgFBZBqDCSLA2MGPUIVQETE9iNUAqLR5gIeoQKRgwXjwAAGn4AtaFeYLEAAAAASUVORK5CYII='; } diff --git a/examples/jsm/postprocessing/SSAARenderPass.js b/examples/jsm/postprocessing/SSAARenderPass.js index 2ac73d5adaa00c..800ecf13b43c8c 100644 --- a/examples/jsm/postprocessing/SSAARenderPass.js +++ b/examples/jsm/postprocessing/SSAARenderPass.js @@ -10,41 +10,102 @@ import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; /** -* -* Supersample Anti-Aliasing Render Pass -* -* This manual approach to SSAA re-renders the scene ones for each sample with camera jitter and accumulates the results. -* -* References: https://en.wikipedia.org/wiki/Supersampling -* -*/ - + * Supersample Anti-Aliasing Render Pass. + * + * This manual approach to SSAA re-renders the scene ones for each sample with camera jitter and accumulates the results. + * + * ```js + * const ssaaRenderPass = new SSAARenderPass( scene, camera ); + * ssaaRenderPass.sampleLevel = 3; + * composer.addPass( ssaaRenderPass ); + * ``` + * + * @augments Pass + */ class SSAARenderPass extends Pass { - constructor( scene, camera, clearColor, clearAlpha ) { + /** + * Constructs a new SSAA render pass. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {?(number|Color|string)} [clearColor=0x000000] - The clear color of the render pass. + * @param {?number} [clearAlpha=0] - The clear alpha of the render pass. + */ + constructor( scene, camera, clearColor = 0x000000, clearAlpha = 0 ) { super(); + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; - this.sampleLevel = 4; // specified as n, where the number of samples is 2^n, so sampleLevel = 4, is 2^4 samples, 16. + /** + * The sample level. Specified as n, where the number of + * samples is 2^n, so sampleLevel = 4, is 2^4 samples, 16. + * + * @type {number} + * @default 4 + */ + this.sampleLevel = 4; + + /** + * Whether the pass should be unbiased or not. This property has the most + * visible effect when rendering to a RGBA8 buffer because it mitigates + * rounding errors. By default RGBA16F is used. + * + * @type {boolean} + * @default true + */ this.unbiased = true; + /** + * Whether to use a stencil buffer or not. This property can't + * be changed after the first render. + * + * @type {boolean} + * @default false + */ this.stencilBuffer = false; - // as we need to clear the buffer in this pass, clearColor must be set to something, defaults to black. - this.clearColor = ( clearColor !== undefined ) ? clearColor : 0x000000; - this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; + /** + * The clear color of the render pass. + * + * @type {?(number|Color|string)} + * @default 0x000000 + */ + this.clearColor = clearColor; + + /** + * The clear alpha of the render pass. + * + * @type {?number} + * @default 0 + */ + this.clearAlpha = clearAlpha; + + // internals + + this._sampleRenderTarget = null; + this._oldClearColor = new Color(); - const copyShader = CopyShader; - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + this._copyUniforms = UniformsUtils.clone( CopyShader.uniforms ); - this.copyMaterial = new ShaderMaterial( { - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, + this._copyMaterial = new ShaderMaterial( { + uniforms: this._copyUniforms, + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, transparent: true, depthTest: false, depthWrite: false, @@ -52,37 +113,58 @@ class SSAARenderPass extends Pass { blending: AdditiveBlending } ); - this.fsQuad = new FullScreenQuad( this.copyMaterial ); + this._fsQuad = new FullScreenQuad( this._copyMaterial ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - if ( this.sampleRenderTarget ) { + if ( this._sampleRenderTarget ) { - this.sampleRenderTarget.dispose(); - this.sampleRenderTarget = null; + this._sampleRenderTarget.dispose(); + this._sampleRenderTarget = null; } - this.copyMaterial.dispose(); + this._copyMaterial.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { - if ( this.sampleRenderTarget ) this.sampleRenderTarget.setSize( width, height ); + if ( this._sampleRenderTarget ) this._sampleRenderTarget.setSize( width, height ); } - render( renderer, writeBuffer, readBuffer ) { + /** + * Performs the SSAA render pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) { - if ( ! this.sampleRenderTarget ) { + if ( ! this._sampleRenderTarget ) { - this.sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType, stencilBuffer: this.stencilBuffer } ); - this.sampleRenderTarget.texture.name = 'SSAARenderPass.sample'; + this._sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType, stencilBuffer: this.stencilBuffer } ); + this._sampleRenderTarget.texture.name = 'SSAARenderPass.sample'; } @@ -96,7 +178,7 @@ class SSAARenderPass extends Pass { const baseSampleWeight = 1.0 / jitterOffsets.length; const roundingRange = 1 / 32; - this.copyUniforms[ 'tDiffuse' ].value = this.sampleRenderTarget.texture; + this._copyUniforms[ 'tDiffuse' ].value = this._sampleRenderTarget.texture; const viewOffset = { @@ -145,9 +227,9 @@ class SSAARenderPass extends Pass { } - this.copyUniforms[ 'opacity' ].value = sampleWeight; + this._copyUniforms[ 'opacity' ].value = sampleWeight; renderer.setClearColor( this.clearColor, this.clearAlpha ); - renderer.setRenderTarget( this.sampleRenderTarget ); + renderer.setRenderTarget( this._sampleRenderTarget ); renderer.clear(); renderer.render( this.scene, this.camera ); @@ -160,7 +242,7 @@ class SSAARenderPass extends Pass { } - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } diff --git a/examples/jsm/postprocessing/SSAOPass.js b/examples/jsm/postprocessing/SSAOPass.js index 8a82169ca42d44..b9d808466a6610 100644 --- a/examples/jsm/postprocessing/SSAOPass.js +++ b/examples/jsm/postprocessing/SSAOPass.js @@ -27,35 +27,123 @@ import { SimplexNoise } from '../math/SimplexNoise.js'; import { SSAOBlurShader, SSAODepthShader, SSAOShader } from '../shaders/SSAOShader.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * A pass for a basic SSAO effect. + * + * {@link SAOPass} and {@link GTAPass} produce a more advanced AO but are also + * more expensive. + * + * ```js + * const ssaoPass = new SSAOPass( scene, camera, width, height ); + * composer.addPass( ssaoPass ); + * ``` + * + * @augments Pass + */ class SSAOPass extends Pass { - constructor( scene, camera, width, height, kernelSize = 32 ) { + /** + * Constructs a new SSAO pass. + * + * @param {Scene} scene - The scene to compute the AO for. + * @param {Camera} camera - The camera. + * @param {number} [width=512] - The width of the effect. + * @param {number} [height=512] - The height of the effect. + * @param {number} [kernelSize=32] - The kernel size. + */ + constructor( scene, camera, width = 512, height = 512, kernelSize = 32 ) { super(); - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + /** + * The width of the effect. + * + * @type {number} + * @default 512 + */ + this.width = width; + + /** + * The height of the effect. + * + * @type {number} + * @default 512 + */ + this.height = height; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + + /** + * The scene to render the AO for. + * + * @type {Scene} + */ this.scene = scene; + /** + * The kernel radius controls how wide the + * AO spreads. + * + * @type {number} + * @default 8 + */ this.kernelRadius = 8; this.kernel = []; this.noiseTexture = null; + + /** + * The output configuration. + * + * @type {number} + * @default 0 + */ this.output = 0; + /** + * Defines the minimum distance that should be + * affected by the AO. + * + * @type {number} + * @default 0.005 + */ this.minDistance = 0.005; + + /** + * Defines the maximum distance that should be + * affected by the AO. + * + * @type {number} + * @default 0.1 + */ this.maxDistance = 0.1; this._visibilityCache = new Map(); // - this.generateSampleKernel( kernelSize ); - this.generateRandomKernelRotations(); + this._generateSampleKernel( kernelSize ); + this._generateRandomKernelRotations(); // depth texture @@ -146,12 +234,18 @@ class SSAOPass extends Pass { blendEquationAlpha: AddEquation } ); - this.fsQuad = new FullScreenQuad( null ); + // internals + + this._fsQuad = new FullScreenQuad( null ); - this.originalClearColor = new Color(); + this._originalClearColor = new Color(); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { // dispose render targets @@ -169,28 +263,39 @@ class SSAOPass extends Pass { // dispose full screen quad - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Performs the SSAO pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { // render normals and depth (honor only meshes, points and lines do not contribute to SSAO) - this.overrideVisibility(); - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); - this.restoreVisibility(); + this._overrideVisibility(); + this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + this._restoreVisibility(); // render SSAO this.ssaoMaterial.uniforms[ 'kernelRadius' ].value = this.kernelRadius; this.ssaoMaterial.uniforms[ 'minDistance' ].value = this.minDistance; this.ssaoMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; - this.renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget ); + this._renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget ); // render blur - this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); + this._renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); // output result to screen @@ -200,7 +305,7 @@ class SSAOPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); break; @@ -208,13 +313,13 @@ class SSAOPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); break; case SSAOPass.OUTPUT.Depth: - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : readBuffer ); + this._renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : readBuffer ); break; @@ -222,7 +327,7 @@ class SSAOPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); break; @@ -230,7 +335,7 @@ class SSAOPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; this.copyMaterial.blending = CustomBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); break; @@ -241,10 +346,35 @@ class SSAOPass extends Pass { } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ + setSize( width, height ) { + + this.width = width; + this.height = height; + + this.ssaoRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.blurRenderTarget.setSize( width, height ); + + this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + + this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); + + } + + // internals + + _renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { // save original state - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -260,19 +390,19 @@ class SSAOPass extends Pass { } - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + this._fsQuad.material = passMaterial; + this._fsQuad.render( renderer ); // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -297,29 +427,12 @@ class SSAOPass extends Pass { // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - setSize( width, height ) { - - this.width = width; - this.height = height; - - this.ssaoRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.blurRenderTarget.setSize( width, height ); - - this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - - this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); - - } - - generateSampleKernel( kernelSize ) { + _generateSampleKernel( kernelSize ) { const kernel = this.kernel; @@ -342,7 +455,7 @@ class SSAOPass extends Pass { } - generateRandomKernelRotations() { + _generateRandomKernelRotations() { const width = 4, height = 4; @@ -368,7 +481,7 @@ class SSAOPass extends Pass { } - overrideVisibility() { + _overrideVisibility() { const scene = this.scene; const cache = this._visibilityCache; @@ -383,7 +496,7 @@ class SSAOPass extends Pass { } - restoreVisibility() { + _restoreVisibility() { const scene = this.scene; const cache = this._visibilityCache; diff --git a/examples/jsm/postprocessing/SSRPass.js b/examples/jsm/postprocessing/SSRPass.js index 30c8d7ebe769ec..41b82142bcdec9 100644 --- a/examples/jsm/postprocessing/SSRPass.js +++ b/examples/jsm/postprocessing/SSRPass.js @@ -19,32 +19,138 @@ import { Pass, FullScreenQuad } from './Pass.js'; import { SSRBlurShader, SSRDepthShader, SSRShader } from '../shaders/SSRShader.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * A pass for a basic SSR effect. + * + * ```js + * const ssrPass = new SSRPass( { + * renderer, + * scene, + * camera, + * width: innerWidth, + * height: innerHeight + * } ); + * composer.addPass( ssrPass ); + * ``` + * + * @augments Pass + */ class SSRPass extends Pass { - constructor( { renderer, scene, camera, width, height, selects, bouncing = false, groundReflector } ) { + /** + * Constructs a new SSR pass. + * + * @param {SSRPass~Options} options - The pass options. + */ + constructor( { renderer, scene, camera, width = 512, height = 512, selects = null, bouncing = false, groundReflector = null } ) { super(); - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + /** + * The width of the effect. + * + * @type {number} + * @default 512 + */ + this.width = width; + + /** + * The height of the effect. + * + * @type {number} + * @default 512 + */ + this.height = height; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + /** + * The renderer. + * + * @type {WebGLRenderer} + */ this.renderer = renderer; + + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + + /** + * The ground reflector. + * + * @type {?ReflectorForSSRPass} + * @default 0 + */ this.groundReflector = groundReflector; + /** + * The opactiy. + * + * @type {number} + * @default 0.5 + */ this.opacity = SSRShader.uniforms.opacity.value; + + /** + * The output configuration. + * + * @type {number} + * @default 0 + */ this.output = 0; + /** + * Controls how far a fragment can reflect. + * + * @type {number} + * @default 180 + */ this.maxDistance = SSRShader.uniforms.maxDistance.value; + + /** + * Controls the cutoff between what counts as a + * possible reflection hit and what does not. + * + * @type {number} + * @default .018 + */ this.thickness = SSRShader.uniforms.thickness.value; this.tempColor = new Color(); this._selects = selects; + + /** + * Whether the pass is selective or not. + * + * @type {boolean} + * @default false + */ this.selective = Array.isArray( this._selects ); + + /** + * Which 3D objects should be affected by SSR. If not set, the entire scene is affected. + * + * @name SSRPass#selects + * @type {?Array} + * @default null + */ Object.defineProperty( this, 'selects', { get() { @@ -73,6 +179,14 @@ class SSRPass extends Pass { } ); this._bouncing = bouncing; + + /** + * Whether bouncing is enabled or not. + * + * @name SSRPass#bouncing + * @type {boolean} + * @default false + */ Object.defineProperty( this, 'bouncing', { get() { @@ -96,9 +210,23 @@ class SSRPass extends Pass { } } ); + /** + * Whether to blur reflections or not. + * + * @type {boolean} + * @default true + */ this.blur = true; this._distanceAttenuation = SSRShader.defines.DISTANCE_ATTENUATION; + + /** + * Whether to use distance attenutation or not. + * + * @name SSRPass#distanceAttenuation + * @type {boolean} + * @default true + */ Object.defineProperty( this, 'distanceAttenuation', { get() { @@ -117,6 +245,14 @@ class SSRPass extends Pass { this._fresnel = SSRShader.defines.FRESNEL; + + /** + * Whether to use fresnel or not. + * + * @name SSRPass#fresnel + * @type {boolean} + * @default true + */ Object.defineProperty( this, 'fresnel', { get() { @@ -134,6 +270,14 @@ class SSRPass extends Pass { } ); this._infiniteThick = SSRShader.defines.INFINITE_THICK; + + /** + * Whether to use infinite thickness or not. + * + * @name SSRPass#infiniteThick + * @type {boolean} + * @default false + */ Object.defineProperty( this, 'infiniteThick', { get() { @@ -312,6 +456,10 @@ class SSRPass extends Pass { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { // dispose render targets @@ -341,6 +489,17 @@ class SSRPass extends Pass { } + /** + * Performs the SSR pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */ ) { // render beauty and depth @@ -360,13 +519,13 @@ class SSRPass extends Pass { // render normals - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0, 0 ); + this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0, 0 ); // render metalnesses if ( this.selective ) { - this.renderMetalness( renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0 ); + this._renderMetalness( renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0 ); } @@ -375,16 +534,16 @@ class SSRPass extends Pass { this.ssrMaterial.uniforms[ 'opacity' ].value = this.opacity; this.ssrMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; - this.renderPass( renderer, this.ssrMaterial, this.ssrRenderTarget ); + this._renderPass( renderer, this.ssrMaterial, this.ssrRenderTarget ); // render blur if ( this.blur ) { - this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); - this.renderPass( renderer, this.blurMaterial2, this.blurRenderTarget2 ); - // this.renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); + this._renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); + this._renderPass( renderer, this.blurMaterial2, this.blurRenderTarget2 ); + // this._renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); } @@ -398,31 +557,31 @@ class SSRPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this._renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); if ( this.blur ) this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this._renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); } else { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); if ( this.blur ) this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); } @@ -434,7 +593,7 @@ class SSRPass extends Pass { else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); if ( this.bouncing ) { @@ -443,11 +602,11 @@ class SSRPass extends Pass { else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this._renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this._renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); } @@ -457,13 +616,13 @@ class SSRPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; case SSRPass.OUTPUT.Depth: - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -471,7 +630,7 @@ class SSRPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -479,7 +638,7 @@ class SSRPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.metalnessRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -490,7 +649,40 @@ class SSRPass extends Pass { } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ + setSize( width, height ) { + + this.width = width; + this.height = height; + + this.ssrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height ); + this.ssrMaterial.needsUpdate = true; + this.beautyRenderTarget.setSize( width, height ); + this.prevRenderTarget.setSize( width, height ); + this.ssrRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.metalnessRenderTarget.setSize( width, height ); + this.blurRenderTarget.setSize( width, height ); + this.blurRenderTarget2.setSize( width, height ); + // this.blurRenderTarget3.setSize(width, height); + + this.ssrMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + + this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.blurMaterial2.uniforms[ 'resolution' ].value.set( width, height ); + + } + + // internals + + _renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { // save original state this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); @@ -519,7 +711,7 @@ class SSRPass extends Pass { } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); @@ -551,7 +743,7 @@ class SSRPass extends Pass { } - renderMetalness( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderMetalness( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); @@ -606,33 +798,22 @@ class SSRPass extends Pass { } - setSize( width, height ) { - - this.width = width; - this.height = height; - - this.ssrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height ); - this.ssrMaterial.needsUpdate = true; - this.beautyRenderTarget.setSize( width, height ); - this.prevRenderTarget.setSize( width, height ); - this.ssrRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.metalnessRenderTarget.setSize( width, height ); - this.blurRenderTarget.setSize( width, height ); - this.blurRenderTarget2.setSize( width, height ); - // this.blurRenderTarget3.setSize(width, height); - - this.ssrMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - - this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.blurMaterial2.uniforms[ 'resolution' ].value.set( width, height ); - - } - } +/** + * Constructor options of `SSRPass`. + * + * @typedef {Object} SSRPass~Options + * @property {WebGLRenderer} renderer - The renderer. + * @property {Scene} scene - The scene to render. + * @property {Camera} camera - The camera. + * @property {number} [width=512] - The width of the effect. + * @property {number} [height=512] - The width of the effect. + * @property {?Array} [selects=null] - Which 3D objects should be affected by SSR. If not set, the entire scene is affected. + * @property {boolean} [bouncing=false] - Whether bouncing is enabled or not. + * @property {?ReflectorForSSRPass} [groundReflector=null] - A ground reflector. + **/ + SSRPass.OUTPUT = { 'Default': 0, 'SSR': 1, diff --git a/examples/jsm/postprocessing/SavePass.js b/examples/jsm/postprocessing/SavePass.js index 55d33df0685a62..b81f3f64a0f801 100644 --- a/examples/jsm/postprocessing/SavePass.js +++ b/examples/jsm/postprocessing/SavePass.js @@ -8,27 +8,54 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * A pass that saves the contents of the current read buffer in a render target. + * + * ```js + * const savePass = new SavePass( customRenderTarget ); + * composer.addPass( savePass ); + * ``` + * + * @augments Pass + */ class SavePass extends Pass { + /** + * Constructs a new save pass. + * + * @param {WebGLRenderTarget} [renderTarget] - The render target for saving the read buffer. + * If not provided, the pass automatically creates a render target. + */ constructor( renderTarget ) { super(); - const shader = CopyShader; - - this.textureID = 'tDiffuse'; - - this.uniforms = UniformsUtils.clone( shader.uniforms ); - + /** + * The pass uniforms. + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( CopyShader.uniforms ); + + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, blending: NoBlending } ); + /** + * The render target which is used to save the read buffer. + * + * @type {WebGLRenderTarget} + */ this.renderTarget = renderTarget; if ( this.renderTarget === undefined ) { @@ -38,39 +65,64 @@ class SavePass extends Pass { } + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the save pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) { - if ( this.uniforms[ this.textureID ] ) { - - this.uniforms[ this.textureID ].value = readBuffer.texture; - - } + this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; renderer.setRenderTarget( this.renderTarget ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { this.renderTarget.setSize( width, height ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.renderTarget.dispose(); this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/ShaderPass.js b/examples/jsm/postprocessing/ShaderPass.js index 597016c9935e25..d0da9051054689 100644 --- a/examples/jsm/postprocessing/ShaderPass.js +++ b/examples/jsm/postprocessing/ShaderPass.js @@ -4,13 +4,53 @@ import { } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; +/** + * This pass can be used to create a post processing effect + * with a raw GLSL shader object. Useful for implementing custom + * effects. + * + * ```js + * const fxaaPass = new ShaderPass( FXAAShader ); + * composer.addPass( fxaaPass ); + * ``` + * + * @augments Pass + */ class ShaderPass extends Pass { - constructor( shader, textureID ) { + /** + * Constructs a new shader pass. + * + * @param {Object|ShaderMaterial} [shader] - A shader object holding vertex and fragment shader as well as + * defines and uniforms. It's also valid to pass a custom shader material. + * @param {string} [textureID='tDiffuse'] - The name of the texture uniform that should sample + * the read buffer. + */ + constructor( shader, textureID = 'tDiffuse' ) { super(); - this.textureID = ( textureID !== undefined ) ? textureID : 'tDiffuse'; + /** + * The name of the texture uniform that should sample the read buffer. + * + * @type {string} + * @default 'tDiffuse' + */ + this.textureID = textureID; + + /** + * The pass uniforms. + * + * @type {?Object} + */ + this.uniforms = null; + + /** + * The pass material. + * + * @type {?ShaderMaterial} + */ + this.material = null; if ( shader instanceof ShaderMaterial ) { @@ -34,10 +74,23 @@ class ShaderPass extends Pass { } - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the shader pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { if ( this.uniforms[ this.textureID ] ) { @@ -46,29 +99,33 @@ class ShaderPass extends Pass { } - this.fsQuad.material = this.material; + this._fsQuad.material = this.material; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/TAARenderPass.js b/examples/jsm/postprocessing/TAARenderPass.js index fb8c5a303b8320..949ab1120c1ae6 100644 --- a/examples/jsm/postprocessing/TAARenderPass.js +++ b/examples/jsm/postprocessing/TAARenderPass.js @@ -6,28 +6,78 @@ import { SSAARenderPass } from './SSAARenderPass.js'; /** * - * Temporal Anti-Aliasing Render Pass + * Temporal Anti-Aliasing Render Pass. * - * When there is no motion in the scene, the TAA render pass accumulates jittered camera samples across frames to create a high quality anti-aliased result. + * When there is no motion in the scene, the TAA render pass accumulates jittered camera + * samples across frames to create a high quality anti-aliased result. * - * References: + * Note: This effect uses no reprojection so it is no TRAA implementation. * - * TODO: Add support for motion vector pas so that accumulation of samples across frames can occur on dynamics scenes. + * ```js + * const taaRenderPass = new TAARenderPass( scene, camera ); + * taaRenderPass.unbiased = false; + * composer.addPass( taaRenderPass ); + * ``` * + * @augments SSAARenderPass */ - class TAARenderPass extends SSAARenderPass { + /** + * Constructs a new TAA render pass. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {?(number|Color|string)} [clearColor=0x000000] - The clear color of the render pass. + * @param {?number} [clearAlpha=0] - The clear alpha of the render pass. + */ constructor( scene, camera, clearColor, clearAlpha ) { super( scene, camera, clearColor, clearAlpha ); + /** + * Overwritten and set to 0 by default. + * + * @type {number} + * @default 0 + */ this.sampleLevel = 0; + + /** + * Whether to accumulate frames or not. This enables + * the TAA. + * + * @type {boolean} + * @default false + */ this.accumulate = false; + + /** + * The accumulation index. + * + * @type {number} + * @default -1 + */ this.accumulateIndex = - 1; + // internals + + this._sampleRenderTarget = null; + this._holdRenderTarget = null; + } + /** + * Performs the TAA render pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime ) { if ( this.accumulate === false ) { @@ -41,23 +91,23 @@ class TAARenderPass extends SSAARenderPass { const jitterOffsets = _JitterVectors[ 5 ]; - if ( this.sampleRenderTarget === undefined ) { + if ( this._sampleRenderTarget === null ) { - this.sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType } ); - this.sampleRenderTarget.texture.name = 'TAARenderPass.sample'; + this._sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType } ); + this._sampleRenderTarget.texture.name = 'TAARenderPass.sample'; } - if ( this.holdRenderTarget === undefined ) { + if ( this._holdRenderTarget === null ) { - this.holdRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType } ); - this.holdRenderTarget.texture.name = 'TAARenderPass.hold'; + this._holdRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType } ); + this._holdRenderTarget.texture.name = 'TAARenderPass.hold'; } if ( this.accumulateIndex === - 1 ) { - super.render( renderer, this.holdRenderTarget, readBuffer, deltaTime ); + super.render( renderer, this._holdRenderTarget, readBuffer, deltaTime ); this.accumulateIndex = 0; @@ -73,8 +123,8 @@ class TAARenderPass extends SSAARenderPass { if ( this.accumulateIndex >= 0 && this.accumulateIndex < jitterOffsets.length ) { - this.copyUniforms[ 'opacity' ].value = sampleWeight; - this.copyUniforms[ 'tDiffuse' ].value = writeBuffer.texture; + this._copyUniforms[ 'opacity' ].value = sampleWeight; + this._copyUniforms[ 'tDiffuse' ].value = writeBuffer.texture; // render the scene multiple times, each slightly jitter offset from the last and accumulate the results. const numSamplesPerFrame = Math.pow( 2, this.sampleLevel ); @@ -96,7 +146,7 @@ class TAARenderPass extends SSAARenderPass { renderer.clear(); renderer.render( this.scene, this.camera ); - renderer.setRenderTarget( this.sampleRenderTarget ); + renderer.setRenderTarget( this._sampleRenderTarget ); if ( this.accumulateIndex === 0 ) { renderer.setClearColor( 0x000000, 0.0 ); @@ -104,7 +154,7 @@ class TAARenderPass extends SSAARenderPass { } - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.accumulateIndex ++; @@ -121,20 +171,20 @@ class TAARenderPass extends SSAARenderPass { if ( accumulationWeight > 0 ) { - this.copyUniforms[ 'opacity' ].value = 1.0; - this.copyUniforms[ 'tDiffuse' ].value = this.sampleRenderTarget.texture; + this._copyUniforms[ 'opacity' ].value = 1.0; + this._copyUniforms[ 'tDiffuse' ].value = this._sampleRenderTarget.texture; renderer.setRenderTarget( writeBuffer ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } if ( accumulationWeight < 1.0 ) { - this.copyUniforms[ 'opacity' ].value = 1.0 - accumulationWeight; - this.copyUniforms[ 'tDiffuse' ].value = this.holdRenderTarget.texture; + this._copyUniforms[ 'opacity' ].value = 1.0 - accumulationWeight; + this._copyUniforms[ 'tDiffuse' ].value = this._holdRenderTarget.texture; renderer.setRenderTarget( writeBuffer ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } @@ -143,11 +193,15 @@ class TAARenderPass extends SSAARenderPass { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { super.dispose(); - if ( this.holdRenderTarget ) this.holdRenderTarget.dispose(); + if ( this._holdRenderTarget ) this._holdRenderTarget.dispose(); } diff --git a/examples/jsm/postprocessing/TexturePass.js b/examples/jsm/postprocessing/TexturePass.js index f9de8185881bfb..6e86070c88f96e 100644 --- a/examples/jsm/postprocessing/TexturePass.js +++ b/examples/jsm/postprocessing/TexturePass.js @@ -5,19 +5,68 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * This pass can be used to render a texture over the entire screen. + * + * ```js + * const texture = new THREE.TextureLoader().load( 'textures/2294472375_24a3b8ef46_o.jpg' ); + * texture.colorSpace = THREE.SRGBColorSpace; + * + * const texturePass = new TexturePass( texture ); + * composer.addPass( texturePass ); + * ``` + * + * @augments Pass + */ class TexturePass extends Pass { - constructor( map, opacity ) { + /** + * Constructs a new texture pass. + * + * @param {Texture} map - The texture to render. + * @param {number} [opacity=1] - The opacity. + */ + constructor( map, opacity = 1 ) { super(); const shader = CopyShader; + /** + * The texture to render. + * + * @type {Texture} + */ this.map = map; - this.opacity = ( opacity !== undefined ) ? opacity : 1.0; + /** + * The opacity. + * + * @type {number} + * @default 1 + */ + this.opacity = opacity; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; + + /** + * The pass uniforms. + * + * @type {Object} + */ this.uniforms = UniformsUtils.clone( shader.uniforms ); + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { uniforms: this.uniforms, @@ -29,18 +78,29 @@ class TexturePass extends Pass { } ); - this.needsSwap = false; + // internals - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); } + /** + * Performs the texture pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { const oldAutoClear = renderer.autoClear; renderer.autoClear = false; - this.fsQuad.material = this.material; + this._fsQuad.material = this.material; this.uniforms[ 'opacity' ].value = this.opacity; this.uniforms[ 'tDiffuse' ].value = this.map; @@ -48,17 +108,20 @@ class TexturePass extends Pass { renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); renderer.autoClear = oldAutoClear; } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/UnrealBloomPass.js b/examples/jsm/postprocessing/UnrealBloomPass.js index 35d6c325cf8229..62e89e5edd1285 100644 --- a/examples/jsm/postprocessing/UnrealBloomPass.js +++ b/examples/jsm/postprocessing/UnrealBloomPass.js @@ -14,28 +14,86 @@ import { CopyShader } from '../shaders/CopyShader.js'; import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js'; /** - * UnrealBloomPass is inspired by the bloom pass of Unreal Engine. It creates a + * This pass is inspired by the bloom pass of Unreal Engine. It creates a * mip map chain of bloom textures and blurs them with different radii. Because * of the weighted combination of mips, and because larger blurs are done on * higher mips, this effect provides good quality and performance. * + * When using this pass, tone mapping must be enabled in the renderer settings. + * * Reference: - * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ + * - [Bloom in Unreal Engine]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/} + * + * ```js + * const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); + * const bloomPass = new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ); + * composer.addPass( bloomPass ); + * ``` + * + * @augments Pass */ class UnrealBloomPass extends Pass { - constructor( resolution, strength, radius, threshold ) { + /** + * Constructs a new Unreal Bloom pass. + * + * @param {Vector2} [resolution] - The effect's resolution. + * @param {number} [strength=1] - The Bloom strength. + * @param {number} radius - The Bloom radius. + * @param {number} threshold - The luminance threshold limits which bright areas contribute to the Bloom effect. + */ + constructor( resolution, strength = 1, radius, threshold ) { super(); - this.strength = ( strength !== undefined ) ? strength : 1; + /** + * The Bloom strength. + * + * @type {number} + * @default 1 + */ + this.strength = strength; + + /** + * The Bloom radius. + * + * @type {number} + */ this.radius = radius; + + /** + * The luminance threshold limits which bright areas contribute to the Bloom effect. + * + * @type {number} + */ this.threshold = threshold; + + /** + * The effect's resolution. + * + * @type {Vector2} + * @default (256,256) + */ this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - // create color only once here, reuse it later inside the render function + /** + * The effect's clear color + * + * @type {Color} + * @default (0,0,0) + */ this.clearColor = new Color( 0, 0, 0 ); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; + + // internals + // render targets this.renderTargetsHorizontal = []; this.renderTargetsVertical = []; @@ -92,7 +150,7 @@ class UnrealBloomPass extends Pass { for ( let i = 0; i < this.nMips; i ++ ) { - this.separableBlurMaterials.push( this.getSeparableBlurMaterial( kernelSizeArray[ i ] ) ); + this.separableBlurMaterials.push( this._getSeparableBlurMaterial( kernelSizeArray[ i ] ) ); this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); @@ -104,7 +162,7 @@ class UnrealBloomPass extends Pass { // composite material - this.compositeMaterial = this.getCompositeMaterial( this.nMips ); + this.compositeMaterial = this._getCompositeMaterial( this.nMips ); this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; @@ -120,32 +178,31 @@ class UnrealBloomPass extends Pass { // blend material - const copyShader = CopyShader; - - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + this.copyUniforms = UniformsUtils.clone( CopyShader.uniforms ); this.blendMaterial = new ShaderMaterial( { uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, blending: AdditiveBlending, depthTest: false, depthWrite: false, transparent: true } ); - this.enabled = true; - this.needsSwap = false; - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this._oldClearAlpha = 1; - this.basic = new MeshBasicMaterial(); + this._basic = new MeshBasicMaterial(); - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { @@ -172,14 +229,20 @@ class UnrealBloomPass extends Pass { this.compositeMaterial.dispose(); this.blendMaterial.dispose(); - this.basic.dispose(); + this._basic.dispose(); // - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ setSize( width, height ) { let resx = Math.round( width / 2 ); @@ -201,10 +264,21 @@ class UnrealBloomPass extends Pass { } + /** + * Performs the Bloom pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); + this._oldClearAlpha = renderer.getClearAlpha(); const oldAutoClear = renderer.autoClear; renderer.autoClear = false; @@ -216,12 +290,12 @@ class UnrealBloomPass extends Pass { if ( this.renderToScreen ) { - this.fsQuad.material = this.basic; - this.basic.map = readBuffer.texture; + this._fsQuad.material = this._basic; + this._basic.map = readBuffer.texture; renderer.setRenderTarget( null ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } @@ -229,11 +303,11 @@ class UnrealBloomPass extends Pass { this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; - this.fsQuad.material = this.materialHighPassFilter; + this._fsQuad.material = this.materialHighPassFilter; renderer.setRenderTarget( this.renderTargetBright ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // 2. Blur All the mips progressively @@ -241,19 +315,19 @@ class UnrealBloomPass extends Pass { for ( let i = 0; i < this.nMips; i ++ ) { - this.fsQuad.material = this.separableBlurMaterials[ i ]; + this._fsQuad.material = this.separableBlurMaterials[ i ]; this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); inputRenderTarget = this.renderTargetsVertical[ i ]; @@ -261,18 +335,18 @@ class UnrealBloomPass extends Pass { // Composite All the mips - this.fsQuad.material = this.compositeMaterial; + this._fsQuad.material = this.compositeMaterial; this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Blend it additively over the input texture - this.fsQuad.material = this.blendMaterial; + this._fsQuad.material = this.blendMaterial; this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); @@ -280,23 +354,25 @@ class UnrealBloomPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } // Restore renderer settings - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.setClearColor( this._oldClearColor, this._oldClearAlpha ); renderer.autoClear = oldAutoClear; } - getSeparableBlurMaterial( kernelRadius ) { + // internals + + _getSeparableBlurMaterial( kernelRadius ) { const coefficients = []; @@ -352,7 +428,7 @@ class UnrealBloomPass extends Pass { } - getCompositeMaterial( nMips ) { + _getCompositeMaterial( nMips ) { return new ShaderMaterial( { diff --git a/examples/jsm/tsl/display/SSRNode.js b/examples/jsm/tsl/display/SSRNode.js index f26f2da089b74a..bb0cdd415988ea 100644 --- a/examples/jsm/tsl/display/SSRNode.js +++ b/examples/jsm/tsl/display/SSRNode.js @@ -98,7 +98,7 @@ class SSRNode extends TempNode { this._ssrRenderTarget.texture.name = 'SSRNode.SSR'; /** - * Controls how far a fragment can reflect + * Controls how far a fragment can reflect. * * * @type {UniformNode} diff --git a/examples/webgl_postprocessing_rgb_halftone.html b/examples/webgl_postprocessing_rgb_halftone.html index 3d02123a05cddc..dd6de97f7930a6 100644 --- a/examples/webgl_postprocessing_rgb_halftone.html +++ b/examples/webgl_postprocessing_rgb_halftone.html @@ -132,7 +132,7 @@ greyscale: false, disable: false }; - const halftonePass = new HalftonePass( window.innerWidth, window.innerHeight, params ); + const halftonePass = new HalftonePass( params ); composer.addPass( renderPass ); composer.addPass( halftonePass ); diff --git a/examples/webgl_postprocessing_smaa.html b/examples/webgl_postprocessing_smaa.html index 18b0a4f030764c..b58d773689b088 100644 --- a/examples/webgl_postprocessing_smaa.html +++ b/examples/webgl_postprocessing_smaa.html @@ -86,7 +86,7 @@ composer = new EffectComposer( renderer ); composer.addPass( new RenderPass( scene, camera ) ); - smaaPass = new SMAAPass( window.innerWidth * renderer.getPixelRatio(), window.innerHeight * renderer.getPixelRatio() ); + smaaPass = new SMAAPass(); composer.addPass( smaaPass ); const outputPass = new OutputPass(); diff --git a/utils/docs/jsdoc.config.json b/utils/docs/jsdoc.config.json index 9fee578a3fc0ac..2678575f3ae69e 100644 --- a/utils/docs/jsdoc.config.json +++ b/utils/docs/jsdoc.config.json @@ -33,6 +33,7 @@ "examples/jsm/modifiers", "examples/jsm/objects", "examples/jsm/physics", + "examples/jsm/postprocessing", "examples/jsm/renderers", "examples/jsm/textures", "examples/jsm/transpiler",