@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
999 lines (998 loc) • 40.3 kB
JavaScript
import { EngineStore } from "../../Engines/engineStore.js";
import { Matrix, Vector3, Vector4, Quaternion } from "../../Maths/math.vector.js";
import { Texture } from "../../Materials/Textures/texture.js";
import { Logger } from "../../Misc/logger.js";
import { _IblShadowsVoxelRenderer } from "./iblShadowsVoxelRenderer.js";
import { _IblShadowsVoxelTracingPass } from "./iblShadowsVoxelTracingPass.js";
import { PostProcess } from "../../PostProcesses/postProcess.js";
import { _IblShadowsSpatialBlurPass } from "./iblShadowsSpatialBlurPass.js";
import { _IblShadowsAccumulationPass } from "./iblShadowsAccumulationPass.js";
import { PostProcessRenderPipeline } from "../../PostProcesses/RenderPipeline/postProcessRenderPipeline.js";
import { PostProcessRenderEffect } from "../../PostProcesses/RenderPipeline/postProcessRenderEffect.js";
import { GeometryBufferRenderer } from "../geometryBufferRenderer.js";
import { RawTexture } from "../../Materials/Textures/rawTexture.js";
import { RawTexture3D } from "../../Materials/Textures/rawTexture3D.js";
import { Engine } from "../../Engines/engine.js";
import { IBLShadowsPluginMaterial } from "./iblShadowsPluginMaterial.js";
import { PBRBaseMaterial } from "../../Materials/PBR/pbrBaseMaterial.js";
import { StandardMaterial } from "../../Materials/standardMaterial.js";
import { Observable } from "../../Misc/observable.js";
import "../geometryBufferRendererSceneComponent.js";
import "../iblCdfGeneratorSceneComponent.js";
/**
* Voxel-based shadow rendering for IBL's.
* This should not be instanciated directly, as it is part of a scene component
*/
export class IblShadowsRenderPipeline extends PostProcessRenderPipeline {
/**
* Reset the shadow accumulation. This has a similar affect to lowering the remanence for a single frame.
* This is useful when making a sudden change to the IBL.
*/
resetAccumulation() {
this._accumulationPass.reset = true;
}
/**
* How dark the shadows appear. 1.0 is full opacity, 0.0 is no shadows.
*/
get shadowOpacity() {
return this._shadowOpacity;
}
set shadowOpacity(value) {
this._shadowOpacity = value;
this._setPluginParameters();
}
/**
* Render the shadows in color rather than black and white.
* This is slightly more expensive than black and white shadows but can be much
* more accurate when the strongest lights in the IBL are non-white.
*/
get coloredShadows() {
return this._coloredShadows;
}
set coloredShadows(value) {
this._coloredShadows = value;
this._voxelTracingPass.coloredShadows = value;
this._setPluginParameters();
}
/**
* A multiplier for the render size of the shadows. Used for rendering lower-resolution shadows.
*/
get shadowRenderSizeFactor() {
return this._renderSizeFactor;
}
set shadowRenderSizeFactor(value) {
this._renderSizeFactor = Math.max(Math.min(value, 1.0), 0.0);
this._voxelTracingPass.resize(value);
this._spatialBlurPass.resize(value);
this._accumulationPass.resize(value);
this._setPluginParameters();
}
/**
* How dark the voxel shadows appear. 1.0 is full opacity, 0.0 is no shadows.
*/
get voxelShadowOpacity() {
return this._voxelTracingPass?.voxelShadowOpacity;
}
set voxelShadowOpacity(value) {
if (!this._voxelTracingPass) {
return;
}
this._voxelTracingPass.voxelShadowOpacity = value;
}
/**
* How dark the screen-space shadows appear. 1.0 is full opacity, 0.0 is no shadows.
*/
get ssShadowOpacity() {
return this._voxelTracingPass?.ssShadowOpacity;
}
set ssShadowOpacity(value) {
if (!this._voxelTracingPass) {
return;
}
this._voxelTracingPass.ssShadowOpacity = value;
}
/**
* The number of samples used in the screen space shadow pass.
*/
get ssShadowSampleCount() {
return this._voxelTracingPass?.sssSamples;
}
set ssShadowSampleCount(value) {
if (!this._voxelTracingPass) {
return;
}
this._voxelTracingPass.sssSamples = value;
}
/**
* The stride of the screen-space shadow pass. This controls the distance between samples
* in pixels.
*/
get ssShadowStride() {
return this._voxelTracingPass?.sssStride;
}
set ssShadowStride(value) {
if (!this._voxelTracingPass) {
return;
}
this._voxelTracingPass.sssStride = value;
}
/**
* A scale for the maximum distance a screen-space shadow can be cast in world-space.
* The maximum distance that screen-space shadows cast is derived from the voxel size
* and this value so shouldn't need to change if you scale your scene
*/
get ssShadowDistanceScale() {
return this._sssMaxDistScale;
}
set ssShadowDistanceScale(value) {
this._sssMaxDistScale = value;
this._updateSsShadowParams();
}
/**
* Screen-space shadow thickness scale. This value controls the assumed thickness of
* on-screen surfaces in world-space. It scales with the size of the shadow-casting
* region so shouldn't need to change if you scale your scene.
*/
get ssShadowThicknessScale() {
return this._sssThicknessScale;
}
set ssShadowThicknessScale(value) {
this._sssThicknessScale = value;
this._updateSsShadowParams();
}
/**
* Returns the texture containing the voxel grid data
* @returns The texture containing the voxel grid data
* @internal
*/
_getVoxelGridTexture() {
const tex = this._voxelRenderer?.getVoxelGrid();
if (tex && tex.isReady()) {
return tex;
}
return this._dummyTexture3d;
}
/**
* Returns the noise texture.
* @returns The noise texture.
* @internal
*/
_getNoiseTexture() {
const tex = this._noiseTexture;
if (tex && tex.isReady()) {
return tex;
}
return this._dummyTexture2d;
}
/**
* Returns the voxel-tracing texture.
* @returns The voxel-tracing texture.
* @internal
*/
_getVoxelTracingTexture() {
const tex = this._voxelTracingPass?.getOutputTexture();
if (tex && tex.isReady()) {
return tex;
}
return this._dummyTexture2d;
}
/**
* Returns the spatial blur texture.
* @returns The spatial blur texture.
* @internal
*/
_getSpatialBlurTexture() {
const tex = this._spatialBlurPass.getOutputTexture();
if (tex && tex.isReady()) {
return tex;
}
return this._dummyTexture2d;
}
/**
* Returns the accumulated shadow texture.
* @returns The accumulated shadow texture.
* @internal
*/
_getAccumulatedTexture() {
const tex = this._accumulationPass?.getOutputTexture();
if (tex && tex.isReady()) {
return tex;
}
return this._dummyTexture2d;
}
/**
* Turn on or off the debug view of the G-Buffer. This will display only the targets
* of the g-buffer that are used by the shadow pipeline.
*/
get gbufferDebugEnabled() {
return this._gbufferDebugEnabled;
}
set gbufferDebugEnabled(enabled) {
if (enabled && !this.allowDebugPasses) {
Logger.Warn("Can't enable G-Buffer debug view without setting allowDebugPasses to true.");
return;
}
this._gbufferDebugEnabled = enabled;
if (enabled) {
this._enableEffect(this._getGBufferDebugPass().name, this.cameras);
}
else {
this._disableEffect(this._getGBufferDebugPass().name, this.cameras);
}
}
/**
* Turn on or off the debug view of the CDF importance sampling data
*/
get cdfDebugEnabled() {
return this.scene.iblCdfGenerator ? this.scene.iblCdfGenerator.debugEnabled : false;
}
/**
* Turn on or off the debug view of the CDF importance sampling data
*/
set cdfDebugEnabled(enabled) {
if (!this.scene.iblCdfGenerator) {
return;
}
if (enabled && !this.allowDebugPasses) {
Logger.Warn("Can't enable importance sampling debug view without setting allowDebugPasses to true.");
return;
}
if (enabled === this.scene.iblCdfGenerator.debugEnabled) {
return;
}
this.scene.iblCdfGenerator.debugEnabled = enabled;
if (enabled) {
this._enableEffect(this.scene.iblCdfGenerator.debugPassName, this.cameras);
}
else {
this._disableEffect(this.scene.iblCdfGenerator.debugPassName, this.cameras);
}
}
/**
* This displays the voxel grid in slices spread across the screen.
* It also displays what slices of the model are stored in each layer
* of the voxel grid. Each red stripe represents one layer while each gradient
* (from bright red to black) represents the layers rendered in a single draw call.
*/
get voxelDebugEnabled() {
return this._voxelRenderer?.voxelDebugEnabled;
}
set voxelDebugEnabled(enabled) {
if (!this._voxelRenderer) {
return;
}
if (enabled && !this.allowDebugPasses) {
Logger.Warn("Can't enable voxel debug view without setting allowDebugPasses to true.");
return;
}
this._voxelRenderer.voxelDebugEnabled = enabled;
if (enabled) {
this._enableEffect(this._voxelRenderer.debugPassName, this.cameras);
}
else {
this._disableEffect(this._voxelRenderer.debugPassName, this.cameras);
}
}
/**
* When using tri-planar voxelization (the default), this value can be used to
* display only the voxelization result for that axis. z-axis = 0, y-axis = 1, x-axis = 2
*/
get voxelDebugAxis() {
return this._voxelRenderer?.voxelDebugAxis;
}
set voxelDebugAxis(axisNum) {
if (!this._voxelRenderer) {
return;
}
this._voxelRenderer.voxelDebugAxis = axisNum;
}
/**
* Displays a given mip of the voxel grid. `voxelDebugAxis` must be undefined in this
* case because we only generate mips for the combined voxel grid.
*/
set voxelDebugDisplayMip(mipNum) {
if (!this._voxelRenderer) {
return;
}
this._voxelRenderer.setDebugMipNumber(mipNum);
}
/**
* Display the debug view for just the shadow samples taken this frame.
*/
get voxelTracingDebugEnabled() {
return this._voxelTracingPass?.debugEnabled;
}
set voxelTracingDebugEnabled(enabled) {
if (!this._voxelTracingPass) {
return;
}
if (enabled && !this.allowDebugPasses) {
Logger.Warn("Can't enable voxel tracing debug view without setting allowDebugPasses to true.");
return;
}
if (enabled === this._voxelTracingPass.debugEnabled) {
return;
}
this._voxelTracingPass.debugEnabled = enabled;
if (enabled) {
this._enableEffect(this._voxelTracingPass.debugPassName, this.cameras);
}
else {
this._disableEffect(this._voxelTracingPass.debugPassName, this.cameras);
}
}
/**
* Display the debug view for the spatial blur pass
*/
get spatialBlurPassDebugEnabled() {
return this._spatialBlurPass.debugEnabled;
}
set spatialBlurPassDebugEnabled(enabled) {
if (!this._spatialBlurPass) {
return;
}
if (enabled && !this.allowDebugPasses) {
Logger.Warn("Can't enable spatial blur debug view without setting allowDebugPasses to true.");
return;
}
if (enabled === this._spatialBlurPass.debugEnabled) {
return;
}
this._spatialBlurPass.debugEnabled = enabled;
if (enabled) {
this._enableEffect(this._spatialBlurPass.debugPassName, this.cameras);
}
else {
this._disableEffect(this._spatialBlurPass.debugPassName, this.cameras);
}
}
/**
* Display the debug view for the shadows accumulated over time.
*/
get accumulationPassDebugEnabled() {
return this._accumulationPass?.debugEnabled;
}
set accumulationPassDebugEnabled(enabled) {
if (!this._accumulationPass) {
return;
}
if (enabled && !this.allowDebugPasses) {
Logger.Warn("Can't enable accumulation pass debug view without setting allowDebugPasses to true.");
return;
}
if (enabled === this._accumulationPass.debugEnabled) {
return;
}
this._accumulationPass.debugEnabled = enabled;
if (enabled) {
this._enableEffect(this._accumulationPass.debugPassName, this.cameras);
}
else {
this._disableEffect(this._accumulationPass.debugPassName, this.cameras);
}
}
/**
* Add a mesh to be used for shadow-casting in the IBL shadow pipeline.
* These meshes will be written to the voxel grid.
* @param mesh A mesh or list of meshes that you want to cast shadows
*/
addShadowCastingMesh(mesh) {
if (Array.isArray(mesh)) {
for (const m of mesh) {
if (m && this._shadowCastingMeshes.indexOf(m) === -1) {
this._shadowCastingMeshes.push(m);
}
}
}
else {
if (mesh && this._shadowCastingMeshes.indexOf(mesh) === -1) {
this._shadowCastingMeshes.push(mesh);
}
}
}
/**
* Remove a mesh from the shadow-casting list. The mesh will no longer be written
* to the voxel grid and will not cast shadows.
* @param mesh The mesh or list of meshes that you don't want to cast shadows.
*/
removeShadowCastingMesh(mesh) {
if (Array.isArray(mesh)) {
for (const m of mesh) {
const index = this._shadowCastingMeshes.indexOf(m);
if (index !== -1) {
this._shadowCastingMeshes.splice(index, 1);
}
}
}
else {
const index = this._shadowCastingMeshes.indexOf(mesh);
if (index !== -1) {
this._shadowCastingMeshes.splice(index, 1);
}
}
}
/**
* Clear the list of shadow-casting meshes. This will remove all meshes from the list
*/
clearShadowCastingMeshes() {
this._shadowCastingMeshes.length = 0;
}
/**
* The exponent of the resolution of the voxel shadow grid. Higher resolutions will result in sharper
* shadows but are more expensive to compute and require more memory.
* The resolution is calculated as 2 to the power of this number.
*/
get resolutionExp() {
return this._voxelRenderer.voxelResolutionExp;
}
set resolutionExp(newResolution) {
if (newResolution === this._voxelRenderer.voxelResolutionExp) {
return;
}
if (this._voxelRenderer.isVoxelizationInProgress()) {
Logger.Warn("Can't change the resolution of the voxel grid while voxelization is in progress.");
return;
}
this._voxelRenderer.voxelResolutionExp = Math.max(1, Math.min(newResolution, 8));
this._accumulationPass.reset = true;
}
/**
* The number of different directions to sample during the voxel tracing pass
*/
get sampleDirections() {
return this._voxelTracingPass?.sampleDirections;
}
/**
* The number of different directions to sample during the voxel tracing pass
*/
set sampleDirections(value) {
if (!this._voxelTracingPass) {
return;
}
this._voxelTracingPass.sampleDirections = value;
}
/**
* The decree to which the shadows persist between frames. 0.0 is no persistence, 1.0 is full persistence.
**/
get shadowRemanence() {
return this._accumulationPass?.remanence;
}
/**
* The decree to which the shadows persist between frames. 0.0 is no persistence, 1.0 is full persistence.
**/
set shadowRemanence(value) {
if (!this._accumulationPass) {
return;
}
this._accumulationPass.remanence = value;
}
/**
* The global Y-axis rotation of the IBL for shadows. This should match the Y-rotation of the environment map applied to materials, skybox, etc.
*/
get envRotation() {
return this._voxelTracingPass?.envRotation;
}
/**
* The global Y-axis rotation of the IBL for shadows. This should match the Y-rotation of the environment map applied to materials, skybox, etc.
*/
set envRotation(value) {
if (!this._voxelTracingPass) {
return;
}
this._voxelTracingPass.envRotation = value;
this._accumulationPass.reset = true;
}
/**
* Allow debug passes to be enabled. Default is false.
*/
get allowDebugPasses() {
return this._allowDebugPasses;
}
/**
* Allow debug passes to be enabled. Default is false.
*/
set allowDebugPasses(value) {
if (this._allowDebugPasses === value) {
return;
}
this._allowDebugPasses = value;
if (value && this.scene.iblCdfGenerator) {
if (this.scene.iblCdfGenerator.isReady()) {
this._createDebugPasses();
}
else {
this.scene.iblCdfGenerator.onGeneratedObservable.addOnce(() => {
this._createDebugPasses();
});
}
}
else {
this._disposeDebugPasses();
}
}
/**
* Support test.
*/
static get IsSupported() {
const engine = EngineStore.LastCreatedEngine;
if (!engine) {
return false;
}
return engine._features.supportIBLShadows;
}
/**
* Toggle the shadow tracing on or off
* @param enabled Toggle the shadow tracing on or off
*/
toggleShadow(enabled) {
this._enabled = enabled;
this._voxelTracingPass.enabled = enabled;
this._spatialBlurPass.enabled = enabled;
this._accumulationPass.enabled = enabled;
for (const mat of this._materialsWithRenderPlugin) {
if (mat.pluginManager) {
const plugin = mat.pluginManager.getPlugin(IBLShadowsPluginMaterial.Name);
plugin.isEnabled = enabled;
}
}
this._setPluginParameters();
}
/**
* Trigger the scene to be re-voxelized. This should be run when any shadow-casters have been added, removed or moved.
*/
updateVoxelization() {
if (this._shadowCastingMeshes.length === 0) {
Logger.Warn("IBL Shadows: updateVoxelization called with no shadow-casting meshes to voxelize.");
return;
}
this._voxelRenderer.updateVoxelGrid(this._shadowCastingMeshes);
this._voxelRenderer.onVoxelizationCompleteObservable.addOnce(() => {
this.onVoxelizationCompleteObservable.notifyObservers();
});
this._updateSsShadowParams();
}
/**
* Trigger the scene bounds of shadow-casters to be calculated. This is the world size that the voxel grid will cover and will always be a cube.
*/
updateSceneBounds() {
const bounds = {
min: new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE),
max: new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE),
};
for (const mesh of this._shadowCastingMeshes) {
const localBounds = mesh.getHierarchyBoundingVectors(true);
bounds.min = Vector3.Minimize(bounds.min, localBounds.min);
bounds.max = Vector3.Maximize(bounds.max, localBounds.max);
}
const size = bounds.max.subtract(bounds.min);
this.voxelGridSize = Math.max(size.x, size.y, size.z);
if (this._shadowCastingMeshes.length === 0 || !isFinite(this.voxelGridSize) || this.voxelGridSize === 0) {
Logger.Warn("IBL Shadows: Scene size is invalid. Can't update bounds.");
this.voxelGridSize = 1.0;
return;
}
const halfSize = this.voxelGridSize / 2.0;
const centre = bounds.max.add(bounds.min).multiplyByFloats(-0.5, -0.5, -0.5);
const invWorldScaleMatrix = Matrix.Compose(new Vector3(1.0 / halfSize, 1.0 / halfSize, 1.0 / halfSize), new Quaternion(), new Vector3(0, 0, 0));
const invTranslationMatrix = Matrix.Compose(new Vector3(1.0, 1.0, 1.0), new Quaternion(), centre);
invTranslationMatrix.multiplyToRef(invWorldScaleMatrix, invWorldScaleMatrix);
this._voxelTracingPass.setWorldScaleMatrix(invWorldScaleMatrix);
this._voxelRenderer.setWorldScaleMatrix(invWorldScaleMatrix);
// Set world scale for spatial blur.
this._spatialBlurPass.setWorldScale(halfSize * 2.0);
this._updateSsShadowParams();
}
/**
* @param name The rendering pipeline name
* @param scene The scene linked to this pipeline
* @param options Options to configure the pipeline
* @param cameras Cameras to apply the pipeline to.
*/
constructor(name, scene, options = {}, cameras) {
super(scene.getEngine(), name);
this._allowDebugPasses = false;
this._debugPasses = [];
this._shadowCastingMeshes = [];
this._shadowOpacity = 0.8;
this._enabled = true;
this._coloredShadows = false;
this._materialsWithRenderPlugin = [];
/**
* Observable that triggers when the shadow renderer is ready
*/
this.onShadowTextureReadyObservable = new Observable();
/**
* Observable that triggers when a new IBL is set and the importance sampling is ready
*/
this.onNewIblReadyObservable = new Observable();
/**
* Observable that triggers when the voxelization is complete
*/
this.onVoxelizationCompleteObservable = new Observable();
/**
* The current world-space size of that the voxel grid covers in the scene.
*/
this.voxelGridSize = 1.0;
this._renderSizeFactor = 1.0;
this._gbufferDebugEnabled = false;
this._gBufferDebugSizeParams = new Vector4(0.0, 0.0, 0.0, 0.0);
this.scene = scene;
this._cameras = cameras || [scene.activeCamera];
// Create the dummy textures to be used when the pipeline is not ready
const blackPixels = new Uint8Array([0, 0, 0, 255]);
this._dummyTexture2d = new RawTexture(blackPixels, 1, 1, Engine.TEXTUREFORMAT_RGBA, scene, false);
this._dummyTexture3d = new RawTexture3D(blackPixels, 1, 1, 1, Engine.TEXTUREFORMAT_RGBA, scene, false);
// Setup the geometry buffer target formats
const textureTypesAndFormats = {};
textureTypesAndFormats[GeometryBufferRenderer.SCREENSPACE_DEPTH_TEXTURE_TYPE] = { textureFormat: 6, textureType: 1 };
textureTypesAndFormats[GeometryBufferRenderer.VELOCITY_LINEAR_TEXTURE_TYPE] = { textureFormat: 7, textureType: 2 };
textureTypesAndFormats[GeometryBufferRenderer.POSITION_TEXTURE_TYPE] = { textureFormat: 5, textureType: 2 };
textureTypesAndFormats[GeometryBufferRenderer.NORMAL_TEXTURE_TYPE] = { textureFormat: 5, textureType: 2 };
const geometryBufferRenderer = scene.enableGeometryBufferRenderer(undefined, 14, textureTypesAndFormats);
if (!geometryBufferRenderer) {
Logger.Error("Geometry buffer renderer is required for IBL shadows to work.");
return;
}
this._geometryBufferRenderer = geometryBufferRenderer;
this._geometryBufferRenderer.enableScreenspaceDepth = true;
this._geometryBufferRenderer.enableVelocityLinear = true;
this._geometryBufferRenderer.enablePosition = true;
this._geometryBufferRenderer.enableNormal = true;
this._geometryBufferRenderer.generateNormalsInWorldSpace = true;
this.scene.enableIblCdfGenerator();
this.shadowOpacity = options.shadowOpacity || 0.8;
this._voxelRenderer = new _IblShadowsVoxelRenderer(this.scene, this, options ? options.resolutionExp : 6, options.triPlanarVoxelization !== undefined ? options.triPlanarVoxelization : true);
this._voxelTracingPass = new _IblShadowsVoxelTracingPass(this.scene, this);
this._spatialBlurPass = new _IblShadowsSpatialBlurPass(this.scene, this);
this._accumulationPass = new _IblShadowsAccumulationPass(this.scene, this);
this._accumulationPass.onReadyObservable.addOnce(() => {
this.onShadowTextureReadyObservable.notifyObservers();
});
this.sampleDirections = options.sampleDirections || 2;
this.voxelShadowOpacity = options.voxelShadowOpacity ?? 1.0;
this.envRotation = options.envRotation ?? 0.0;
this.shadowRenderSizeFactor = options.shadowRenderSizeFactor || 1.0;
this.ssShadowOpacity = options.ssShadowsEnabled === undefined || options.ssShadowsEnabled ? 1.0 : 0.0;
this.ssShadowDistanceScale = options.ssShadowDistanceScale || 1.25;
this.ssShadowSampleCount = options.ssShadowSampleCount || 16;
this.ssShadowStride = options.ssShadowStride || 8;
this.ssShadowThicknessScale = options.ssShadowThicknessScale || 1.0;
this.shadowRemanence = options.shadowRemanence ?? 0.75;
this._noiseTexture = new Texture("https://assets.babylonjs.com/textures/blue_noise/blue_noise_rgb.png", this.scene, false, true, 1);
scene.postProcessRenderPipelineManager.addPipeline(this);
this.scene.onActiveCameraChanged.add(this._listenForCameraChanges.bind(this));
this.scene.onBeforeRenderObservable.add(this._updateBeforeRender.bind(this));
this._listenForCameraChanges();
this.scene.getEngine().onResizeObservable.add(this._handleResize.bind(this));
// Assigning the shadow texture to the materials needs to be done after the RT's are created.
if (this.scene.iblCdfGenerator) {
this.scene.iblCdfGenerator.onGeneratedObservable.add(() => {
this._setPluginParameters();
this.onNewIblReadyObservable.notifyObservers();
});
}
}
_handleResize() {
this._voxelRenderer.resize();
this._voxelTracingPass.resize(this.shadowRenderSizeFactor);
this._spatialBlurPass.resize(this.shadowRenderSizeFactor);
this._accumulationPass.resize(this.shadowRenderSizeFactor);
this._setPluginParameters();
}
_getGBufferDebugPass() {
if (this._gbufferDebugPass) {
return this._gbufferDebugPass;
}
const isWebGPU = this.engine.isWebGPU;
const textureNames = ["depthSampler", "normalSampler", "positionSampler", "velocitySampler"];
const options = {
width: this.scene.getEngine().getRenderWidth(),
height: this.scene.getEngine().getRenderHeight(),
samplingMode: 1,
engine: this.scene.getEngine(),
textureType: 0,
textureFormat: 5,
uniforms: ["sizeParams"],
samplers: textureNames,
reusable: false,
shaderLanguage: isWebGPU ? 1 /* ShaderLanguage.WGSL */ : 0 /* ShaderLanguage.GLSL */,
extraInitializations: (useWebGPU, list) => {
if (useWebGPU) {
list.push(import("../../ShadersWGSL/iblShadowGBufferDebug.fragment.js"));
}
else {
list.push(import("../../Shaders/iblShadowGBufferDebug.fragment.js"));
}
},
};
this._gbufferDebugPass = new PostProcess("iblShadowGBufferDebug", "iblShadowGBufferDebug", options);
if (this.engine.isWebGPU) {
this._gbufferDebugPass.samples = this.engine.currentSampleCount ?? 1;
}
this._gbufferDebugPass.autoClear = false;
this._gbufferDebugPass.onApplyObservable.add((effect) => {
const depthIndex = this._geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.SCREENSPACE_DEPTH_TEXTURE_TYPE);
effect.setTexture("depthSampler", this._geometryBufferRenderer.getGBuffer().textures[depthIndex]);
const normalIndex = this._geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.NORMAL_TEXTURE_TYPE);
effect.setTexture("normalSampler", this._geometryBufferRenderer.getGBuffer().textures[normalIndex]);
const positionIndex = this._geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.POSITION_TEXTURE_TYPE);
effect.setTexture("positionSampler", this._geometryBufferRenderer.getGBuffer().textures[positionIndex]);
const velocityIndex = this._geometryBufferRenderer.getTextureIndex(GeometryBufferRenderer.VELOCITY_LINEAR_TEXTURE_TYPE);
effect.setTexture("velocitySampler", this._geometryBufferRenderer.getGBuffer().textures[velocityIndex]);
effect.setVector4("sizeParams", this._gBufferDebugSizeParams);
if (this.scene.activeCamera) {
effect.setFloat("maxDepth", this.scene.activeCamera.maxZ);
}
});
return this._gbufferDebugPass;
}
_createDebugPasses() {
if (this.scene.iblCdfGenerator) {
this._debugPasses = [{ pass: this.scene.iblCdfGenerator.getDebugPassPP(), enabled: this.cdfDebugEnabled }];
}
else {
this._debugPasses = [];
}
this._debugPasses.push({ pass: this._voxelRenderer.getDebugPassPP(), enabled: this.voxelDebugEnabled }, { pass: this._voxelTracingPass.getDebugPassPP(), enabled: this.voxelTracingDebugEnabled }, { pass: this._spatialBlurPass.getDebugPassPP(), enabled: this.spatialBlurPassDebugEnabled }, { pass: this._accumulationPass.getDebugPassPP(), enabled: this.accumulationPassDebugEnabled }, { pass: this._getGBufferDebugPass(), enabled: this.gbufferDebugEnabled });
for (let i = 0; i < this._debugPasses.length; i++) {
if (!this._debugPasses[i].pass) {
continue;
}
this.addEffect(new PostProcessRenderEffect(this.scene.getEngine(), this._debugPasses[i].pass.name, () => {
return this._debugPasses[i].pass;
}, true));
}
const cameras = this.cameras.slice();
this.scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this.name, this.cameras);
this.scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(this.name, cameras);
for (let i = 0; i < this._debugPasses.length; i++) {
if (!this._debugPasses[i].pass) {
continue;
}
if (this._debugPasses[i].enabled) {
this._enableEffect(this._debugPasses[i].pass.name, this.cameras);
}
else {
this._disableEffect(this._debugPasses[i].pass.name, this.cameras);
}
}
}
_disposeEffectPasses() {
this.scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this.name, this.cameras);
this._disposeDebugPasses();
this._reset();
}
_disposeDebugPasses() {
for (let i = 0; i < this._debugPasses.length; i++) {
this._disableEffect(this._debugPasses[i].pass.name, this.cameras);
this._debugPasses[i].pass.dispose();
}
this._debugPasses = [];
}
_updateDebugPasses() {
let count = 0;
if (this._gbufferDebugEnabled) {
count++;
}
if (this.cdfDebugEnabled) {
count++;
}
if (this.voxelDebugEnabled) {
count++;
}
if (this.voxelTracingDebugEnabled) {
count++;
}
if (this.spatialBlurPassDebugEnabled) {
count++;
}
if (this.accumulationPassDebugEnabled) {
count++;
}
const rows = Math.ceil(Math.sqrt(count));
const cols = Math.ceil(count / rows);
const width = 1.0 / cols;
const height = 1.0 / rows;
let x = 0;
let y = 0;
if (this.gbufferDebugEnabled) {
this._gBufferDebugSizeParams.set(x, y, cols, rows);
x -= width;
if (x <= -1) {
x = 0;
y -= height;
}
}
if (this.cdfDebugEnabled && this.scene.iblCdfGenerator) {
this.scene.iblCdfGenerator.setDebugDisplayParams(x, y, cols, rows);
x -= width;
if (x <= -1) {
x = 0;
y -= height;
}
}
if (this.voxelDebugEnabled) {
this._voxelRenderer.setDebugDisplayParams(x, y, cols, rows);
x -= width;
if (x <= -1) {
x = 0;
y -= height;
}
}
if (this.voxelTracingDebugEnabled) {
this._voxelTracingPass.setDebugDisplayParams(x, y, cols, rows);
x -= width;
if (x <= -1) {
x = 0;
y -= height;
}
}
if (this.spatialBlurPassDebugEnabled) {
this._spatialBlurPass.setDebugDisplayParams(x, y, cols, rows);
x -= width;
if (x <= -1) {
x = 0;
y -= height;
}
}
if (this.accumulationPassDebugEnabled) {
this._accumulationPass.setDebugDisplayParams(x, y, cols, rows);
x -= width;
if (x <= -1) {
x = 0;
y -= height;
}
}
}
/**
* Update the SS shadow max distance and thickness based on the voxel grid size and resolution.
* The max distance should be just a little larger than the world size of a single voxel.
*/
_updateSsShadowParams() {
this._voxelTracingPass.sssMaxDist = (this._sssMaxDistScale * this.voxelGridSize) / (1 << this.resolutionExp);
this._voxelTracingPass.sssThickness = this._sssThicknessScale * 0.005 * this.voxelGridSize;
}
/**
* Apply the shadows to a material or array of materials. If no material is provided, all
* materials in the scene will be added.
* @param material Material that will be affected by the shadows. If not provided, all materials of the scene will be affected.
*/
addShadowReceivingMaterial(material) {
if (material) {
if (Array.isArray(material)) {
for (const m of material) {
this._addShadowSupportToMaterial(m);
}
}
else {
this._addShadowSupportToMaterial(material);
}
}
else {
for (const mat of this.scene.materials) {
this._addShadowSupportToMaterial(mat);
}
}
}
/**
* Remove a material from the list of materials that receive shadows. If no material
* is provided, all materials in the scene will be removed.
* @param material The material or array of materials that will no longer receive shadows
*/
removeShadowReceivingMaterial(material) {
if (Array.isArray(material)) {
for (const m of material) {
const matIndex = this._materialsWithRenderPlugin.indexOf(m);
if (matIndex !== -1) {
this._materialsWithRenderPlugin.splice(matIndex, 1);
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
const plugin = m.pluginManager?.getPlugin(IBLShadowsPluginMaterial.Name);
plugin.isEnabled = false;
}
}
}
else {
const matIndex = this._materialsWithRenderPlugin.indexOf(material);
if (matIndex !== -1) {
this._materialsWithRenderPlugin.splice(matIndex, 1);
const plugin = material.pluginManager.getPlugin(IBLShadowsPluginMaterial.Name);
plugin.isEnabled = false;
}
}
}
/**
* Clear the list of materials that receive shadows. This will remove all materials from the list
*/
clearShadowReceivingMaterials() {
for (const mat of this._materialsWithRenderPlugin) {
const plugin = mat.pluginManager?.getPlugin(IBLShadowsPluginMaterial.Name);
if (plugin) {
plugin.isEnabled = false;
}
}
this._materialsWithRenderPlugin.length = 0;
}
_addShadowSupportToMaterial(material) {
if (!(material instanceof PBRBaseMaterial) && !(material instanceof StandardMaterial)) {
return;
}
let plugin = material.pluginManager?.getPlugin(IBLShadowsPluginMaterial.Name);
if (!plugin) {
plugin = new IBLShadowsPluginMaterial(material);
}
if (this._materialsWithRenderPlugin.indexOf(material) !== -1) {
return;
}
if (this._enabled) {
plugin.iblShadowsTexture = this._getAccumulatedTexture().getInternalTexture();
plugin.shadowOpacity = this.shadowOpacity;
}
plugin.isEnabled = this._enabled;
plugin.isColored = this._coloredShadows;
this._materialsWithRenderPlugin.push(material);
}
_setPluginParameters() {
if (!this._enabled) {
return;
}
for (const mat of this._materialsWithRenderPlugin) {
if (mat.pluginManager) {
const plugin = mat.pluginManager.getPlugin(IBLShadowsPluginMaterial.Name);
plugin.iblShadowsTexture = this._getAccumulatedTexture().getInternalTexture();
plugin.shadowOpacity = this.shadowOpacity;
plugin.isColored = this._coloredShadows;
}
}
}
_updateBeforeRender() {
this._updateDebugPasses();
}
_listenForCameraChanges() {
// We want to listen for camera changes and change settings while the camera is moving.
this.scene.activeCamera?.onViewMatrixChangedObservable.add(() => {
this._accumulationPass.isMoving = true;
});
}
/**
* Checks if the IBL shadow pipeline is ready to render shadows
* @returns true if the IBL shadow pipeline is ready to render the shadows
*/
isReady() {
return (this._noiseTexture.isReady() &&
this._voxelRenderer.isReady() &&
this.scene.iblCdfGenerator &&
this.scene.iblCdfGenerator.isReady() &&
(!this._voxelTracingPass || this._voxelTracingPass.isReady()) &&
(!this._spatialBlurPass || this._spatialBlurPass.isReady()) &&
(!this._accumulationPass || this._accumulationPass.isReady()));
}
/**
* Get the class name
* @returns "IBLShadowsRenderPipeline"
*/
getClassName() {
return "IBLShadowsRenderPipeline";
}
/**
* Disposes the IBL shadow pipeline and associated resources
*/
dispose() {
const materials = this._materialsWithRenderPlugin.splice(0);
for (const mat of materials) {
this.removeShadowReceivingMaterial(mat);
}
this._disposeEffectPasses();
this._noiseTexture.dispose();
this._voxelRenderer.dispose();
this._voxelTracingPass.dispose();
this._spatialBlurPass.dispose();
this._accumulationPass.dispose();
this._dummyTexture2d.dispose();
this._dummyTexture3d.dispose();
this.onNewIblReadyObservable.clear();
this.onShadowTextureReadyObservable.clear();
this.onVoxelizationCompleteObservable.clear();
super.dispose();
}
}
//# sourceMappingURL=iblShadowsRenderPipeline.js.map