UNPKG

postprocessing-fork

Version:

A post processing library for three.js.

1,732 lines (1,698 loc) 1.97 MB
/** * postprocessing-fork v6.37.4 build Mon Sep 15 2025 * https://github.com/pmndrs/postprocessing * Copyright 2015-2025 Raoul van Rüschen * @license Zlib */ var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // package.json var version = "6.37.4"; // src/core/index.js import { PerspectiveCamera } from "three"; // 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, UnsignedInt248Type, UnsignedIntType, Vector2 as Vector22, WebGLRenderTarget as WebGLRenderTarget4, NearestFilter as NearestFilter2, RGBAFormat, HalfFloatType } 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 "run-scene-core"; 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.needsNormalTexture = 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. * * @deprecated Use getDepthTexture instead. * @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) { } /** * Returns the current normal texture. * * @return {Texture} The current normal texture, or null if there is none. */ getNormalTexture() { return null; } /** * Sets the normal texture. * * @param {Texture} normalTexture - A normal texture. */ setNormalTexture(normalTexture) { } /** * 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. * @param {DepthPass} [depthPass] - An optional shared depth pass for optimizing multiple effects. */ render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest, depthPass) { 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/DepthPass.js import { Color as Color3, MeshDepthMaterial, NearestFilter, RGBADepthPacking, WebGLRenderTarget as WebGLRenderTarget3 } from "three"; // src/core/Resolution.js import { EventDispatcher, Vector2 } 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 Vector2(1, 1); this.preferredSize = new Vector2(width, height); this.target = this.preferredSize; this.s = scale; this.effectiveSize = new Vector2(); this.addEventListener("change", () => this.updateEffectiveSize()); this.updateEffectiveSize(); } /** * Calculates the effective size. * * @private */ updateEffectiveSize() { const base3 = 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 * (base3.width / Math.max(base3.height, 1))); } else { effective.width = Math.round(base3.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(base3.width / Math.max(base3.height, 1), 1)); } else { effective.height = Math.round(base3.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 and height are set to {@link Resizer.AUTO_SIZE}, the base height will be returned. * * @type {Number} */ get height() { return this.effectiveSize.height; } set height(value) { this.preferredHeight = value; } /** * Returns the effective width. * * If the preferred width and height are set to {@link Resizer.AUTO_SIZE}, the base width will be returned. * * @deprecated Use width instead. * @return {Number} The effective width. */ getWidth() { return this.width; } /** * Returns the effective height. * * If the preferred width and height are set to {@link Resizer.AUTO_SIZE}, the base height will be returned. * * @deprecated Use height instead. * @return {Number} The effective height. */ getHeight() { return this.height; } /** * The resolution scale. * * @type {Number} */ get scale() { return this.s; } set scale(value) { if (this.s !== value) { this.s = value; this.preferredSize.setScalar(AUTO_SIZE); this.dispatchEvent({ type: "change" }); this.resizable.setSize(this.baseSize.width, this.baseSize.height); } } /** * Returns the current resolution scale. * * @deprecated Use scale instead. * @return {Number} The scale. */ getScale() { return this.scale; } /** * Sets the resolution scale. * * Also sets the preferred resolution to {@link Resizer.AUTO_SIZE}. * * @deprecated Use scale instead. * @param {Number} value - The scale. */ setScale(value) { this.scale = value; } /** * The base width. * * @type {Number} */ get baseWidth() { return this.baseSize.width; } set baseWidth(value) { if (this.baseSize.width !== value) { this.baseSize.width = value; this.dispatchEvent({ type: "change" }); this.resizable.setSize(this.baseSize.width, this.baseSize.height); } } /** * Returns the base width. * * @deprecated Use baseWidth instead. * @return {Number} The base width. */ getBaseWidth() { return this.baseWidth; } /** * Sets the base width. * * @deprecated Use baseWidth instead. * @param {Number} value - The width. */ setBaseWidth(value) { this.baseWidth = value; } /** * The base height. * * @type {Number} */ get baseHeight() { return this.baseSize.height; } set baseHeight(value) { if (this.baseSize.height !== value) { this.baseSize.height = value; this.dispatchEvent({ type: "change" }); this.resizable.setSize(this.baseSize.width, this.baseSize.height); } } /** * Returns the base height. * * @deprecated Use baseHeight instead. * @return {Number} The base height. */ getBaseHeight() { return this.baseHeight; } /** * Sets the base height. * * @deprecated Use baseHeight instead. * @param {Number} value - The height. */ setBaseHeight(value) { this.baseHeight = value; } /** * Sets the base size. * * @param {Number} width - The width. * @param {Number} height - The height. */ setBaseSize(width, height) { if (this.baseSize.width !== width || this.baseSize.height !== height) { this.baseSize.set(width, height); this.dispatchEvent({ type: "change" }); this.resizable.setSize(this.baseSize.width, this.baseSize.height); } } /** * The preferred width. * * @type {Number} */ get preferredWidth() { return this.preferredSize.width; } set preferredWidth(value) { if (this.preferredSize.width !== value) { this.preferredSize.width = value; this.dispatchEvent({ type: "change" }); this.resizable.setSize(this.baseSize.width, this.baseSize.height); } } /** * Returns the preferred width. * * @deprecated Use preferredWidth instead. * @return {Number} The preferred width. */ getPreferredWidth() { return this.preferredWidth; } /** * Sets the preferred width. * * Use {@link Resizer.AUTO_SIZE} to automatically calculate the width based on the height and aspect ratio. * * @deprecated Use preferredWidth instead. * @param {Number} value - The width. */ setPreferredWidth(value) { this.preferredWidth = value; } /** * The preferred height. * * @type {Number} */ get preferredHeight() { return this.preferredSize.height; } set preferredHeight(value) { if (this.preferredSize.height !== value) { this.preferredSize.height = value; this.dispatchEvent({ type: "change" }); this.resizable.setSize(this.baseSize.width, this.baseSize.height); } } /** * Returns the preferred height. * * @deprecated Use preferredHeight instead. * @return {Number} The preferred height. */ getPreferredHeight() { return this.preferredHeight; } /** * Sets the preferred height. * * Use {@link Resizer.AUTO_SIZE} to automatically calculate the height based on the width and aspect ratio. * * @deprecated Use preferredHeight instead. * @param {Number} value - The height. */ setPreferredHeight(value) { this.preferredHeight = value; } /** * Sets the preferred size. * * @param {Number} width - The width. * @param {Number} height - The height. */ setPreferredSize(width, height) { if (this.preferredSize.width !== width || this.preferredSize.height !== height) { this.preferredSize.set(width, height); this.dispatchEvent({ type: "change" }); this.resizable.setSize(this.baseSize.width, this.baseSize.height); } } /** * Copies the given resolution. * * @param {Resolution} resolution - The resolution. */ copy(resolution) { this.s = resolution.scale; this.baseSize.set(resolution.baseWidth, resolution.baseHeight); this.preferredSize.set(resolution.preferredWidth, resolution.preferredHeight); this.dispatchEvent({ type: "change" }); this.resizable.setSize(this.baseSize.width, this.baseSize.height); } /** * An auto sizing constant. * * Can be used to automatically calculate the width or height based on the original aspect ratio. * * @type {Number} */ static get AUTO_SIZE() { return AUTO_SIZE; } }; // 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, renderOpts = {}) { const shadowMapEnabled = renderer.shadowMap.enabled; renderer.shadowMap.enabled = false; let renderResult = null; if (workaroundEnabled) { const originalMaterials = this.originalMaterials; this.meshCount = 0; scene.traverse(this.replaceMaterial); renderResult = renderer.render(scene, camera, renderOpts); for (const entry of originalMaterials) { entry[0].material = entry[1]; } if (this.meshCount !== originalMaterials.size) { originalMaterials.clear(); } } else { const overrideMaterial2 = scene.overrideMaterial; scene.overrideMaterial = this.material; renderResult = renderer.render(scene, camera, renderOpts); scene.overrideMaterial = overrideMaterial2; } renderer.shadowMap.enabled = shadowMapEnabled; return renderResult; } /** * 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/passes/ClearPass.js import { Color as Color2 } from "three"; var color = /* @__PURE__ */ new Color2(); 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/utils/PerformanceLogger.js var theEnabled = false; var _PerformanceLogger = class _PerformanceLogger { /** * 获取单例实例 * @return {PerformanceLogger} 日志实例 */ static getInstance() { if (_PerformanceLogger.instance === null) { _PerformanceLogger.instance = new _PerformanceLogger(); } return _PerformanceLogger.instance; } /** * 创建一个新的性能日志实例 * @param {Boolean} enabled - 是否启用日志 */ constructor(enabled = theEnabled) { this.enabled = enabled; this.timers = /* @__PURE__ */ new Map(); this.collectTimes = false; this.timeData = /* @__PURE__ */ new Map(); } /** * 启用日志 */ enable() { this.enabled = true; } /** * 禁用日志 */ disable() { this.enabled = false; } /** * 启用时间数据收集,用于性能分析 */ enableTimeCollection() { this.collectTimes = true; } /** * 禁用时间数据收集 */ disableTimeCollection() { this.collectTimes = false; } /** * 获取收集的时间数据 * @return {Object} 时间数据 */ getTimeData() { const result = {}; for (const [key, times] of this.timeData.entries()) { if (times.length > 0) { const sum = times.reduce((a, b) => a + b, 0); result[key] = { count: times.length, total: sum, average: sum / times.length, min: Math.min(...times), max: Math.max(...times) }; } } return result; } /** * 清除收集的时间数据 */ clearTimeData() { this.timeData.clear(); } /** * 开始计时 * @param {String} label - 计时标签 */ time(label) { if (!this.enabled) { return; } console.time(label); this.timers.set(label, performance.now()); } /** * 结束计时并打印结果 * @param {String} label - 计时标签 */ timeEnd(label) { if (!this.enabled) { return; } console.timeEnd(label); if (this.collectTimes && this.timers.has(label)) { const startTime = this.timers.get(label); const endTime = performance.now(); const duration = endTime - startTime; if (!this.timeData.has(label)) { this.timeData.set(label, []); } this.timeData.get(label).push(duration); this.timers.delete(label); } } /** * 打印日志 * @param {...any} args - 日志参数 */ log(...args) { if (!this.enabled) { return; } console.log(...args); } /** * 打印警告 * @param {...any} args - 警告参数 */ warn(...args) { if (!this.enabled) { return; } console.warn(...args); } /** * 打印错误 * @param {...any} args - 错误参数 */ error(...args) { console.error(...args); } /** * 打印性能摘要 */ printSummary() { if (!this.enabled || !this.collectTimes) { return; } console.group("\u6027\u80FD\u65E5\u5FD7\u6458\u8981"); const data = this.getTimeData(); for (const [key, stats] of Object.entries(data)) { console.log( `${key}: ${stats.count}\u6B21\u8C03\u7528, \u5E73\u5747: ${stats.average.toFixed(2)}ms, \u603B\u8BA1: ${stats.total.toFixed(2)}ms, \u6700\u5C0F: ${stats.min.toFixed(2)}ms, \u6700\u5927: ${stats.max.toFixed(2)}ms` ); } console.groupEnd(); } }; /** * 单例实例 * @type {PerformanceLogger} * @private */ __publicField(_PerformanceLogger, "instance", null); var PerformanceLogger = _PerformanceLogger; var logger = PerformanceLogger.getInstance(); var enableLogs = () => logger.enable(); var disableLogs = () => logger.disable(); var enableTimeCollection = () => logger.enableTimeCollection(); var disableTimeCollection = () => logger.disableTimeCollection(); var getTimeData = () => logger.getTimeData(); var clearTimeData = () => logger.clearTimeData(); var printSummary = () => logger.printSummary(); var timeLog = (label) => logger.time(label); var timeEndLog = (label) => logger.timeEnd(label); var log = (...args) => logger.log(...args); var warn = (...args) => logger.warn(...args); var error = (...args) => logger.error(...args); // src/passes/RenderPass.js var RenderPass = class extends Pass { /** * Constructs a new render pass. * * @param {Scene} scene - The scene to render. * @param {Camera} camera - The camera to use to render the scene. * @param {Material} [overrideMaterial=null] - An override material. */ constructor(scene, camera, overrideMaterial2 = null) { super("RenderPass", scene, camera); __publicField(this, "isRenderPass", true); this.needsSwap = false; this.clearPass = new ClearPass(); this.overrideMaterialManager = overrideMaterial2 === null ? null : new OverrideMaterialManager(overrideMaterial2); this.ignoreBackground = false; this.skipShadowMapUpdate = false; this.selection = null; } set mainScene(value) { this.scene = value; } set mainCamera(value) { this.camera = value; } get renderToScreen() { return super.renderToScreen; } set renderToScreen(value) { super.renderToScreen = value; this.clearPass.renderToScreen = value; } /** * The current override material. * * @type {Material} */ get overrideMaterial() { const manager = this.overrideMaterialManager; return manager !== null ? manager.material : null; } set overrideMaterial(value) { const manager = this.overrideMaterialManager; if (value !== null) { if (manager !== null) { manager.setMaterial(value); } else { this.overrideMaterialManager = new OverrideMaterialManager(value); } } else if (manager !== null) { manager.dispose(); this.overrideMaterialManager = null; } } /** * Returns the current override material. * * @deprecated Use overrideMaterial instead. * @return {Material} The material. */ getOverrideMaterial() { return this.overrideMaterial; } /** * Sets the override material. * * @deprecated Use overrideMaterial instead. * @return {Material} value - The material. */ setOverrideMaterial(value) { this.overrideMaterial = value; } /** * Indicates whether the target buffer should be cleared before rendering. * * @type {Boolean} * @deprecated Use clearPass.enabled instead. */ get clear() { return this.clearPass.enabled; } set clear(value) { this.clearPass.enabled = value; } /** * Returns the selection. Default is `null` (no restriction). * * @deprecated Use selection instead. * @return {Selection} The selection. */ getSelection() { return this.selection; } /** * Sets the selection. Set to `null` to disable. * * @deprecated Use selection instead. * @param {Selection} value - The selection. */ setSelection(value) { this.selection = value; } /** * Indicates whether the scene background is disabled. * * @deprecated Use ignoreBackground instead. * @return {Boolean} Whether the scene background is disabled. */ isBackgroundDisabled() { return this.ignoreBackground; } /** * Enables or disables the scene background. * * @deprecated Use ignoreBackground instead. * @param {Boolean} value - Whether the scene background should be disabled. */ setBackgroundDisabled(value) { this.ignoreBackground = value; } /** * Indicates whether the shadow map auto update is disabled. * * @deprecated Use skipShadowMapUpdate instead. * @return {Boolean} Whether the shadow map update is disabled. */ isShadowMapDisabled() { return this.skipShadowMapUpdate; } /** * Enables or disables the shadow map auto update. * * @deprecated Use skipShadowMapUpdate instead. * @param {Boolean} value - Whether the shadow map auto update should be disabled. */ setShadowMapDisabled(value) { this.skipShadowMapUpdate = value; } /** * Returns the clear pass. * * @deprecated Use clearPass.enabled instead. * @return {ClearPass} The clear pass. */ getClearPass() { return this.clearPass; } /** * Renders the scene. * * @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, depthPass, renderOpts = {}, effectPassOpts) { timeLog("RenderPass.render"); const scene = this.scene; const camera = this.camera; const selection = this.selection; const mask = camera.layers.mask; const background = scene.background; const shadowMapAutoUpdate = renderer.shadowMap.autoUpdate; const renderTarget = this.renderToScreen ? null : inputBuffer; let renderResult = null; if (selection !== null) { camera.layers.set(selection.getLayer()); } if (this.skipShadowMapUpdate) { renderer.shadowMap.autoUpdate = false; } if (this.ignoreBackground || this.clearPass.overrideClearColor !== null) { scene.background = null; } if (this.clearPass.enabled) { this.clearPass.render(renderer, inputBuffer); } renderer.setRenderTarget(renderTarget); if (this.overrideMaterialManager !== null) { renderResult = this.overrideMaterialManager.render(renderer, scene, camera, renderOpts); } else { renderResult = renderer.render(scene, camera, renderOpts); } camera.layers.mask = mask; scene.background = background; renderer.shadowMap.autoUpdate = shadowMapAutoUpdate; timeEndLog("RenderPass.render"); return renderResult; } }; // src/passes/DepthPass.js var DepthPass = class extends Pass { /** * Constructs a new depth pass. * * @param {Scene} scene - The scene to render. * @param {Camera} camera - The camera to use to render the scene. * @param {Object} [options] - The options. * @param {WebGLRenderTarget} [options.renderTarget] - A custom render target. * @param {Number} [options.resolutionScale=1.0] - The resolution scale. * @param {Number} [options.resolutionX=Resolution.AUTO_SIZE] - The horizontal resolution. * @param {Number} [options.resolutionY=Resolution.AUTO_SIZE] - The vertical resolution. * @param {Number} [options.width=Resolution.AUTO_SIZE] - Deprecated. Use resolutionX instead. * @param {Number} [options.height=Resolution.AUTO_SIZE] - Deprecated. Use resolutionY instead. */ constructor(scene, camera, { renderTarget, resolutionScale = 1, width = Resolution.AUTO_SIZE, height = Resolution.AUTO_SIZE, resolutionX = width, resolutionY = height } = {}) { super("DepthPass"); this.needsSwap = false; this.isDepthPass = true; this.depthPacking = RGBADepthPacking; this.renderPass = new RenderPass(scene, camera, new MeshDepthMaterial({ depthPacking: RGBADepthPacking })); const renderPass = this.renderPass; renderPass.skipShadowMapUpdate = true; renderPass.ignoreBackground = true; const clearPass = renderPass.clearPass; clearPass.overrideClearColor = new Color3(16777215); clearPass.overrideClearAlpha = 1; this.renderTarget = renderTarget; if (this.renderTarget === void 0) { this.renderTarget = new WebGLRenderTarget3(1, 1, { minFilter: NearestFilter, magFilter: NearestFilter }); this.renderTarget.texture.name = "DepthPass.Target"; } const resolution = this.resolution = new Resolution(this, resolutionX, resolutionY, resolutionScale); resolution.addEventListener("change", (e) => this.setSize(resolution.baseWidth, resolution.baseHeight)); } set mainScene(value) { this.renderPass.mainScene = value; } set mainCamera(value) { this.renderPass.mainCamera = value; } /** * The depth texture. * * @type {Texture} */ get texture() { return this.renderTarget.texture; } /** * Returns the depth texture. * * @deprecated Use texture instead. * @return {Texture} The texture. */ getTexture() { return this.renderTarget.texture; } /** * Returns the resolution settings. * * @deprecated Use resolution instead. * @return {Resolution} The resolution. */ getResolution() { return this.resolution; } /** * Returns the current resolution scale. * * @return {Number} The resolution scale. * @deprecated Use resolution instead. */ getResolutionScale() { return this.resolution.scale; } /** * Sets the resolution scale. * * @param {Number} scale - The new resolution scale. * @deprecated Use resolution instead. */ setResolutionScale(scale) { this.resolution.scale = scale; } /** * Renders the scene depth. * * @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, depthPass, renderOpts = {}) { timeLog("DepthPass.render"); const renderTarget = this.renderToScreen ? null : this.renderTarget; let childClassName = "\u65E0\u5B50\u5BF9\u8C61"; if (this.renderPass.scene && this.renderPass.scene.children && this.renderPass.scene.children.length > 0) { childClassName = this.renderPass.scene.children[0].constructor.name; } log(`DepthPass \u6E32\u67D3\u573A\u666F, \u7B2C\u4E00\u4E2A\u5B50\u5BF9\u8C61\u7C7B\u578B: ${childClassName}`); this.renderPass.render(renderer, renderTarget, outputBuffer, deltaTime, stencilTest, depthPass, renderOpts); timeEndLog("DepthPass.render"); } /** * Updates the size of this pass. * * @param {Number} width - The