UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

556 lines (551 loc) 23.6 kB
import { aZ as SceneComponentConstants, ao as Scene, a as EngineStore, m as Texture, bb as _RetryWithInterval, bc as Vector4, O as Observable, y as Logger, aH as RawTexture, C as Constants, aP as PostProcess, V as Vector3, ba as _WarnImport } from './index-MZPybX0H.esm.js'; import { P as ProceduralTexture } from './proceduralTexture.pure-BnRujZUW.esm.js'; import { C as CubeTexture } from './cubeTexture.pure-D6LcyeP8.esm.js'; /** This file must only contain pure code and pure imports */ /** * Defines the IBL CDF Generator scene component responsible for generating CDF maps for a given IBL. */ class IblCdfGeneratorSceneComponent { /** * Creates a new instance of the component for the given scene * @param scene Defines the scene to register the component in */ constructor(scene) { /** * The component name helpful to identify the component in the list of scene components. */ this.name = SceneComponentConstants.NAME_IBLCDFGENERATOR; this._newIblObserver = null; this.scene = scene; } /** * Registers the component in a given scene */ register() { this._updateIblSource(); this._newIblObserver = this.scene.onEnvironmentTextureChangedObservable.add(this._updateIblSource.bind(this)); this.scene.addIsReadyCheck(this); } /** * Rebuilds the elements related to this component in case of * context lost for instance. */ rebuild() { // Nothing to do for this component } /** * Disposes the component and the associated resources */ dispose() { this.scene.onEnvironmentTextureChangedObservable.remove(this._newIblObserver); this.scene.removeIsReadyCheck(this); } /** * @returns true once the CDF generator's procedural textures and effects are ready. * Used by `Scene.isReady` so that `executeWhenReady` waits for the CDF maps to be * generated before declaring the scene ready to render. */ isReady() { const generator = this.scene._iblCdfGenerator; // If there's no generator, or no environment texture for it to consume, // there's nothing to wait for - report ready so Scene.isReady() doesn't stall. if (!generator || !this.scene.environmentTexture) { return true; } return !!generator.isReady(); } _updateIblSource() { if (this.scene.iblCdfGenerator && this.scene.environmentTexture) { this.scene.iblCdfGenerator.iblSource = this.scene.environmentTexture; } } } let _Registered = false; /** * Register side effects for iblCdfGeneratorSceneComponent. * Safe to call multiple times; only the first call has an effect. * @param iblCdfGeneratorClass The IblCdfGenerator class to register the component for */ function RegisterIblCdfGeneratorSceneComponent(iblCdfGeneratorClass) { if (_Registered) { return; } _Registered = true; Object.defineProperty(Scene.prototype, "iblCdfGenerator", { get: function () { return this._iblCdfGenerator; }, set: function (value) { if (value) { this._iblCdfGenerator = value; } }, enumerable: true, configurable: true, }); Scene.prototype.enableIblCdfGenerator = function () { if (this._iblCdfGenerator) { return this._iblCdfGenerator; } this._iblCdfGenerator = new iblCdfGeneratorClass(this); if (!this._iblCdfGenerator.isSupported) { this._iblCdfGenerator = null; return null; } if (this.environmentTexture) { this._iblCdfGenerator.iblSource = this.environmentTexture; } return this._iblCdfGenerator; }; Scene.prototype.disableIblCdfGenerator = function () { if (!this._iblCdfGenerator) { return; } this._iblCdfGenerator.dispose(); this._iblCdfGenerator = null; }; iblCdfGeneratorClass._SceneComponentInitialization = (scene) => { // Register the CDF generator component to the scene. let component = scene._getComponent(SceneComponentConstants.NAME_IBLCDFGENERATOR); if (!component) { component = new IblCdfGeneratorSceneComponent(scene); scene._addComponent(component); } }; } /** * Build cdf maps to be used for IBL importance sampling. */ class IblCdfGenerator { /** * Returns whether the CDF renderer is supported by the current engine */ get isSupported() { const engine = EngineStore.LastCreatedEngine; if (!engine) { return false; } return engine.getCaps().texelFetch; } /** * Gets the IBL source texture being used by the CDF renderer */ get iblSource() { return this._iblSource; } /** * Sets the IBL source texture to be used by the CDF renderer. * This will trigger recreation of the CDF assets. */ set iblSource(source) { if (this._iblSource === source) { return; } this._clearIblSourceReadinessObservers(); this._disposeTextures(); this._iblSource = source; if (!source) { return; } const recreateFromObservedSourceIfReady = () => { if (this._iblSource !== source || !this._isIblSourceReady(source)) { return; } this._clearIblSourceReadinessObservers(); this._recreateAssetsFromNewIbl(); }; if (this._isIblSourceReady(source)) { this._recreateAssetsFromNewIbl(); return; } if (source instanceof Texture) { const observer = source.onLoadObservable.addOnce(recreateFromObservedSourceIfReady); if (observer) { this._iblSourceLoadUnsubscribe = () => source.onLoadObservable.remove(observer); } } else if (source instanceof CubeTexture) { const observer = source.onLoadObservable.addOnce(recreateFromObservedSourceIfReady); if (observer) { this._iblSourceLoadUnsubscribe = () => source.onLoadObservable.remove(observer); } } this._iblSourceReadyRetryObserver = _RetryWithInterval(() => this._iblSource !== source || this._isIblSourceReady(source), recreateFromObservedSourceIfReady, undefined, 16, 30000, false); } _isIblSourceReady(source) { if (!source.isReadyOrNotBlocking()) { return false; } if (!source.isCube) { return true; } const internalTexture = source.getInternalTexture(); return !!internalTexture && internalTexture.isReady; } _clearIblSourceReadinessObservers() { this._iblSourceReadyRetryObserver?.(); this._iblSourceReadyRetryObserver = null; this._iblSourceLoadUnsubscribe?.(); this._iblSourceLoadUnsubscribe = null; } _recreateAssetsFromNewIbl() { if (this._debugPass) { this._debugPass.dispose(); } this._createTextures(); if (this._debugPass) { // Recreate the debug pass because of the new textures this._createDebugPass(); } } /** * Return the cumulative distribution function (CDF) texture * @returns Return the cumulative distribution function (CDF) texture */ getIcdfTexture() { return this._icdfPT ? this._icdfPT : this._dummyTexture; } /** * Sets params that control the position and scaling of the debug display on the screen. * @param x Screen X offset of the debug display (0-1) * @param y Screen Y offset of the debug display (0-1) * @param widthScale X scale of the debug display (0-1) * @param heightScale Y scale of the debug display (0-1) */ setDebugDisplayParams(x, y, widthScale, heightScale) { this._debugSizeParams.set(x, y, widthScale, heightScale); } /** * The name of the debug pass post process */ get debugPassName() { return this._debugPassName; } /** * Gets the debug pass post process * @returns The post process */ getDebugPassPP() { if (!this._debugPass) { this._createDebugPass(); } return this._debugPass; } /** * Instanciates the CDF renderer * @param sceneOrEngine Scene to attach to * @returns The CDF renderer */ constructor(sceneOrEngine) { this._iblSourceLoadUnsubscribe = null; this._iblSourceReadyRetryObserver = null; this._cachedDominantDirection = null; /** Enable the debug view for this pass */ this.debugEnabled = false; this._debugSizeParams = new Vector4(0.0, 0.0, 1.0, 1.0); this._debugPassName = "CDF Debug"; /** * Observable that triggers when the CDF renderer is ready */ this.onGeneratedObservable = new Observable(); /** * Observable that triggers when CDF texture references change. * It is raised after disposing textures (so fallback ICDF can be used) * and after creating new textures (so consumers can rebind immediately). */ this.onTextureChangedObservable = new Observable(); if (sceneOrEngine) { if (IblCdfGenerator._IsScene(sceneOrEngine)) { this._scene = sceneOrEngine; } else { this._engine = sceneOrEngine; } } else { this._scene = EngineStore.LastCreatedScene; } if (this._scene) { this._engine = this._scene.getEngine(); } if (!this.isSupported) { Logger.Warn("CDF renderer is not supported by the current engine."); return; } const blackPixels = new Uint16Array([0, 0, 0, 255]); this._dummyTexture = new RawTexture(blackPixels, 1, 1, Constants.TEXTUREFORMAT_RGBA, sceneOrEngine, false, false, undefined, Constants.TEXTURETYPE_HALF_FLOAT); if (this._scene) { RegisterIblCdfGeneratorSceneComponent(IblCdfGenerator); IblCdfGenerator._SceneComponentInitialization(this._scene); } } _createTextures() { const size = this._iblSource ? { width: this._iblSource.getSize().width, height: this._iblSource.getSize().height } : { width: 1, height: 1 }; if (!this._iblSource) { this._iblSource = RawTexture.CreateRTexture(new Uint8Array([255]), 1, 1, this._engine, false, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE, Constants.TEXTURETYPE_UNSIGNED_BYTE); this._iblSource.name = "Placeholder IBL Source"; } if (this._iblSource.isCube) { size.width *= 4; size.height *= 2; // Force the resolution to be a power of 2 because we rely on the // auto-mipmap generation for the scaled luminance texture to produce // a 1x1 mip that represents the true average pixel intensity of the IBL. size.width = 1 << Math.floor(Math.log2(size.width)); size.height = 1 << Math.floor(Math.log2(size.height)); } const isWebGPU = this._engine.isWebGPU; // Create CDF maps (Cumulative Distribution Function) to assist in importance sampling const cdfOptions = { generateDepthBuffer: false, generateMipMaps: false, format: Constants.TEXTUREFORMAT_R, type: Constants.TEXTURETYPE_FLOAT, samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE, shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */, gammaSpace: false, extraInitializationsAsync: async () => { if (isWebGPU) { await Promise.all([import('./iblCdfx.fragment-CA0LQq2s.esm.js'), import('./iblCdfy.fragment-BerHSIZR.esm.js'), import('./iblScaledLuminance.fragment-2jkSYJfe.esm.js')]); } else { await Promise.all([import('./iblCdfx.fragment-D_FAc3Ui.esm.js'), import('./iblCdfy.fragment-BwqKwvbv.esm.js'), import('./iblScaledLuminance.fragment-BkoNHgyN.esm.js')]); } }, }; const icdfOptions = { generateDepthBuffer: false, generateMipMaps: false, format: Constants.TEXTUREFORMAT_RGBA, type: Constants.TEXTURETYPE_HALF_FLOAT, samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE, shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */, gammaSpace: false, extraInitializationsAsync: async () => { if (isWebGPU) { await Promise.all([import('./iblIcdf.fragment-D8ZNSB8i.esm.js'), import('./iblDominantDirection.fragment-CTa2OmM9.esm.js')]); } else { await Promise.all([import('./iblIcdf.fragment-DTX9lLPJ.esm.js'), import('./iblDominantDirection.fragment-CyAmmjk6.esm.js')]); } }, }; this._cdfyPT = new ProceduralTexture("cdfyTexture", { width: size.width, height: size.height + 1 }, "iblCdfy", this._scene, cdfOptions, false, false); this._cdfyPT.autoClear = false; this._cdfyPT.setTexture("iblSource", this._iblSource); this._cdfyPT.setInt("iblHeight", size.height); this._cdfyPT.wrapV = Constants.TEXTURE_CLAMP_ADDRESSMODE; this._cdfyPT.refreshRate = 0; if (this._iblSource.isCube) { this._cdfyPT.defines = "#define IBL_USE_CUBE_MAP\n"; } this._cdfxPT = new ProceduralTexture("cdfxTexture", { width: size.width + 1, height: 1 }, "iblCdfx", this._scene, cdfOptions, false, false); this._cdfxPT.autoClear = false; this._cdfxPT.setTexture("cdfy", this._cdfyPT); this._cdfxPT.refreshRate = 0; this._cdfxPT.wrapU = Constants.TEXTURE_CLAMP_ADDRESSMODE; this._scaledLuminancePT = new ProceduralTexture("iblScaledLuminance", { width: size.width, height: size.height }, "iblScaledLuminance", this._scene, { ...cdfOptions, samplingMode: Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, generateMipMaps: true }, true, false); this._scaledLuminancePT.autoClear = false; this._scaledLuminancePT.setTexture("iblSource", this._iblSource); this._scaledLuminancePT.setInt("iblHeight", size.height); this._scaledLuminancePT.setInt("iblWidth", size.width); this._scaledLuminancePT.refreshRate = 0; if (this._iblSource.isCube) { this._scaledLuminancePT.defines = "#define IBL_USE_CUBE_MAP\n"; } this._icdfPT = new ProceduralTexture("icdfTexture", { width: size.width, height: size.height }, "iblIcdf", this._scene, icdfOptions, false, false); this._icdfPT.autoClear = false; this._icdfPT.setTexture("cdfy", this._cdfyPT); this._icdfPT.setTexture("cdfx", this._cdfxPT); this._icdfPT.setTexture("iblSource", this._iblSource); this._icdfPT.setTexture("scaledLuminanceSampler", this._scaledLuminancePT); this._icdfPT.refreshRate = 0; this._icdfPT.wrapV = Constants.TEXTURE_CLAMP_ADDRESSMODE; this._icdfPT.wrapU = Constants.TEXTURE_CLAMP_ADDRESSMODE; if (this._iblSource.isCube) { this._icdfPT.defines = "#define IBL_USE_CUBE_MAP\n"; } // Once the textures are generated, notify that they are ready to use. this._icdfPT.onGeneratedObservable.addOnce(() => { this.onGeneratedObservable.notifyObservers(); }); this._dominantDirectionPT = new ProceduralTexture("iblDominantDirection", { width: 1, height: 1 }, "iblDominantDirection", this._scene, icdfOptions, false, false); this._dominantDirectionPT.autoClear = false; this._dominantDirectionPT.setTexture("icdfSampler", this._icdfPT); this._dominantDirectionPT.refreshRate = 0; this._dominantDirectionPT.defines = "#define NUM_SAMPLES 32u\n"; this.onTextureChangedObservable.notifyObservers(); } _disposeTextures() { this._cdfyPT?.dispose(); this._cdfxPT?.dispose(); this._icdfPT?.dispose(); this._scaledLuminancePT?.dispose(); this._dominantDirectionPT?.dispose(); this._icdfPT = null; this.onTextureChangedObservable.notifyObservers(); } _createDebugPass() { if (this._debugPass) { this._debugPass.dispose(); } const isWebGPU = this._engine.isWebGPU; const debugOptions = { width: this._engine.getRenderWidth(), height: this._engine.getRenderHeight(), samplingMode: Texture.BILINEAR_SAMPLINGMODE, engine: this._engine, textureType: Constants.TEXTURETYPE_UNSIGNED_BYTE, uniforms: ["sizeParams"], samplers: ["cdfy", "icdf", "cdfx", "iblSource"], defines: this._iblSource?.isCube ? "#define IBL_USE_CUBE_MAP\n" : "", shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */, extraInitializations: (useWebGPU, list) => { if (useWebGPU) { list.push(import('./iblCdfDebug.fragment-CLoK96cM.esm.js')); } else { list.push(import('./iblCdfDebug.fragment-QhMwTYUF.esm.js')); } }, }; this._debugPass = new PostProcess(this._debugPassName, "iblCdfDebug", debugOptions); const debugEffect = this._debugPass.getEffect(); if (debugEffect) { debugEffect.defines = this._iblSource?.isCube ? "#define IBL_USE_CUBE_MAP\n" : ""; } if (this._iblSource?.isCube) { this._debugPass.updateEffect("#define IBL_USE_CUBE_MAP\n"); } this._debugPass.onApplyObservable.add((effect) => { effect.setTexture("cdfy", this._cdfyPT); effect.setTexture("icdf", this._icdfPT); effect.setTexture("cdfx", this._cdfxPT); effect.setTexture("iblSource", this._iblSource); effect.setFloat4("sizeParams", this._debugSizeParams.x, this._debugSizeParams.y, this._debugSizeParams.z, this._debugSizeParams.w); }); } /** * Checks if the CDF renderer is ready * @returns true if the CDF renderer is ready */ isReady() { return (this._iblSource && this._iblSource.name !== "Placeholder IBL Source" && this._iblSource.isReady() && this._cdfyPT && this._cdfyPT.isReady() && this._icdfPT && this._icdfPT.isReady() && this._cdfxPT && this._cdfxPT.isReady() && this._scaledLuminancePT && this._scaledLuminancePT.isReady()); } /** * Explicitly trigger generation of CDF maps when they are ready to render. * @returns Promise that resolves when the CDF maps are rendered. */ // eslint-disable-next-line @typescript-eslint/promise-function-async renderWhenReady() { this._cachedDominantDirection = null; // Even if a IBL source must be set before calling this function, _icdfPT may not yet be created because the creation may be asynchronous (see @set iblSource). const icdfPTPromise = new Promise((resolve, reject) => { _RetryWithInterval(() => !!this._icdfPT, () => resolve(void 0), () => reject(new Error("Waiting for _icdfPT creation failed"))); }); // eslint-disable-next-line github/no-then, @typescript-eslint/promise-function-async return icdfPTPromise.then(() => { const icdfTexture = this._icdfPT; const promises = []; const renderTargets = [this._cdfyPT, this._cdfxPT, this._scaledLuminancePT, icdfTexture]; for (const target of renderTargets) { promises.push(new Promise((resolve) => { if (target.isReady()) { resolve(); } else { target.getEffect().executeWhenCompiled(() => { resolve(); }); } })); } // eslint-disable-next-line github/no-then return Promise.all(promises).then(() => { for (const target of renderTargets) { target.render(); } }); }); } /** * Finds the average direction of the highest intensity areas of the IBL source * @returns Async promise that resolves to the dominant direction of the IBL source */ // eslint-disable-next-line @typescript-eslint/promise-function-async findDominantDirection() { if (this._cachedDominantDirection) { return Promise.resolve(this._cachedDominantDirection); } return new Promise((resolve) => { this._dominantDirectionPT.onGeneratedObservable.addOnce(() => { const data = new Float32Array(4); // eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then this._dominantDirectionPT.readPixels(0, 0, data, true).then(() => { const dominantDirection = new Vector3(data[0], data[1], data[2]); this._cachedDominantDirection = dominantDirection; resolve(dominantDirection); }); }); if (this.isReady()) { if (this._dominantDirectionPT.isReady()) { this._dominantDirectionPT.render(); } else { this._dominantDirectionPT.getEffect().executeWhenCompiled(() => { this._dominantDirectionPT.render(); }); } } else { this.onGeneratedObservable.addOnce(() => { if (this._dominantDirectionPT.isReady()) { this._dominantDirectionPT.render(); } else { this._dominantDirectionPT.getEffect().executeWhenCompiled(() => { this._dominantDirectionPT.render(); }); } }); } }); } /** * Disposes the CDF renderer and associated resources */ dispose() { this._clearIblSourceReadinessObservers(); this._disposeTextures(); this._dummyTexture.dispose(); if (this._debugPass) { this._debugPass.dispose(); } this.onGeneratedObservable.clear(); } static _IsScene(sceneOrEngine) { return sceneOrEngine.getClassName() === "Scene"; } } /** * @internal */ IblCdfGenerator._SceneComponentInitialization = (_) => { throw _WarnImport("IblCdfGeneratorSceneComponentSceneComponent"); }; var iblCdfGenerator = /*#__PURE__*/Object.freeze({ __proto__: null, IblCdfGenerator: IblCdfGenerator }); export { IblCdfGenerator as I, RegisterIblCdfGeneratorSceneComponent as R, IblCdfGeneratorSceneComponent as a, iblCdfGenerator as i }; //# sourceMappingURL=iblCdfGenerator-gclpoAcj.esm.js.map