UNPKG

three

Version:

JavaScript 3D library

364 lines (266 loc) 8.48 kB
import { Clock, HalfFloatType, NoBlending, Vector2, WebGLRenderTarget } from 'three'; 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() * * } * ``` * * @three_import import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; */ 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(); if ( renderTarget === undefined ) { const size = renderer.getSize( new Vector2() ); this._width = size.width; this._height = size.height; renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); renderTarget.texture.name = 'EffectComposer.rt1'; } else { this._width = renderTarget.width; this._height = renderTarget.height; } this.renderTarget1 = renderTarget; 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<Pass>} */ this.passes = []; /** * A copy pass used for internal swap operations. * * @private * @type {ShaderPass} */ this.copyPass = new ShaderPass( CopyShader ); this.copyPass.material.blending = NoBlending; /** * The internal clock for managing time data. * * @private * @type {Clock} */ this.clock = new Clock(); } /** * Swaps the internal read/write buffers. */ swapBuffers() { const tmp = this.readBuffer; this.readBuffer = this.writeBuffer; this.writeBuffer = tmp; } /** * Adds the given pass to the pass chain. * * @param {Pass} pass - The pass to add. */ addPass( pass ) { this.passes.push( pass ); pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); } /** * 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 ); pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); } /** * Removes the given pass from the pass chain. * * @param {Pass} pass - The pass to remove. */ removePass( pass ) { const index = this.passes.indexOf( pass ); if ( index !== - 1 ) { this.passes.splice( index, 1 ); } } /** * 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 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 ++ ) { if ( this.passes[ i ].enabled ) { return false; } } return true; } /** * 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 if ( deltaTime === undefined ) { deltaTime = this.clock.getDelta(); } const currentRenderTarget = this.renderer.getRenderTarget(); let maskActive = false; for ( let i = 0, il = this.passes.length; i < il; i ++ ) { const pass = this.passes[ i ]; if ( pass.enabled === false ) continue; pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) ); pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive ); if ( pass.needsSwap ) { if ( maskActive ) { const context = this.renderer.getContext(); const stencil = this.renderer.state.buffers.stencil; //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff ); this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime ); //context.stencilFunc( context.EQUAL, 1, 0xffffffff ); stencil.setFunc( context.EQUAL, 1, 0xffffffff ); } this.swapBuffers(); } if ( MaskPass !== undefined ) { if ( pass instanceof MaskPass ) { maskActive = true; } else if ( pass instanceof ClearMaskPass ) { maskActive = false; } } } this.renderer.setRenderTarget( currentRenderTarget ); } /** * 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 ) { const size = this.renderer.getSize( new Vector2() ); this._pixelRatio = this.renderer.getPixelRatio(); this._width = size.width; this._height = size.height; renderTarget = this.renderTarget1.clone(); renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); } this.renderTarget1.dispose(); this.renderTarget2.dispose(); this.renderTarget1 = renderTarget; this.renderTarget2 = renderTarget.clone(); this.writeBuffer = this.renderTarget1; this.readBuffer = this.renderTarget2; } /** * 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; this._height = height; const effectiveWidth = this._width * this._pixelRatio; const effectiveHeight = this._height * this._pixelRatio; this.renderTarget1.setSize( effectiveWidth, effectiveHeight ); this.renderTarget2.setSize( effectiveWidth, effectiveHeight ); for ( let i = 0; i < this.passes.length; i ++ ) { this.passes[ i ].setSize( effectiveWidth, effectiveHeight ); } } /** * 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; this.setSize( this._width, this._height ); } /** * 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(); this.renderTarget2.dispose(); this.copyPass.dispose(); } } export { EffectComposer };