UNPKG

threepipe

Version:

A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.

704 lines 31.5 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var RenderManager_1; import { AddEquation, Color, ConstantAlphaFactor, CustomBlending, FloatType, HalfFloatType, NoBlending, NoColorSpace, NormalBlending, NoToneMapping, OneMinusConstantAlphaFactor, PCFShadowMap, Vector2, Vector4, WebGLRenderer, } from 'three'; import { EffectComposer2, sortPasses } from '../postprocessing'; import { RenderTargetManager } from './RenderTargetManager'; import { upgradeWebGLRenderer, } from '../core'; import { base64ToArrayBuffer, canvasFlipY, getOrCall, onChange2, serializable, serialize, } from 'ts-browser-helpers'; import { uiButton, uiConfig, uiDropdown, uiFolderContainer, uiMonitor, uiSlider, uiToggle } from 'uiconfig.js'; import { bindToValue, generateUUID, textureDataToImageData } from '../three'; import { EXRExporter2 } from '../assetmanager'; let RenderManager = RenderManager_1 = class RenderManager extends RenderTargetManager { get renderScale() { return this._renderScale; } set renderScale(value) { if (value !== this._renderScale) { this._renderScale = value; this.setSize(undefined, undefined, true); } } _shadowMapTypeChanged() { this.resetShadows(); this.reset(); } get renderer() { return this._renderer; } rebuildPipeline(setDirty = true) { this._passesNeedsUpdate = true; if (setDirty) this._updated({ change: 'rebuild' }); } /** * Regenerates the render pipeline by resolving dependencies and sorting the passes. * This is called automatically when the passes are changed. */ _refreshPipeline() { if (!this.autoBuildPipeline) return this._pipeline; const ps = this._passes; try { this._pipeline = sortPasses(ps); } catch (e) { console.error('RenderManager: Unable to sort rendering passes', e); } return this._pipeline; } _animationLoop(time, frame) { const deltaTime = time - this._lastTime; this._lastTime = time; this.frameWaitTime -= deltaTime; if (this.frameWaitTime > 0) return; this.frameWaitTime = 0; this.dispatchEvent({ type: 'animationLoop', deltaTime, time, renderer: this._renderer, xrFrame: frame }); } constructor({ canvas, alpha = true, renderScale = 1, targetOptions }) { super(); this._renderSize = new Vector2(512, 512); // this is updated automatically. this._renderScale = 1.; this._passes = []; this._pipeline = []; this._passesNeedsUpdate = true; this._frameCount = 0; this._lastTime = 0; this._totalFrameCount = 0; /** * Use total frame count, if this is set to true, then frameCount won't be reset when the viewer is set to dirty. * Which will generate different random numbers for each frame during postprocessing steps. With TAA set properly, this will give a smoother result. */ this.stableNoise = false; this.frameWaitTime = 0; // time to wait before next frame // used by canvas recorder //todo/ this._dirty = true; /** * Set autoBuildPipeline = false to be able to set the pipeline manually. */ this.autoBuildPipeline = true; // render(scene: RenderScene): void { // const camera = scene.activeCamera // const activeScene = scene.activeScene // if(!camera) return // this._renderer.render(scene.threeScene, camera) // // todo gizmos // } /** * Default value for renderToScreen in {@link render} */ this.defaultRenderToScreen = true; this.onPostFrame = () => { for (const pass of this._passes) { if (pass.enabled && pass.onPostFrame) pass.onPostFrame?.(this); } }; this._animationLoop = this._animationLoop.bind(this); // this._xrPreAnimationLoop = this._xrPreAnimationLoop.bind(this) this._renderSize = new Vector2(canvas.clientWidth, canvas.clientHeight); this._renderScale = renderScale; this._renderer = this._initWebGLRenderer(canvas, alpha, targetOptions?.stencilBuffer ?? false); this._context = this._renderer.getContext(); this._isWebGL2 = this._renderer.capabilities.isWebGL2; if (!this._isWebGL2) console.error('RenderManager: WebGL 1 is not officially supported anymore. Some features may not work.'); this.resetShadows(); const composerTarget = this.createTarget(targetOptions, false); composerTarget.texture.name = 'EffectComposer.rt1'; this._composer = new EffectComposer2(this._renderer, composerTarget); // if (animationLoop) this.addEventListener('animationLoop', animationLoop) // todo: from viewer } _initWebGLRenderer(canvas, alpha, stencil) { const renderer = new WebGLRenderer({ canvas, antialias: false, alpha, premultipliedAlpha: false, // todo: see this, maybe use this with rgbm mode. preserveDrawingBuffer: true, powerPreference: RenderManager_1.POWER_PREFERENCE, stencil, }); // renderer.info.autoReset = false // Not supported by ExtendedRenderPass // renderer.useLegacyLights = false renderer.setAnimationLoop(this._animationLoop); renderer.onContextLost = (event) => { this.dispatchEvent({ type: 'contextLost', event }); }; renderer.onContextRestore = () => { // console.log('restored') this.dispatchEvent({ type: 'contextRestored' }); }; renderer.setSize(this._renderSize.width, this._renderSize.height, false); renderer.setPixelRatio(this._renderScale); renderer.toneMapping = NoToneMapping; renderer.toneMappingExposure = 1; renderer.outputColorSpace = NoColorSpace; // or SRGBColorSpace renderer.shadowMap.enabled = true; renderer.shadowMap.type = PCFShadowMap; // use? THREE.PCFShadowMap. dont use VSM if need ground: https://github.com/mrdoob/three.js/issues/17473 // renderer.shadowMap.type = BasicShadowMap // use? THREE.PCFShadowMap. dont use VSM if need ground: https://github.com/mrdoob/three.js/issues/17473 renderer.shadowMap.autoUpdate = false; return upgradeWebGLRenderer.call(renderer, this); } setSize(width, height, force = false) { if (!force && (width ? Math.abs(width - this._renderSize.width) : 0) + (height ? Math.abs(height - this._renderSize.height) : 0) < 0.1) return; if (width) this._renderSize.width = width; if (height) this._renderSize.height = height; if (!(this.webglRenderer.xr.enabled && this.webglRenderer.xr.isPresenting)) { this._renderer.setSize(this._renderSize.width, this._renderSize.height, false); this._renderer.setPixelRatio(this._renderScale); } this._composer.setPixelRatio(this._renderScale, false); this._composer.setSize(this._renderSize.width, this._renderSize.height); this.resizeTrackedTargets(); // console.log('setSize', {...this._renderSize}, this._trackedTargets.length) this.dispatchEvent({ type: 'resize' }); this._updated({ change: 'size', data: this._renderSize.toArray() }); this.reset(); } render(scene, renderToScreen) { if (this._passesNeedsUpdate) { this._refreshPipeline(); this.refreshPasses(); } for (const pass of this._passes) { if (pass.enabled && pass.beforeRender) pass.beforeRender(scene, scene.renderCamera, this); } this._composer.renderToScreen = renderToScreen ?? this.defaultRenderToScreen; this.dispatchEvent({ type: 'preRender', scene, renderToScreen: this._composer.renderToScreen }); this._composer.render(); this.dispatchEvent({ type: 'postRender', scene, renderToScreen: this._composer.renderToScreen }); this._composer.renderToScreen = true; if (renderToScreen) { this.incRenderToScreen(); } this._dirty = false; } // todo better name incRenderToScreen() { this._frameCount += 1; this._totalFrameCount += 1; } get needsRender() { if (this.renderSize.x < 1 || this.renderSize.y < 1) return false; this._dirty = this._dirty || this._passes.findIndex(value => getOrCall(value.dirty)) >= 0; // todo: check for enabled passes only. return this._dirty; } setDirty(reset = false) { this._dirty = true; if (reset) this.reset(); // do NOT call _updated from here. } reset() { this._frameCount = 0; this._dirty = true; // do NOT call _updated from here. } resetShadows() { this._renderer.shadowMap.needsUpdate = true; } refreshPasses() { if (!this._passesNeedsUpdate) return; this._passesNeedsUpdate = false; const p = []; for (const passId of this._pipeline) { const a = this._passes.find(value => value.passId === passId); if (!a) { console.warn('Unable to find pass: ', passId); continue; } p.push(a); } [...this._composer.passes].forEach(p1 => this._composer.removePass(p1)); p.forEach(p1 => this._composer.addPass(p1)); this._updated({ change: 'passRefresh' }); } dispose(clear = true) { super.dispose(clear); this._renderer.dispose(); } updateShaderProperties(material) { if (material.uniforms.currentFrameCount) material.uniforms.currentFrameCount.value = this.frameCount; if (!this.stableNoise) { if (material.uniforms.frameCount) material.uniforms.frameCount.value = this._totalFrameCount; else console.warn('RenderManager: no uniform: frameCount'); } else { if (material.uniforms.frameCount) material.uniforms.frameCount.value = this.frameCount; else console.warn('RenderManager: no uniform: frameCount'); } return this; } // region Passes registerPass(pass, replaceId = true) { if (replaceId) { for (const pass1 of [...this._passes]) { if (pass.passId === pass1.passId) this.unregisterPass(pass1); } } this._passes.push(pass); pass.onRegister?.(this); this.rebuildPipeline(false); this._updated({ change: 'registerPass', pass }); } unregisterPass(pass) { const i = this._passes.indexOf(pass); if (i >= 0) { pass.onUnregister?.(this); this._passes.splice(i, 1); this.rebuildPipeline(false); this._updated({ change: 'unregisterPass', pass }); } } // endregion // region Getters and Setters get frameCount() { return this._frameCount; } get totalFrameCount() { return this._totalFrameCount; } resetTotalFrameCount() { this._totalFrameCount = 0; } set pipeline(value) { this._pipeline = value; if (this.autoBuildPipeline) { console.warn('RenderManager: pipeline changed, but autoBuildPipeline is true. This will not have any effect.'); } this.rebuildPipeline(); } get pipeline() { return this._pipeline; } get composer() { return this._composer; } get passes() { return this._passes; } get isWebGL2() { return this._isWebGL2; } get composerTarget() { return this._composer.renderTarget1; } get composerTarget2() { return this._composer.renderTarget2; } /** * The size set in the three.js renderer. * Final size is renderSize * renderScale */ get renderSize() { return this._renderSize; } get context() { return this._context; } /** * Same as {@link renderer} */ get webglRenderer() { return this._renderer; } /** * @deprecated will be removed in the future */ get useLegacyLights() { return this._renderer.useLegacyLights; } set useLegacyLights(v) { this._renderer.useLegacyLights = v; this._updated({ change: 'useLegacyLights', data: v }); this.resetShadows(); } get clock() { return this._composer.clock; } // endregion // region Utils /** * blit - blits a texture to the screen or another render target. * @param destination - destination target, or screen if undefined or null * @param source - source Texture * @param viewport - viewport and scissor * @param material - override material * @param clear - clear before blit * @param respectColorSpace - does color space conversion when reading and writing to the target * @param blending - Note - Set to NormalBlending if transparent is set to false * @param transparent * @param opacity - opacity of the material, if not set, uses the material's opacity * @param blendAlpha - custom blending factor, if set, overrides blending. The material will use CustomBlending with ConstantAlphaFactor and OneMinusConstantAlphaFactor, useful to blend between textures. */ blit(destination, { source, viewport, material, clear = true, respectColorSpace = false, blending = NoBlending, transparent = true, opacity, blendAlpha } = {}) { const copyPass = !respectColorSpace ? this._composer.copyPass : this._composer.copyPass2; const { renderToScreen, material: oldMaterial, uniforms: oldUniforms, clear: oldClear } = copyPass; if (material) { copyPass.material = material; } const oldTransparent = copyPass.material.transparent; const oldViewport = !destination ? this._renderer.getViewport(new Vector4()) : destination.viewport.clone(); const oldScissor = !destination ? this._renderer.getScissor(new Vector4()) : destination.scissor.clone(); const oldScissorTest = !destination ? this._renderer.getScissorTest() : destination.scissorTest; const oldAutoClear = this._renderer.autoClear; const oldTarget = this._renderer.getRenderTarget(); const oldBlending = copyPass.material.blending; const oldOpacity = copyPass.material.uniforms.opacity?.value ?? 1; const oldBlendAlpha = copyPass.material.blendAlpha; const oldBlendSrc = copyPass.material.blendSrc; const oldBlendDst = copyPass.material.blendDst; const oldBlendEquation = copyPass.material.blendEquation; if (viewport) { if (!destination) { this._renderer.setViewport(viewport); this._renderer.setScissor(viewport); this._renderer.setScissorTest(true); } else { destination.viewport.copy(viewport); destination.scissor.copy(viewport); destination.scissorTest = true; } } this._renderer.autoClear = false; copyPass.material.blending = !transparent ? NormalBlending : blending; copyPass.uniforms = copyPass.material.uniforms; copyPass.renderToScreen = false; copyPass.clear = clear; copyPass.material.transparent = transparent; copyPass.material.needsUpdate = true; if (copyPass.material.uniforms.opacity && opacity !== undefined) { copyPass.material.uniforms.opacity.value = opacity; } if (blendAlpha !== undefined) { // blendAlpha is custom blending factor copyPass.material.blending = CustomBlending; copyPass.material.blendSrc = ConstantAlphaFactor; copyPass.material.blendDst = OneMinusConstantAlphaFactor; copyPass.material.blendEquation = AddEquation; copyPass.material.blendAlpha = blendAlpha; } this._renderer.renderWithModes({ sceneRender: true, opaqueRender: true, shadowMapRender: false, backgroundRender: false, transparentRender: true, transmissionRender: false, }, () => { copyPass.render(this._renderer, destination || null, { texture: source }, 0, false); }); if (copyPass.material.uniforms.opacity && opacity !== undefined) { copyPass.material.uniforms.opacity.value = oldOpacity; } copyPass.renderToScreen = renderToScreen; copyPass.clear = oldClear; copyPass.material.blending = oldBlending; copyPass.material.blendSrc = oldBlendSrc; copyPass.material.blendDst = oldBlendDst; copyPass.material.blendEquation = oldBlendEquation; copyPass.material.blendAlpha = oldBlendAlpha; copyPass.material.transparent = oldTransparent; copyPass.material = oldMaterial; copyPass.uniforms = oldUniforms; this._renderer.autoClear = oldAutoClear; if (viewport) { if (!destination) { this._renderer.setViewport(oldViewport); this._renderer.setScissor(oldScissor); this._renderer.setScissorTest(oldScissorTest); } else { destination.viewport.copy(oldViewport); destination.scissor.copy(oldScissor); destination.scissorTest = oldScissorTest; } } this._renderer.setRenderTarget(oldTarget); // todo: active cubeface etc } clearColor({ r, g, b, a, target, depth = true, stencil = true, viewport }) { const color = this._renderer.getClearColor(new Color()); const alpha = this._renderer.getClearAlpha(); this._renderer.setClearColor(new Color(r ?? color.r, g ?? color.g, b ?? color.b), a ?? alpha); const lastTarget = this._renderer.getRenderTarget(); const activeCubeFace = this._renderer.getActiveCubeFace(); const activeMipLevel = this._renderer.getActiveMipmapLevel(); const oldViewport = !target ? this._renderer.getViewport(new Vector4()) : target.viewport.clone(); const oldScissor = !target ? this._renderer.getScissor(new Vector4()) : target.scissor.clone(); const oldScissorTest = !target ? this._renderer.getScissorTest() : target.scissorTest; if (viewport) { if (!target) { this._renderer.setViewport(viewport); this._renderer.setScissor(viewport); this._renderer.setScissorTest(true); } else { target.viewport.copy(viewport); target.scissor.copy(viewport); target.scissorTest = true; } } this._renderer.setRenderTarget(target ?? null); this._renderer.clear(true, depth, stencil); if (target && typeof target.clear === 'function') { // WebGLCubeRenderTarget target.clear(this._renderer, true, depth, stencil); } else { this._renderer.setRenderTarget(target ?? null); this._renderer.clear(true, depth, stencil); } if (viewport) { if (!target) { this._renderer.setViewport(oldViewport); this._renderer.setScissor(oldScissor); this._renderer.setScissorTest(oldScissorTest); } else { target.viewport.copy(oldViewport); target.scissor.copy(oldScissor); target.scissorTest = oldScissorTest; } } this._renderer.setRenderTarget(lastTarget, activeCubeFace, activeMipLevel); this._renderer.setClearColor(color, alpha); } /** * Copies a render target to a new/existing canvas element. * Note: this will clamp the values to [0, 1] and converts to srgb for float and half-float render targets. * @param target * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) * @param canvas - optional canvas to render to, if not provided a new canvas will be created. */ renderTargetToCanvas(target, textureIndex = 0, canvas) { canvas = canvas ?? document.createElement('canvas'); canvas.width = target.width; canvas.height = target.height; const ctx = canvas.getContext('2d'); if (!ctx) throw new Error('Unable to get 2d context'); const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture; const imageData = ctx.createImageData(target.width, target.height, { colorSpace: ['display-p3', 'srgb'].includes(texture.colorSpace) ? texture.colorSpace : undefined }); if (texture.type === HalfFloatType || texture.type === FloatType) { const buffer = this.renderTargetToBuffer(target, textureIndex); textureDataToImageData({ data: buffer, width: target.width, height: target.height }, texture.colorSpace, imageData); // this handles converting to srgb } else { // todo: handle rgbm to srgb conversion? this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, imageData.data, undefined, textureIndex); } ctx.putImageData(imageData, 0, 0); return canvas; } /** * Converts a render target to a png/jpeg data url string. * Note: this will clamp the values to [0, 1] and converts to srgb for float and half-float render targets. * @param target * @param mimeType * @param quality * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) */ renderTargetToDataUrl(target, mimeType = 'image/png', quality = 90, textureIndex = 0) { const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture; const canvas = this.renderTargetToCanvas(target, textureIndex); const string = (texture.flipY ? canvas : canvasFlipY(canvas)).toDataURL(mimeType, quality); // intentionally inverted ternary canvas.remove(); return string; } /** * Rend pixels from a render target into a new Uint8Array|Uint16Array|Float32Array buffer * @param target - render target to read from * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) */ renderTargetToBuffer(target, textureIndex = 0) { const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture; const buffer = texture.type === HalfFloatType ? new Uint16Array(target.width * target.height * 4) : texture.type === FloatType ? new Float32Array(target.width * target.height * 4) : new Uint8Array(target.width * target.height * 4); this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, buffer, undefined, textureIndex); return buffer; } /** * Exports a render target to a blob. The type is automatically picked from exr to png based on the render target. * @param target - render target to export * @param mimeType - mime type to use. * If auto (default), then it will be picked based on the render target type. * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) */ exportRenderTarget(target, mimeType = 'auto', textureIndex = 0) { const hdrFormats = ['image/x-exr']; const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture; let hdr = texture.type === HalfFloatType || texture.type === FloatType; if (mimeType === 'auto') { mimeType = hdr ? 'image/x-exr' : 'image/png'; } if (!hdrFormats.includes(mimeType)) hdr = false; let buffer; if (!hdr) { const url = this.renderTargetToDataUrl(target, mimeType === 'auto' ? undefined : mimeType, 90, textureIndex); buffer = base64ToArrayBuffer(url.split(',')[1]); mimeType = url.split(';')[0].split(':')[1]; } else { if (mimeType !== 'image/x-exr') { console.warn('RenderManager: mimeType ', mimeType, ' is not supported for HDR. Using EXR instead'); mimeType = 'image/x-exr'; } const exporter = new EXRExporter2(); buffer = exporter.parse(this._renderer, target, { textureIndex }).buffer; } const b = new Blob([buffer], { type: mimeType }); b.ext = mimeType === 'image/x-exr' ? 'exr' : mimeType.split('/')[1]; return b; } // endregion // region Events Dispatch _updated(data) { this.dispatchEvent({ ...data, type: 'update' }); } // endregion _createTargetClass(clazz, size, options) { const processNewTarget = this._processNewTarget; const disposeTarget = this.disposeTarget.bind(this); return new class RenderTarget extends clazz { constructor(renderManager, ...ps) { super(...ps); this.renderManager = renderManager; this.assetType = 'renderTarget'; this.name = 'RenderTarget'; // required for uiconfig.js. see UiConfigMethods.getValue // eslint-disable-next-line @typescript-eslint/naming-convention this._ui_isPrimitive = true; this.uuid = generateUUID(); const ops = ps[ps.length - 1]; const colorSpace = ops?.colorSpace; this._initTexture(colorSpace); } _initTexture(colorSpace) { if (Array.isArray(this.texture)) { this.texture.forEach(t => { if (colorSpace !== undefined) t.colorSpace = colorSpace; t._target = this; t.toJSON = () => { console.warn('Multiple render target texture.toJSON not supported yet.'); return {}; }; }); } else { this.texture._target = this; // if (colorSpace !== undefined) this.texture.colorSpace = colorSpace this.texture.toJSON = () => ({ isRenderTargetTexture: true, }); // so that it doesn't get serialized } } setSize(w, h, depth) { super.setSize(Math.floor(w), Math.floor(h), depth); // console.log('setSize', w, h, depth) return this; } clone(trackTarget = true) { if (this.isTemporary) throw 'Cloning temporary render targets not supported'; // todo why? if (Array.isArray(this.texture)) throw 'Cloning multiple render targets not supported'; // Note: todo: webgl render target.clone messes up the texture, by not copying isRenderTargetTexture prop and maybe some other stuff. So its better to just create a new one // const cloned = super.clone() as IRenderTarget const cloned = new this.constructor(this.renderManager); cloned.copy(this); cloned._initTexture((Array.isArray(this.texture) ? this.texture[0] : this.texture)?.colorSpace); const tex = cloned.texture; if (Array.isArray(tex)) tex.forEach(t => t.isRenderTargetTexture = true); else tex.isRenderTargetTexture = true; return processNewTarget(cloned, this.sizeMultiplier || 1, trackTarget); } // copy(source: IRenderTarget|RenderTarget): this { // super.copy(source as any) // return this // } // Note - by default unregister need to be false. dispose(unregister = false) { if (unregister === true) disposeTarget(this, true); else super.dispose(); } }(this, ...size, options); } /** * @deprecated use renderScale instead */ get displayCanvasScaling() { console.error('displayCanvasScaling is deprecated, use renderScale instead'); return this.renderScale; } /** * @deprecated use renderScale instead */ set displayCanvasScaling(value) { console.error('displayCanvasScaling is deprecated, use renderScale instead'); this.renderScale = value; } }; RenderManager.POWER_PREFERENCE = 'high-performance'; __decorate([ uiMonitor('Render Size') ], RenderManager.prototype, "_renderSize", void 0); __decorate([ uiSlider('Render Scale', [0.1, 8], 0.05) // keep here in code so its at the top in the UI ], RenderManager.prototype, "renderScale", null); __decorate([ serialize(), uiDropdown('Shadow Map Type', ['BasicShadowMap', 'PCFShadowMap', 'PCFSoftShadowMap', 'VSMShadowMap'].map((v, i) => ({ label: v, value: i })), { tags: ['advanced'] }), bindToValue({ obj: 'shadowMap', key: 'type', onChange: RenderManager.prototype._shadowMapTypeChanged }) ], RenderManager.prototype, "shadowMapType", void 0); __decorate([ bindToValue({ obj: 'renderer', key: 'shadowMap' }) ], RenderManager.prototype, "shadowMap", void 0); __decorate([ uiConfig(undefined, { label: 'Passes', tags: ['advanced'], order: 1000 }) ], RenderManager.prototype, "_passes", void 0); __decorate([ uiToggle(), serialize() ], RenderManager.prototype, "stableNoise", void 0); __decorate([ onChange2(RenderManager.prototype.rebuildPipeline) ], RenderManager.prototype, "autoBuildPipeline", void 0); __decorate([ uiButton('Rebuild Pipeline', { sendArgs: false, tags: ['advanced'] }) ], RenderManager.prototype, "rebuildPipeline", null); __decorate([ serialize() ], RenderManager.prototype, "useLegacyLights", null); RenderManager = RenderManager_1 = __decorate([ serializable('RenderManager'), uiFolderContainer('Render Manager') ], RenderManager); export { RenderManager }; //# sourceMappingURL=RenderManager.js.map