@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,131 lines (1,117 loc) • 93.7 kB
JavaScript
import { ShadowGenerator } from './shadowGenerator-Dfi5uzM_.esm.js';
import { S as ShaderStore, C as Constants, T as Texture, j as Color4, h as RenderTargetTexture, m as VertexBuffer, $ as EffectFallbacks, n as PrepareDefinesAndAttributesForMorphTargets, o as PushAttributesForInstances, p as PrepareStringDefinesForClipPlanes, A as AddClipPlaneUniforms, a2 as Camera, U as BindBonesParameters, r as BindClipPlane, s as BindMorphTargetParameters, _ as _WarnImport, E as EffectWrapper, k as Engine, O as Observable, a3 as PostProcessManager, P as PostProcess, V as Vector3, M as Matrix, L as Logger, H as EngineStore, a4 as BoundingInfo, G as SceneComponentConstants } from './index-FzOfPXLV.esm.js';
import './clipPlaneFragment-OFOZXtyS.esm.js';
import './packingFunctions-C2Dsz6z6.esm.js';
import './bakedVertexAnimation-D5Wmzm8F.esm.js';
import './morphTargetsVertex-Bimm08Fe.esm.js';
import './clipPlaneVertex-wC8bw87F.esm.js';
import './instancesDeclaration-D6f54GuO.esm.js';
import './pointCloudVertex-DNFirpKZ.esm.js';
// Do not edit.
const name$4 = "depthPixelShader";
const shader$4 = `#ifdef ALPHATEST
varying vec2 vUV;uniform sampler2D diffuseSampler;
#endif
#include<clipPlaneFragmentDeclaration>
varying float vDepthMetric;
#ifdef PACKED
#include<packingFunctions>
#endif
#ifdef STORE_CAMERASPACE_Z
varying vec4 vViewPos;
#endif
#define CUSTOM_FRAGMENT_DEFINITIONS
void main(void)
{
#include<clipPlaneFragment>
#ifdef ALPHATEST
if (texture2D(diffuseSampler,vUV).a<0.4)
discard;
#endif
#ifdef STORE_CAMERASPACE_Z
#ifdef PACKED
gl_FragColor=pack(vViewPos.z);
#else
gl_FragColor=vec4(vViewPos.z,0.0,0.0,1.0);
#endif
#else
#ifdef NONLINEARDEPTH
#ifdef PACKED
gl_FragColor=pack(gl_FragCoord.z);
#else
gl_FragColor=vec4(gl_FragCoord.z,0.0,0.0,0.0);
#endif
#else
#ifdef PACKED
gl_FragColor=pack(vDepthMetric);
#else
gl_FragColor=vec4(vDepthMetric,0.0,0.0,1.0);
#endif
#endif
#endif
}`;
// Sideeffect
if (!ShaderStore.ShadersStore[name$4]) {
ShaderStore.ShadersStore[name$4] = shader$4;
}
/** @internal */
const depthPixelShader = { name: name$4, shader: shader$4 };
var depth_fragment = /*#__PURE__*/Object.freeze({
__proto__: null,
depthPixelShader: depthPixelShader
});
// Do not edit.
const name$3 = "pointCloudVertexDeclaration";
const shader$3 = `#ifdef POINTSIZE
uniform float pointSize;
#endif
`;
// Sideeffect
if (!ShaderStore.IncludesShadersStore[name$3]) {
ShaderStore.IncludesShadersStore[name$3] = shader$3;
}
// Do not edit.
const name$2 = "depthVertexShader";
const shader$2 = `attribute vec3 position;
#include<bonesDeclaration>
#include<bakedVertexAnimationDeclaration>
#include<morphTargetsVertexGlobalDeclaration>
#include<morphTargetsVertexDeclaration>[0..maxSimultaneousMorphTargets]
#include<clipPlaneVertexDeclaration>
#include<instancesDeclaration>
uniform mat4 viewProjection;uniform vec2 depthValues;
#if defined(ALPHATEST) || defined(NEED_UV)
varying vec2 vUV;uniform mat4 diffuseMatrix;
#ifdef UV1
attribute vec2 uv;
#endif
#ifdef UV2
attribute vec2 uv2;
#endif
#endif
#ifdef STORE_CAMERASPACE_Z
uniform mat4 view;varying vec4 vViewPos;
#endif
#include<pointCloudVertexDeclaration>
varying float vDepthMetric;
#define CUSTOM_VERTEX_DEFINITIONS
void main(void)
{vec3 positionUpdated=position;
#ifdef UV1
vec2 uvUpdated=uv;
#endif
#ifdef UV2
vec2 uv2Updated=uv2;
#endif
#include<morphTargetsVertexGlobal>
#include<morphTargetsVertex>[0..maxSimultaneousMorphTargets]
#include<instancesVertex>
#include<bonesVertex>
#include<bakedVertexAnimation>
vec4 worldPos=finalWorld*vec4(positionUpdated,1.0);
#include<clipPlaneVertex>
gl_Position=viewProjection*worldPos;
#ifdef STORE_CAMERASPACE_Z
vViewPos=view*worldPos;
#else
#ifdef USE_REVERSE_DEPTHBUFFER
vDepthMetric=((-gl_Position.z+depthValues.x)/(depthValues.y));
#else
vDepthMetric=((gl_Position.z+depthValues.x)/(depthValues.y));
#endif
#endif
#if defined(ALPHATEST) || defined(BASIC_RENDER)
#ifdef UV1
vUV=vec2(diffuseMatrix*vec4(uvUpdated,1.0,0.0));
#endif
#ifdef UV2
vUV=vec2(diffuseMatrix*vec4(uv2Updated,1.0,0.0));
#endif
#endif
#include<pointCloudVertex>
}
`;
// Sideeffect
if (!ShaderStore.ShadersStore[name$2]) {
ShaderStore.ShadersStore[name$2] = shader$2;
}
/** @internal */
const depthVertexShader = { name: name$2, shader: shader$2 };
var depth_vertex = /*#__PURE__*/Object.freeze({
__proto__: null,
depthVertexShader: depthVertexShader
});
/**
* This represents a depth renderer in Babylon.
* A depth renderer will render to it's depth map every frame which can be displayed or used in post processing
*/
class DepthRenderer {
/**
* Gets the shader language used in this material.
*/
get shaderLanguage() {
return this._shaderLanguage;
}
/**
* Sets a specific material to be used to render a mesh/a list of meshes by the depth renderer
* @param mesh mesh or array of meshes
* @param material material to use by the depth render when rendering the mesh(es). If undefined is passed, the specific material created by the depth renderer will be used.
*/
setMaterialForRendering(mesh, material) {
this._depthMap.setMaterialForRendering(mesh, material);
}
/**
* Instantiates a depth renderer
* @param scene The scene the renderer belongs to
* @param type The texture type of the depth map (default: Engine.TEXTURETYPE_FLOAT)
* @param camera The camera to be used to render the depth map (default: scene's active camera)
* @param storeNonLinearDepth Defines whether the depth is stored linearly like in Babylon Shadows or directly like glFragCoord.z
* @param samplingMode The sampling mode to be used with the render target (Linear, Nearest...) (default: TRILINEAR_SAMPLINGMODE)
* @param storeCameraSpaceZ Defines whether the depth stored is the Z coordinate in camera space. If true, storeNonLinearDepth has no effect. (Default: false)
* @param name Name of the render target (default: DepthRenderer)
*/
constructor(scene, type = Constants.TEXTURETYPE_FLOAT, camera = null, storeNonLinearDepth = false, samplingMode = Texture.TRILINEAR_SAMPLINGMODE, storeCameraSpaceZ = false, name) {
/** Shader language used by the material */
this._shaderLanguage = 0 /* ShaderLanguage.GLSL */;
/** Enable or disable the depth renderer. When disabled, the depth texture is not updated */
this.enabled = true;
/** Force writing the transparent objects into the depth map */
this.forceDepthWriteTransparentMeshes = false;
/**
* Specifies that the depth renderer will only be used within
* the camera it is created for.
* This can help forcing its rendering during the camera processing.
*/
this.useOnlyInActiveCamera = false;
/** If true, reverse the culling of materials before writing to the depth texture.
* So, basically, when "true", back facing instead of front facing faces are rasterized into the texture
*/
this.reverseCulling = false;
this._shadersLoaded = false;
this._scene = scene;
this._storeNonLinearDepth = storeNonLinearDepth;
this._storeCameraSpaceZ = storeCameraSpaceZ;
this.isPacked = type === Constants.TEXTURETYPE_UNSIGNED_BYTE;
if (this.isPacked) {
this.clearColor = new Color4(1.0, 1.0, 1.0, 1.0);
}
else {
this.clearColor = new Color4(storeCameraSpaceZ ? 0.0 : 1.0, 0.0, 0.0, 1.0);
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this._initShaderSourceAsync();
DepthRenderer._SceneComponentInitialization(this._scene);
const engine = scene.getEngine();
this._camera = camera;
if (samplingMode !== Texture.NEAREST_SAMPLINGMODE) {
if (type === Constants.TEXTURETYPE_FLOAT && !engine._caps.textureFloatLinearFiltering) {
samplingMode = Texture.NEAREST_SAMPLINGMODE;
}
if (type === Constants.TEXTURETYPE_HALF_FLOAT && !engine._caps.textureHalfFloatLinearFiltering) {
samplingMode = Texture.NEAREST_SAMPLINGMODE;
}
}
// Render target
const format = this.isPacked || !engine._features.supportExtendedTextureFormats ? Constants.TEXTUREFORMAT_RGBA : Constants.TEXTUREFORMAT_R;
this._depthMap = new RenderTargetTexture(name ?? "DepthRenderer", { width: engine.getRenderWidth(), height: engine.getRenderHeight() }, this._scene, false, true, type, false, samplingMode, undefined, undefined, undefined, format);
this._depthMap.wrapU = Texture.CLAMP_ADDRESSMODE;
this._depthMap.wrapV = Texture.CLAMP_ADDRESSMODE;
this._depthMap.refreshRate = 1;
this._depthMap.renderParticles = false;
this._depthMap.renderList = null;
this._depthMap.noPrePassRenderer = true;
// Camera to get depth map from to support multiple concurrent cameras
this._depthMap.activeCamera = this._camera;
this._depthMap.ignoreCameraViewport = true;
this._depthMap.useCameraPostProcesses = false;
// set default depth value to 1.0 (far away)
this._depthMap.onClearObservable.add((engine) => {
engine.clear(this.clearColor, true, true, true);
});
this._depthMap.onBeforeBindObservable.add(() => {
engine._debugPushGroup?.("depth renderer", 1);
});
this._depthMap.onAfterUnbindObservable.add(() => {
engine._debugPopGroup?.(1);
});
this._depthMap.customIsReadyFunction = (mesh, refreshRate, preWarm) => {
if ((preWarm || refreshRate === 0) && mesh.subMeshes) {
for (let i = 0; i < mesh.subMeshes.length; ++i) {
const subMesh = mesh.subMeshes[i];
const renderingMesh = subMesh.getRenderingMesh();
const batch = renderingMesh._getInstancesRenderList(subMesh._id, !!subMesh.getReplacementMesh());
const hardwareInstancedRendering = engine.getCaps().instancedArrays &&
((batch.visibleInstances[subMesh._id] !== null && batch.visibleInstances[subMesh._id] !== undefined) || renderingMesh.hasThinInstances);
if (!this.isReady(subMesh, hardwareInstancedRendering)) {
return false;
}
}
}
return true;
};
// Custom render function
const renderSubMesh = (subMesh) => {
const renderingMesh = subMesh.getRenderingMesh();
const effectiveMesh = subMesh.getEffectiveMesh();
const scene = this._scene;
const engine = scene.getEngine();
const material = subMesh.getMaterial();
effectiveMesh._internalAbstractMeshDataInfo._isActiveIntermediate = false;
if (!material || effectiveMesh.infiniteDistance || material.disableDepthWrite || subMesh.verticesCount === 0 || subMesh._renderId === scene.getRenderId()) {
return;
}
// Culling
const detNeg = effectiveMesh._getWorldMatrixDeterminant() < 0;
let sideOrientation = material._getEffectiveOrientation(renderingMesh);
if (detNeg) {
sideOrientation =
sideOrientation === Constants.MATERIAL_ClockWiseSideOrientation
? Constants.MATERIAL_CounterClockWiseSideOrientation
: Constants.MATERIAL_ClockWiseSideOrientation;
}
const reverseSideOrientation = sideOrientation === Constants.MATERIAL_ClockWiseSideOrientation;
engine.setState(material.backFaceCulling, 0, false, reverseSideOrientation, this.reverseCulling ? !material.cullBackFaces : material.cullBackFaces);
// Managing instances
const batch = renderingMesh._getInstancesRenderList(subMesh._id, !!subMesh.getReplacementMesh());
if (batch.mustReturn) {
return;
}
const hardwareInstancedRendering = engine.getCaps().instancedArrays &&
((batch.visibleInstances[subMesh._id] !== null && batch.visibleInstances[subMesh._id] !== undefined) || renderingMesh.hasThinInstances);
const camera = this._camera || scene.activeCamera;
if (this.isReady(subMesh, hardwareInstancedRendering) && camera) {
subMesh._renderId = scene.getRenderId();
const renderingMaterial = effectiveMesh._internalAbstractMeshDataInfo._materialForRenderPass?.[engine.currentRenderPassId];
let drawWrapper = subMesh._getDrawWrapper();
if (!drawWrapper && renderingMaterial) {
drawWrapper = renderingMaterial._getDrawWrapper();
}
const cameraIsOrtho = camera.mode === Camera.ORTHOGRAPHIC_CAMERA;
if (!drawWrapper) {
return;
}
const effect = drawWrapper.effect;
engine.enableEffect(drawWrapper);
if (!hardwareInstancedRendering) {
renderingMesh._bind(subMesh, effect, material.fillMode);
}
if (!renderingMaterial) {
effect.setMatrix("viewProjection", scene.getTransformMatrix());
effect.setMatrix("world", effectiveMesh.getWorldMatrix());
if (this._storeCameraSpaceZ) {
effect.setMatrix("view", scene.getViewMatrix());
}
}
else {
renderingMaterial.bindForSubMesh(effectiveMesh.getWorldMatrix(), effectiveMesh, subMesh);
}
let minZ, maxZ;
if (cameraIsOrtho) {
minZ = !engine.useReverseDepthBuffer && engine.isNDCHalfZRange ? 0 : 1;
maxZ = engine.useReverseDepthBuffer && engine.isNDCHalfZRange ? 0 : 1;
}
else {
minZ = engine.useReverseDepthBuffer && engine.isNDCHalfZRange ? camera.minZ : engine.isNDCHalfZRange ? 0 : camera.minZ;
maxZ = engine.useReverseDepthBuffer && engine.isNDCHalfZRange ? 0 : camera.maxZ;
}
effect.setFloat2("depthValues", minZ, minZ + maxZ);
if (!renderingMaterial) {
// Alpha test
if (material.needAlphaTestingForMesh(effectiveMesh)) {
const alphaTexture = material.getAlphaTestTexture();
if (alphaTexture) {
effect.setTexture("diffuseSampler", alphaTexture);
effect.setMatrix("diffuseMatrix", alphaTexture.getTextureMatrix());
}
}
// Bones
BindBonesParameters(renderingMesh, effect);
// Clip planes
BindClipPlane(effect, material, scene);
// Morph targets
BindMorphTargetParameters(renderingMesh, effect);
if (renderingMesh.morphTargetManager && renderingMesh.morphTargetManager.isUsingTextureForTargets) {
renderingMesh.morphTargetManager._bind(effect);
}
// Baked vertex animations
const bvaManager = subMesh.getMesh().bakedVertexAnimationManager;
if (bvaManager && bvaManager.isEnabled) {
bvaManager.bind(effect, hardwareInstancedRendering);
}
// Points cloud rendering
if (material.pointsCloud) {
effect.setFloat("pointSize", material.pointSize);
}
}
// Draw
renderingMesh._processRendering(effectiveMesh, subMesh, effect, material.fillMode, batch, hardwareInstancedRendering, (isInstance, world) => effect.setMatrix("world", world));
}
};
this._depthMap.customRenderFunction = (opaqueSubMeshes, alphaTestSubMeshes, transparentSubMeshes, depthOnlySubMeshes) => {
let index;
if (depthOnlySubMeshes.length) {
for (index = 0; index < depthOnlySubMeshes.length; index++) {
renderSubMesh(depthOnlySubMeshes.data[index]);
}
}
for (index = 0; index < opaqueSubMeshes.length; index++) {
renderSubMesh(opaqueSubMeshes.data[index]);
}
for (index = 0; index < alphaTestSubMeshes.length; index++) {
renderSubMesh(alphaTestSubMeshes.data[index]);
}
if (this.forceDepthWriteTransparentMeshes) {
for (index = 0; index < transparentSubMeshes.length; index++) {
renderSubMesh(transparentSubMeshes.data[index]);
}
}
else {
for (index = 0; index < transparentSubMeshes.length; index++) {
transparentSubMeshes.data[index].getEffectiveMesh()._internalAbstractMeshDataInfo._isActiveIntermediate = false;
}
}
};
}
async _initShaderSourceAsync(forceGLSL = false) {
const engine = this._scene.getEngine();
if (engine.isWebGPU && !forceGLSL && !DepthRenderer.ForceGLSL) {
this._shaderLanguage = 1 /* ShaderLanguage.WGSL */;
await Promise.all([import('./depth.vertex-DuShK4Gs.esm.js'), import('./depth.fragment-MkU1KQBC.esm.js')]);
}
else {
await Promise.all([Promise.resolve().then(function () { return depth_vertex; }), Promise.resolve().then(function () { return depth_fragment; })]);
}
this._shadersLoaded = true;
}
/**
* Creates the depth rendering effect and checks if the effect is ready.
* @param subMesh The submesh to be used to render the depth map of
* @param useInstances If multiple world instances should be used
* @returns if the depth renderer is ready to render the depth map
*/
isReady(subMesh, useInstances) {
if (!this._shadersLoaded) {
return false;
}
const engine = this._scene.getEngine();
const mesh = subMesh.getMesh();
const scene = mesh.getScene();
const renderingMaterial = mesh._internalAbstractMeshDataInfo._materialForRenderPass?.[engine.currentRenderPassId];
if (renderingMaterial) {
return renderingMaterial.isReadyForSubMesh(mesh, subMesh, useInstances);
}
const material = subMesh.getMaterial();
if (!material || material.disableDepthWrite) {
return false;
}
const defines = [];
const attribs = [VertexBuffer.PositionKind];
let uv1 = false;
let uv2 = false;
const color = false;
// Alpha test
if (material.needAlphaTestingForMesh(mesh) && material.getAlphaTestTexture()) {
defines.push("#define ALPHATEST");
if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
attribs.push(VertexBuffer.UVKind);
defines.push("#define UV1");
uv1 = true;
}
if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
attribs.push(VertexBuffer.UV2Kind);
defines.push("#define UV2");
uv2 = true;
}
}
// Bones
const fallbacks = new EffectFallbacks();
if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
attribs.push(VertexBuffer.MatricesIndicesKind);
attribs.push(VertexBuffer.MatricesWeightsKind);
if (mesh.numBoneInfluencers > 4) {
attribs.push(VertexBuffer.MatricesIndicesExtraKind);
attribs.push(VertexBuffer.MatricesWeightsExtraKind);
}
defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
if (mesh.numBoneInfluencers > 0) {
fallbacks.addCPUSkinningFallback(0, mesh);
}
const skeleton = mesh.skeleton;
if (skeleton.isUsingTextureForMatrices) {
defines.push("#define BONETEXTURE");
}
else {
defines.push("#define BonesPerMesh " + (skeleton.bones.length + 1));
}
}
else {
defines.push("#define NUM_BONE_INFLUENCERS 0");
}
// Morph targets
const numMorphInfluencers = mesh.morphTargetManager
? PrepareDefinesAndAttributesForMorphTargets(mesh.morphTargetManager, defines, attribs, mesh, true, // usePositionMorph
false, // useNormalMorph
false, // useTangentMorph
uv1, // useUVMorph
uv2, // useUV2Morph
color // useColorMorph
)
: 0;
// Points cloud rendering
if (material.pointsCloud) {
defines.push("#define POINTSIZE");
}
// Instances
if (useInstances) {
defines.push("#define INSTANCES");
PushAttributesForInstances(attribs);
if (subMesh.getRenderingMesh().hasThinInstances) {
defines.push("#define THIN_INSTANCES");
}
}
// Baked vertex animations
const bvaManager = mesh.bakedVertexAnimationManager;
if (bvaManager && bvaManager.isEnabled) {
defines.push("#define BAKED_VERTEX_ANIMATION_TEXTURE");
if (useInstances) {
attribs.push("bakedVertexAnimationSettingsInstanced");
}
}
// None linear depth
if (this._storeNonLinearDepth) {
defines.push("#define NONLINEARDEPTH");
}
// Store camera space Z coordinate instead of NDC Z
if (this._storeCameraSpaceZ) {
defines.push("#define STORE_CAMERASPACE_Z");
}
// Float Mode
if (this.isPacked) {
defines.push("#define PACKED");
}
// Clip planes
PrepareStringDefinesForClipPlanes(material, scene, defines);
// Get correct effect
const drawWrapper = subMesh._getDrawWrapper(undefined, true);
const cachedDefines = drawWrapper.defines;
const join = defines.join("\n");
if (cachedDefines !== join) {
const uniforms = [
"world",
"mBones",
"boneTextureWidth",
"pointSize",
"viewProjection",
"view",
"diffuseMatrix",
"depthValues",
"morphTargetInfluences",
"morphTargetCount",
"morphTargetTextureInfo",
"morphTargetTextureIndices",
"bakedVertexAnimationSettings",
"bakedVertexAnimationTextureSizeInverted",
"bakedVertexAnimationTime",
"bakedVertexAnimationTexture",
];
const samplers = ["diffuseSampler", "morphTargets", "boneSampler", "bakedVertexAnimationTexture"];
AddClipPlaneUniforms(uniforms);
drawWrapper.setEffect(engine.createEffect("depth", {
attributes: attribs,
uniformsNames: uniforms,
uniformBuffersNames: [],
samplers: samplers,
defines: join,
fallbacks: fallbacks,
onCompiled: null,
onError: null,
indexParameters: { maxSimultaneousMorphTargets: numMorphInfluencers },
shaderLanguage: this._shaderLanguage,
}, engine), join);
}
return drawWrapper.effect.isReady();
}
/**
* Gets the texture which the depth map will be written to.
* @returns The depth map texture
*/
getDepthMap() {
return this._depthMap;
}
/**
* Disposes of the depth renderer.
*/
dispose() {
const keysToDelete = [];
for (const key in this._scene._depthRenderer) {
const depthRenderer = this._scene._depthRenderer[key];
if (depthRenderer === this) {
keysToDelete.push(key);
}
}
if (keysToDelete.length > 0) {
this._depthMap.dispose();
for (const key of keysToDelete) {
delete this._scene._depthRenderer[key];
}
}
}
}
/**
* Force all the depth renderer to compile to glsl even on WebGPU engines.
* False by default. This is mostly meant for backward compatibility.
*/
DepthRenderer.ForceGLSL = false;
/**
* @internal
*/
DepthRenderer._SceneComponentInitialization = (_) => {
throw _WarnImport("DepthRendererSceneComponent");
};
/**
* @internal
*/
var DepthTextureType;
(function (DepthTextureType) {
DepthTextureType[DepthTextureType["NormalizedViewDepth"] = 0] = "NormalizedViewDepth";
DepthTextureType[DepthTextureType["ViewDepth"] = 1] = "ViewDepth";
DepthTextureType[DepthTextureType["ScreenDepth"] = 2] = "ScreenDepth";
})(DepthTextureType || (DepthTextureType = {}));
/**
* @internal
*/
class ThinMinMaxReducerPostProcess extends EffectWrapper {
_gatherImports(useWebGPU, list) {
if (useWebGPU) {
this._webGPUReady = true;
list.push(Promise.resolve().then(function () { return minmaxRedux_fragment; }));
}
else {
list.push(Promise.resolve().then(function () { return minmaxRedux_fragment$1; }));
}
}
constructor(name, engine = null, defines = "", options) {
super({
...options,
name,
engine: engine || Engine.LastCreatedEngine,
useShaderStore: true,
useAsPostProcess: true,
fragmentShader: ThinMinMaxReducerPostProcess.FragmentUrl,
uniforms: ThinMinMaxReducerPostProcess.Uniforms,
defines,
});
this.textureWidth = 0;
this.textureHeight = 0;
}
bind(noDefaultBindings = false) {
super.bind(noDefaultBindings);
const effect = this.drawWrapper.effect;
if (this.textureWidth === 1 || this.textureHeight === 1) {
effect.setInt2("texSize", this.textureWidth, this.textureHeight);
}
else {
effect.setFloat2("texSize", this.textureWidth, this.textureHeight);
}
}
}
ThinMinMaxReducerPostProcess.FragmentUrl = "minmaxRedux";
ThinMinMaxReducerPostProcess.Uniforms = ["texSize"];
const BufferFloat = new Float32Array(4 * 1 * 1);
const BufferUint8 = new Uint8Array(4 * 1 * 1);
const MinMax = { min: 0, max: 0 };
/**
* @internal
*/
class ThinMinMaxReducer {
get depthRedux() {
return this._depthRedux;
}
set depthRedux(value) {
if (this._depthRedux === value) {
return;
}
this._depthRedux = value;
this._recreatePostProcesses();
}
get textureWidth() {
return this._textureWidth;
}
get textureHeight() {
return this._textureHeight;
}
constructor(scene, depthRedux = true) {
this.onAfterReductionPerformed = new Observable();
this._textureWidth = 0;
this._textureHeight = 0;
this._scene = scene;
this._depthRedux = depthRedux;
this.reductionSteps = [];
}
setTextureDimensions(width, height, depthTextureType = 0 /* DepthTextureType.NormalizedViewDepth */) {
if (width === this._textureWidth && height === this._textureHeight && depthTextureType === this._depthTextureType) {
return false;
}
this._textureWidth = width;
this._textureHeight = height;
this._depthTextureType = depthTextureType;
this._recreatePostProcesses();
return true;
}
readMinMax(texture) {
// Note that we should normally await the call to _readTexturePixels!
// But because WebGL does the read synchronously, we know the values will be updated without waiting for the promise to be resolved, which will let us get the updated values
// in the current frame, whereas in WebGPU, the read is asynchronous and we should normally wait for the promise to be resolved to get the updated values.
// However, it's safe to avoid waiting for the promise to be resolved in WebGPU as well, because we will simply use the current values until "buffer" is updated later on.
// Note that it means we can suffer some rendering artifacts in WebGPU because we may use previous min/max values for the current frame.
const isFloat = texture.type === Engine.TEXTURETYPE_FLOAT || texture.type === Engine.TEXTURETYPE_HALF_FLOAT;
const buffer = isFloat ? BufferFloat : BufferUint8;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this._scene.getEngine()._readTexturePixels(texture, 1, 1, -1, 0, buffer, false);
MinMax.min = buffer[0];
MinMax.max = buffer[1];
if (!isFloat) {
MinMax.min = MinMax.min / 255.0;
MinMax.max = MinMax.max / 255.0;
}
if (MinMax.min >= MinMax.max) {
MinMax.min = 0;
MinMax.max = 1;
}
this.onAfterReductionPerformed.notifyObservers(MinMax);
}
dispose(disposeAll = true) {
if (disposeAll) {
this.onAfterReductionPerformed.clear();
this._textureWidth = 0;
this._textureHeight = 0;
}
for (let i = 0; i < this.reductionSteps.length; ++i) {
this.reductionSteps[i].dispose();
}
this.reductionSteps.length = 0;
}
_recreatePostProcesses() {
this.dispose(false);
const scene = this._scene;
let w = this.textureWidth, h = this.textureHeight;
const reductionInitial = new ThinMinMaxReducerPostProcess("Initial reduction phase", scene.getEngine(), "#define INITIAL" + (this._depthRedux ? "\n#define DEPTH_REDUX" : "") + (this._depthTextureType === 1 /* DepthTextureType.ViewDepth */ ? "\n#define VIEW_DEPTH" : ""));
reductionInitial.textureWidth = w;
reductionInitial.textureHeight = h;
this.reductionSteps.push(reductionInitial);
let index = 1;
// create the additional steps
while (w > 1 || h > 1) {
w = Math.max(Math.round(w / 2), 1);
h = Math.max(Math.round(h / 2), 1);
const reduction = new ThinMinMaxReducerPostProcess("Reduction phase " + index, scene.getEngine(), "#define " + (w == 1 && h == 1 ? "LAST" : w == 1 || h == 1 ? "ONEBEFORELAST" : "MAIN"));
reduction.textureWidth = w;
reduction.textureHeight = h;
this.reductionSteps.push(reduction);
index++;
}
}
}
// Do not edit.
const name$1 = "minmaxReduxPixelShader";
const shader$1 = `varying vec2 vUV;uniform sampler2D textureSampler;
#if defined(INITIAL)
uniform vec2 texSize;void main(void)
{ivec2 coord=ivec2(vUV*(texSize-1.0));float f1=texelFetch(textureSampler,coord,0).r;float f2=texelFetch(textureSampler,coord+ivec2(1,0),0).r;float f3=texelFetch(textureSampler,coord+ivec2(1,1),0).r;float f4=texelFetch(textureSampler,coord+ivec2(0,1),0).r;
#ifdef DEPTH_REDUX
#ifdef VIEW_DEPTH
float minz=3.4e38;if (f1 != 0.0) { minz=f1; }
if (f2 != 0.0) { minz=min(minz,f2); }
if (f3 != 0.0) { minz=min(minz,f3); }
if (f4 != 0.0) { minz=min(minz,f4); }
float maxz=max(max(max(f1,f2),f3),f4);
#else
float minz=min(min(min(f1,f2),f3),f4);float maxz=max(max(max(sign(1.0-f1)*f1,sign(1.0-f2)*f2),sign(1.0-f3)*f3),sign(1.0-f4)*f4);
#endif
#else
float minz=min(min(min(f1,f2),f3),f4);float maxz=max(max(max(f1,f2),f3),f4);
#endif
glFragColor=vec4(minz,maxz,0.,0.);}
#elif defined(MAIN)
uniform vec2 texSize;void main(void)
{ivec2 coord=ivec2(vUV*(texSize-1.0));vec2 f1=texelFetch(textureSampler,coord,0).rg;vec2 f2=texelFetch(textureSampler,coord+ivec2(1,0),0).rg;vec2 f3=texelFetch(textureSampler,coord+ivec2(1,1),0).rg;vec2 f4=texelFetch(textureSampler,coord+ivec2(0,1),0).rg;float minz=min(min(min(f1.x,f2.x),f3.x),f4.x);float maxz=max(max(max(f1.y,f2.y),f3.y),f4.y);glFragColor=vec4(minz,maxz,0.,0.);}
#elif defined(ONEBEFORELAST)
uniform ivec2 texSize;void main(void)
{ivec2 coord=ivec2(vUV*vec2(texSize-1));vec2 f1=texelFetch(textureSampler,coord % texSize,0).rg;vec2 f2=texelFetch(textureSampler,(coord+ivec2(1,0)) % texSize,0).rg;vec2 f3=texelFetch(textureSampler,(coord+ivec2(1,1)) % texSize,0).rg;vec2 f4=texelFetch(textureSampler,(coord+ivec2(0,1)) % texSize,0).rg;float minz=min(min(min(f1.x,f2.x),f3.x),f4.x);float maxz=max(max(max(f1.y,f2.y),f3.y),f4.y);glFragColor=vec4(minz,maxz,0.,0.);}
#elif defined(LAST)
void main(void)
{glFragColor=vec4(0.);if (true) {
discard;}}
#endif
`;
// Sideeffect
if (!ShaderStore.ShadersStore[name$1]) {
ShaderStore.ShadersStore[name$1] = shader$1;
}
/** @internal */
const minmaxReduxPixelShader = { name: name$1, shader: shader$1 };
var minmaxRedux_fragment$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
minmaxReduxPixelShader: minmaxReduxPixelShader
});
// Do not edit.
const name = "minmaxReduxPixelShader";
const shader = `varying vUV: vec2f;var textureSampler: texture_2d<f32>;
#if defined(INITIAL)
uniform texSize: vec2f;@fragment
fn main(input: FragmentInputs)->FragmentOutputs {let coord=vec2i(fragmentInputs.vUV*(uniforms.texSize-1.0));let f1=textureLoad(textureSampler,coord,0).r;let f2=textureLoad(textureSampler,coord+vec2i(1,0),0).r;let f3=textureLoad(textureSampler,coord+vec2i(1,1),0).r;let f4=textureLoad(textureSampler,coord+vec2i(0,1),0).r;
#ifdef DEPTH_REDUX
#ifdef VIEW_DEPTH
var minz=3.4e38;if (f1 != 0.0) { minz=f1; }
if (f2 != 0.0) { minz=min(minz,f2); }
if (f3 != 0.0) { minz=min(minz,f3); }
if (f4 != 0.0) { minz=min(minz,f4); }
let maxz=max(max(max(f1,f2),f3),f4);
#else
let minz=min(min(min(f1,f2),f3),f4);let maxz=max(max(max(sign(1.0-f1)*f1,sign(1.0-f2)*f2),sign(1.0-f3)*f3),sign(1.0-f4)*f4);
#endif
#else
let minz=min(min(min(f1,f2),f3),f4);let maxz=max(max(max(f1,f2),f3),f4);
#endif
fragmentOutputs.color=vec4f(minz,maxz,0.,0.);}
#elif defined(MAIN)
uniform texSize: vec2f;@fragment
fn main(input: FragmentInputs)->FragmentOutputs {let coord=vec2i(fragmentInputs.vUV*(uniforms.texSize-1.0));let f1=textureLoad(textureSampler,coord,0).rg;let f2=textureLoad(textureSampler,coord+vec2i(1,0),0).rg;let f3=textureLoad(textureSampler,coord+vec2i(1,1),0).rg;let f4=textureLoad(textureSampler,coord+vec2i(0,1),0).rg;let minz=min(min(min(f1.x,f2.x),f3.x),f4.x);let maxz=max(max(max(f1.y,f2.y),f3.y),f4.y);fragmentOutputs.color=vec4(minz,maxz,0.,0.);}
#elif defined(ONEBEFORELAST)
uniform texSize: vec2i;@fragment
fn main(input: FragmentInputs)->FragmentOutputs {let coord=vec2i(fragmentInputs.vUV*vec2f(uniforms.texSize-1));let f1=textureLoad(textureSampler,coord % uniforms.texSize,0).rg;let f2=textureLoad(textureSampler,(coord+vec2i(1,0)) % uniforms.texSize,0).rg;let f3=textureLoad(textureSampler,(coord+vec2i(1,1)) % uniforms.texSize,0).rg;let f4=textureLoad(textureSampler,(coord+vec2i(0,1)) % uniforms.texSize,0).rg;let minz=min(min(min(f1.x,f2.x),f3.x),f4.x);let maxz=max(max(max(f1.y,f2.y),f3.y),f4.y);fragmentOutputs.color=vec4(minz,maxz,0.,0.);}
#elif defined(LAST)
@fragment
fn main(input: FragmentInputs)->FragmentOutputs {fragmentOutputs.color=vec4f(0.);if (true) {
discard;}}
#endif
`;
// Sideeffect
if (!ShaderStore.ShadersStoreWGSL[name]) {
ShaderStore.ShadersStoreWGSL[name] = shader;
}
/** @internal */
const minmaxReduxPixelShaderWGSL = { name, shader };
var minmaxRedux_fragment = /*#__PURE__*/Object.freeze({
__proto__: null,
minmaxReduxPixelShaderWGSL: minmaxReduxPixelShaderWGSL
});
/**
* This class computes a min/max reduction from a texture: it means it computes the minimum
* and maximum values from all values of the texture.
* It is performed on the GPU for better performances, thanks to a succession of post processes.
* The source values are read from the red channel of the texture.
*/
class MinMaxReducer {
/**
* Observable triggered when the computation has been performed
*/
get onAfterReductionPerformed() {
return this._thinMinMaxReducer.onAfterReductionPerformed;
}
/**
* Creates a min/max reducer
* @param camera The camera to use for the post processes
*/
constructor(camera) {
this._onAfterUnbindObserver = null;
this._forceFullscreenViewport = true;
this._activated = false;
this._camera = camera;
this._postProcessManager = new PostProcessManager(camera.getScene());
this._thinMinMaxReducer = new ThinMinMaxReducer(camera.getScene());
this._reductionSteps = [];
this._onContextRestoredObserver = camera.getEngine().onContextRestoredObservable.add(() => {
this._postProcessManager._rebuild();
});
}
/**
* Gets the texture used to read the values from.
*/
get sourceTexture() {
return this._sourceTexture;
}
/**
* Sets the source texture to read the values from.
* One must indicate if the texture is a depth texture or not through the depthRedux parameter
* because in such textures '1' value must not be taken into account to compute the maximum
* as this value is used to clear the texture.
* Note that the computation is not activated by calling this function, you must call activate() for that!
* @param sourceTexture The texture to read the values from. The values should be in the red channel.
* @param depthRedux Indicates if the texture is a depth texture or not
* @param type The type of the textures created for the reduction (defaults to TEXTURETYPE_HALF_FLOAT)
* @param forceFullscreenViewport Forces the post processes used for the reduction to be applied without taking into account viewport (defaults to true)
*/
setSourceTexture(sourceTexture, depthRedux, type = Constants.TEXTURETYPE_HALF_FLOAT, forceFullscreenViewport = true) {
if (sourceTexture === this._sourceTexture) {
return;
}
this._thinMinMaxReducer.depthRedux = depthRedux;
this.deactivate();
this._sourceTexture = sourceTexture;
this._forceFullscreenViewport = forceFullscreenViewport;
if (this._thinMinMaxReducer.setTextureDimensions(sourceTexture.getRenderWidth(), sourceTexture.getRenderHeight())) {
this._disposePostProcesses();
const reductionSteps = this._thinMinMaxReducer.reductionSteps;
for (let i = 0; i < reductionSteps.length; ++i) {
const reductionStep = reductionSteps[i];
const postProcess = new PostProcess(reductionStep.name, ThinMinMaxReducerPostProcess.FragmentUrl, {
effectWrapper: reductionStep,
samplingMode: Constants.TEXTURE_NEAREST_NEAREST,
engine: this._camera.getScene().getEngine(),
textureType: type,
textureFormat: Constants.TEXTUREFORMAT_RG,
size: { width: reductionStep.textureWidth, height: reductionStep.textureHeight },
});
this._reductionSteps.push(postProcess);
postProcess.autoClear = false;
postProcess.forceFullscreenViewport = forceFullscreenViewport;
if (i === 0) {
postProcess.externalTextureSamplerBinding = true;
postProcess.onApplyObservable.add((effect) => {
effect.setTexture("textureSampler", this._sourceTexture);
});
}
if (i === reductionSteps.length - 1) {
this._reductionSteps[i - 1].onAfterRenderObservable.add(() => {
this._thinMinMaxReducer.readMinMax(postProcess.inputTexture.texture);
});
}
}
}
}
/**
* Defines the refresh rate of the computation.
* Use 0 to compute just once, 1 to compute on every frame, 2 to compute every two frames and so on...
*/
get refreshRate() {
return this._sourceTexture ? this._sourceTexture.refreshRate : -1;
}
set refreshRate(value) {
if (this._sourceTexture) {
this._sourceTexture.refreshRate = value;
}
}
/**
* Gets the activation status of the reducer
*/
get activated() {
return this._activated;
}
/**
* Activates the reduction computation.
* When activated, the observers registered in onAfterReductionPerformed are
* called after the computation is performed
*/
activate() {
if (this._onAfterUnbindObserver || !this._sourceTexture) {
return;
}
this._onAfterUnbindObserver = this._sourceTexture.onAfterUnbindObservable.add(() => {
const engine = this._camera.getScene().getEngine();
engine._debugPushGroup?.(`min max reduction`, 1);
this._reductionSteps[0].activate(this._camera);
this._postProcessManager.directRender(this._reductionSteps, this._reductionSteps[0].inputTexture, this._forceFullscreenViewport, 0, 0, true, this._reductionSteps.length - 1);
engine.unBindFramebuffer(this._reductionSteps[this._reductionSteps.length - 1].inputTexture, false);
engine._debugPopGroup?.(1);
});
this._activated = true;
}
/**
* Deactivates the reduction computation.
*/
deactivate() {
if (!this._onAfterUnbindObserver || !this._sourceTexture) {
return;
}
this._sourceTexture.onAfterUnbindObservable.remove(this._onAfterUnbindObserver);
this._onAfterUnbindObserver = null;
this._activated = false;
}
/**
* Disposes the min/max reducer
* @param disposeAll true to dispose all the resources. You should always call this function with true as the parameter (or without any parameter as it is the default one). This flag is meant to be used internally.
*/
dispose(disposeAll = true) {
if (!disposeAll) {
return;
}
this.onAfterReductionPerformed.clear();
this._camera.getEngine().onContextRestoredObservable.remove(this._onContextRestoredObserver);
this._onContextRestoredObserver = undefined;
this._disposePostProcesses();
this._postProcessManager.dispose();
this._postProcessManager = undefined;
this._thinMinMaxReducer.dispose();
this._thinMinMaxReducer = undefined;
this._sourceTexture = null;
}
_disposePostProcesses() {
for (let i = 0; i < this._reductionSteps.length; ++i) {
this._reductionSteps[i].dispose();
}
this._reductionSteps.length = 0;
}
}
/**
* This class is a small wrapper around the MinMaxReducer class to compute the min/max values of a depth texture
*/
class DepthReducer extends MinMaxReducer {
/**
* Gets the depth renderer used for the computation.
* Note that the result is null if you provide your own renderer when calling setDepthRenderer.
*/
get depthRenderer() {
return this._depthRenderer;
}
/**
* Creates a depth reducer
* @param camera The camera used to render the depth texture
*/
constructor(camera) {
super(camera);
}
/**
* Sets the depth renderer to use to generate the depth map
* @param depthRenderer The depth renderer to use. If not provided, a new one will be created automatically
* @param type The texture type of the depth map (default: TEXTURETYPE_HALF_FLOAT)
* @param forceFullscreenViewport Forces the post processes used for the reduction to be applied without taking into account viewport (defaults to true)
*/
setDepthRenderer(depthRenderer = null, type = Constants.TEXTURETYPE_HALF_FLOAT, forceFullscreenViewport = true) {
const scene = this._camera.getScene();
if (this._depthRenderer) {
delete scene._depthRenderer[this._depthRendererId];
this._depthRenderer.dispose();
this._depthRenderer = null;
}
if (depthRenderer === null) {
if (!scene._depthRenderer) {
scene._depthRenderer = {};
}
this._depthRendererId = "minmax_" + this._camera.id;
depthRenderer = this._depthRenderer = new DepthRenderer(scene, type, this._camera, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE, false, `DepthRenderer ${this._depthRendererId}`);
depthRenderer.enabled = false;
scene._depthRenderer[this._depthRendererId] = depthRenderer;
}
super.setSourceTexture(depthRenderer.getDepthMap(), true, type, forceFullscreenViewport);
}
/**
* @internal
*/
setSourceTexture(sourceTexture, depthRedux, type = Constants.TEXTURETYPE_HALF_FLOAT, forceFullscreenViewport = true) {
super.setSourceTexture(sourceTexture, depthRedux, type, forceFullscreenViewport);
}
/**
* Activates the reduction computation.
* When activated, the observers registered in onAfterReductionPerformed are
* called after the computation is performed
*/
activate() {
if (this._depthRenderer) {
this._depthRenderer.enabled = true;
}
super.activate();
}
/**
* Deactivates the reduction computation.
*/
deactivate() {
super.deactivate();
if (this._depthRenderer) {
this._depthRenderer.enabled = false;
}
}
/**
* Disposes the depth reducer
* @param disposeAll true to dispose all the resources. You should always call this function with true as the parameter (or without any parameter as it is the default one). This flag is meant to be used internally.
*/
dispose(disposeAll = true) {
super.dispose(disposeAll);
if (this._depthRenderer && disposeAll) {
this._depthRenderer.dispose();
this._depthRenderer = null;
}
}
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const UpDir = Vector3.Up();
// eslint-disable-next-line @typescript-eslint/naming-convention
const ZeroVec = Vector3.Zero();
const Tmpv1 = new Vector3();
const Tmpv2 = new Vector3();
const TmpMatrix = new Matrix();
/**
* A CSM implementation allowing casting shadows on large scenes.
* Documentation : https://doc.babylonjs.com/babylon101/cascadedShadows
* Based on: https://github.com/TheRealMJP/Shadows and https://johanmedestrom.wordpress.com/2016/03/18/opengl-cascaded-shadow-maps/
*/
class CascadedShadowGenerator extends ShadowGenerator {
_validateFilter(filter) {
if (filter === ShadowGenerator.FILTER_NONE || filter === ShadowGenerator.FILTER_PCF || filter === ShadowGenerator.FILTER_PCSS) {
return filter;
}
Logger.Error('Unsupported filter "' + filter + '"!');
return ShadowGenerator.FILTER_NONE;
}
/**
* Gets or set the number of cascades used by the CSM.
*/
get numCascades() {
return this._numCascades;
}
set numCascades(value) {
value = Math.min(Math.max(value, CascadedShadowGenerator.MIN_CASCADES_COUNT), CascadedShadowGenerator.MAX_CASCADES_COUNT);
if (value === this._numCascades) {
return;
}
this._numCascades = value;
this.recreateShadowMap();
this._recreateSceneUBOs();
}
/**
* Enables or disables the shadow casters bounding info computation.
* If your shadow casters don't move, you can disable this feature.
* If it is enabled, the bounding box computation is done every frame.
*/
get freezeShadowCastersBoundingInfo() {
return this._freezeShadowCastersBoundingInfo;
}
set freezeShadowCastersBoundingInfo(freeze) {
if (this._freezeShadowCastersBoundingInfoObservable && freeze) {
this._scene.onBeforeRenderObservable.remove(this._freezeShadowCastersBoundingInfoObservable);
this._freezeShadowCastersBoundingInfoObservable = null;
}
if (!this._freezeShadowCastersBoundingInfoObservable && !freeze) {
this._freezeShadowCastersBoundingInfoObservable = this._scene.onBeforeRenderObservable.add(() => this._computeShadowCastersBoundingInfo());
}
this._freezeShadowCastersBoundingInfo = freeze;
if (freeze) {
this._computeShadowCastersBoundingInfo();
}
}
_computeShadowCastersBoundingInfo() {
this._scbiMin.copyFromFloats(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
this._scbiMax.copyFromFloats(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
if (this._shadowMap && this._shadowMap.renderList) {
const renderList = this._shadowMap.renderList;
for (let meshIndex = 0; meshIndex < renderList.length; meshIndex++) {
const mesh = renderList[meshIndex];
if (!mesh) {
continue;
}
const boundingInfo = mesh.getBoundingInfo(), boundingBox = boundingInfo.boundingBox;
this._scbiMin.minimizeInPlace(boundingBox.minimumWorld);
this._scbiMax.maximizeInPlace(boundingBox.maximumWorld);
}
}
this._shadowCastersBoundingInfo.reConstruct(this._scbiMin, this._scbiMax);
}
/**
* Gets or sets the shadow casters bounding info.
* If you provide your own shadow casters bounding info, first enable freezeShadowCastersBoundingInfo
* so that the system won't overwrite the bounds you provide
*/
get shadowCastersBoundingInfo() {
return this._shadowCastersBoundingInfo;
}
set shadowCastersBou