@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.
1,110 lines (1,108 loc) • 218 kB
JavaScript
import { h as RenderTargetTexture, C as Constants, T as Texture, P as PostProcess, O as Observable, M as Matrix, i as Vector4, L as Logger, a as EffectRenderer, E as EffectWrapper, j as Color4, k as Engine, V as Vector3, S as ShaderStore, l as MaterialFlags, m as VertexBuffer, n as PrepareDefinesAndAttributesForMorphTargets, o as PushAttributesForInstances, p as PrepareStringDefinesForClipPlanes, q as BindSceneUniformBuffer, r as BindClipPlane, s as BindMorphTargetParameters, t as Material, A as AddClipPlaneUniforms, _ as _WarnImport, b as Tools, u as __decorate, v as serialize, w as MaterialPluginBase, x as PBRBaseMaterial, y as MaterialDefines, z as serializeAsTexture, D as expandToProperty, R as RegisterClass, F as Scene, G as SceneComponentConstants, H as EngineStore, Q as Quaternion } from './index-FzOfPXLV.esm.js';
import { ShaderMaterial } from './shaderMaterial-Bz73fOLU.esm.js';
import './engine.multiRender-D0bO0nxA.esm.js';
import { P as ProceduralTexture, I as IblCdfGenerator } from './iblCdfGenerator-BDPVKn79.esm.js';
import './clipPlaneFragment-OFOZXtyS.esm.js';
import './bumpFragment-PSTyPyLQ.esm.js';
import './helperFunctions-CLFdU2UC.esm.js';
import './bakedVertexAnimation-D5Wmzm8F.esm.js';
import './morphTargetsVertex-Bimm08Fe.esm.js';
import './instancesDeclaration-D6f54GuO.esm.js';
import './sceneUboDeclaration-CeGVxetF.esm.js';
import './clipPlaneVertex-wC8bw87F.esm.js';
import './bumpVertex-CQnDcsrx.esm.js';
import { R as RawTexture } from './rawTexture-B2DimmQ5.esm.js';
import { S as StandardMaterial } from './standardMaterial-DpmQ1Io2.esm.js';
/**
* A multi render target, like a render target provides the ability to render to a texture.
* Unlike the render target, it can render to several draw buffers (render textures) in one draw.
* This is specially interesting in deferred rendering or for any effects requiring more than
* just one color from a single pass.
*/
class MultiRenderTarget extends RenderTargetTexture {
/**
* Get if draw buffers (render textures) are currently supported by the used hardware and browser.
*/
get isSupported() {
return this._engine?.getCaps().drawBuffersExtension ?? false;
}
/**
* Get the list of textures generated by the multi render target.
*/
get textures() {
return this._textures;
}
/**
* Gets the number of textures in this MRT. This number can be different from `_textures.length` in case a depth texture is generated.
*/
get count() {
return this._count;
}
/**
* Get the depth texture generated by the multi render target if options.generateDepthTexture has been set
*/
get depthTexture() {
return this._textures[this._textures.length - 1];
}
/**
* Set the wrapping mode on U of all the textures we are rendering to.
* Can be any of the Texture. (CLAMP_ADDRESSMODE, MIRROR_ADDRESSMODE or WRAP_ADDRESSMODE)
*/
set wrapU(wrap) {
if (this._textures) {
for (let i = 0; i < this._textures.length; i++) {
this._textures[i].wrapU = wrap;
}
}
}
/**
* Set the wrapping mode on V of all the textures we are rendering to.
* Can be any of the Texture. (CLAMP_ADDRESSMODE, MIRROR_ADDRESSMODE or WRAP_ADDRESSMODE)
*/
set wrapV(wrap) {
if (this._textures) {
for (let i = 0; i < this._textures.length; i++) {
this._textures[i].wrapV = wrap;
}
}
}
/**
* Instantiate a new multi render target texture.
* A multi render target, like a render target provides the ability to render to a texture.
* Unlike the render target, it can render to several draw buffers (render textures) in one draw.
* This is specially interesting in deferred rendering or for any effects requiring more than
* just one color from a single pass.
* @param name Define the name of the texture
* @param size Define the size of the buffers to render to
* @param count Define the number of target we are rendering into
* @param scene Define the scene the texture belongs to
* @param options Define the options used to create the multi render target
* @param textureNames Define the names to set to the textures (if count \> 0 - optional)
*/
constructor(name, size, count, scene, options, textureNames) {
const generateMipMaps = options && options.generateMipMaps ? options.generateMipMaps : false;
const generateDepthTexture = options && options.generateDepthTexture ? options.generateDepthTexture : false;
const depthTextureFormat = options && options.depthTextureFormat ? options.depthTextureFormat : Constants.TEXTUREFORMAT_DEPTH16;
const doNotChangeAspectRatio = !options || options.doNotChangeAspectRatio === undefined ? true : options.doNotChangeAspectRatio;
const drawOnlyOnFirstAttachmentByDefault = options && options.drawOnlyOnFirstAttachmentByDefault ? options.drawOnlyOnFirstAttachmentByDefault : false;
super(name, size, scene, generateMipMaps, doNotChangeAspectRatio, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true);
if (!this.isSupported) {
this.dispose();
return;
}
this._textureNames = textureNames;
const types = [];
const samplingModes = [];
const useSRGBBuffers = [];
const formats = [];
const targetTypes = [];
const faceIndex = [];
const layerIndex = [];
const layerCounts = [];
this._initTypes(count, types, samplingModes, useSRGBBuffers, formats, targetTypes, faceIndex, layerIndex, layerCounts, options);
const generateDepthBuffer = !options || options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer;
const generateStencilBuffer = !options || options.generateStencilBuffer === undefined ? false : options.generateStencilBuffer;
const samples = options && options.samples ? options.samples : 1;
this._multiRenderTargetOptions = {
samplingModes: samplingModes,
generateMipMaps: generateMipMaps,
generateDepthBuffer: generateDepthBuffer,
generateStencilBuffer: generateStencilBuffer,
generateDepthTexture: generateDepthTexture,
depthTextureFormat: depthTextureFormat,
types: types,
textureCount: count,
useSRGBBuffers: useSRGBBuffers,
samples,
formats: formats,
targetTypes: targetTypes,
faceIndex: faceIndex,
layerIndex: layerIndex,
layerCounts: layerCounts,
labels: textureNames,
label: name,
};
this._count = count;
this._drawOnlyOnFirstAttachmentByDefault = drawOnlyOnFirstAttachmentByDefault;
if (count > 0) {
this._createInternalTextures();
this._createTextures(textureNames);
}
}
_initTypes(count, types, samplingModes, useSRGBBuffers, formats, targets, faceIndex, layerIndex, layerCounts, options) {
for (let i = 0; i < count; i++) {
if (options && options.types && options.types[i] !== undefined) {
types.push(options.types[i]);
}
else {
types.push(options && options.defaultType ? options.defaultType : Constants.TEXTURETYPE_UNSIGNED_BYTE);
}
if (options && options.samplingModes && options.samplingModes[i] !== undefined) {
samplingModes.push(options.samplingModes[i]);
}
else {
samplingModes.push(Texture.BILINEAR_SAMPLINGMODE);
}
if (options && options.useSRGBBuffers && options.useSRGBBuffers[i] !== undefined) {
useSRGBBuffers.push(options.useSRGBBuffers[i]);
}
else {
useSRGBBuffers.push(false);
}
if (options && options.formats && options.formats[i] !== undefined) {
formats.push(options.formats[i]);
}
else {
formats.push(Constants.TEXTUREFORMAT_RGBA);
}
if (options && options.targetTypes && options.targetTypes[i] !== undefined) {
targets.push(options.targetTypes[i]);
}
else {
targets.push(Constants.TEXTURE_2D);
}
if (options && options.faceIndex && options.faceIndex[i] !== undefined) {
faceIndex.push(options.faceIndex[i]);
}
else {
faceIndex.push(0);
}
if (options && options.layerIndex && options.layerIndex[i] !== undefined) {
layerIndex.push(options.layerIndex[i]);
}
else {
layerIndex.push(0);
}
if (options && options.layerCounts && options.layerCounts[i] !== undefined) {
layerCounts.push(options.layerCounts[i]);
}
else {
layerCounts.push(1);
}
}
}
_createInternaTextureIndexMapping() {
const mapMainInternalTexture2Index = {};
const mapInternalTexture2MainIndex = [];
if (!this._renderTarget) {
return mapInternalTexture2MainIndex;
}
const internalTextures = this._renderTarget.textures;
for (let i = 0; i < internalTextures.length; i++) {
const texture = internalTextures[i];
if (!texture) {
continue;
}
const mainIndex = mapMainInternalTexture2Index[texture.uniqueId];
if (mainIndex !== undefined) {
mapInternalTexture2MainIndex[i] = mainIndex;
}
else {
mapMainInternalTexture2Index[texture.uniqueId] = i;
}
}
return mapInternalTexture2MainIndex;
}
/**
* @internal
*/
_rebuild(fromContextLost = false, forceFullRebuild = false, textureNames) {
if (this._count < 1 || fromContextLost) {
return;
}
const mapInternalTexture2MainIndex = this._createInternaTextureIndexMapping();
this.releaseInternalTextures();
this._createInternalTextures();
if (forceFullRebuild) {
this._releaseTextures();
this._createTextures(textureNames);
}
const internalTextures = this._renderTarget.textures;
for (let i = 0; i < internalTextures.length; i++) {
const texture = this._textures[i];
if (mapInternalTexture2MainIndex[i] !== undefined) {
this._renderTarget.setTexture(internalTextures[mapInternalTexture2MainIndex[i]], i);
}
texture._texture = internalTextures[i];
if (texture._texture) {
texture._noMipmap = !texture._texture.useMipMaps;
texture._useSRGBBuffer = texture._texture._useSRGBBuffer;
}
}
if (this.samples !== 1) {
this._renderTarget.setSamples(this.samples, !this._drawOnlyOnFirstAttachmentByDefault, true);
}
}
_createInternalTextures() {
this._renderTarget = this._getEngine().createMultipleRenderTarget(this._size, this._multiRenderTargetOptions, !this._drawOnlyOnFirstAttachmentByDefault);
this._texture = this._renderTarget.texture;
}
_releaseTextures() {
if (this._textures) {
for (let i = 0; i < this._textures.length; i++) {
this._textures[i]._texture = null; // internal textures are released by a call to releaseInternalTextures()
this._textures[i].dispose();
}
}
}
_createTextures(textureNames) {
const internalTextures = this._renderTarget.textures;
this._textures = [];
for (let i = 0; i < internalTextures.length; i++) {
const texture = new Texture(null, this.getScene());
if (textureNames?.[i]) {
texture.name = textureNames[i];
}
texture._texture = internalTextures[i];
if (texture._texture) {
texture._noMipmap = !texture._texture.useMipMaps;
texture._useSRGBBuffer = texture._texture._useSRGBBuffer;
}
this._textures.push(texture);
}
}
/**
* Replaces an internal texture within the MRT. Useful to share textures between MultiRenderTarget.
* @param texture The new texture to set in the MRT
* @param index The index of the texture to replace
* @param disposePrevious Set to true if the previous internal texture should be disposed
*/
setInternalTexture(texture, index, disposePrevious = true) {
if (!this.renderTarget) {
return;
}
if (index === 0) {
this._texture = texture;
}
this.renderTarget.setTexture(texture, index, disposePrevious);
if (!this.textures[index]) {
this.textures[index] = new Texture(null, this.getScene());
this.textures[index].name = this._textureNames?.[index] ?? this.textures[index].name;
}
this.textures[index]._texture = texture;
this.textures[index]._noMipmap = !texture.useMipMaps;
this.textures[index]._useSRGBBuffer = texture._useSRGBBuffer;
this._count = this.renderTarget.textures ? this.renderTarget.textures.length : 0;
if (this._multiRenderTargetOptions.types) {
this._multiRenderTargetOptions.types[index] = texture.type;
}
if (this._multiRenderTargetOptions.samplingModes) {
this._multiRenderTargetOptions.samplingModes[index] = texture.samplingMode;
}
if (this._multiRenderTargetOptions.useSRGBBuffers) {
this._multiRenderTargetOptions.useSRGBBuffers[index] = texture._useSRGBBuffer;
}
if (this._multiRenderTargetOptions.targetTypes && this._multiRenderTargetOptions.targetTypes[index] !== -1) {
let target = 0;
if (texture.is2DArray) {
target = Constants.TEXTURE_2D_ARRAY;
}
else if (texture.isCube) {
target = Constants.TEXTURE_CUBE_MAP;
} /*else if (texture.isCubeArray) {
target = Constants.TEXTURE_CUBE_MAP_ARRAY;
}*/
else if (texture.is3D) {
target = Constants.TEXTURE_3D;
}
else {
target = Constants.TEXTURE_2D;
}
this._multiRenderTargetOptions.targetTypes[index] = target;
}
}
/**
* Changes an attached texture's face index or layer.
* @param index The index of the texture to modify the attachment of
* @param layerIndex The layer index of the texture to be attached to the framebuffer
* @param faceIndex The face index of the texture to be attached to the framebuffer
*/
setLayerAndFaceIndex(index, layerIndex = -1, faceIndex = -1) {
if (!this.textures[index] || !this.renderTarget) {
return;
}
if (this._multiRenderTargetOptions.layerIndex) {
this._multiRenderTargetOptions.layerIndex[index] = layerIndex;
}
if (this._multiRenderTargetOptions.faceIndex) {
this._multiRenderTargetOptions.faceIndex[index] = faceIndex;
}
this.renderTarget.setLayerAndFaceIndex(index, layerIndex, faceIndex);
}
/**
* Changes every attached texture's face index or layer.
* @param layerIndices The layer indices of the texture to be attached to the framebuffer
* @param faceIndices The face indices of the texture to be attached to the framebuffer
*/
setLayerAndFaceIndices(layerIndices, faceIndices) {
if (!this.renderTarget) {
return;
}
this._multiRenderTargetOptions.layerIndex = layerIndices;
this._multiRenderTargetOptions.faceIndex = faceIndices;
this.renderTarget.setLayerAndFaceIndices(layerIndices, faceIndices);
}
/**
* Define the number of samples used if MSAA is enabled.
*/
get samples() {
return this._samples;
}
set samples(value) {
if (this._renderTarget) {
this._samples = this._renderTarget.setSamples(value);
}
else {
// In case samples are set with 0 textures created, we must save the desired samples value
this._samples = value;
}
}
/**
* Resize all the textures in the multi render target.
* Be careful as it will recreate all the data in the new texture.
* @param size Define the new size
*/
resize(size) {
this._processSizeParameter(size);
this._rebuild(false, undefined, this._textureNames);
}
/**
* Changes the number of render targets in this MRT
* Be careful as it will recreate all the data in the new texture.
* @param count new texture count
* @param options Specifies texture types and sampling modes for new textures
* @param textureNames Specifies the names of the textures (optional)
*/
updateCount(count, options, textureNames) {
this._multiRenderTargetOptions.textureCount = count;
this._count = count;
const types = [];
const samplingModes = [];
const useSRGBBuffers = [];
const formats = [];
const targetTypes = [];
const faceIndex = [];
const layerIndex = [];
const layerCounts = [];
this._textureNames = textureNames;
this._initTypes(count, types, samplingModes, useSRGBBuffers, formats, targetTypes, faceIndex, layerIndex, layerCounts, options);
this._multiRenderTargetOptions.types = types;
this._multiRenderTargetOptions.samplingModes = samplingModes;
this._multiRenderTargetOptions.useSRGBBuffers = useSRGBBuffers;
this._multiRenderTargetOptions.formats = formats;
this._multiRenderTargetOptions.targetTypes = targetTypes;
this._multiRenderTargetOptions.faceIndex = faceIndex;
this._multiRenderTargetOptions.layerIndex = layerIndex;
this._multiRenderTargetOptions.layerCounts = layerCounts;
this._multiRenderTargetOptions.labels = textureNames;
this._rebuild(false, true, textureNames);
}
_unbindFrameBuffer(engine, faceIndex) {
if (this._renderTarget) {
engine.unBindMultiColorAttachmentFramebuffer(this._renderTarget, this.isCube, () => {
this.onAfterRenderObservable.notifyObservers(faceIndex);
});
}
}
/**
* Dispose the render targets and their associated resources
* @param doNotDisposeInternalTextures if set to true, internal textures won't be disposed (default: false).
*/
dispose(doNotDisposeInternalTextures = false) {
this._releaseTextures();
if (!doNotDisposeInternalTextures) {
this.releaseInternalTextures();
}
else {
// Prevent internal texture dispose in super.dispose
this._texture = null;
}
super.dispose();
}
/**
* Release all the underlying texture used as draw buffers (render textures).
*/
releaseInternalTextures() {
const internalTextures = this._renderTarget?.textures;
if (!internalTextures) {
return;
}
for (let i = internalTextures.length - 1; i >= 0; i--) {
this._textures[i]._texture = null;
}
this._renderTarget?.dispose();
this._renderTarget = null;
}
}
/**
* Voxel-based shadow rendering for IBL's.
* This should not be instanciated directly, as it is part of a scene component
* @internal
* @see https://playground.babylonjs.com/#8R5SSE#222
*/
class _IblShadowsVoxelRenderer {
/**
* Return the voxel grid texture.
* @returns The voxel grid texture.
*/
getVoxelGrid() {
if (this._triPlanarVoxelization) {
return this._voxelGridRT;
}
else {
return this._voxelGridZaxis;
}
}
/**
* The debug pass post process
* @returns The debug pass post process
*/
getDebugPassPP() {
if (!this._voxelDebugPass) {
this._createDebugPass();
}
return this._voxelDebugPass;
}
/**
* Whether to use tri-planar voxelization. More expensive, but can help with artifacts.
*/
get triPlanarVoxelization() {
return this._triPlanarVoxelization;
}
/**
* Whether to use tri-planar voxelization. More expensive, but can help with artifacts.
*/
set triPlanarVoxelization(enabled) {
if (this._triPlanarVoxelization === enabled) {
return;
}
this._triPlanarVoxelization = enabled;
this._disposeVoxelTextures();
this._createTextures();
}
/**
* Set the matrix to use for scaling the world space to voxel space
* @param matrix The matrix to use for scaling the world space to voxel space
*/
setWorldScaleMatrix(matrix) {
this._invWorldScaleMatrix = matrix;
}
/**
* @returns Whether voxelization is currently happening.
*/
isVoxelizationInProgress() {
return this._voxelizationInProgress;
}
/**
* Resolution of the voxel grid. The final resolution will be 2^resolutionExp.
*/
get voxelResolutionExp() {
return this._voxelResolutionExp;
}
/**
* Resolution of the voxel grid. The final resolution will be 2^resolutionExp.
*/
set voxelResolutionExp(resolutionExp) {
if (this._voxelResolutionExp === resolutionExp && this._voxelGridZaxis) {
return;
}
this._voxelResolutionExp = Math.round(Math.min(Math.max(resolutionExp, 3), 9));
this._voxelResolution = Math.pow(2.0, this._voxelResolutionExp);
this._disposeVoxelTextures();
this._createTextures();
}
/**
* Shows only the voxels that were rendered along a particular axis (while using triPlanarVoxelization).
* If not set, the combined voxel grid will be shown.
* Note: This only works when the debugMipNumber is set to 0 because we don't generate mips for each axis.
* @param axis The axis to show (0 = x, 1 = y, 2 = z)
*/
set voxelDebugAxis(axis) {
this._voxelDebugAxis = axis;
}
get voxelDebugAxis() {
return this._voxelDebugAxis;
}
/**
* 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 mip level to show in the debug display
* @param mipNum The mip level to show in the debug display
*/
setDebugMipNumber(mipNum) {
this._debugMipNumber = mipNum;
}
/**
* Sets the name of the debug pass
*/
get debugPassName() {
return this._debugPassName;
}
/**
* Enable or disable the debug view for this pass
*/
get voxelDebugEnabled() {
return this._voxelDebugEnabled;
}
set voxelDebugEnabled(enabled) {
if (this._voxelDebugEnabled === enabled) {
return;
}
this._voxelDebugEnabled = enabled;
if (enabled) {
this._voxelSlabDebugRT = new RenderTargetTexture("voxelSlabDebug", { width: this._engine.getRenderWidth(), height: this._engine.getRenderHeight() }, this._scene, {
generateDepthBuffer: true,
generateMipMaps: false,
type: Constants.TEXTURETYPE_UNSIGNED_BYTE,
format: Constants.TEXTUREFORMAT_RGBA,
samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,
});
this._voxelSlabDebugRT.noPrePassRenderer = true;
}
if (this._voxelSlabDebugRT) {
this._removeVoxelRTs([this._voxelSlabDebugRT]);
}
// Add the slab debug RT if needed.
if (this._voxelDebugEnabled) {
this._addRTsForRender([this._voxelSlabDebugRT], this._includedMeshes, this._voxelDebugAxis, 1, true);
this._setDebugBindingsBound = this._setDebugBindings.bind(this);
this._scene.onBeforeRenderObservable.add(this._setDebugBindingsBound);
}
else {
this._scene.onBeforeRenderObservable.removeCallback(this._setDebugBindingsBound);
}
}
/**
* Creates the debug post process effect for this pass
*/
_createDebugPass() {
const isWebGPU = this._engine.isWebGPU;
if (!this._voxelDebugPass) {
const debugOptions = {
width: this._engine.getRenderWidth(),
height: this._engine.getRenderHeight(),
textureFormat: Constants.TEXTUREFORMAT_RGBA,
textureType: Constants.TEXTURETYPE_UNSIGNED_BYTE,
samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,
uniforms: ["sizeParams", "mipNumber"],
samplers: ["voxelTexture", "voxelSlabTexture"],
engine: this._engine,
reusable: false,
shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */,
extraInitializations: (useWebGPU, list) => {
if (this._isVoxelGrid3D) {
if (useWebGPU) {
list.push(import('./iblVoxelGrid3dDebug.fragment-BVx-tQk5.esm.js'));
}
else {
list.push(import('./iblVoxelGrid3dDebug.fragment-D1RADqwQ.esm.js'));
}
return;
}
if (useWebGPU) {
list.push(import('./iblVoxelGrid2dArrayDebug.fragment-B7bP3JYg.esm.js'));
}
else {
list.push(import('./iblVoxelGrid2dArrayDebug.fragment-DqJ6_ih8.esm.js'));
}
},
};
this._voxelDebugPass = new PostProcess(this.debugPassName, this._isVoxelGrid3D ? "iblVoxelGrid3dDebug" : "iblVoxelGrid2dArrayDebug", debugOptions);
this._voxelDebugPass.onApplyObservable.add((effect) => {
if (this._voxelDebugAxis === 0) {
effect.setTexture("voxelTexture", this._voxelGridXaxis);
}
else if (this._voxelDebugAxis === 1) {
effect.setTexture("voxelTexture", this._voxelGridYaxis);
}
else if (this._voxelDebugAxis === 2) {
effect.setTexture("voxelTexture", this._voxelGridZaxis);
}
else {
effect.setTexture("voxelTexture", this.getVoxelGrid());
}
effect.setTexture("voxelSlabTexture", this._voxelSlabDebugRT);
effect.setVector4("sizeParams", this._debugSizeParams);
effect.setFloat("mipNumber", this._debugMipNumber);
});
}
}
/**
* Instanciates the voxel renderer
* @param scene Scene to attach to
* @param iblShadowsRenderPipeline The render pipeline this pass is associated with
* @param resolutionExp Resolution of the voxel grid. The final resolution will be 2^resolutionExp.
* @param triPlanarVoxelization Whether to use tri-planar voxelization. More expensive, but can help with artifacts.
* @returns The voxel renderer
*/
constructor(scene, iblShadowsRenderPipeline, resolutionExp = 6, triPlanarVoxelization = true) {
this._voxelMrtsXaxis = [];
this._voxelMrtsYaxis = [];
this._voxelMrtsZaxis = [];
this._isVoxelGrid3D = true;
/**
* Observable that triggers when the voxelization is complete
*/
this.onVoxelizationCompleteObservable = new Observable();
this._renderTargets = [];
this._triPlanarVoxelization = true;
this._voxelizationInProgress = false;
this._invWorldScaleMatrix = Matrix.Identity();
this._voxelResolution = 64;
this._voxelResolutionExp = 6;
this._mipArray = [];
this._voxelDebugEnabled = false;
this._voxelDebugAxis = -1;
this._debugSizeParams = new Vector4(0.0, 0.0, 0.0, 0.0);
this._includedMeshes = [];
this._debugMipNumber = 0;
this._debugPassName = "Voxelization Debug Pass";
this._scene = scene;
this._engine = scene.getEngine();
this._triPlanarVoxelization = triPlanarVoxelization;
if (!this._engine.getCaps().drawBuffersExtension) {
Logger.Error("Can't do voxel rendering without the draw buffers extension.");
}
const isWebGPU = this._engine.isWebGPU;
this._maxDrawBuffers = this._engine.getCaps().maxDrawBuffers || 0;
this._copyMipEffectRenderer = new EffectRenderer(this._engine);
this._copyMipEffectWrapper = new EffectWrapper({
engine: this._engine,
fragmentShader: "copyTexture3DLayerToTexture",
useShaderStore: true,
uniformNames: ["layerNum"],
samplerNames: ["textureSampler"],
shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */,
extraInitializationsAsync: async () => {
if (isWebGPU) {
await import('./copyTexture3DLayerToTexture.fragment-CXt1seoX.esm.js');
}
else {
await import('./copyTexture3DLayerToTexture.fragment-C7LNUal1.esm.js');
}
},
});
this.voxelResolutionExp = resolutionExp;
}
_generateMipMaps() {
const iterations = Math.ceil(Math.log2(this._voxelResolution));
for (let i = 1; i < iterations + 1; i++) {
this._generateMipMap(i);
}
}
_generateMipMap(lodLevel) {
// Generate a mip map for the given level by triggering the render of the procedural mip texture.
const mipTarget = this._mipArray[lodLevel - 1];
if (!mipTarget) {
return;
}
mipTarget.setTexture("srcMip", lodLevel === 1 ? this.getVoxelGrid() : this._mipArray[lodLevel - 2]);
mipTarget.render();
}
_copyMipMaps() {
const iterations = Math.ceil(Math.log2(this._voxelResolution));
for (let i = 1; i < iterations + 1; i++) {
this._copyMipMap(i);
}
}
_copyMipMap(lodLevel) {
// Now, copy this mip into the mip chain of the voxel grid.
// TODO - this currently isn't working. "textureSampler" isn't being properly set to mipTarget.
const mipTarget = this._mipArray[lodLevel - 1];
if (!mipTarget) {
return;
}
const voxelGrid = this.getVoxelGrid();
let rt;
if (voxelGrid instanceof RenderTargetTexture && voxelGrid.renderTarget) {
rt = voxelGrid.renderTarget;
}
else {
rt = voxelGrid._rtWrapper;
}
if (rt) {
this._copyMipEffectRenderer.saveStates();
const bindSize = mipTarget.getSize().width;
// Render to each layer of the voxel grid.
for (let layer = 0; layer < bindSize; layer++) {
this._engine.bindFramebuffer(rt, 0, bindSize, bindSize, true, lodLevel, layer);
this._copyMipEffectRenderer.applyEffectWrapper(this._copyMipEffectWrapper);
this._copyMipEffectWrapper.effect.setTexture("textureSampler", mipTarget);
this._copyMipEffectWrapper.effect.setInt("layerNum", layer);
this._copyMipEffectRenderer.draw();
this._engine.unBindFramebuffer(rt, true);
}
this._copyMipEffectRenderer.restoreStates();
}
}
_computeNumberOfSlabs() {
return Math.ceil(this._voxelResolution / this._maxDrawBuffers);
}
_createTextures() {
const isWebGPU = this._engine.isWebGPU;
const size = {
width: this._voxelResolution,
height: this._voxelResolution,
layers: this._isVoxelGrid3D ? undefined : this._voxelResolution,
depth: this._isVoxelGrid3D ? this._voxelResolution : undefined,
};
const voxelAxisOptions = {
generateDepthBuffer: false,
generateMipMaps: false,
type: Constants.TEXTURETYPE_UNSIGNED_BYTE,
format: Constants.TEXTUREFORMAT_R,
samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,
};
// We can render up to maxDrawBuffers voxel slices of the grid per render.
// We call this a slab.
const numSlabs = this._computeNumberOfSlabs();
const voxelCombinedOptions = {
generateDepthBuffer: false,
generateMipMaps: true,
type: Constants.TEXTURETYPE_UNSIGNED_BYTE,
format: Constants.TEXTUREFORMAT_R,
samplingMode: Constants.TEXTURE_NEAREST_NEAREST_MIPNEAREST,
shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */,
extraInitializationsAsync: async () => {
if (isWebGPU) {
await import('./iblCombineVoxelGrids.fragment-mTCcwBrp.esm.js');
}
else {
await import('./iblCombineVoxelGrids.fragment-DLzGXvyG.esm.js');
}
},
};
if (this._triPlanarVoxelization) {
this._voxelGridXaxis = new RenderTargetTexture("voxelGridXaxis", size, this._scene, voxelAxisOptions);
this._voxelGridYaxis = new RenderTargetTexture("voxelGridYaxis", size, this._scene, voxelAxisOptions);
this._voxelGridZaxis = new RenderTargetTexture("voxelGridZaxis", size, this._scene, voxelAxisOptions);
this._voxelMrtsXaxis = this._createVoxelMRTs("x_axis_", this._voxelGridXaxis, numSlabs);
this._voxelMrtsYaxis = this._createVoxelMRTs("y_axis_", this._voxelGridYaxis, numSlabs);
this._voxelMrtsZaxis = this._createVoxelMRTs("z_axis_", this._voxelGridZaxis, numSlabs);
this._voxelGridRT = new ProceduralTexture("combinedVoxelGrid", size, "iblCombineVoxelGrids", this._scene, voxelCombinedOptions, false);
this._scene.proceduralTextures.splice(this._scene.proceduralTextures.indexOf(this._voxelGridRT), 1);
this._voxelGridRT.setFloat("layer", 0.0);
this._voxelGridRT.setTexture("voxelXaxisSampler", this._voxelGridXaxis);
this._voxelGridRT.setTexture("voxelYaxisSampler", this._voxelGridYaxis);
this._voxelGridRT.setTexture("voxelZaxisSampler", this._voxelGridZaxis);
// We will render this only after voxelization is completed for the 3 axes.
this._voxelGridRT.autoClear = false;
this._voxelGridRT.wrapU = Texture.CLAMP_ADDRESSMODE;
this._voxelGridRT.wrapV = Texture.CLAMP_ADDRESSMODE;
}
else {
this._voxelGridZaxis = new RenderTargetTexture("voxelGridZaxis", size, this._scene, voxelCombinedOptions);
this._voxelMrtsZaxis = this._createVoxelMRTs("z_axis_", this._voxelGridZaxis, numSlabs);
}
const generateVoxelMipOptions = {
generateDepthBuffer: false,
generateMipMaps: false,
type: Constants.TEXTURETYPE_UNSIGNED_BYTE,
format: Constants.TEXTUREFORMAT_R,
samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,
shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */,
extraInitializationsAsync: async () => {
if (isWebGPU) {
await import('./iblGenerateVoxelMip.fragment-BpQfiWGP.esm.js');
}
else {
await import('./iblGenerateVoxelMip.fragment-cq-mKZHq.esm.js');
}
},
};
this._mipArray = new Array(Math.ceil(Math.log2(this._voxelResolution)));
for (let mipIdx = 1; mipIdx <= this._mipArray.length; mipIdx++) {
const mipDim = this._voxelResolution >> mipIdx;
const mipSize = { width: mipDim, height: mipDim, depth: mipDim };
this._mipArray[mipIdx - 1] = new ProceduralTexture("voxelMip" + mipIdx, mipSize, "iblGenerateVoxelMip", this._scene, generateVoxelMipOptions, false);
this._scene.proceduralTextures.splice(this._scene.proceduralTextures.indexOf(this._mipArray[mipIdx - 1]), 1);
const mipTarget = this._mipArray[mipIdx - 1];
mipTarget.autoClear = false;
mipTarget.wrapU = Texture.CLAMP_ADDRESSMODE;
mipTarget.wrapV = Texture.CLAMP_ADDRESSMODE;
mipTarget.setTexture("srcMip", mipIdx > 1 ? this._mipArray[mipIdx - 2] : this.getVoxelGrid());
mipTarget.setInt("layerNum", 0);
}
this._createVoxelMaterials();
}
_createVoxelMRTs(name, voxelRT, numSlabs) {
voxelRT.wrapU = Texture.CLAMP_ADDRESSMODE;
voxelRT.wrapV = Texture.CLAMP_ADDRESSMODE;
voxelRT.noPrePassRenderer = true;
const mrtArray = [];
const targetTypes = new Array(this._maxDrawBuffers).fill(this._isVoxelGrid3D ? Constants.TEXTURE_3D : Constants.TEXTURE_2D_ARRAY);
for (let mrtIndex = 0; mrtIndex < numSlabs; mrtIndex++) {
let layerIndices = new Array(this._maxDrawBuffers).fill(0);
layerIndices = layerIndices.map((value, index) => mrtIndex * this._maxDrawBuffers + index);
let textureNames = new Array(this._maxDrawBuffers).fill("");
textureNames = textureNames.map((value, index) => "voxel_grid_" + name + (mrtIndex * this._maxDrawBuffers + index));
const mrt = new MultiRenderTarget("mrt_" + name + mrtIndex, { width: this._voxelResolution, height: this._voxelResolution, depth: this._isVoxelGrid3D ? this._voxelResolution : undefined }, this._maxDrawBuffers, // number of draw buffers
this._scene, {
types: new Array(this._maxDrawBuffers).fill(Constants.TEXTURETYPE_UNSIGNED_BYTE),
samplingModes: new Array(this._maxDrawBuffers).fill(Constants.TEXTURE_TRILINEAR_SAMPLINGMODE),
generateMipMaps: false,
targetTypes,
formats: new Array(this._maxDrawBuffers).fill(Constants.TEXTUREFORMAT_R),
faceIndex: new Array(this._maxDrawBuffers).fill(0),
layerIndex: layerIndices,
layerCounts: new Array(this._maxDrawBuffers).fill(this._voxelResolution),
generateDepthBuffer: false,
generateStencilBuffer: false,
}, textureNames);
mrt.clearColor = new Color4(0, 0, 0, 1);
mrt.noPrePassRenderer = true;
for (let i = 0; i < this._maxDrawBuffers; i++) {
mrt.setInternalTexture(voxelRT.getInternalTexture(), i);
}
mrtArray.push(mrt);
}
return mrtArray;
}
_disposeVoxelTextures() {
this._stopVoxelization();
for (let i = 0; i < this._voxelMrtsZaxis.length; i++) {
if (this._triPlanarVoxelization) {
this._voxelMrtsXaxis[i].dispose(true);
this._voxelMrtsYaxis[i].dispose(true);
}
this._voxelMrtsZaxis[i].dispose(true);
}
if (this._triPlanarVoxelization) {
this._voxelGridXaxis?.dispose();
this._voxelGridYaxis?.dispose();
this._voxelGridRT?.dispose();
}
this._voxelGridZaxis?.dispose();
for (const mip of this._mipArray) {
mip.dispose();
}
this._voxelMaterial?.dispose();
this._voxelSlabDebugMaterial?.dispose();
this._mipArray = [];
this._voxelMrtsXaxis = [];
this._voxelMrtsYaxis = [];
this._voxelMrtsZaxis = [];
}
_createVoxelMaterials() {
const isWebGPU = this._engine.isWebGPU;
this._voxelMaterial = new ShaderMaterial("voxelization", this._scene, "iblVoxelGrid", {
uniforms: ["world", "viewMatrix", "invWorldScale", "nearPlane", "farPlane", "stepSize"],
defines: ["MAX_DRAW_BUFFERS " + this._maxDrawBuffers],
shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */,
extraInitializationsAsync: async () => {
if (isWebGPU) {
await Promise.all([import('./iblVoxelGrid.fragment-B8PkAbQL.esm.js'), import('./iblVoxelGrid.vertex-BJktRmGp.esm.js')]);
}
else {
await Promise.all([import('./iblVoxelGrid.fragment-6jQ-b5NZ.esm.js'), import('./iblVoxelGrid.vertex-DKwAHkCJ.esm.js')]);
}
},
});
this._voxelMaterial.cullBackFaces = false;
this._voxelMaterial.backFaceCulling = false;
this._voxelMaterial.depthFunction = Engine.ALWAYS;
this._voxelSlabDebugMaterial = new ShaderMaterial("voxelSlabDebug", this._scene, "iblVoxelSlabDebug", {
uniforms: ["world", "viewMatrix", "cameraViewMatrix", "projection", "invWorldScale", "nearPlane", "farPlane", "stepSize"],
defines: ["MAX_DRAW_BUFFERS " + this._maxDrawBuffers],
shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */,
extraInitializationsAsync: async () => {
if (isWebGPU) {
await Promise.all([import('./iblVoxelSlabDebug.fragment-CaEe7_dA.esm.js'), import('./iblVoxelSlabDebug.vertex-CfVVUMNf.esm.js')]);
}
else {
await Promise.all([import('./iblVoxelSlabDebug.fragment-DAe6_iT0.esm.js'), import('./iblVoxelSlabDebug.vertex-cmrQi3Vb.esm.js')]);
}
},
});
}
_setDebugBindings() {
this._voxelSlabDebugMaterial.setMatrix("projection", this._scene.activeCamera.getProjectionMatrix());
this._voxelSlabDebugMaterial.setMatrix("cameraViewMatrix", this._scene.activeCamera.getViewMatrix());
}
/**
* Checks if the voxel renderer is ready to voxelize scene
* @returns true if the voxel renderer is ready to voxelize scene
*/
isReady() {
let allReady = this.getVoxelGrid().isReady();
for (let i = 0; i < this._mipArray.length; i++) {
const mipReady = this._mipArray[i].isReady();
allReady &&= mipReady;
}
if (!allReady || this._voxelizationInProgress) {
return false;
}
return true;
}
/**
* If the MRT's are already in the list of render targets, this will
* remove them so that they don't get rendered again.
*/
_stopVoxelization() {
// If the MRT's are already in the list of render targets, remove them.
this._removeVoxelRTs(this._voxelMrtsXaxis);
this._removeVoxelRTs(this._voxelMrtsYaxis);
this._removeVoxelRTs(this._voxelMrtsZaxis);
}
_removeVoxelRTs(rts) {
// const currentRTs = this._scene.customRenderTargets;
const rtIdx = this._renderTargets.findIndex((rt) => {
if (rt === rts[0]) {
return true;
}
return false;
});
if (rtIdx >= 0) {
this._renderTargets.splice(rtIdx, rts.length);
}
else {
const rtIdx = this._scene.customRenderTargets.findIndex((rt) => {
if (rt === rts[0]) {
return true;
}
return false;
});
if (rtIdx >= 0) {
this._scene.customRenderTargets.splice(rtIdx, rts.length);
}
}
}
/**
* Renders voxel grid of scene for IBL shadows
* @param includedMeshes
*/
updateVoxelGrid(includedMeshes) {
this._stopVoxelization();
this._includedMeshes = includedMeshes;
this._voxelizationInProgress = true;
if (this._triPlanarVoxelization) {
this._addRTsForRender(this._voxelMrtsXaxis, includedMeshes, 0);
this._addRTsForRender(this._voxelMrtsYaxis, includedMeshes, 1);
this._addRTsForRender(this._voxelMrtsZaxis, includedMeshes, 2);
}
else {
this._addRTsForRender(this._voxelMrtsZaxis, includedMeshes, 2);
}
if (this._voxelDebugEnabled) {
this._addRTsForRender([this._voxelSlabDebugRT], includedMeshes, this._voxelDebugAxis, 1, true);
}
this._renderVoxelGridBound = this._renderVoxelGrid.bind(this);
this._scene.onAfterRenderObservable.add(this._renderVoxelGridBound);
}
_renderVoxelGrid() {
if (this._voxelizationInProgress) {
let allReady = this.getVoxelGrid().isReady();
for (let i = 0; i < this._mipArray.length; i++) {
const mipReady = this._mipArray[i].isReady();
allReady &&= mipReady;
}
for (let i = 0; i < this._renderTargets.length; i++) {
const rttReady = this._renderTargets[i].isReadyForRendering();
allReady &&= rttReady;
}
if (allReady) {
for (const rt of this._renderTargets) {
rt.render();
}
this._stopVoxelization();
if (this._triPlanarVoxelization) {
this._voxelGridRT.render();
}
this._generateMipMaps();
// eslint-disable-next-line @typescript-eslint/no-floating-promises, github/no-then
this._copyMipEffectWrapper.effect.whenCompiledAsync().then(() => {
this._copyMipMaps();
this._scene.onAfterRenderObservable.removeCallback(this._renderVoxelGridBound);
this._voxelizationInProgress = false;
this.onVoxelizationCompleteObservable.notifyObservers();
});
}
}
}
_addRTsForRender(mrts, includedMeshes, axis, shaderType = 0, continuousRender = false) {
const slabSize = 1.0 / this._computeNumberOfSlabs();
let voxelMaterial;
if (shaderType === 0) {
voxelMaterial = this._voxelMaterial;
}
else {
voxelMaterial = this._voxelSlabDebugMaterial;
}
// We need to update the world scale uniform for every mesh being rendered to the voxel grid.
for (let mrtIndex = 0; mrtIndex < mrts.length; mrtIndex++) {
const mrt = mrts[mrtIndex];
mrt.renderList = [];
const nearPlane = mrtIndex * slabSize;
const farPlane = (mrtIndex + 1) * slabSize;
const stepSize = slabSize / this._maxDrawBuffers;
const cameraPosition = new Vector3(0, 0, 0);
let targetPosition = new Vector3(0, 0, 1);
if (axis === 0) {
targetPosition = new Vector3(1, 0, 0);
}
else if (axis === 1) {
targetPosition = new Vector3(0, 1, 0);
}
let upDirection = new Vector3(0, 1, 0);
if (axis === 1) {
upDirection = new Vector3(1, 0, 0);
}
mrt.onBeforeRenderObservable.add(() => {
voxelMaterial.setMatrix("viewMatrix", Matrix.LookAtLH(cameraPosition, targetPosition, upDirection));
voxelMaterial.setMatrix("invWorldScale", this._invWorldScaleMatrix);
voxelMaterial.setFloat("nearPlane", nearPlane);
voxelMaterial.setFloat("farPlane", farPlane);
voxelMaterial.setFloat("stepSize", stepSize);
});
// Set this material on every mesh in the scene (for this RT)
if (includedMeshes.length === 0) {
return;
}
for (const mesh of includedMeshes) {
if (mesh) {
if (mesh.subMeshes && mesh.subMeshes.length > 0) {
mrt.renderList?.push(mesh);
mrt.setMaterialForRendering(mesh, voxelMaterial);
}
const meshes = mesh.getChildMeshes();
for (const childMesh of meshes) {
if (childMesh.subMeshes && childMesh.subMeshes.length > 0) {
mrt.renderList?.push(childMesh);
mrt.setMaterialForRendering(childMesh, voxelMaterial);
}
}
}
}
}
// Add the MRT's to render.
if (continuousRender) {
for (const mrt of mrts) {
if (this._scene.customRenderTargets.indexOf(mrt) === -1) {
this._scene.customRenderTargets.push(mrt);
}
}
}
else {
this._renderTargets = this._renderTargets.concat(mrts);
}
}
/**
* Called by the pipeline to resize resources.
*/
resize() {
this._voxelSlabDebugRT?.resize({ width: this._scene.getEngine().getRenderWidth(), height: this._scene.getEngine().getRenderHeight() });
}
/**
* Disposes the voxel renderer and associated resources
*/
dispose() {
this._disposeVoxelTextures();
if