@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
JavaScript
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