UNPKG

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

289 lines (288 loc) 12.1 kB
import { CascadedShadowGenerator } from "../../../Lights/Shadows/cascadedShadowGenerator.js"; import { FrameGraphShadowGeneratorTask } from "./shadowGeneratorTask.js"; import { DirectionalLight } from "../../../Lights/directionalLight.js"; import { ThinMinMaxReducer } from "../../../Misc/thinMinMaxReducer.js"; import { FrameGraphPostProcessTask } from "../PostProcesses/postProcessTask.js"; import { textureSizeIsObject } from "../../../Materials/Textures/textureCreationOptions.js"; /** * Task used to generate a cascaded shadow map from a list of objects. */ export class FrameGraphCascadedShadowGeneratorTask extends FrameGraphShadowGeneratorTask { /** * Checks if a shadow generator task is a cascaded shadow generator task. * @param task The task to check. * @returns True if the task is a cascaded shadow generator task, else false. */ static IsCascadedShadowGenerator(task) { return task.numCascades !== undefined; } /** * The number of cascades. */ get numCascades() { return this._numCascades; } set numCascades(value) { if (value === this._numCascades) { return; } this._numCascades = value; this._setupShadowGenerator(); } /** * Gets or sets a value indicating whether the shadow generator should display the cascades. */ get debug() { return this._debug; } set debug(value) { if (value === this._debug) { return; } this._debug = value; if (this._shadowGenerator) { this._shadowGenerator.debug = value; } } /** * Gets or sets a value indicating whether the shadow generator should stabilize the cascades. */ get stabilizeCascades() { return this._stabilizeCascades; } set stabilizeCascades(value) { if (value === this._stabilizeCascades) { return; } this._stabilizeCascades = value; if (this._shadowGenerator) { this._shadowGenerator.stabilizeCascades = value; } } /** * Gets or sets the lambda parameter of the shadow generator. */ get lambda() { return this._lambda; } set lambda(value) { if (value === this._lambda) { return; } this._lambda = value; if (this._shadowGenerator) { this._shadowGenerator.lambda = value; } } /** * Gets or sets the cascade blend percentage. */ get cascadeBlendPercentage() { return this._cascadeBlendPercentage; } set cascadeBlendPercentage(value) { if (value === this._cascadeBlendPercentage) { return; } this._cascadeBlendPercentage = value; if (this._shadowGenerator) { this._shadowGenerator.cascadeBlendPercentage = value; } } /** * Gets or sets a value indicating whether the shadow generator should use depth clamping. */ get depthClamp() { return this._depthClamp; } set depthClamp(value) { if (value === this._depthClamp) { return; } this._depthClamp = value; if (this._shadowGenerator) { this._shadowGenerator.depthClamp = value; } } /** * Gets or sets a value indicating whether the shadow generator should automatically calculate the depth bounds. */ get autoCalcDepthBounds() { return this._autoCalcDepthBounds; } set autoCalcDepthBounds(value) { if (value === this._autoCalcDepthBounds) { return; } this._autoCalcDepthBounds = value; this._currentAutoCalcDepthBoundsCounter = this._autoCalcDepthBoundsRefreshRate; if (!value) { this._shadowGenerator?.setMinMaxDistance(0, 1); } // All passes but the last one are related to min/max reduction and should be enabled/disabled depending on autoCalcDepthBounds value const passes = this.passes; for (let i = 0; i < passes.length - 1; ++i) { passes[i].disabled = !value; } } /** * Defines the refresh rate of the min/max computation used when autoCalcDepthBounds is set to true * Use 0 to compute just once, 1 to compute on every frame, 2 to compute every two frames and so on... */ get autoCalcDepthBoundsRefreshRate() { return this._autoCalcDepthBoundsRefreshRate; } set autoCalcDepthBoundsRefreshRate(value) { this._autoCalcDepthBoundsRefreshRate = value; this._currentAutoCalcDepthBoundsCounter = this._autoCalcDepthBoundsRefreshRate; } /** * Gets or sets the maximum shadow Z value. */ get shadowMaxZ() { return this._shadowMaxZ; } set shadowMaxZ(value) { if (value === this._shadowMaxZ) { return; } this._shadowMaxZ = value; if (this._shadowGenerator) { this._shadowGenerator.shadowMaxZ = value; } } /** * Creates a new shadow generator task. * @param name The name of the task. * @param frameGraph The frame graph the task belongs to. * @param scene The scene to create the shadow generator for. */ constructor(name, frameGraph, scene) { super(name, frameGraph, scene); /** * The type of the depth texture used by the autoCalcDepthBounds feature. */ this.depthTextureType = 0 /* DepthTextureType.NormalizedViewDepth */; this._numCascades = CascadedShadowGenerator.DEFAULT_CASCADES_COUNT; this._debug = false; this._stabilizeCascades = false; this._lambda = 0.5; this._cascadeBlendPercentage = 0.1; this._depthClamp = true; this._autoCalcDepthBounds = false; this._currentAutoCalcDepthBoundsCounter = 0; this._autoCalcDepthBoundsRefreshRate = 1; this._shadowMaxZ = 10000; this._thinMinMaxReducer = new ThinMinMaxReducer(scene); this._thinMinMaxReducer.onAfterReductionPerformed.add((minmax) => { if (!this._shadowGenerator) { return; } const camera = this.camera; let min = minmax.min, max = minmax.max; if (min >= max) { min = 0; max = 1; } else if (camera && this.depthTextureType !== 0 /* DepthTextureType.NormalizedViewDepth */) { if (this.depthTextureType === 2 /* DepthTextureType.ScreenDepth */) { const engine = this._frameGraph.engine; const projectionMatrix = camera.getProjectionMatrix(); const p2z = projectionMatrix.m[10]; const p3z = projectionMatrix.m[14]; if (!engine.isNDCHalfZRange) { // Convert to NDC depth min = min * 2 - 1; max = max * 2 - 1; } // Convert to view depth min = p3z / (min - p2z); max = p3z / (max - p2z); } // Convert to normalized view depth const zNear = camera.minZ; const zFar = camera.maxZ; min = (min - zNear) / (zFar - zNear); max = (max - zNear) / (zFar - zNear); } if (min !== this._shadowGenerator.minDistance || max !== this._shadowGenerator.maxDistance) { this._shadowGenerator.setMinMaxDistance(min, max); } }); } _createShadowGenerator() { if (!(this.light instanceof DirectionalLight)) { throw new Error(`FrameGraphCascadedShadowGeneratorTask ${this.name}: the CSM shadow generator only supports directional lights.`); } this._shadowGenerator = new CascadedShadowGenerator(this.mapSize, this.light, this.useFloat32TextureType, this.camera, this.useRedTextureFormat); this._shadowGenerator.numCascades = this._numCascades; } _setupShadowGenerator() { super._setupShadowGenerator(); const shadowGenerator = this._shadowGenerator; if (shadowGenerator === undefined) { return; } shadowGenerator.debug = this._debug; shadowGenerator.stabilizeCascades = this._stabilizeCascades; shadowGenerator.lambda = this._lambda; shadowGenerator.cascadeBlendPercentage = this._cascadeBlendPercentage; shadowGenerator.depthClamp = this._depthClamp; shadowGenerator.shadowMaxZ = this._shadowMaxZ; } record() { if (this.light === undefined || this.objectList === undefined || this.camera === undefined) { throw new Error(`FrameGraphCascadedShadowGeneratorTask ${this.name}: light, objectList and camera are required`); } if (this.depthTexture !== undefined) { const depthTextureCreationOptions = this._frameGraph.textureManager.getTextureCreationOptions(this.depthTexture); const size = !depthTextureCreationOptions.sizeIsPercentage ? textureSizeIsObject(depthTextureCreationOptions.size) ? depthTextureCreationOptions.size : { width: depthTextureCreationOptions.size, height: depthTextureCreationOptions.size } : this._frameGraph.textureManager.getAbsoluteDimensions(depthTextureCreationOptions.size); const width = size.width; const height = size.height; depthTextureCreationOptions.sizeIsPercentage = false; depthTextureCreationOptions.options.formats = [7]; depthTextureCreationOptions.options.samples = 1; this._thinMinMaxReducer.setTextureDimensions(width, height, this.depthTextureType); const reductionSteps = this._thinMinMaxReducer.reductionSteps; let targetTexture; this._frameGraph.addPass(`${this.name} Before Min Max Reduction`).setExecuteFunc((context) => { context.pushDebugGroup(`Min Max Reduction`); }); for (let i = 0; i < reductionSteps.length - 1; ++i) { const reductionStep = reductionSteps[i]; depthTextureCreationOptions.size = { width: reductionSteps[i + 1].textureWidth, height: reductionSteps[i + 1].textureHeight }; const postProcess = new FrameGraphPostProcessTask(reductionStep.name, this._frameGraph, reductionStep); postProcess.sourceTexture = i == 0 ? this.depthTexture : targetTexture; postProcess.sourceSamplingMode = 1; postProcess.targetTexture = this._frameGraph.textureManager.createRenderTargetTexture(`${this.name} ${reductionStep.name}`, depthTextureCreationOptions); postProcess.record(true); targetTexture = postProcess.outputTexture; } this._frameGraph.addPass(`${this.name} After Min Max Reduction`).setExecuteFunc((context) => { context.popDebugGroup(); if (this._autoCalcDepthBounds && this._currentAutoCalcDepthBoundsCounter >= 0) { if (++this._currentAutoCalcDepthBoundsCounter >= this._autoCalcDepthBoundsRefreshRate) { const minMaxTexture = context.getTextureFromHandle(targetTexture); if (minMaxTexture) { this._thinMinMaxReducer.readMinMax(minMaxTexture); } } this._currentAutoCalcDepthBoundsCounter %= this._autoCalcDepthBoundsRefreshRate; if (this._autoCalcDepthBoundsRefreshRate === 0) { this._currentAutoCalcDepthBoundsCounter = -1; } } }); } super.record(); } dispose() { super.dispose(); this._thinMinMaxReducer.dispose(); } } //# sourceMappingURL=csmShadowGeneratorTask.js.map