UNPKG

postprocessing

Version:

A post processing library for three.js.

1,639 lines (1,614 loc) 624 kB
/** * postprocessing v6.37.2 build Fri Mar 28 2025 * https://github.com/pmndrs/postprocessing * Copyright 2015-2025 Raoul van Rüschen * @license Zlib */ // package.json var version = "6.37.2"; // src/core/Disposable.js var Disposable = class { /** * Frees internal resources. */ dispose() { } }; // src/core/EffectComposer.js import { DepthStencilFormat, DepthTexture, LinearFilter as LinearFilter2, SRGBColorSpace as SRGBColorSpace2, UnsignedByteType as UnsignedByteType2, UnsignedIntType, UnsignedInt248Type, Vector2, WebGLRenderTarget as WebGLRenderTarget3 } from "three"; // src/core/Timer.js var MILLISECONDS_TO_SECONDS = 1 / 1e3; var SECONDS_TO_MILLISECONDS = 1e3; var Timer = class { /** * Constructs a new timer. */ constructor() { this.startTime = performance.now(); this.previousTime = 0; this.currentTime = 0; this._delta = 0; this._elapsed = 0; this._fixedDelta = 1e3 / 60; this.timescale = 1; this.useFixedDelta = false; this._autoReset = false; } /** * Enables or disables auto reset based on page visibility. * * If enabled, the timer will be reset when the page becomes visible. This effectively pauses the timer when the page * is hidden. Has no effect if the API is not supported. * * @type {Boolean} * @see https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API */ get autoReset() { return this._autoReset; } set autoReset(value) { if (typeof document !== "undefined" && document.hidden !== void 0) { if (value) { document.addEventListener("visibilitychange", this); } else { document.removeEventListener("visibilitychange", this); } this._autoReset = value; } } get delta() { return this._delta * MILLISECONDS_TO_SECONDS; } get fixedDelta() { return this._fixedDelta * MILLISECONDS_TO_SECONDS; } set fixedDelta(value) { this._fixedDelta = value * SECONDS_TO_MILLISECONDS; } get elapsed() { return this._elapsed * MILLISECONDS_TO_SECONDS; } /** * Updates this timer. * * @param {Boolean} [timestamp] - The current time in milliseconds. */ update(timestamp) { if (this.useFixedDelta) { this._delta = this.fixedDelta; } else { this.previousTime = this.currentTime; this.currentTime = (timestamp !== void 0 ? timestamp : performance.now()) - this.startTime; this._delta = this.currentTime - this.previousTime; } this._delta *= this.timescale; this._elapsed += this._delta; } /** * Resets this timer. */ reset() { this._delta = 0; this._elapsed = 0; this.currentTime = performance.now() - this.startTime; } getDelta() { return this.delta; } getElapsed() { return this.elapsed; } handleEvent(e) { if (!document.hidden) { this.currentTime = performance.now() - this.startTime; } } dispose() { this.autoReset = false; } }; // src/passes/Pass.js import { BasicDepthPacking, BufferAttribute, BufferGeometry, Camera, Material, Mesh, Scene, Texture, WebGLRenderTarget } from "three"; var fullscreenGeometry = /* @__PURE__ */ (() => { const vertices = new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]); const uvs = new Float32Array([0, 0, 2, 0, 0, 2]); const geometry = new BufferGeometry(); geometry.setAttribute("position", new BufferAttribute(vertices, 3)); geometry.setAttribute("uv", new BufferAttribute(uvs, 2)); return geometry; })(); var Pass = class _Pass { /** * A shared fullscreen triangle. * * The screen size is 2x2 units (NDC). A triangle needs to be 4x4 units to fill the screen. * @see https://michaldrobot.com/2014/04/01/gcn-execution-patterns-in-full-screen-passes/ * @type {BufferGeometry} * @internal */ static get fullscreenGeometry() { return fullscreenGeometry; } /** * Constructs a new pass. * * @param {String} [name] - The name of this pass. Does not have to be unique. * @param {Scene} [scene] - The scene to render. The default scene contains a single mesh that fills the screen. * @param {Camera} [camera] - A camera. Fullscreen effect passes don't require a camera. */ constructor(name = "Pass", scene = new Scene(), camera = new Camera()) { this.name = name; this.renderer = null; this.scene = scene; this.camera = camera; this.screen = null; this.rtt = true; this.needsSwap = true; this.needsDepthTexture = false; this.enabled = true; } /** * Sets the render to screen flag. * * If this flag is changed, the fullscreen material will be updated as well. * * @type {Boolean} */ get renderToScreen() { return !this.rtt; } set renderToScreen(value) { if (this.rtt === value) { const material = this.fullscreenMaterial; if (material !== null) { material.needsUpdate = true; } this.rtt = !value; } } /** * Sets the main scene. * * @type {Scene} */ set mainScene(value) { } /** * Sets the main camera. * * @type {Camera} */ set mainCamera(value) { } /** * Sets the renderer * * @deprecated * @param {WebGLRenderer} renderer - The renderer. */ setRenderer(renderer) { this.renderer = renderer; } /** * Indicates whether this pass is enabled. * * @deprecated Use enabled instead. * @return {Boolean} Whether this pass is enabled. */ isEnabled() { return this.enabled; } /** * Enables or disables this pass. * * @deprecated Use enabled instead. * @param {Boolean} value - Whether the pass should be enabled. */ setEnabled(value) { this.enabled = value; } /** * The fullscreen material. * * @type {Material} */ get fullscreenMaterial() { return this.screen !== null ? this.screen.material : null; } set fullscreenMaterial(value) { let screen = this.screen; if (screen !== null) { screen.material = value; } else { screen = new Mesh(_Pass.fullscreenGeometry, value); screen.frustumCulled = false; if (this.scene === null) { this.scene = new Scene(); } this.scene.add(screen); this.screen = screen; } } /** * Returns the current fullscreen material. * * @deprecated Use fullscreenMaterial instead. * @return {Material} The current fullscreen material, or null if there is none. */ getFullscreenMaterial() { return this.fullscreenMaterial; } /** * Sets the fullscreen material. * * @deprecated Use fullscreenMaterial instead. * @protected * @param {Material} value - A fullscreen material. */ setFullscreenMaterial(value) { this.fullscreenMaterial = value; } /** * Returns the current depth texture. * * @return {Texture} The current depth texture, or null if there is none. */ getDepthTexture() { return null; } /** * Sets the depth texture. * * This method will be called automatically by the {@link EffectComposer}. * You may override this method if your pass relies on the depth information of a preceding {@link RenderPass}. * * @param {Texture} depthTexture - A depth texture. * @param {DepthPackingStrategy} [depthPacking=BasicDepthPacking] - The depth packing. */ setDepthTexture(depthTexture, depthPacking = BasicDepthPacking) { } /** * Renders this pass. * * This is an abstract method that must be overridden. * * @abstract * @throws {Error} An error is thrown if the method is not overridden. * @param {WebGLRenderer} renderer - The renderer. * @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass. * @param {WebGLRenderTarget} outputBuffer - A frame buffer that serves as the output render target unless this pass renders to screen. * @param {Number} [deltaTime] - The time between the last frame and the current one in seconds. * @param {Boolean} [stencilTest] - Indicates whether a stencil mask is active. */ render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) { throw new Error("Render method not implemented!"); } /** * Sets the size. * * You may override this method if you want to be informed about the size of the backbuffer/canvas. * This method is called before {@link initialize} and every time the size of the {@link EffectComposer} changes. * * @param {Number} width - The width. * @param {Number} height - The height. */ setSize(width, height) { } /** * Performs initialization tasks. * * This method is called when this pass is added to an {@link EffectComposer}. * * @param {WebGLRenderer} renderer - The renderer. * @param {Boolean} alpha - Whether the renderer uses the alpha channel or not. * @param {Number} frameBufferType - The type of the main frame buffers. */ initialize(renderer, alpha, frameBufferType) { } /** * Performs a shallow search for disposable properties and deletes them. * * The {@link EffectComposer} calls this method when it is being destroyed. You can use it independently to free * memory when you're certain that you don't need this pass anymore. */ dispose() { for (const key of Object.keys(this)) { const property = this[key]; const isDisposable = property instanceof WebGLRenderTarget || property instanceof Material || property instanceof Texture || property instanceof _Pass; if (isDisposable) { this[key].dispose(); } } if (this.fullscreenMaterial !== null) { this.fullscreenMaterial.dispose(); } } }; // src/passes/ClearMaskPass.js var ClearMaskPass = class extends Pass { /** * Constructs a new clear mask pass. */ constructor() { super("ClearMaskPass", null, null); this.needsSwap = false; } /** * Disables the global stencil test. * * @param {WebGLRenderer} renderer - The renderer. * @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass. * @param {WebGLRenderTarget} outputBuffer - A frame buffer that serves as the output render target unless this pass renders to screen. * @param {Number} [deltaTime] - The time between the last frame and the current one in seconds. * @param {Boolean} [stencilTest] - Indicates whether a stencil mask is active. */ render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) { const stencil = renderer.state.buffers.stencil; stencil.setLocked(false); stencil.setTest(false); } }; // src/passes/CopyPass.js import { LinearFilter, SRGBColorSpace, UnsignedByteType, WebGLRenderTarget as WebGLRenderTarget2 } from "three"; // src/materials/CopyMaterial.js import { NoBlending, ShaderMaterial, Uniform } from "three"; // src/materials/glsl/copy.frag var copy_default = `#include <common> #include <dithering_pars_fragment> #ifdef FRAMEBUFFER_PRECISION_HIGH uniform mediump sampler2D inputBuffer; #else uniform lowp sampler2D inputBuffer; #endif uniform float opacity;varying vec2 vUv;void main(){vec4 texel=texture2D(inputBuffer,vUv);gl_FragColor=opacity*texel; #include <colorspace_fragment> #include <dithering_fragment> }`; // src/materials/glsl/common.vert var common_default = `varying vec2 vUv;void main(){vUv=position.xy*0.5+0.5;gl_Position=vec4(position.xy,1.0,1.0);}`; // src/materials/CopyMaterial.js var CopyMaterial = class extends ShaderMaterial { /** * Constructs a new copy material. */ constructor() { super({ name: "CopyMaterial", uniforms: { inputBuffer: new Uniform(null), opacity: new Uniform(1) }, blending: NoBlending, toneMapped: false, depthWrite: false, depthTest: false, fragmentShader: copy_default, vertexShader: common_default }); } /** * The input buffer. * * @type {Texture} */ set inputBuffer(value) { this.uniforms.inputBuffer.value = value; } /** * Sets the input buffer. * * @deprecated Use inputBuffer instead. * @param {Number} value - The buffer. */ setInputBuffer(value) { this.uniforms.inputBuffer.value = value; } /** * Returns the opacity. * * @deprecated Use opacity instead. * @return {Number} The opacity. */ getOpacity(value) { return this.uniforms.opacity.value; } /** * Sets the opacity. * * @deprecated Use opacity instead. * @param {Number} value - The opacity. */ setOpacity(value) { this.uniforms.opacity.value = value; } }; // src/passes/CopyPass.js var CopyPass = class extends Pass { /** * Constructs a new save pass. * * @param {WebGLRenderTarget} [renderTarget] - A render target. * @param {Boolean} [autoResize=true] - Whether the render target size should be updated automatically. */ constructor(renderTarget, autoResize = true) { super("CopyPass"); this.fullscreenMaterial = new CopyMaterial(); this.needsSwap = false; this.renderTarget = renderTarget; if (renderTarget === void 0) { this.renderTarget = new WebGLRenderTarget2(1, 1, { minFilter: LinearFilter, magFilter: LinearFilter, stencilBuffer: false, depthBuffer: false }); this.renderTarget.texture.name = "CopyPass.Target"; } this.autoResize = autoResize; } /** * Enables or disables auto resizing of the render target. * * @deprecated Use autoResize instead. * @type {Boolean} */ get resize() { return this.autoResize; } set resize(value) { this.autoResize = value; } /** * The output texture. * * @type {Texture} */ get texture() { return this.renderTarget.texture; } /** * Returns the output texture. * * @deprecated Use texture instead. * @return {Texture} The texture. */ getTexture() { return this.renderTarget.texture; } /** * Enables or disables auto resizing of the render target. * * @deprecated Use autoResize instead. * @param {Boolean} value - Whether the render target size should be updated automatically. */ setAutoResizeEnabled(value) { this.autoResize = value; } /** * Saves the input buffer. * * @param {WebGLRenderer} renderer - The renderer. * @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass. * @param {WebGLRenderTarget} outputBuffer - A frame buffer that serves as the output render target unless this pass renders to screen. * @param {Number} [deltaTime] - The time between the last frame and the current one in seconds. * @param {Boolean} [stencilTest] - Indicates whether a stencil mask is active. */ render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) { this.fullscreenMaterial.inputBuffer = inputBuffer.texture; renderer.setRenderTarget(this.renderToScreen ? null : this.renderTarget); renderer.render(this.scene, this.camera); } /** * Updates the size of this pass. * * @param {Number} width - The width. * @param {Number} height - The height. */ setSize(width, height) { if (this.autoResize) { this.renderTarget.setSize(width, height); } } /** * Performs initialization tasks. * * @param {WebGLRenderer} renderer - A renderer. * @param {Boolean} alpha - Whether the renderer uses the alpha channel. * @param {Number} frameBufferType - The type of the main frame buffers. */ initialize(renderer, alpha, frameBufferType) { if (frameBufferType !== void 0) { this.renderTarget.texture.type = frameBufferType; if (frameBufferType !== UnsignedByteType) { this.fullscreenMaterial.defines.FRAMEBUFFER_PRECISION_HIGH = "1"; } else if (renderer !== null && renderer.outputColorSpace === SRGBColorSpace) { this.renderTarget.texture.colorSpace = SRGBColorSpace; } } } }; // src/passes/ClearPass.js import { Color } from "three"; var color = /* @__PURE__ */ new Color(); var ClearPass = class extends Pass { /** * Constructs a new clear pass. * * @param {Boolean} [color=true] - Determines whether the color buffer should be cleared. * @param {Boolean} [depth=true] - Determines whether the depth buffer should be cleared. * @param {Boolean} [stencil=false] - Determines whether the stencil buffer should be cleared. */ constructor(color2 = true, depth = true, stencil = false) { super("ClearPass", null, null); this.needsSwap = false; this.color = color2; this.depth = depth; this.stencil = stencil; this.overrideClearColor = null; this.overrideClearAlpha = -1; } /** * Sets the clear flags. * * @param {Boolean} color - Whether the color buffer should be cleared. * @param {Boolean} depth - Whether the depth buffer should be cleared. * @param {Boolean} stencil - Whether the stencil buffer should be cleared. */ setClearFlags(color2, depth, stencil) { this.color = color2; this.depth = depth; this.stencil = stencil; } /** * Returns the override clear color. Default is null. * * @deprecated Use overrideClearColor instead. * @return {Color} The clear color. */ getOverrideClearColor() { return this.overrideClearColor; } /** * Sets the override clear color. * * @deprecated Use overrideClearColor instead. * @param {Color} value - The clear color. */ setOverrideClearColor(value) { this.overrideClearColor = value; } /** * Returns the override clear alpha. Default is -1. * * @deprecated Use overrideClearAlpha instead. * @return {Number} The clear alpha. */ getOverrideClearAlpha() { return this.overrideClearAlpha; } /** * Sets the override clear alpha. * * @deprecated Use overrideClearAlpha instead. * @param {Number} value - The clear alpha. */ setOverrideClearAlpha(value) { this.overrideClearAlpha = value; } /** * Clears the input buffer or the screen. * * @param {WebGLRenderer} renderer - The renderer. * @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass. * @param {WebGLRenderTarget} outputBuffer - A frame buffer that serves as the output render target unless this pass renders to screen. * @param {Number} [deltaTime] - The time between the last frame and the current one in seconds. * @param {Boolean} [stencilTest] - Indicates whether a stencil mask is active. */ render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) { const overrideClearColor = this.overrideClearColor; const overrideClearAlpha = this.overrideClearAlpha; const clearAlpha = renderer.getClearAlpha(); const hasOverrideClearColor = overrideClearColor !== null; const hasOverrideClearAlpha = overrideClearAlpha >= 0; if (hasOverrideClearColor) { renderer.getClearColor(color); renderer.setClearColor(overrideClearColor, hasOverrideClearAlpha ? overrideClearAlpha : clearAlpha); } else if (hasOverrideClearAlpha) { renderer.setClearAlpha(overrideClearAlpha); } renderer.setRenderTarget(this.renderToScreen ? null : inputBuffer); renderer.clear(this.color, this.depth, this.stencil); if (hasOverrideClearColor) { renderer.setClearColor(color, clearAlpha); } else if (hasOverrideClearAlpha) { renderer.setClearAlpha(clearAlpha); } } }; // src/passes/MaskPass.js var MaskPass = class extends Pass { /** * Constructs a new mask pass. * * @param {Scene} scene - The scene to render. * @param {Camera} camera - The camera to use. */ constructor(scene, camera) { super("MaskPass", scene, camera); this.needsSwap = false; this.clearPass = new ClearPass(false, false, true); this.inverse = false; } set mainScene(value) { this.scene = value; } set mainCamera(value) { this.camera = value; } /** * Indicates whether the mask should be inverted. * * @type {Boolean} */ get inverted() { return this.inverse; } set inverted(value) { this.inverse = value; } /** * Indicates whether this pass should clear the stencil buffer. * * @type {Boolean} * @deprecated Use clearPass.enabled instead. */ get clear() { return this.clearPass.enabled; } set clear(value) { this.clearPass.enabled = value; } /** * Returns the internal clear pass. * * @deprecated Use clearPass.enabled instead. * @return {ClearPass} The clear pass. */ getClearPass() { return this.clearPass; } /** * Indicates whether the mask is inverted. * * @deprecated Use inverted instead. * @return {Boolean} Whether the mask is inverted. */ isInverted() { return this.inverted; } /** * Enables or disable mask inversion. * * @deprecated Use inverted instead. * @param {Boolean} value - Whether the mask should be inverted. */ setInverted(value) { this.inverted = value; } /** * Renders the effect. * * @param {WebGLRenderer} renderer - The renderer. * @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass. * @param {WebGLRenderTarget} outputBuffer - A frame buffer that serves as the output render target unless this pass renders to screen. * @param {Number} [deltaTime] - The time between the last frame and the current one in seconds. * @param {Boolean} [stencilTest] - Indicates whether a stencil mask is active. */ render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) { const context = renderer.getContext(); const buffers = renderer.state.buffers; const scene = this.scene; const camera = this.camera; const clearPass = this.clearPass; const writeValue = this.inverted ? 0 : 1; const clearValue = 1 - writeValue; buffers.color.setMask(false); buffers.depth.setMask(false); buffers.color.setLocked(true); buffers.depth.setLocked(true); buffers.stencil.setTest(true); buffers.stencil.setOp(context.REPLACE, context.REPLACE, context.REPLACE); buffers.stencil.setFunc(context.ALWAYS, writeValue, 4294967295); buffers.stencil.setClear(clearValue); buffers.stencil.setLocked(true); if (this.clearPass.enabled) { if (this.renderToScreen) { clearPass.render(renderer, null); } else { clearPass.render(renderer, inputBuffer); clearPass.render(renderer, outputBuffer); } } if (this.renderToScreen) { renderer.setRenderTarget(null); renderer.render(scene, camera); } else { renderer.setRenderTarget(inputBuffer); renderer.render(scene, camera); renderer.setRenderTarget(outputBuffer); renderer.render(scene, camera); } buffers.color.setLocked(false); buffers.depth.setLocked(false); buffers.stencil.setLocked(false); buffers.stencil.setFunc(context.EQUAL, 1, 4294967295); buffers.stencil.setOp(context.KEEP, context.KEEP, context.KEEP); buffers.stencil.setLocked(true); } }; // src/core/EffectComposer.js var EffectComposer = class { /** * Constructs a new effect composer. * * @param {WebGLRenderer} renderer - The renderer that should be used. * @param {Object} [options] - The options. * @param {Boolean} [options.depthBuffer=true] - Whether the main render targets should have a depth buffer. * @param {Boolean} [options.stencilBuffer=false] - Whether the main render targets should have a stencil buffer. * @param {Boolean} [options.alpha] - Deprecated. Buffers are always RGBA since three r137. * @param {Number} [options.multisampling=0] - The number of samples used for multisample antialiasing. Requires WebGL 2. * @param {Number} [options.frameBufferType] - The type of the internal frame buffers. It's recommended to use HalfFloatType if possible. */ constructor(renderer = null, { depthBuffer = true, stencilBuffer = false, multisampling = 0, frameBufferType } = {}) { this.renderer = null; this.inputBuffer = this.createBuffer(depthBuffer, stencilBuffer, frameBufferType, multisampling); this.outputBuffer = this.inputBuffer.clone(); this.copyPass = new CopyPass(); this.depthTexture = null; this.passes = []; this.timer = new Timer(); this.autoRenderToScreen = true; this.setRenderer(renderer); } /** * The current amount of samples used for multisample anti-aliasing. * * @type {Number} */ get multisampling() { return this.inputBuffer.samples || 0; } /** * Sets the amount of MSAA samples. * * Requires WebGL 2. Set to zero to disable multisampling. * * @type {Number} */ set multisampling(value) { const buffer = this.inputBuffer; const multisampling = this.multisampling; if (multisampling > 0 && value > 0) { this.inputBuffer.samples = value; this.outputBuffer.samples = value; this.inputBuffer.dispose(); this.outputBuffer.dispose(); } else if (multisampling !== value) { this.inputBuffer.dispose(); this.outputBuffer.dispose(); this.inputBuffer = this.createBuffer( buffer.depthBuffer, buffer.stencilBuffer, buffer.texture.type, value ); this.inputBuffer.depthTexture = this.depthTexture; this.outputBuffer = this.inputBuffer.clone(); } } /** * Returns the internal timer. * * @return {Timer} The timer. */ getTimer() { return this.timer; } /** * Returns the renderer. * * @return {WebGLRenderer} The renderer. */ getRenderer() { return this.renderer; } /** * Sets the renderer. * * @param {WebGLRenderer} renderer - The renderer. */ setRenderer(renderer) { this.renderer = renderer; if (renderer !== null) { const size = renderer.getSize(new Vector2()); const alpha = renderer.getContext().getContextAttributes().alpha; const frameBufferType = this.inputBuffer.texture.type; if (frameBufferType === UnsignedByteType2 && renderer.outputColorSpace === SRGBColorSpace2) { this.inputBuffer.texture.colorSpace = SRGBColorSpace2; this.outputBuffer.texture.colorSpace = SRGBColorSpace2; this.inputBuffer.dispose(); this.outputBuffer.dispose(); } renderer.autoClear = false; this.setSize(size.width, size.height); for (const pass of this.passes) { pass.initialize(renderer, alpha, frameBufferType); } } } /** * Replaces the current renderer with the given one. * * The auto clear mechanism of the provided renderer will be disabled. If the new render size differs from the * previous one, all passes will be updated. * * By default, the DOM element of the current renderer will automatically be removed from its parent node and the DOM * element of the new renderer will take its place. * * @deprecated Use setRenderer instead. * @param {WebGLRenderer} renderer - The new renderer. * @param {Boolean} updateDOM - Indicates whether the old canvas should be replaced by the new one in the DOM. * @return {WebGLRenderer} The old renderer. */ replaceRenderer(renderer, updateDOM = true) { const oldRenderer = this.renderer; const parent = oldRenderer.domElement.parentNode; this.setRenderer(renderer); if (updateDOM && parent !== null) { parent.removeChild(oldRenderer.domElement); parent.appendChild(renderer.domElement); } return oldRenderer; } /** * Creates a depth texture attachment that will be provided to all passes. * * Note: When a shader reads from a depth texture and writes to a render target that uses the same depth texture * attachment, the depth information will be lost. This happens even if `depthWrite` is disabled. * * @private * @return {DepthTexture} The depth texture. */ createDepthTexture() { const depthTexture = this.depthTexture = new DepthTexture(); this.inputBuffer.depthTexture = depthTexture; this.inputBuffer.dispose(); if (this.inputBuffer.stencilBuffer) { depthTexture.format = DepthStencilFormat; depthTexture.type = UnsignedInt248Type; } else { depthTexture.type = UnsignedIntType; } return depthTexture; } /** * Deletes the current depth texture. * * @private */ deleteDepthTexture() { if (this.depthTexture !== null) { this.depthTexture.dispose(); this.depthTexture = null; this.inputBuffer.depthTexture = null; this.inputBuffer.dispose(); for (const pass of this.passes) { pass.setDepthTexture(null); } } } /** * Creates a new render target. * * @deprecated Create buffers manually via WebGLRenderTarget instead. * @param {Boolean} depthBuffer - Whether the render target should have a depth buffer. * @param {Boolean} stencilBuffer - Whether the render target should have a stencil buffer. * @param {Number} type - The frame buffer type. * @param {Number} multisampling - The number of samples to use for antialiasing. * @return {WebGLRenderTarget} A new render target that equals the renderer's canvas. */ createBuffer(depthBuffer, stencilBuffer, type, multisampling) { const renderer = this.renderer; const size = renderer === null ? new Vector2() : renderer.getDrawingBufferSize(new Vector2()); const options = { minFilter: LinearFilter2, magFilter: LinearFilter2, stencilBuffer, depthBuffer, type }; const renderTarget = new WebGLRenderTarget3(size.width, size.height, options); if (multisampling > 0) { renderTarget.ignoreDepthForMultisampleCopy = false; renderTarget.samples = multisampling; } if (type === UnsignedByteType2 && renderer !== null && renderer.outputColorSpace === SRGBColorSpace2) { renderTarget.texture.colorSpace = SRGBColorSpace2; } renderTarget.texture.name = "EffectComposer.Buffer"; renderTarget.texture.generateMipmaps = false; return renderTarget; } /** * Can be used to change the main scene for all registered passes and effects. * * @param {Scene} scene - The scene. */ setMainScene(scene) { for (const pass of this.passes) { pass.mainScene = scene; } } /** * Can be used to change the main camera for all registered passes and effects. * * @param {Camera} camera - The camera. */ setMainCamera(camera) { for (const pass of this.passes) { pass.mainCamera = camera; } } /** * Adds a pass, optionally at a specific index. * * @param {Pass} pass - A new pass. * @param {Number} [index] - An index at which the pass should be inserted. */ addPass(pass, index) { const passes = this.passes; const renderer = this.renderer; const drawingBufferSize = renderer.getDrawingBufferSize(new Vector2()); const alpha = renderer.getContext().getContextAttributes().alpha; const frameBufferType = this.inputBuffer.texture.type; pass.setRenderer(renderer); pass.setSize(drawingBufferSize.width, drawingBufferSize.height); pass.initialize(renderer, alpha, frameBufferType); if (this.autoRenderToScreen) { if (passes.length > 0) { passes[passes.length - 1].renderToScreen = false; } if (pass.renderToScreen) { this.autoRenderToScreen = false; } } if (index !== void 0) { passes.splice(index, 0, pass); } else { passes.push(pass); } if (this.autoRenderToScreen) { passes[passes.length - 1].renderToScreen = true; } if (pass.needsDepthTexture || this.depthTexture !== null) { if (this.depthTexture === null) { const depthTexture = this.createDepthTexture(); for (pass of passes) { pass.setDepthTexture(depthTexture); } } else { pass.setDepthTexture(this.depthTexture); } } } /** * Removes a pass. * * @param {Pass} pass - The pass. */ removePass(pass) { const passes = this.passes; const index = passes.indexOf(pass); const exists = index !== -1; const removed = exists && passes.splice(index, 1).length > 0; if (removed) { if (this.depthTexture !== null) { const reducer = (a, b) => a || b.needsDepthTexture; const depthTextureRequired = passes.reduce(reducer, false); if (!depthTextureRequired) { if (pass.getDepthTexture() === this.depthTexture) { pass.setDepthTexture(null); } this.deleteDepthTexture(); } } if (this.autoRenderToScreen) { if (index === passes.length) { pass.renderToScreen = false; if (passes.length > 0) { passes[passes.length - 1].renderToScreen = true; } } } } } /** * Removes all passes. */ removeAllPasses() { const passes = this.passes; this.deleteDepthTexture(); if (passes.length > 0) { if (this.autoRenderToScreen) { passes[passes.length - 1].renderToScreen = false; } this.passes = []; } } /** * Renders all enabled passes in the order in which they were added. * * @param {Number} [deltaTime] - The time since the last frame in seconds. */ render(deltaTime) { const renderer = this.renderer; const copyPass = this.copyPass; let inputBuffer = this.inputBuffer; let outputBuffer = this.outputBuffer; let stencilTest = false; let context, stencil, buffer; if (deltaTime === void 0) { this.timer.update(); deltaTime = this.timer.getDelta(); } for (const pass of this.passes) { if (pass.enabled) { pass.render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest); if (pass.needsSwap) { if (stencilTest) { copyPass.renderToScreen = pass.renderToScreen; context = renderer.getContext(); stencil = renderer.state.buffers.stencil; stencil.setFunc(context.NOTEQUAL, 1, 4294967295); copyPass.render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest); stencil.setFunc(context.EQUAL, 1, 4294967295); } buffer = inputBuffer; inputBuffer = outputBuffer; outputBuffer = buffer; } if (pass instanceof MaskPass) { stencilTest = true; } else if (pass instanceof ClearMaskPass) { stencilTest = false; } } } } /** * Sets the size of the buffers, passes and the renderer. * * @param {Number} width - The width. * @param {Number} height - The height. * @param {Boolean} [updateStyle] - Determines whether the style of the canvas should be updated. */ setSize(width, height, updateStyle) { const renderer = this.renderer; const currentSize = renderer.getSize(new Vector2()); if (width === void 0 || height === void 0) { width = currentSize.width; height = currentSize.height; } if (currentSize.width !== width || currentSize.height !== height) { renderer.setSize(width, height, updateStyle); } const drawingBufferSize = renderer.getDrawingBufferSize(new Vector2()); this.inputBuffer.setSize(drawingBufferSize.width, drawingBufferSize.height); this.outputBuffer.setSize(drawingBufferSize.width, drawingBufferSize.height); for (const pass of this.passes) { pass.setSize(drawingBufferSize.width, drawingBufferSize.height); } } /** * Resets this composer by deleting all passes and creating new buffers. */ reset() { this.dispose(); this.autoRenderToScreen = true; } /** * Disposes this composer and all passes. */ dispose() { for (const pass of this.passes) { pass.dispose(); } this.passes = []; if (this.inputBuffer !== null) { this.inputBuffer.dispose(); } if (this.outputBuffer !== null) { this.outputBuffer.dispose(); } this.deleteDepthTexture(); this.copyPass.dispose(); this.timer.dispose(); Pass.fullscreenGeometry.dispose(); } }; // src/core/EffectShaderData.js import { LinearSRGBColorSpace } from "three"; // src/enums/EffectAttribute.js var EffectAttribute = { NONE: 0, DEPTH: 1, CONVOLUTION: 2 }; // src/enums/EffectShaderSection.js var EffectShaderSection = { FRAGMENT_HEAD: "FRAGMENT_HEAD", FRAGMENT_MAIN_UV: "FRAGMENT_MAIN_UV", FRAGMENT_MAIN_IMAGE: "FRAGMENT_MAIN_IMAGE", VERTEX_HEAD: "VERTEX_HEAD", VERTEX_MAIN_SUPPORT: "VERTEX_MAIN_SUPPORT" }; // src/core/EffectShaderData.js var EffectShaderData = class { /** * Constructs new shader data. */ constructor() { this.shaderParts = /* @__PURE__ */ new Map([ [EffectShaderSection.FRAGMENT_HEAD, null], [EffectShaderSection.FRAGMENT_MAIN_UV, null], [EffectShaderSection.FRAGMENT_MAIN_IMAGE, null], [EffectShaderSection.VERTEX_HEAD, null], [EffectShaderSection.VERTEX_MAIN_SUPPORT, null] ]); this.defines = /* @__PURE__ */ new Map(); this.uniforms = /* @__PURE__ */ new Map(); this.blendModes = /* @__PURE__ */ new Map(); this.extensions = /* @__PURE__ */ new Set(); this.attributes = EffectAttribute.NONE; this.varyings = /* @__PURE__ */ new Set(); this.uvTransformation = false; this.readDepth = false; this.colorSpace = LinearSRGBColorSpace; } }; // src/core/GaussKernel.js function getCoefficients(n) { let result; if (n === 0) { result = new Float64Array(0); } else if (n === 1) { result = new Float64Array([1]); } else if (n > 1) { let row0 = new Float64Array(n); let row1 = new Float64Array(n); for (let y = 1; y <= n; ++y) { for (let x = 0; x < y; ++x) { row1[x] = x === 0 || x === y - 1 ? 1 : row0[x - 1] + row0[x]; } result = row1; row1 = row0; row0 = result; } } return result; } var GaussKernel = class { /** * Constructs a new Gauss kernel. * * @param {Number} kernelSize - The kernel size. Should be an odd number in the range [3, 1020]. * @param {Number} [edgeBias=2] - Determines how many edge coefficients should be cut off for increased accuracy. */ constructor(kernelSize, edgeBias = 2) { this.weights = null; this.offsets = null; this.linearWeights = null; this.linearOffsets = null; this.generate(kernelSize, edgeBias); } /** * The number of steps for discrete sampling. * * @type {Number} */ get steps() { return this.offsets === null ? 0 : this.offsets.length; } /** * The number of steps for linear sampling. * * @type {Number} */ get linearSteps() { return this.linearOffsets === null ? 0 : this.linearOffsets.length; } /** * Generates the kernel. * * @private * @param {Number} kernelSize - The kernel size. * @param {Number} edgeBias - The amount of edge coefficients to ignore. */ generate(kernelSize, edgeBias) { if (kernelSize < 3 || kernelSize > 1020) { throw new Error("The kernel size must be in the range [3, 1020]"); } const n = kernelSize + edgeBias * 2; const coefficients = edgeBias > 0 ? getCoefficients(n).slice(edgeBias, -edgeBias) : getCoefficients(n); const mid = Math.floor((coefficients.length - 1) / 2); const sum = coefficients.reduce((a, b) => a + b, 0); const weights = coefficients.slice(mid); const offsets = [...Array(mid + 1).keys()]; const linearWeights = new Float64Array(Math.floor(offsets.length / 2)); const linearOffsets = new Float64Array(linearWeights.length); linearWeights[0] = weights[0] / sum; for (let i = 1, j = 1, l = offsets.length - 1; i < l; i += 2, ++j) { const offset0 = offsets[i], offset1 = offsets[i + 1]; const weight0 = weights[i], weight1 = weights[i + 1]; const w = weight0 + weight1; const o = (offset0 * weight0 + offset1 * weight1) / w; linearWeights[j] = w / sum; linearOffsets[j] = o; } for (let i = 0, l = weights.length, s = 1 / sum; i < l; ++i) { weights[i] *= s; } const linearWeightSum = (linearWeights.reduce((a, b) => a + b, 0) - linearWeights[0] * 0.5) * 2; if (linearWeightSum !== 0) { for (let i = 0, l = linearWeights.length, s = 1 / linearWeightSum; i < l; ++i) { linearWeights[i] *= s; } } this.offsets = offsets; this.weights = weights; this.linearOffsets = linearOffsets; this.linearWeights = linearWeights; } }; // src/core/ImmutableTimer.js var ImmutableTimer = class { /** * The current delta time in seconds. * * @type {Number} */ getDelta() { return NaN; } /** * The elapsed time in seconds. * * @type {Number} */ getElapsed() { return NaN; } }; // src/core/Initializable.js var Initializable = class { /** * Performs initialization tasks. * * @param {WebGLRenderer} renderer - A renderer. * @param {Boolean} alpha - Whether the renderer uses the alpha channel. * @param {Number} frameBufferType - The type of the main frame buffers. */ initialize(renderer, alpha, frameBufferType) { } }; // src/core/OverrideMaterialManager.js import { BackSide, DoubleSide, FrontSide, ShaderMaterial as ShaderMaterial2 } from "three"; var workaroundEnabled = false; var OverrideMaterialManager = class { /** * Constructs a new override material manager. * * @param {Material} [material=null] - An override material. */ constructor(material = null) { this.originalMaterials = /* @__PURE__ */ new Map(); this.material = null; this.materials = null; this.materialsBackSide = null; this.materialsDoubleSide = null; this.materialsFlatShaded = null; this.materialsFlatShadedBackSide = null; this.materialsFlatShadedDoubleSide = null; this.setMaterial(material); this.meshCount = 0; this.replaceMaterial = (node) => { if (node.isMesh) { let materials; if (node.material.flatShading) { switch (node.material.side) { case DoubleSide: materials = this.materialsFlatShadedDoubleSide; break; case BackSide: materials = this.materialsFlatShadedBackSide; break; default: materials = this.materialsFlatShaded; break; } } else { switch (node.material.side) { case DoubleSide: materials = this.materialsDoubleSide; break; case BackSide: materials = this.materialsBackSide; break; default: materials = this.materials; break; } } this.originalMaterials.set(node, node.material); if (node.isSkinnedMesh) { node.material = materials[2]; } else if (node.isInstancedMesh) { node.material = materials[1]; } else { node.material = materials[0]; } ++this.meshCount; } }; } /** * Clones the given material. * * @private * @param {Material} material - The material. * @return {Material} The cloned material. */ cloneMaterial(material) { if (!(material instanceof ShaderMaterial2)) { return material.clone(); } const uniforms = material.uniforms; const textureUniforms = /* @__PURE__ */ new Map(); for (const key in uniforms) { const value = uniforms[key].value; if (value.isRenderTargetTexture) { uniforms[key].value = null; textureUniforms.set(key, value); } } const clone = material.clone(); for (const entry of textureUniforms) { uniforms[entry[0]].value = entry[1]; clone.uniforms[entry[0]].value = entry[1]; } return clone; } /** * Sets the override material. * * @param {Material} material - The material. */ setMaterial(material) { this.disposeMaterials(); this.material = material; if (material !== null) { const materials = this.materials = [ this.cloneMaterial(material), this.cloneMaterial(material), this.cloneMaterial(material) ]; for (const m2 of materials) { m2.uniforms = Object.assign({}, material.uniforms); m2.side = FrontSide; } materials[2].skinning = true; this.materialsBackSide = materials.map((m2) => { const c2 = this.cloneMaterial(m2); c2.uniforms = Object.assign({}, material.uniforms); c2.side = BackSide; return c2; }); this.materialsDoubleSide = materials.map((m2) => { const c2 = this.cloneMaterial(m2); c2.uniforms = Object.assign({}, material.uniforms); c2.side = DoubleSide; return c2; }); this.materialsFlatShaded = materials.map((m2) => { const c2 = this.cloneMaterial(m2); c2.uniforms = Object.assign({}, material.uniforms); c2.flatShading = true; return c2; }); this.materialsFlatShadedBackSide = materials.map((m2) => { const c2 = this.cloneMaterial(m2); c2.uniforms = Object.assign({}, material.uniforms); c2.flatShading = true; c2.side = BackSide; return c2; }); this.materialsFlatShadedDoubleSide = materials.map((m2) => { const c2 = this.cloneMaterial(m2); c2.uniforms = Object.assign({}, material.uniforms); c2.flatShading = true; c2.side = DoubleSide; return c2; }); } } /** * Renders the scene with the override material. * * @private * @param {WebGLRenderer} renderer - The renderer. * @param {Scene} scene - A scene. * @param {Camera} camera - A camera. */ render(renderer, scene, camera) { const shadowMapEnabled = renderer.shadowMap.enabled; renderer.shadowMap.enabled = false; if (workaroundEnabled) { const originalMaterials = this.originalMaterials; this.meshCount = 0; scene.traverse(this.replaceMaterial); renderer.render(scene, camera); for (const entry of originalMaterials) { entry[0].material = entry[1]; } if (this.meshCount !== originalMaterials.size) { originalMaterials.clear(); } } else { const overrideMaterial = scene.overrideMaterial; scene.overrideMaterial = this.material; renderer.render(scene, camera); scene.overrideMaterial = overrideMaterial; } renderer.shadowMap.enabled = shadowMapEnabled; } /** * Deletes cloned override materials. * * @private */ disposeMaterials() { if (this.material !== null) { const materials = this.materials.concat(this.materialsBackSide).concat(this.materialsDoubleSide).concat(this.materialsFlatShaded).concat(this.materialsFlatShadedBackSide).concat(this.materialsFlatShadedDoubleSide); for (const m2 of materials) { m2.dispose(); } } } /** * Performs cleanup tasks. */ dispose() { this.originalMaterials.clear(); this.disposeMaterials(); } /** * Indicates whether the override material workaround is enabled. * * @type {Boolean} */ static get workaroundEnabled() { return workaroundEnabled; } /** * Enables or disables the override material workaround globally. * * This only affects post processing passes and effects. * * @type {Boolean} */ static set workaroundEnabled(value) { workaroundEnabled = value; } }; // src/core/Resizable.js var Resizable = class { /** * Sets the size of this object. * * @param {Number} width - The width. * @param {Number} height - The height. */ setSize(width, height) { } }; // src/core/Resolution.js import { EventDispatcher, Vector2 as Vector22 } from "three"; var AUTO_SIZE = -1; var Resolution = class extends EventDispatcher { /** * Constructs a new resolution. * * TODO Remove resizable param. * @param {Resizable} resizable - A resizable object. * @param {Number} [width=Resolution.AUTO_SIZE] - The preferred width. * @param {Number} [height=Resolution.AUTO_SIZE] - The preferred height. * @param {Number} [scale=1.0] - A resolution scale. */ constructor(resizable, width = AUTO_SIZE, height = AUTO_SIZE, scale = 1) { super(); this.resizable = resizable; this.baseSize = new Vector22(1, 1); this.preferredSize = new Vector22(width, height); this.target = this.preferredSize; this.s = scale; this.effectiveSize = new Vector22(); this.addEventListener("change", () => this.updateEffectiveSize()); this.updateEffectiveSize(); } /** * Calculates the effective size. * * @private */ updateEffectiveSize() { const base = this.baseSize; const preferred = this.preferredSize; const effective = this.effectiveSize; const scale = this.scale; if (preferred.width !== AUTO_SIZE) { effective.width = preferred.width; } else if (preferred.height !== AUTO_SIZE) { effective.width = Math.round(preferred.height * (base.width / Math.max(base.height, 1))); } else { effective.width = Math.round(base.width * scale); } if (preferred.height !== AUTO_SIZE) { effective.height = preferred.height; } else if (preferred.width !== AUTO_SIZE) { effective.height = Math.round(preferred.width / Math.max(base.width / Math.max(base.height, 1), 1)); } else { effective.height = Math.round(base.height * scale); } } /** * The effective width. * * If the preferred width and height are set to {@link Resizer.AUTO_SIZE}, the base width will be returned. * * @type {Number} */ get width() { return this.effectiveSize.width; } set width(value) { this.preferredWidth = value; } /** * The effective height. * * If the preferred width a