diff --git a/packages/dev/core/src/Debug/directionalLightFrustumViewer.ts b/packages/dev/core/src/Debug/directionalLightFrustumViewer.ts index 73227e3e0ca..739c916f2a2 100644 --- a/packages/dev/core/src/Debug/directionalLightFrustumViewer.ts +++ b/packages/dev/core/src/Debug/directionalLightFrustumViewer.ts @@ -1,3 +1,4 @@ +import type { Nullable } from "core/types"; import type { Camera } from "../Cameras/camera"; import type { DirectionalLight } from "../Lights/directionalLight"; import { StandardMaterial } from "../Materials/standardMaterial"; @@ -9,6 +10,7 @@ import { Mesh } from "../Meshes/mesh"; import { VertexData } from "../Meshes/mesh.vertexData"; import { TransformNode } from "../Meshes/transformNode"; import type { Scene } from "../scene"; +import { Constants } from "core/Engines/constants"; /** * Class used to render a debug view of the frustum for a directional light @@ -18,7 +20,7 @@ import type { Scene } from "../scene"; export class DirectionalLightFrustumViewer { private _scene: Scene; private _light: DirectionalLight; - private _camera: Camera; + private _camera: Nullable; private _inverseViewMatrix: Matrix; private _visible: boolean; @@ -101,7 +103,7 @@ export class DirectionalLightFrustumViewer { * @param light directional light to display the frustum for * @param camera camera used to retrieve the minZ / maxZ values if the shadowMinZ/shadowMaxZ values of the light are not setup */ - constructor(light: DirectionalLight, camera: Camera) { + constructor(light: DirectionalLight, camera: Nullable = null) { this._scene = light.getScene(); this._light = light; this._camera = camera; @@ -158,8 +160,16 @@ export class DirectionalLightFrustumViewer { this._oldMinZ = this._light.shadowMinZ; this._oldMaxZ = this._light.shadowMaxZ; - TmpVectors.Vector3[0].set(this._light.orthoLeft, this._light.orthoBottom, this._light.shadowMinZ !== undefined ? this._light.shadowMinZ : this._camera.minZ); // min light extents - TmpVectors.Vector3[1].set(this._light.orthoRight, this._light.orthoTop, this._light.shadowMaxZ !== undefined ? this._light.shadowMaxZ : this._camera.maxZ); // max light extents + TmpVectors.Vector3[0].set( + this._light.orthoLeft, + this._light.orthoBottom, + this._light.shadowMinZ !== undefined ? this._light.shadowMinZ : (this._camera?.minZ ?? Constants.ShadowMinZ) + ); // min light extents + TmpVectors.Vector3[1].set( + this._light.orthoRight, + this._light.orthoTop, + this._light.shadowMaxZ !== undefined ? this._light.shadowMaxZ : (this._camera?.maxZ ?? Constants.ShadowMaxZ) + ); // max light extents const invLightView = this._getInvertViewMatrix(); diff --git a/packages/dev/core/src/Engines/Extensions/engine.multiRender.ts b/packages/dev/core/src/Engines/Extensions/engine.multiRender.ts index 00b0185a6d3..a39c723ec0f 100644 --- a/packages/dev/core/src/Engines/Extensions/engine.multiRender.ts +++ b/packages/dev/core/src/Engines/Extensions/engine.multiRender.ts @@ -568,4 +568,5 @@ ThinEngine.prototype.resolveMultiFramebuffer = function (texture: RenderTargetWr } gl.drawBuffers(attachments); + gl.bindFramebuffer(this._gl.FRAMEBUFFER, rtWrapper._MSAAFramebuffer); }; diff --git a/packages/dev/core/src/Engines/constants.ts b/packages/dev/core/src/Engines/constants.ts index 2b942196d63..ae6a51a7ca9 100644 --- a/packages/dev/core/src/Engines/constants.ts +++ b/packages/dev/core/src/Engines/constants.ts @@ -948,4 +948,13 @@ export class Constants { * Additional matrix weights (for bones) */ public static MatricesWeightsExtraKind = "matricesWeightsExtra"; + + /** + * The default minZ value for the near plane of a frustum light + */ + public static ShadowMinZ = 0; + /** + * The default maxZ value for the far plane of a frustum light + */ + public static ShadowMaxZ = 10000; } diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/blackAndWhitePostProcessBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/blackAndWhitePostProcessBlock.ts index e882283a0ce..407d42c5eb9 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/blackAndWhitePostProcessBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/blackAndWhitePostProcessBlock.ts @@ -94,19 +94,10 @@ export class NodeRenderGraphBlackAndWhitePostProcessBlock extends NodeRenderGrap protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputTexture; // the value of the output connection point is the "output" texture of the task - const sourceConnectedPoint = this.source.connectedPoint; - if (sourceConnectedPoint) { - this._frameGraphTask.sourceTexture = sourceConnectedPoint.value as FrameGraphTextureHandle; - } - - const destinationConnectedPoint = this.destination.connectedPoint; - if (destinationConnectedPoint) { - this._frameGraphTask.destinationTexture = destinationConnectedPoint.value as FrameGraphTextureHandle; - } + this._frameGraphTask.sourceTexture = this.source.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.destinationTexture = this.destination.connectedPoint?.value as FrameGraphTextureHandle; } protected override _dumpPropertiesCode() { diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/bloomPostProcessBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/bloomPostProcessBlock.ts index d46f4bbaa58..52f49e47687 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/bloomPostProcessBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/bloomPostProcessBlock.ts @@ -151,19 +151,10 @@ export class NodeRenderGraphBloomPostProcessBlock extends NodeRenderGraphBlock { protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputTexture; // the value of the output connection point is the "output" texture of the task - const sourceConnectedPoint = this.source.connectedPoint; - if (sourceConnectedPoint) { - this._frameGraphTask.sourceTexture = sourceConnectedPoint.value as FrameGraphTextureHandle; - } - - const destinationConnectedPoint = this.destination.connectedPoint; - if (destinationConnectedPoint) { - this._frameGraphTask.destinationTexture = destinationConnectedPoint.value as FrameGraphTextureHandle; - } + this._frameGraphTask.sourceTexture = this.source.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.destinationTexture = this.destination.connectedPoint?.value as FrameGraphTextureHandle; } protected override _dumpPropertiesCode() { diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/blurPostProcessBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/blurPostProcessBlock.ts index 7d0a5467d78..a09bf5f9a20 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/blurPostProcessBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/blurPostProcessBlock.ts @@ -105,19 +105,10 @@ export class NodeRenderGraphBlurPostProcessBlock extends NodeRenderGraphBlock { protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputTexture; // the value of the output connection point is the "output" texture of the task - const sourceConnectedPoint = this.source.connectedPoint; - if (sourceConnectedPoint) { - this._frameGraphTask.sourceTexture = sourceConnectedPoint.value as FrameGraphTextureHandle; - } - - const destinationConnectedPoint = this.destination.connectedPoint; - if (destinationConnectedPoint) { - this._frameGraphTask.destinationTexture = destinationConnectedPoint.value as FrameGraphTextureHandle; - } + this._frameGraphTask.sourceTexture = this.source.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.destinationTexture = this.destination.connectedPoint?.value as FrameGraphTextureHandle; } protected override _dumpPropertiesCode() { diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/circleOfConfusionPostProcessBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/circleOfConfusionPostProcessBlock.ts index 6e6b368dde2..7204e8bf3fe 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/circleOfConfusionPostProcessBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/circleOfConfusionPostProcessBlock.ts @@ -154,29 +154,12 @@ export class NodeRenderGraphCircleOfConfusionPostProcessBlock extends NodeRender protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputTexture; - const sourceConnectedPoint = this.source.connectedPoint; - if (sourceConnectedPoint) { - this._frameGraphTask.sourceTexture = sourceConnectedPoint.value as FrameGraphTextureHandle; - } - - const geomViewDepthConnectedPoint = this.geomViewDepth.connectedPoint; - if (geomViewDepthConnectedPoint) { - this._frameGraphTask.depthTexture = geomViewDepthConnectedPoint.value as FrameGraphTextureHandle; - } - - const destinationConnectedPoint = this.destination.connectedPoint; - if (destinationConnectedPoint) { - this._frameGraphTask.destinationTexture = destinationConnectedPoint.value as FrameGraphTextureHandle; - } - - const cameraConnectedPoint = this.camera.connectedPoint; - if (cameraConnectedPoint) { - this._frameGraphTask.camera = cameraConnectedPoint.value as Camera; - } + this._frameGraphTask.sourceTexture = this.source.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.depthTexture = this.geomViewDepth.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.destinationTexture = this.destination.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.camera = this.camera.connectedPoint?.value as Camera; } protected override _dumpPropertiesCode() { diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/depthOfFieldPostProcessBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/depthOfFieldPostProcessBlock.ts index 28cdf453c98..be09781b3e7 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/depthOfFieldPostProcessBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/depthOfFieldPostProcessBlock.ts @@ -201,29 +201,12 @@ export class NodeRenderGraphDepthOfFieldPostProcessBlock extends NodeRenderGraph protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputTexture; - const sourceConnectedPoint = this.source.connectedPoint; - if (sourceConnectedPoint) { - this._frameGraphTask.sourceTexture = sourceConnectedPoint.value as FrameGraphTextureHandle; - } - - const geomViewDepthConnectedPoint = this.geomViewDepth.connectedPoint; - if (geomViewDepthConnectedPoint) { - this._frameGraphTask.depthTexture = geomViewDepthConnectedPoint.value as FrameGraphTextureHandle; - } - - const destinationConnectedPoint = this.destination.connectedPoint; - if (destinationConnectedPoint) { - this._frameGraphTask.destinationTexture = destinationConnectedPoint.value as FrameGraphTextureHandle; - } - - const cameraConnectedPoint = this.camera.connectedPoint; - if (cameraConnectedPoint) { - this._frameGraphTask.camera = cameraConnectedPoint.value as Camera; - } + this._frameGraphTask.sourceTexture = this.source.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.depthTexture = this.geomViewDepth.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.destinationTexture = this.destination.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.camera = this.camera.connectedPoint?.value as Camera; } protected override _dumpPropertiesCode() { diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/extractHighlightsPostProcessBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/extractHighlightsPostProcessBlock.ts index 720928a2a13..eb29375b823 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/extractHighlightsPostProcessBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/extractHighlightsPostProcessBlock.ts @@ -94,19 +94,10 @@ export class NodeRenderGraphExtractHighlightsPostProcessBlock extends NodeRender protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputTexture; // the value of the output connection point is the "output" texture of the task - const sourceConnectedPoint = this.source.connectedPoint; - if (sourceConnectedPoint) { - this._frameGraphTask.sourceTexture = sourceConnectedPoint.value as FrameGraphTextureHandle; - } - - const destinationConnectedPoint = this.destination.connectedPoint; - if (destinationConnectedPoint) { - this._frameGraphTask.destinationTexture = destinationConnectedPoint.value as FrameGraphTextureHandle; - } + this._frameGraphTask.sourceTexture = this.source.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.destinationTexture = this.destination.connectedPoint?.value as FrameGraphTextureHandle; } protected override _dumpPropertiesCode() { diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/baseObjectRendererBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/baseObjectRendererBlock.ts index e9048e2380e..deff27ea437 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/baseObjectRendererBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/baseObjectRendererBlock.ts @@ -1,5 +1,4 @@ import type { - NodeRenderGraphConnectionPoint, Scene, NodeRenderGraphBuildState, FrameGraph, @@ -7,11 +6,14 @@ import type { FrameGraphObjectList, Camera, FrameGraphObjectRendererTask, + NodeRenderGraphResourceContainerBlock, + FrameGraphShadowGeneratorTask, // eslint-disable-next-line import/no-internal-modules } from "core/index"; import { NodeRenderGraphBlock } from "../../nodeRenderGraphBlock"; import { NodeRenderGraphBlockConnectionPointTypes } from "../../Types/nodeRenderGraphTypes"; import { editableInPropertyPage, PropertyTypeForEdition } from "../../../../Decorators/nodeDecorator"; +import { NodeRenderGraphConnectionPoint } from "../../nodeRenderGraphBlockConnectionPoint"; /** * @internal @@ -40,13 +42,20 @@ export class NodeRenderGraphBaseObjectRendererBlock extends NodeRenderGraphBlock this.registerInput("camera", NodeRenderGraphBlockConnectionPointTypes.Camera); this.registerInput("objects", NodeRenderGraphBlockConnectionPointTypes.ObjectList); this.registerInput("dependencies", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + this.registerInput("shadowGenerators", NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator, true); this.registerOutput("output", NodeRenderGraphBlockConnectionPointTypes.BasedOnInput); this.registerOutput("outputDepth", NodeRenderGraphBlockConnectionPointTypes.BasedOnInput); this.destination.addAcceptedConnectionPointTypes(NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBufferDepthStencil); this.depth.addAcceptedConnectionPointTypes(NodeRenderGraphBlockConnectionPointTypes.TextureDepthStencilAttachment); - this.dependencies.addAcceptedConnectionPointTypes(NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer); + this.dependencies.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ResourceContainer + ); + this.shadowGenerators.addAcceptedConnectionPointTypes(NodeRenderGraphBlockConnectionPointTypes.ResourceContainer); this.output._typeConnectionSource = this.destination; this.outputDepth._typeConnectionSource = this.depth; @@ -115,6 +124,13 @@ export class NodeRenderGraphBaseObjectRendererBlock extends NodeRenderGraphBlock return this._inputs[4]; } + /** + * Gets the shadowGenerators input component + */ + public get shadowGenerators(): NodeRenderGraphConnectionPoint { + return this._inputs[5]; + } + /** * Gets the output component */ @@ -132,37 +148,45 @@ export class NodeRenderGraphBaseObjectRendererBlock extends NodeRenderGraphBlock protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputTexture; // the value of the output connection point is the "output" texture of the task this.outputDepth.value = this._frameGraphTask.outputDepthTexture; // the value of the outputDepth connection point is the "outputDepth" texture of the task - const destinationConnectedPoint = this.destination.connectedPoint; - if (destinationConnectedPoint) { - this._frameGraphTask.destinationTexture = destinationConnectedPoint.value as FrameGraphTextureHandle; - } - - const depthConnectedPoint = this.depth.connectedPoint; - if (depthConnectedPoint) { - this._frameGraphTask.depthTexture = depthConnectedPoint.value as FrameGraphTextureHandle; - } - - const cameraConnectedPoint = this.camera.connectedPoint; - if (cameraConnectedPoint) { - this._frameGraphTask.camera = cameraConnectedPoint.value as Camera; - } - - const objectsConnectedPoint = this.objects.connectedPoint; - if (objectsConnectedPoint) { - this._frameGraphTask.objectList = objectsConnectedPoint.value as FrameGraphObjectList; - } + this._frameGraphTask.destinationTexture = this.destination.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.depthTexture = this.depth.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.camera = this.camera.connectedPoint?.value as Camera; + this._frameGraphTask.objectList = this.objects.connectedPoint?.value as FrameGraphObjectList; this._frameGraphTask.dependencies = []; const dependenciesConnectedPoint = this.dependencies.connectedPoint; if (dependenciesConnectedPoint) { - this._frameGraphTask.dependencies[0] = dependenciesConnectedPoint.value as FrameGraphTextureHandle; + if (dependenciesConnectedPoint.type === NodeRenderGraphBlockConnectionPointTypes.ResourceContainer) { + const container = dependenciesConnectedPoint.ownerBlock as NodeRenderGraphResourceContainerBlock; + container.inputs.forEach((input) => { + if (input.connectedPoint && input.connectedPoint.value !== undefined && NodeRenderGraphConnectionPoint.IsTextureHandle(input.connectedPoint.value)) { + this._frameGraphTask.dependencies!.push(input.connectedPoint.value as FrameGraphTextureHandle); + } + }); + } else { + this._frameGraphTask.dependencies[0] = dependenciesConnectedPoint.value as FrameGraphTextureHandle; + } + } + + this._frameGraphTask.shadowGenerators = []; + + const shadowGeneratorsConnectedPoint = this.shadowGenerators.connectedPoint; + if (shadowGeneratorsConnectedPoint) { + if (shadowGeneratorsConnectedPoint.type === NodeRenderGraphBlockConnectionPointTypes.ResourceContainer) { + const container = shadowGeneratorsConnectedPoint.ownerBlock as NodeRenderGraphResourceContainerBlock; + container.inputs.forEach((input) => { + if (input.connectedPoint && input.connectedPoint.value !== undefined && NodeRenderGraphConnectionPoint.IsShadowGenerator(input.connectedPoint.value)) { + this._frameGraphTask.shadowGenerators!.push(input.connectedPoint.value as FrameGraphShadowGeneratorTask); + } + }); + } else { + this._frameGraphTask.shadowGenerators[0] = shadowGeneratorsConnectedPoint.value as FrameGraphShadowGeneratorTask; + } } } diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/baseShadowGeneratorBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/baseShadowGeneratorBlock.ts new file mode 100644 index 00000000000..8349fb62335 --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/baseShadowGeneratorBlock.ts @@ -0,0 +1,267 @@ +// eslint-disable-next-line import/no-internal-modules +import type { NodeRenderGraphConnectionPoint, Scene, NodeRenderGraphBuildState, FrameGraph, IShadowLight, FrameGraphObjectList, FrameGraphShadowGeneratorTask } from "core/index"; +import { NodeRenderGraphBlock } from "../../nodeRenderGraphBlock"; +import { NodeRenderGraphBlockConnectionPointTypes } from "../../Types/nodeRenderGraphTypes"; +import { editableInPropertyPage, PropertyTypeForEdition } from "../../../../Decorators/nodeDecorator"; +import { ShadowGenerator } from "../../../../Lights/Shadows/shadowGenerator"; + +/** + * @internal + */ +export class NodeRenderGraphBaseShadowGeneratorBlock extends NodeRenderGraphBlock { + protected override _frameGraphTask: FrameGraphShadowGeneratorTask; + + /** + * Gets the frame graph task associated with this block + */ + public override get task() { + return this._frameGraphTask; + } + + /** + * Create a new NodeRenderGraphBaseShadowGeneratorBlock + * @param name defines the block name + * @param frameGraph defines the hosting frame graph + * @param scene defines the hosting scene + */ + public constructor(name: string, frameGraph: FrameGraph, scene: Scene) { + super(name, frameGraph, scene); + + this.registerInput("light", NodeRenderGraphBlockConnectionPointTypes.ShadowLight); + this.registerInput("objects", NodeRenderGraphBlockConnectionPointTypes.ObjectList); + + this.registerOutput("generator", NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator); + this.registerOutput("output", NodeRenderGraphBlockConnectionPointTypes.Texture); + } + + /** Sets the size of the shadow texture */ + @editableInPropertyPage("Map size", PropertyTypeForEdition.List, "PROPERTIES", { + options: [ + { label: "128", value: 128 }, + { label: "256", value: 256 }, + { label: "512", value: 512 }, + { label: "1024", value: 1024 }, + { label: "2048", value: 2048 }, + { label: "4096", value: 4096 }, + { label: "8192", value: 8192 }, + ], + }) + public get mapSize() { + return this._frameGraphTask.mapSize; + } + + public set mapSize(value: number) { + this._frameGraphTask.mapSize = value; + } + + /** Sets the texture type to float (by default, half float is used if supported) */ + @editableInPropertyPage("Use 32 bits float texture type", PropertyTypeForEdition.Boolean, "PROPERTIES") + public get useFloat32TextureType() { + return this._frameGraphTask.useFloat32TextureType; + } + + public set useFloat32TextureType(value: boolean) { + this._frameGraphTask.useFloat32TextureType = value; + } + + /** Sets the texture type to Red */ + @editableInPropertyPage("Use red texture format", PropertyTypeForEdition.Boolean, "PROPERTIES") + public get useRedTextureFormat() { + return this._frameGraphTask.useRedTextureFormat; + } + + public set useRedTextureFormat(value: boolean) { + this._frameGraphTask.useRedTextureFormat = value; + } + + /** Sets the bias */ + @editableInPropertyPage("Bias", PropertyTypeForEdition.Float, "PROPERTIES", { min: 0, max: 1 }) + public get bias() { + return this._frameGraphTask.bias; + } + + public set bias(value: number) { + this._frameGraphTask.bias = value; + } + + /** Sets the normal bias */ + @editableInPropertyPage("Normal bias", PropertyTypeForEdition.Float, "PROPERTIES", { min: 0, max: 1 }) + public get normalBias() { + return this._frameGraphTask.normalBias; + } + + public set normalBias(value: number) { + this._frameGraphTask.normalBias = value; + } + + /** Sets the darkness of the shadows */ + @editableInPropertyPage("Darkness", PropertyTypeForEdition.Float, "PROPERTIES", { min: 0, max: 1 }) + public get darkness() { + return this._frameGraphTask.darkness; + } + + public set darkness(value: number) { + this._frameGraphTask.darkness = value; + } + + /** Sets the filter method */ + @editableInPropertyPage("Filter", PropertyTypeForEdition.List, "PROPERTIES", { + options: [ + { label: "None", value: ShadowGenerator.FILTER_NONE }, + { label: "Exponential", value: ShadowGenerator.FILTER_EXPONENTIALSHADOWMAP }, + { label: "Poisson Sampling", value: ShadowGenerator.FILTER_POISSONSAMPLING }, + { label: "Blur exponential", value: ShadowGenerator.FILTER_BLUREXPONENTIALSHADOWMAP }, + { label: "Close exponential", value: ShadowGenerator.FILTER_CLOSEEXPONENTIALSHADOWMAP }, + { label: "Blur close exponential", value: ShadowGenerator.FILTER_BLURCLOSEEXPONENTIALSHADOWMAP }, + { label: "PCF", value: ShadowGenerator.FILTER_PCF }, + { label: "PCSS", value: ShadowGenerator.FILTER_PCSS }, + ], + }) + public get filter() { + return this._frameGraphTask.filter; + } + + public set filter(value: number) { + this._frameGraphTask.filter = value; + } + + /** Sets the filter quality (for PCF and PCSS) */ + @editableInPropertyPage("Filter quality", PropertyTypeForEdition.List, "PROPERTIES", { + options: [ + { label: "Low", value: ShadowGenerator.QUALITY_LOW }, + { label: "Medium", value: ShadowGenerator.QUALITY_MEDIUM }, + { label: "High", value: ShadowGenerator.QUALITY_HIGH }, + ], + }) + public get filteringQuality() { + return this._frameGraphTask.filteringQuality; + } + + public set filteringQuality(value: number) { + this._frameGraphTask.filteringQuality = value; + } + + /** Gets or sets the ability to have transparent shadow */ + @editableInPropertyPage("Transparency shadow", PropertyTypeForEdition.Boolean, "PROPERTIES") + public get transparencyShadow() { + return this._frameGraphTask.transparencyShadow; + } + + public set transparencyShadow(value: boolean) { + this._frameGraphTask.transparencyShadow = value; + } + + /** Enables or disables shadows with varying strength based on the transparency */ + @editableInPropertyPage("Enable soft transparent shadows", PropertyTypeForEdition.Boolean, "PROPERTIES") + public get enableSoftTransparentShadow() { + return this._frameGraphTask.enableSoftTransparentShadow; + } + + public set enableSoftTransparentShadow(value: boolean) { + this._frameGraphTask.enableSoftTransparentShadow = value; + } + + /** If this is true, use the opacity texture's alpha channel for transparent shadows instead of the diffuse one */ + @editableInPropertyPage("Use opacity texture for transparent shadows", PropertyTypeForEdition.Boolean, "PROPERTIES") + public get useOpacityTextureForTransparentShadow() { + return this._frameGraphTask.useOpacityTextureForTransparentShadow; + } + + public set useOpacityTextureForTransparentShadow(value: boolean) { + this._frameGraphTask.useOpacityTextureForTransparentShadow = value; + } + + /** + * Gets the current class name + * @returns the class name + */ + public override getClassName() { + return "NodeRenderGraphBaseShadowGeneratorBlock"; + } + + /** + * Gets the light input component + */ + public get light(): NodeRenderGraphConnectionPoint { + return this._inputs[0]; + } + + /** + * Gets the objects input component + */ + public get objects(): NodeRenderGraphConnectionPoint { + return this._inputs[1]; + } + + /** + * Gets the shadow generator component + */ + public get generator(): NodeRenderGraphConnectionPoint { + return this._outputs[0]; + } + + /** + * Gets the output texture component + */ + public get output(): NodeRenderGraphConnectionPoint { + return this._outputs[1]; + } + + protected override _buildBlock(state: NodeRenderGraphBuildState) { + super._buildBlock(state); + + this._frameGraphTask.light = this.light.connectedPoint?.value as IShadowLight; + this._frameGraphTask.objectList = this.objects.connectedPoint?.value as FrameGraphObjectList; + + // Important: the shadow generator object is created by the task when we set the light, that's why we must set generator.value after setting the light! + this.generator.value = this._frameGraphTask; + this.output.value = this._frameGraphTask.outputTexture; + } + + protected override _dumpPropertiesCode() { + const codes: string[] = []; + codes.push(`${this._codeVariableName}.mapSize = ${this.mapSize};`); + codes.push(`${this._codeVariableName}.useFloat32TextureType = ${this.useFloat32TextureType};`); + codes.push(`${this._codeVariableName}.useRedTextureFormat = ${this.useRedTextureFormat};`); + codes.push(`${this._codeVariableName}.bias = ${this.bias};`); + codes.push(`${this._codeVariableName}.normalBias = ${this.normalBias};`); + codes.push(`${this._codeVariableName}.darkness = ${this.darkness};`); + codes.push(`${this._codeVariableName}.filter = ${this.filter};`); + codes.push(`${this._codeVariableName}.filteringQuality = ${this.filteringQuality};`); + codes.push(`${this._codeVariableName}.transparencyShadow = ${this.transparencyShadow};`); + codes.push(`${this._codeVariableName}.enableSoftTransparentShadow = ${this.enableSoftTransparentShadow};`); + codes.push(`${this._codeVariableName}.useOpacityTextureForTransparentShadow = ${this.useOpacityTextureForTransparentShadow};`); + return super._dumpPropertiesCode() + codes.join("\n"); + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.mapSize = this.mapSize; + serializationObject.useFloat32TextureType = this.useFloat32TextureType; + serializationObject.useRedTextureFormat = this.useRedTextureFormat; + serializationObject.bias = this.bias; + serializationObject.normalBias = this.normalBias; + serializationObject.darkness = this.darkness; + serializationObject.filter = this.filter; + serializationObject.filteringQuality = this.filteringQuality; + serializationObject.transparencyShadow = this.transparencyShadow; + serializationObject.enableSoftTransparentShadow = this.enableSoftTransparentShadow; + serializationObject.useOpacityTextureForTransparentShadow = this.useOpacityTextureForTransparentShadow; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.mapSize = serializationObject.mapSize; + this.useFloat32TextureType = serializationObject.useFloat32TextureType; + this.useRedTextureFormat = serializationObject.useRedTextureFormat; + this.bias = serializationObject.bias; + this.normalBias = serializationObject.normalBias; + this.darkness = serializationObject.darkness; + this.filter = serializationObject.filter; + this.filteringQuality = serializationObject.filteringQuality; + this.transparencyShadow = serializationObject.transparencyShadow; + this.enableSoftTransparentShadow = serializationObject.enableSoftTransparentShadow; + this.useOpacityTextureForTransparentShadow = serializationObject.useOpacityTextureForTransparentShadow; + } +} diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/csmShadowGeneratorBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/csmShadowGeneratorBlock.ts new file mode 100644 index 00000000000..26003c4927e --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/csmShadowGeneratorBlock.ts @@ -0,0 +1,182 @@ +// eslint-disable-next-line import/no-internal-modules +import type { Scene, FrameGraph, NodeRenderGraphConnectionPoint, NodeRenderGraphBuildState, Camera } from "core/index"; +import { NodeRenderGraphBaseShadowGeneratorBlock } from "./baseShadowGeneratorBlock"; +import { RegisterClass } from "../../../../Misc/typeStore"; +import { editableInPropertyPage, PropertyTypeForEdition } from "../../../../Decorators/nodeDecorator"; +import { FrameGraphCascadedShadowGeneratorTask } from "../../../Tasks/Rendering/csmShadowGeneratorTask"; +import { NodeRenderGraphBlockConnectionPointTypes } from "../../Types/nodeRenderGraphTypes"; + +/** + * Block that generates shadows through a shadow generator + */ +export class NodeRenderGraphCascadedShadowGeneratorBlock extends NodeRenderGraphBaseShadowGeneratorBlock { + protected override _frameGraphTask: FrameGraphCascadedShadowGeneratorTask; + + /** + * Gets the frame graph task associated with this block + */ + public override get task() { + return this._frameGraphTask; + } + + /** + * Create a new NodeRenderGraphCascadedShadowGeneratorBlock + * @param name defines the block name + * @param frameGraph defines the hosting frame graph + * @param scene defines the hosting scene + */ + public constructor(name: string, frameGraph: FrameGraph, scene: Scene) { + super(name, frameGraph, scene); + + this.registerInput("camera", NodeRenderGraphBlockConnectionPointTypes.Camera); + + this._frameGraphTask = new FrameGraphCascadedShadowGeneratorTask(this.name, frameGraph, scene); + } + + /** Sets the number of cascades */ + @editableInPropertyPage("Number of cascades", PropertyTypeForEdition.List, "CSM PROPERTIES", { + options: [ + { label: "2", value: 2 }, + { label: "3", value: 3 }, + { label: "4", value: 4 }, + ], + }) + public get numCascades() { + return this._frameGraphTask.numCascades; + } + + public set numCascades(value: number) { + this._frameGraphTask.numCascades = value; + } + + /** Gets or sets a value indicating whether the shadow generator should display the cascades. */ + @editableInPropertyPage("Debug mode", PropertyTypeForEdition.Boolean, "CSM PROPERTIES") + public get debug() { + return this._frameGraphTask.debug; + } + + public set debug(value: boolean) { + this._frameGraphTask.debug = value; + } + + /** Gets or sets a value indicating whether the shadow generator should stabilize the cascades. */ + @editableInPropertyPage("Stabilize cascades", PropertyTypeForEdition.Boolean, "CSM PROPERTIES") + public get stabilizeCascades() { + return this._frameGraphTask.stabilizeCascades; + } + + public set stabilizeCascades(value: boolean) { + this._frameGraphTask.stabilizeCascades = value; + } + + /** Gets or sets the lambda parameter of the shadow generator. */ + @editableInPropertyPage("Lambda", PropertyTypeForEdition.Float, "CSM PROPERTIES", { min: 0, max: 1 }) + public get lambda() { + return this._frameGraphTask.lambda; + } + + public set lambda(value: number) { + this._frameGraphTask.lambda = value; + } + + /** Gets or sets the cascade blend percentage. */ + @editableInPropertyPage("Cascade blend", PropertyTypeForEdition.Float, "CSM PROPERTIES", { min: 0, max: 1 }) + public get cascadeBlendPercentage() { + return this._frameGraphTask.cascadeBlendPercentage; + } + + public set cascadeBlendPercentage(value: number) { + this._frameGraphTask.cascadeBlendPercentage = value; + } + + /** Gets or sets a value indicating whether the shadow generator should use depth clamping. */ + @editableInPropertyPage("Depth clamp", PropertyTypeForEdition.Boolean, "CSM PROPERTIES") + public get depthClamp() { + return this._frameGraphTask.depthClamp; + } + + public set depthClamp(value: boolean) { + this._frameGraphTask.depthClamp = value; + } + + /** Gets or sets a value indicating whether the shadow generator should automatically calculate the depth bounds. */ + @editableInPropertyPage("Auto-Calc depth bounds", PropertyTypeForEdition.Boolean, "CSM PROPERTIES") + public get autoCalcDepthBounds() { + return this._frameGraphTask.autoCalcDepthBounds; + } + + public set autoCalcDepthBounds(value: boolean) { + this._frameGraphTask.autoCalcDepthBounds = value; + } + + /** Gets or sets the maximum shadow Z value. */ + @editableInPropertyPage("Shadow maxZ", PropertyTypeForEdition.Float, "CSM PROPERTIES") + public get shadowMaxZ() { + return this._frameGraphTask.shadowMaxZ; + } + + public set shadowMaxZ(value: number) { + this._frameGraphTask.shadowMaxZ = value; + } + + /** + * Gets the current class name + * @returns the class name + */ + public override getClassName() { + return "NodeRenderGraphCascadedShadowGeneratorBlock"; + } + + /** + * Gets the camera input component + */ + public get camera(): NodeRenderGraphConnectionPoint { + return this._inputs[2]; + } + + protected override _buildBlock(state: NodeRenderGraphBuildState) { + super._buildBlock(state); + + this._frameGraphTask.camera = this.camera.connectedPoint?.value as Camera; + } + + protected override _dumpPropertiesCode() { + const codes: string[] = []; + codes.push(`${this._codeVariableName}.numCascades = ${this.numCascades};`); + codes.push(`${this._codeVariableName}.debug = ${this.debug};`); + codes.push(`${this._codeVariableName}.stabilizeCascades = ${this.stabilizeCascades};`); + codes.push(`${this._codeVariableName}.lambda = ${this.lambda};`); + codes.push(`${this._codeVariableName}.cascadeBlendPercentage = ${this.cascadeBlendPercentage};`); + codes.push(`${this._codeVariableName}.depthClamp = ${this.depthClamp};`); + codes.push(`${this._codeVariableName}.autoCalcDepthBounds = ${this.autoCalcDepthBounds};`); + codes.push(`${this._codeVariableName}.shadowMaxZ = ${this.shadowMaxZ};`); + return super._dumpPropertiesCode() + codes.join("\n"); + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.numCascades = this.numCascades; + serializationObject.debug = this.debug; + serializationObject.stabilizeCascades = this.stabilizeCascades; + serializationObject.lambda = this.lambda; + serializationObject.cascadeBlendPercentage = this.cascadeBlendPercentage; + serializationObject.depthClamp = this.depthClamp; + serializationObject.autoCalcDepthBounds = this.autoCalcDepthBounds; + serializationObject.shadowMaxZ = this.shadowMaxZ; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.numCascades = serializationObject.numCascades; + this.debug = serializationObject.debug; + this.stabilizeCascades = serializationObject.stabilizeCascades; + this.lambda = serializationObject.lambda; + this.cascadeBlendPercentage = serializationObject.cascadeBlendPercentage; + this.depthClamp = serializationObject.depthClamp; + this.autoCalcDepthBounds = serializationObject.autoCalcDepthBounds; + this.shadowMaxZ = serializationObject.shadowMaxZ; + } +} + +RegisterClass("BABYLON.NodeRenderGraphCascadedShadowGeneratorBlock", NodeRenderGraphCascadedShadowGeneratorBlock); diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/cullObjectsBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/cullObjectsBlock.ts index ecfb1d65da8..6ef7093e671 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/cullObjectsBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/cullObjectsBlock.ts @@ -69,19 +69,10 @@ export class NodeRenderGraphCullObjectsBlock extends NodeRenderGraphBlock { protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputObjectList; - const cameraConnectedPoint = this.camera.connectedPoint; - if (cameraConnectedPoint) { - this._frameGraphTask.camera = cameraConnectedPoint.value as Camera; - } - - const objectsConnectedPoint = this.objects.connectedPoint; - if (objectsConnectedPoint) { - this._frameGraphTask.objectList = objectsConnectedPoint.value as FrameGraphObjectList; - } + this._frameGraphTask.camera = this.camera.connectedPoint?.value as Camera; + this._frameGraphTask.objectList = this.objects.connectedPoint?.value as FrameGraphObjectList; } protected override _dumpPropertiesCode() { diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/geometryRendererBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/geometryRendererBlock.ts index 54b479fef92..68785d9e2c2 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/geometryRendererBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/geometryRendererBlock.ts @@ -323,8 +323,6 @@ export class NodeRenderGraphGeometryRendererBlock extends NodeRenderGraphBlock { throw new Error("NodeRenderGraphGeometryRendererBlock: At least one output geometry buffer must be connected"); } - this._frameGraphTask.name = this.name; - this.outputDepth.value = this._frameGraphTask.outputDepthTexture; this.geomViewDepth.value = this._frameGraphTask.geometryViewDepthTexture; this.geomScreenDepth.value = this._frameGraphTask.geometryScreenDepthTexture; @@ -337,20 +335,9 @@ export class NodeRenderGraphGeometryRendererBlock extends NodeRenderGraphBlock { this.geomVelocity.value = this._frameGraphTask.geometryVelocityTexture; this.geomLinearVelocity.value = this._frameGraphTask.geometryLinearVelocityTexture; - const depthConnectedPoint = this.depth.connectedPoint; - if (depthConnectedPoint) { - this._frameGraphTask.depthTexture = depthConnectedPoint.value as FrameGraphTextureHandle; - } - - const cameraConnectedPoint = this.camera.connectedPoint; - if (cameraConnectedPoint) { - this._frameGraphTask.camera = cameraConnectedPoint.value as Camera; - } - - const objectsConnectedPoint = this.objects.connectedPoint; - if (objectsConnectedPoint) { - this._frameGraphTask.objectList = objectsConnectedPoint.value as FrameGraphObjectList; - } + this._frameGraphTask.depthTexture = this.depth.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.camera = this.camera.connectedPoint?.value as Camera; + this._frameGraphTask.objectList = this.objects.connectedPoint?.value as FrameGraphObjectList; this._frameGraphTask.textureDescriptions = []; diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/objectRendererBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/objectRendererBlock.ts index 55da231f402..751c5641c33 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/objectRendererBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/objectRendererBlock.ts @@ -36,6 +36,16 @@ export class NodeRenderGraphObjectRendererBlock extends NodeRenderGraphBaseObjec this._additionalConstructionParameters = [value]; } + /** Indicates if shadows must be enabled or disabled */ + @editableInPropertyPage("Disable shadows", PropertyTypeForEdition.Boolean, "PROPERTIES") + public get disableShadows() { + return this._frameGraphTask.disableShadows; + } + + public set disableShadows(value: boolean) { + this._frameGraphTask.disableShadows = value; + } + /** * Gets the current class name * @returns the class name @@ -43,6 +53,26 @@ export class NodeRenderGraphObjectRendererBlock extends NodeRenderGraphBaseObjec public override getClassName() { return "NodeRenderGraphObjectRendererBlock"; } + + protected override _dumpPropertiesCode() { + const codes: string[] = []; + codes.push(`${this._codeVariableName}.doNotChangeAspectRatio = ${this.doNotChangeAspectRatio};`); + codes.push(`${this._codeVariableName}.disableShadows = ${this.disableShadows};`); + return super._dumpPropertiesCode() + codes.join("\n"); + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.doNotChangeAspectRatio = this.doNotChangeAspectRatio; + serializationObject.disableShadows = this.disableShadows; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.doNotChangeAspectRatio = serializationObject.doNotChangeAspectRatio; + this.disableShadows = serializationObject.disableShadows; + } } RegisterClass("BABYLON.NodeRenderGraphObjectRendererBlock", NodeRenderGraphObjectRendererBlock); diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/shadowGeneratorBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/shadowGeneratorBlock.ts new file mode 100644 index 00000000000..827313a88d8 --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Rendering/shadowGeneratorBlock.ts @@ -0,0 +1,32 @@ +// eslint-disable-next-line import/no-internal-modules +import type { Scene, FrameGraph } from "core/index"; +import { NodeRenderGraphBaseShadowGeneratorBlock } from "./baseShadowGeneratorBlock"; +import { RegisterClass } from "../../../../Misc/typeStore"; +import { FrameGraphShadowGeneratorTask } from "../../../Tasks/Rendering/shadowGeneratorTask"; + +/** + * Block that generate shadows through a shadow generator + */ +export class NodeRenderGraphShadowGeneratorBlock extends NodeRenderGraphBaseShadowGeneratorBlock { + /** + * Create a new NodeRenderGraphShadowGeneratorBlock + * @param name defines the block name + * @param frameGraph defines the hosting frame graph + * @param scene defines the hosting scene + */ + public constructor(name: string, frameGraph: FrameGraph, scene: Scene) { + super(name, frameGraph, scene); + + this._frameGraphTask = new FrameGraphShadowGeneratorTask(this.name, frameGraph, scene); + } + + /** + * Gets the current class name + * @returns the class name + */ + public override getClassName() { + return "NodeRenderGraphShadowGeneratorBlock"; + } +} + +RegisterClass("BABYLON.NodeRenderGraphShadowGeneratorBlock", NodeRenderGraphShadowGeneratorBlock); diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/clearBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/clearBlock.ts index d282a01b8ea..b1b3a3c891d 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/clearBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/clearBlock.ts @@ -123,20 +123,11 @@ export class NodeRenderGraphClearBlock extends NodeRenderGraphBlock { protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this._propagateInputValueToOutput(this.texture, this.output); this._propagateInputValueToOutput(this.depth, this.outputDepth); - const textureConnectedPoint = this.texture.connectedPoint; - if (textureConnectedPoint) { - this._frameGraphTask.destinationTexture = textureConnectedPoint.value as FrameGraphTextureHandle; - } - - const depthConnectedPoint = this.depth.connectedPoint; - if (depthConnectedPoint) { - this._frameGraphTask.depthTexture = depthConnectedPoint.value as FrameGraphTextureHandle; - } + this._frameGraphTask.destinationTexture = this.texture.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.depthTexture = this.depth.connectedPoint?.value as FrameGraphTextureHandle; } protected override _dumpPropertiesCode() { diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/copyTextureBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/copyTextureBlock.ts index d517fd393db..7cf5a84bd07 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/copyTextureBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/copyTextureBlock.ts @@ -69,19 +69,10 @@ export class NodeRenderGraphCopyTextureBlock extends NodeRenderGraphBlock { protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this.output.value = this._frameGraphTask.outputTexture; // the value of the output connection point is the "output" texture of the task - const sourceConnectedPoint = this.source.connectedPoint; - if (sourceConnectedPoint) { - this._frameGraphTask.sourceTexture = sourceConnectedPoint.value as FrameGraphTextureHandle; - } - - const destinationConnectedPoint = this.destination.connectedPoint; - if (destinationConnectedPoint) { - this._frameGraphTask.destinationTexture = destinationConnectedPoint.value as FrameGraphTextureHandle; - } + this._frameGraphTask.sourceTexture = this.source.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.destinationTexture = this.destination.connectedPoint?.value as FrameGraphTextureHandle; } } diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/generateMipmapsBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/generateMipmapsBlock.ts index b23385c160e..8883e2a063b 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/generateMipmapsBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/Textures/generateMipmapsBlock.ts @@ -60,14 +60,9 @@ export class NodeRenderGraphGenerateMipmapsBlock extends NodeRenderGraphBlock { protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); - this._frameGraphTask.name = this.name; - this._propagateInputValueToOutput(this.texture, this.output); - const textureConnectedPoint = this.texture.connectedPoint; - if (textureConnectedPoint) { - this._frameGraphTask.destinationTexture = textureConnectedPoint.value as FrameGraphTextureHandle; - } + this._frameGraphTask.destinationTexture = this.texture.connectedPoint?.value as FrameGraphTextureHandle; } } diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/index.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/index.ts index d3cc8f99cea..3eb3f5ec156 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/index.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/index.ts @@ -1,6 +1,7 @@ export * from "./elbowBlock"; export * from "./inputBlock"; export * from "./outputBlock"; +export * from "./resourceContainerBlock"; export * from "./PostProcesses/blackAndWhitePostProcessBlock"; export * from "./PostProcesses/bloomPostProcessBlock"; @@ -9,9 +10,11 @@ export * from "./PostProcesses/circleOfConfusionPostProcessBlock"; export * from "./PostProcesses/depthOfFieldPostProcessBlock"; export * from "./PostProcesses/extractHighlightsPostProcessBlock"; +export * from "./Rendering/csmShadowGeneratorBlock"; export * from "./Rendering/cullObjectsBlock"; export * from "./Rendering/geometryRendererBlock"; export * from "./Rendering/objectRendererBlock"; +export * from "./Rendering/shadowGeneratorBlock"; export * from "./Rendering/taaObjectRendererBlock"; export * from "./Teleport/teleportInBlock"; diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/inputBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/inputBlock.ts index df490d4e017..59f6877b157 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/inputBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/inputBlock.ts @@ -10,6 +10,7 @@ import type { FrameGraphTextureCreationOptions, FrameGraphTextureHandle, FrameGraphObjectList, + IShadowLight, } from "core/index"; import { Observable } from "../../../Misc/observable"; import { NodeRenderGraphBlockConnectionPointTypes } from "../Types/nodeRenderGraphTypes"; @@ -19,7 +20,7 @@ import { editableInPropertyPage, PropertyTypeForEdition } from "../../../Decorat import { backbufferColorTextureHandle, backbufferDepthStencilTextureHandle } from "../../../FrameGraph/frameGraphTypes"; import { Constants } from "../../../Engines/constants"; -export type NodeRenderGraphValueType = InternalTexture | Camera | FrameGraphObjectList; +export type NodeRenderGraphValueType = InternalTexture | Camera | FrameGraphObjectList | IShadowLight; export type NodeRenderGraphInputCreationOptions = FrameGraphTextureCreationOptions; @@ -211,6 +212,14 @@ export class NodeRenderGraphInputBlock extends NodeRenderGraphBlock { return (this.type & NodeRenderGraphBlockConnectionPointTypes.ObjectList) !== 0; } + /** + * Check if the block is a shadow light + * @returns true if the block is a shadow light + */ + public isShadowLight(): boolean { + return (this.type & NodeRenderGraphBlockConnectionPointTypes.ShadowLight) !== 0; + } + protected override _buildBlock(state: NodeRenderGraphBuildState) { super._buildBlock(state); @@ -223,6 +232,8 @@ export class NodeRenderGraphInputBlock extends NodeRenderGraphBlock { this.output.value = this.getTypedValue(); } else if (this.isObjectList()) { this.output.value = this.getTypedValue(); + } else if (this.isShadowLight()) { + this.output.value = this.getTypedValue(); } else { if (this._storedValue === undefined || this._storedValue === null) { throw new Error(`NodeRenderGraphInputBlock: External input "${this.name}" is not set`); @@ -265,6 +276,8 @@ export class NodeRenderGraphInputBlock extends NodeRenderGraphBlock { codes.push(`${this._codeVariableName}.value = EXTERNAL_CAMERA; // TODO: set the external camera`); } else if (this.isObjectList()) { codes.push(`${this._codeVariableName}.value = EXTERNAL_OBJECT_LIST; // TODO: set the external object list`); + } else if (this.isShadowLight()) { + codes.push(`${this._codeVariableName}.value = EXTERNAL_SHADOW_LIGHT; // TODO: set the external shadow light`); } return super._dumpPropertiesCode() + codes.join("\n"); } diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/resourceContainerBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/resourceContainerBlock.ts new file mode 100644 index 00000000000..09afa9e66a5 --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/resourceContainerBlock.ts @@ -0,0 +1,153 @@ +// eslint-disable-next-line import/no-internal-modules +import type { NodeRenderGraphConnectionPoint, Scene, FrameGraph } from "core/index"; +import { RegisterClass } from "../../../Misc/typeStore"; +import { NodeRenderGraphBlockConnectionPointTypes } from "../Types/nodeRenderGraphTypes"; +import { NodeRenderGraphBlock } from "../nodeRenderGraphBlock"; + +/** + * Block used as a resource (textures, buffers) container + */ +export class NodeRenderGraphResourceContainerBlock extends NodeRenderGraphBlock { + /** + * Creates a new NodeRenderGraphResourceContainerBlock + * @param name defines the block name + * @param frameGraph defines the hosting frame graph + * @param scene defines the hosting scene + */ + public constructor(name: string, frameGraph: FrameGraph, scene: Scene) { + super(name, frameGraph, scene); + + this.registerInput("resource0", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + this.registerInput("resource1", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + this.registerInput("resource2", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + this.registerInput("resource3", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + this.registerInput("resource4", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + this.registerInput("resource5", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + this.registerInput("resource6", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + this.registerInput("resource7", NodeRenderGraphBlockConnectionPointTypes.Texture, true); + + this.registerOutput("output", NodeRenderGraphBlockConnectionPointTypes.ResourceContainer); + + this.resource0.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator + ); + this.resource1.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator + ); + this.resource2.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator + ); + this.resource3.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator + ); + this.resource4.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator + ); + this.resource5.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator + ); + this.resource6.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator + ); + this.resource7.addAcceptedConnectionPointTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer | + NodeRenderGraphBlockConnectionPointTypes.StorageTexture | + NodeRenderGraphBlockConnectionPointTypes.StorageBuffer | + NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator + ); + } + + /** + * Gets the current class name + * @returns the class name + */ + public override getClassName() { + return "NodeRenderGraphResourceContainerBlock"; + } + + /** + * Gets the resource0 component + */ + public get resource0(): NodeRenderGraphConnectionPoint { + return this._inputs[0]; + } + + /** + * Gets the resource1 component + */ + public get resource1(): NodeRenderGraphConnectionPoint { + return this._inputs[1]; + } + + /** + * Gets the resource2 component + */ + public get resource2(): NodeRenderGraphConnectionPoint { + return this._inputs[2]; + } + + /** + * Gets the resource3 component + */ + public get resource3(): NodeRenderGraphConnectionPoint { + return this._inputs[3]; + } + + /** + * Gets the resource4 component + */ + public get resource4(): NodeRenderGraphConnectionPoint { + return this._inputs[4]; + } + + /** + * Gets the resource5 component + */ + public get resource5(): NodeRenderGraphConnectionPoint { + return this._inputs[5]; + } + + /** + * Gets the resource6 component + */ + public get resource6(): NodeRenderGraphConnectionPoint { + return this._inputs[6]; + } + + /** + * Gets the resource7 component + */ + public get resource7(): NodeRenderGraphConnectionPoint { + return this._inputs[7]; + } + + /** + * Gets the output component + */ + public get output(): NodeRenderGraphConnectionPoint { + return this._outputs[0]; + } +} + +RegisterClass("BABYLON.NodeRenderGraphResourceContainerBlock", NodeRenderGraphResourceContainerBlock); diff --git a/packages/dev/core/src/FrameGraph/Node/Types/nodeRenderGraphTypes.ts b/packages/dev/core/src/FrameGraph/Node/Types/nodeRenderGraphTypes.ts index 533dee5b8dd..a52efd39ede 100644 --- a/packages/dev/core/src/FrameGraph/Node/Types/nodeRenderGraphTypes.ts +++ b/packages/dev/core/src/FrameGraph/Node/Types/nodeRenderGraphTypes.ts @@ -1,8 +1,5 @@ -import type { Color4 } from "../../../Maths/math.color"; -import type { Scene } from "../../../scene"; -import type { FrameGraphTextureHandle } from "../../../FrameGraph/frameGraphTypes"; -import type { Camera } from "../../../Cameras/camera"; -import type { FrameGraphObjectList } from "core/FrameGraph/frameGraphObjectList"; +// eslint-disable-next-line import/no-internal-modules +import type { Color4, Scene, FrameGraphTextureHandle, Camera, FrameGraphObjectList, IShadowLight, FrameGraphShadowGeneratorTask } from "core/index"; /** * Interface used to configure the node render graph editor @@ -69,13 +66,24 @@ export enum NodeRenderGraphBlockConnectionPointTypes { TextureLocalPosition = 0x00004000, /** Linear velocity geometry texture */ TextureLinearVelocity = 0x00008000, + /** Storage texture */ + StorageTexture = 0x00010000, /** Bit field for all textures but back buffer depth/stencil */ - TextureAllButBackBufferDepthStencil = 0x00fffffb, - /** Bit field for all textures but back buffer */ - TextureAllButBackBuffer = 0x00fffff9, - TextureAll = 0x00ffffff, + TextureAllButBackBufferDepthStencil = 0x000ffffb, + /** Bit field for all textures but back buffer color and depth/stencil */ + TextureAllButBackBuffer = 0x000ffff9, + /** Bit field for all textures */ + TextureAll = 0x000fffff, + /** Resource container */ + ResourceContainer = 0x00100000, + /** Shadow generator */ + ShadowGenerator = 0x00200000, + /** Light */ + ShadowLight = 0x00400000, + /** Storage Buffer */ + StorageBuffer = 0x00800000, /** Camera */ Camera = 0x01000000, /** List of objects (meshes, particle systems, sprites) */ @@ -116,4 +124,4 @@ export const enum NodeRenderGraphConnectionPointDirection { /** * Defines the type of a connection point value */ -export type NodeRenderGraphBlockConnectionPointValueType = FrameGraphTextureHandle | Camera | FrameGraphObjectList; +export type NodeRenderGraphBlockConnectionPointValueType = FrameGraphTextureHandle | Camera | FrameGraphObjectList | IShadowLight | FrameGraphShadowGeneratorTask; diff --git a/packages/dev/core/src/FrameGraph/Node/nodeRenderGraph.ts b/packages/dev/core/src/FrameGraph/Node/nodeRenderGraph.ts index 151f8e11651..93a747685fe 100644 --- a/packages/dev/core/src/FrameGraph/Node/nodeRenderGraph.ts +++ b/packages/dev/core/src/FrameGraph/Node/nodeRenderGraph.ts @@ -10,6 +10,7 @@ import type { INodeRenderGraphEditorOptions, Scene, WritableObject, + IShadowLight, } from "core/index"; import { Observable } from "../../Misc/observable"; import { NodeRenderGraphOutputBlock } from "./Blocks/outputBlock"; @@ -24,6 +25,7 @@ import { Tools } from "../../Misc/tools"; import { Engine } from "../../Engines/engine"; import { NodeRenderGraphBlockConnectionPointTypes } from "./Types/nodeRenderGraphTypes"; import { NodeRenderGraphClearBlock } from "./Blocks/Textures/clearBlock"; +import { NodeRenderGraphObjectRendererBlock } from "./Blocks/Rendering/objectRendererBlock"; import { NodeRenderGraphBuildState } from "./nodeRenderGraphBuildState"; // declare NODERENDERGRAPHEDITOR namespace for compilation issue @@ -301,7 +303,16 @@ export class NodeRenderGraph { private _autoFillExternalInputs() { const allInputs = this.getInputBlocks(); + + const shadowLights: IShadowLight[] = []; + for (const light of this._scene.lights) { + if ((light as IShadowLight).setShadowProjectionMatrix !== undefined) { + shadowLights.push(light as IShadowLight); + } + } + let cameraIndex = 0; + let lightIndex = 0; for (const input of allInputs) { if (!input.isExternal) { continue; @@ -320,6 +331,11 @@ export class NodeRenderGraph { input.value = camera; } else if (input.isObjectList()) { input.value = { meshes: this._scene.meshes, particleSystems: this._scene.particleSystems }; + } else if (input.isShadowLight()) { + if (lightIndex < shadowLights.length) { + input.value = shadowLights[lightIndex++]; + lightIndex = lightIndex % shadowLights.length; + } } } } @@ -581,17 +597,35 @@ export class NodeRenderGraph { this.editorData = null; - // Source - const backBuffer = new NodeRenderGraphInputBlock("BackBuffer color", this._frameGraph, this._scene, NodeRenderGraphBlockConnectionPointTypes.TextureBackBuffer); + // Source textures + const colorTexture = new NodeRenderGraphInputBlock("Color Texture", this._frameGraph, this._scene, NodeRenderGraphBlockConnectionPointTypes.Texture); + colorTexture.creationOptions.options.samples = 4; + + const depthTexture = new NodeRenderGraphInputBlock("Depth Texture", this._frameGraph, this._scene, NodeRenderGraphBlockConnectionPointTypes.TextureDepthStencilAttachment); + depthTexture.creationOptions.options.samples = 4; // Clear texture const clear = new NodeRenderGraphClearBlock("Clear", this._frameGraph, this._scene); + clear.clearDepth = true; + clear.clearStencil = true; + + colorTexture.output.connectTo(clear.texture); + depthTexture.output.connectTo(clear.depth); + + // Render objects + const camera = new NodeRenderGraphInputBlock("Camera", this._frameGraph, this._scene, NodeRenderGraphBlockConnectionPointTypes.Camera); + const objectList = new NodeRenderGraphInputBlock("Object List", this._frameGraph, this._scene, NodeRenderGraphBlockConnectionPointTypes.ObjectList); + + const mainRendering = new NodeRenderGraphObjectRendererBlock("Main Rendering", this._frameGraph, this._scene); - backBuffer.output.connectTo(clear.texture); + camera.output.connectTo(mainRendering.camera); + objectList.output.connectTo(mainRendering.objects); + clear.output.connectTo(mainRendering.destination); + clear.outputDepth.connectTo(mainRendering.depth); // Final output const output = new NodeRenderGraphOutputBlock("Output", this._frameGraph, this._scene); - clear.output.connectTo(output.texture); + mainRendering.output.connectTo(output.texture); this.outputBlock = output; } diff --git a/packages/dev/core/src/FrameGraph/Node/nodeRenderGraphBlock.ts b/packages/dev/core/src/FrameGraph/Node/nodeRenderGraphBlock.ts index 735b2365859..aec66137401 100644 --- a/packages/dev/core/src/FrameGraph/Node/nodeRenderGraphBlock.ts +++ b/packages/dev/core/src/FrameGraph/Node/nodeRenderGraphBlock.ts @@ -1,5 +1,13 @@ -// eslint-disable-next-line import/no-internal-modules -import type { NodeRenderGraphBuildState, Nullable, NodeRenderGraphInputBlock, AbstractEngine, Scene, FrameGraphTask, FrameGraph } from "core/index"; +import type { + NodeRenderGraphBuildState, + Nullable, + NodeRenderGraphInputBlock, + AbstractEngine, + Scene, + FrameGraphTask, + FrameGraph, + // eslint-disable-next-line import/no-internal-modules +} from "core/index"; import { GetClass } from "../../Misc/typeStore"; import { serialize } from "../../Misc/decorators"; import { UniqueIdGenerator } from "../../Misc/uniqueIdGenerator"; @@ -322,7 +330,12 @@ export class NodeRenderGraphBlock { Logger.Log(`Building ${this.name} [${this.getClassName()}]`); } + if (this._frameGraphTask) { + this._frameGraphTask.name = this.name; + } + this._buildBlock(state); + if (this._frameGraphTask) { this._frameGraph.addTask(this._frameGraphTask); } diff --git a/packages/dev/core/src/FrameGraph/Node/nodeRenderGraphBlockConnectionPoint.ts b/packages/dev/core/src/FrameGraph/Node/nodeRenderGraphBlockConnectionPoint.ts index 650f4dad023..1f5e3ef01f8 100644 --- a/packages/dev/core/src/FrameGraph/Node/nodeRenderGraphBlockConnectionPoint.ts +++ b/packages/dev/core/src/FrameGraph/Node/nodeRenderGraphBlockConnectionPoint.ts @@ -1,5 +1,12 @@ -// eslint-disable-next-line import/no-internal-modules -import type { Nullable, NodeRenderGraphBlock, NodeRenderGraphBlockConnectionPointValueType, NodeRenderGraphInputBlock } from "core/index"; +import type { + Nullable, + NodeRenderGraphBlock, + NodeRenderGraphBlockConnectionPointValueType, + NodeRenderGraphInputBlock, + IShadowLight, + FrameGraphShadowGeneratorTask, + // eslint-disable-next-line import/no-internal-modules +} from "core/index"; import { Observable } from "../../Misc/observable"; import { NodeRenderGraphBlockConnectionPointTypes, NodeRenderGraphConnectionPointCompatibilityStates, NodeRenderGraphConnectionPointDirection } from "./Types/nodeRenderGraphTypes"; @@ -34,6 +41,33 @@ export class NodeRenderGraphConnectionPoint { return this._direction; } + /** + * Checks if the value is a texture handle + * @param value The value to check + * @returns True if the value is a texture handle + */ + public static IsTextureHandle(value: NodeRenderGraphBlockConnectionPointValueType): boolean { + return Number.isFinite(value); + } + + /** + * Checks if the value is a shadow generator task + * @param value The value to check + * @returns True if the value is a shadow generator + */ + public static IsShadowGenerator(value: NodeRenderGraphBlockConnectionPointValueType): boolean { + return (value as FrameGraphShadowGeneratorTask).mapSize !== undefined; + } + + /** + * Checks if the value is a shadow light + * @param value The value to check + * @returns True if the value is a shadow light + */ + public static IsShadowLight(value: NodeRenderGraphBlockConnectionPointValueType): boolean { + return (value as IShadowLight).setShadowProjectionMatrix !== undefined; + } + /** * The value stored in this connection point */ diff --git a/packages/dev/core/src/FrameGraph/Tasks/Rendering/csmShadowGeneratorTask.ts b/packages/dev/core/src/FrameGraph/Tasks/Rendering/csmShadowGeneratorTask.ts new file mode 100644 index 00000000000..1229c745cb5 --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Tasks/Rendering/csmShadowGeneratorTask.ts @@ -0,0 +1,217 @@ +// eslint-disable-next-line import/no-internal-modules +import type { Camera } from "core/index"; +import { CascadedShadowGenerator } from "../../../Lights/Shadows/cascadedShadowGenerator"; +import { FrameGraphShadowGeneratorTask } from "./shadowGeneratorTask"; +import { DirectionalLight } from "../../../Lights/directionalLight"; + +/** + * Task used to generate a cascaded shadow map from a list of objects. + */ +export class FrameGraphCascadedShadowGeneratorTask extends FrameGraphShadowGeneratorTask { + protected override _shadowGenerator: CascadedShadowGenerator | undefined; + + /** + * 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. + */ + public static IsCascadedShadowGenerator(task: FrameGraphShadowGeneratorTask): task is FrameGraphCascadedShadowGeneratorTask { + return (task as FrameGraphCascadedShadowGeneratorTask).numCascades !== undefined; + } + + private _numCascades = CascadedShadowGenerator.DEFAULT_CASCADES_COUNT; + /** + * The number of cascades. + */ + public get numCascades() { + return this._numCascades; + } + + public set numCascades(value: number) { + if (value === this._numCascades) { + return; + } + + this._numCascades = value; + this._setupShadowGenerator(); + } + + private _debug = false; + /** + * Gets or sets a value indicating whether the shadow generator should display the cascades. + */ + public get debug() { + return this._debug; + } + + public set debug(value: boolean) { + if (value === this._debug) { + return; + } + + this._debug = value; + if (this._shadowGenerator) { + this._shadowGenerator.debug = value; + } + } + + private _stabilizeCascades = false; + /** + * Gets or sets a value indicating whether the shadow generator should stabilize the cascades. + */ + public get stabilizeCascades() { + return this._stabilizeCascades; + } + + public set stabilizeCascades(value: boolean) { + if (value === this._stabilizeCascades) { + return; + } + + this._stabilizeCascades = value; + if (this._shadowGenerator) { + this._shadowGenerator.stabilizeCascades = value; + } + } + + private _lambda = 0.5; + /** + * Gets or sets the lambda parameter of the shadow generator. + */ + public get lambda() { + return this._lambda; + } + + public set lambda(value: number) { + if (value === this._lambda) { + return; + } + + this._lambda = value; + if (this._shadowGenerator) { + this._shadowGenerator.lambda = value; + } + } + + private _cascadeBlendPercentage = 0.1; + /** + * Gets or sets the cascade blend percentage. + */ + public get cascadeBlendPercentage() { + return this._cascadeBlendPercentage; + } + + public set cascadeBlendPercentage(value: number) { + if (value === this._cascadeBlendPercentage) { + return; + } + + this._cascadeBlendPercentage = value; + if (this._shadowGenerator) { + this._shadowGenerator.cascadeBlendPercentage = value; + } + } + + private _depthClamp = true; + /** + * Gets or sets a value indicating whether the shadow generator should use depth clamping. + */ + public get depthClamp() { + return this._depthClamp; + } + + public set depthClamp(value: boolean) { + if (value === this._depthClamp) { + return; + } + + this._depthClamp = value; + if (this._shadowGenerator) { + this._shadowGenerator.depthClamp = value; + } + } + + private _autoCalcDepthBounds = false; + /** + * Gets or sets a value indicating whether the shadow generator should automatically calculate the depth bounds. + */ + public get autoCalcDepthBounds() { + return this._autoCalcDepthBounds; + } + + public set autoCalcDepthBounds(value: boolean) { + if (value === this._autoCalcDepthBounds) { + return; + } + + this._autoCalcDepthBounds = value; + if (this._shadowGenerator) { + this._shadowGenerator.autoCalcDepthBounds = value; + } + } + + private _shadowMaxZ = 10000; + /** + * Gets or sets the maximum shadow Z value. + */ + public get shadowMaxZ() { + return this._shadowMaxZ; + } + + public set shadowMaxZ(value: number) { + if (value === this._shadowMaxZ) { + return; + } + + this._shadowMaxZ = value; + if (this._shadowGenerator) { + this._shadowGenerator.shadowMaxZ = value; + } + } + + private _camera: Camera; + /** + * Gets or sets the camera used to generate the shadow generator. + */ + public get camera() { + return this._camera; + } + + public set camera(camera: Camera) { + this._camera = camera; + this._setupShadowGenerator(); + } + + public override record() { + if (this.camera === undefined) { + throw new Error(`FrameGraphCascadedShadowGeneratorTask ${this.name}: camera is required`); + } + + super.record(); + } + + protected override _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; + } + + protected override _setupShadowGenerator() { + super._setupShadowGenerator(); + + const shadowGenerator = this._shadowGenerator as CascadedShadowGenerator | undefined; + 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.autoCalcDepthBounds = this._autoCalcDepthBounds; + shadowGenerator.shadowMaxZ = this._shadowMaxZ; + } +} diff --git a/packages/dev/core/src/FrameGraph/Tasks/Rendering/objectRendererTask.ts b/packages/dev/core/src/FrameGraph/Tasks/Rendering/objectRendererTask.ts index a2da695b6d1..d28dbccc13e 100644 --- a/packages/dev/core/src/FrameGraph/Tasks/Rendering/objectRendererTask.ts +++ b/packages/dev/core/src/FrameGraph/Tasks/Rendering/objectRendererTask.ts @@ -1,8 +1,21 @@ -// eslint-disable-next-line import/no-internal-modules -import type { FrameGraph, FrameGraphTextureHandle, Scene, Camera, FrameGraphObjectList, FrameGraphRenderContext, ObjectRendererOptions } from "core/index"; +import type { + FrameGraph, + FrameGraphTextureHandle, + Scene, + Camera, + FrameGraphObjectList, + FrameGraphRenderContext, + ObjectRendererOptions, + Light, + Nullable, + Observer, + FrameGraphShadowGeneratorTask, + // eslint-disable-next-line import/no-internal-modules +} from "core/index"; import { backbufferColorTextureHandle, backbufferDepthStencilTextureHandle } from "../../frameGraphTypes"; import { FrameGraphTask } from "../../frameGraphTask"; import { ObjectRenderer } from "../../../Rendering/objectRenderer"; +import { FrameGraphCascadedShadowGeneratorTask } from "./csmShadowGeneratorTask"; /** * Task used to render objects to a texture. @@ -23,6 +36,11 @@ export class FrameGraphObjectRendererTask extends FrameGraphTask { */ public dependencies?: FrameGraphTextureHandle[] = []; + /** + * The shadow generators used to render the objects (optional). + */ + public shadowGenerators?: FrameGraphShadowGeneratorTask[] = []; + private _camera: Camera; /** @@ -52,6 +70,11 @@ export class FrameGraphObjectRendererTask extends FrameGraphTask { */ public depthWrite = true; + /** + * If shadows should be disabled (default is false). + */ + public disableShadows = false; + /** * The output texture. * This texture will point to the same texture than the destinationTexture property if it is set. @@ -66,11 +89,6 @@ export class FrameGraphObjectRendererTask extends FrameGraphTask { */ public readonly outputDepthTexture: FrameGraphTextureHandle; - protected readonly _scene: Scene; - protected readonly _renderer: ObjectRenderer; - protected _textureWidth: number; - protected _textureHeight: number; - /** * The object renderer used to render the objects. */ @@ -89,6 +107,13 @@ export class FrameGraphObjectRendererTask extends FrameGraphTask { } } + protected readonly _scene: Scene; + protected readonly _renderer: ObjectRenderer; + protected _textureWidth: number; + protected _textureHeight: number; + protected _onBeforeRenderObservable: Nullable> = null; + protected _onAfterRenderObservable: Nullable> = null; + /** * Constructs a new object renderer task. * @param name The name of the task. @@ -154,6 +179,8 @@ export class FrameGraphObjectRendererTask extends FrameGraphTask { this._textureWidth = outputTextureDescription.size.width; this._textureHeight = outputTextureDescription.size.height; + this._setLightsForShadow(); + const pass = this._frameGraph.addRenderPass(this.name); pass.setRenderTarget(this.destinationTexture); @@ -193,4 +220,41 @@ export class FrameGraphObjectRendererTask extends FrameGraphTask { this._renderer.dispose(); super.dispose(); } + + private _setLightsForShadow() { + const lightsForShadow: Set = new Set(); + const shadowEnabled: Map = new Map(); + + if (this.shadowGenerators) { + for (const shadowGeneratorTask of this.shadowGenerators) { + const shadowGenerator = shadowGeneratorTask.shadowGenerator; + const light = shadowGenerator.getLight(); + if (light.isEnabled() && light.shadowEnabled) { + lightsForShadow.add(light); + if (FrameGraphCascadedShadowGeneratorTask.IsCascadedShadowGenerator(shadowGeneratorTask)) { + light._shadowGenerators!.set(shadowGeneratorTask.camera, shadowGenerator); + } else { + light._shadowGenerators!.set(null, shadowGenerator); + } + } + } + } + + this._renderer.onBeforeRenderObservable.remove(this._onBeforeRenderObservable); + this._onBeforeRenderObservable = this._renderer.onBeforeRenderObservable.add(() => { + for (let i = 0; i < this._scene.lights.length; i++) { + const light = this._scene.lights[i]; + shadowEnabled.set(light, light.shadowEnabled); + light.shadowEnabled = !this.disableShadows && lightsForShadow.has(light); + } + }); + + this._renderer.onAfterRenderObservable.remove(this._onAfterRenderObservable); + this._onAfterRenderObservable = this._renderer.onAfterRenderObservable.add(() => { + for (let i = 0; i < this._scene.lights.length; i++) { + const light = this._scene.lights[i]; + light.shadowEnabled = shadowEnabled.get(light)!; + } + }); + } } diff --git a/packages/dev/core/src/FrameGraph/Tasks/Rendering/shadowGeneratorTask.ts b/packages/dev/core/src/FrameGraph/Tasks/Rendering/shadowGeneratorTask.ts new file mode 100644 index 00000000000..149962e2732 --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Tasks/Rendering/shadowGeneratorTask.ts @@ -0,0 +1,341 @@ +// eslint-disable-next-line import/no-internal-modules +import type { Scene, FrameGraph, FrameGraphObjectList, IShadowLight, WritableObject, AbstractEngine, FrameGraphTextureHandle } from "core/index"; +import { FrameGraphTask } from "../../frameGraphTask"; +import { ShadowGenerator } from "../../../Lights/Shadows/shadowGenerator"; + +/** + * Task used to generate shadows from a list of objects. + */ +export class FrameGraphShadowGeneratorTask extends FrameGraphTask { + /** + * The object list that generates shadows. + */ + public objectList: FrameGraphObjectList; + + private _light: IShadowLight; + /** + * The light to generate shadows from. + */ + public get light(): IShadowLight { + return this._light; + } + + public set light(value: IShadowLight) { + if (value === this._light) { + return; + } + + this._light = value; + this._setupShadowGenerator(); + } + + private _mapSize = 1024; + /** + * The size of the shadow map. + */ + public get mapSize() { + return this._mapSize; + } + + public set mapSize(value: number) { + if (value === this._mapSize) { + return; + } + + this._mapSize = value; + this._setupShadowGenerator(); + } + + private _useFloat32TextureType = false; + /** + * If true, the shadow map will use a 32 bits float texture type (else, 16 bits float is used if supported). + */ + public get useFloat32TextureType() { + return this._useFloat32TextureType; + } + + public set useFloat32TextureType(value: boolean) { + if (value === this._useFloat32TextureType) { + return; + } + + this._useFloat32TextureType = value; + this._setupShadowGenerator(); + } + + private _useRedTextureFormat = true; + /** + * If true, the shadow map will use a red texture format (else, a RGBA format is used). + */ + public get useRedTextureFormat() { + return this._useRedTextureFormat; + } + + public set useRedTextureFormat(value: boolean) { + if (value === this._useRedTextureFormat) { + return; + } + + this._useRedTextureFormat = value; + this._setupShadowGenerator(); + } + + private _bias = 0.01; + /** + * The bias to apply to the shadow map. + */ + public get bias() { + return this._bias; + } + + public set bias(value: number) { + if (value === this._bias) { + return; + } + + this._bias = value; + if (this._shadowGenerator) { + this._shadowGenerator.bias = value; + } + } + + private _normalBias = 0; + /** + * The normal bias to apply to the shadow map. + */ + public get normalBias() { + return this._normalBias; + } + + public set normalBias(value: number) { + if (value === this._normalBias) { + return; + } + + this._normalBias = value; + if (this._shadowGenerator) { + this._shadowGenerator.normalBias = value; + } + } + + private _darkness = 0; + /** + * The darkness of the shadows. + */ + public get darkness() { + return this._darkness; + } + + public set darkness(value: number) { + if (value === this._darkness) { + return; + } + + this._darkness = value; + if (this._shadowGenerator) { + this._shadowGenerator.darkness = value; + } + } + + private _transparencyShadow = false; + /** + * Gets or sets the ability to have transparent shadow + */ + public get transparencyShadow() { + return this._transparencyShadow; + } + + public set transparencyShadow(value: boolean) { + if (value === this._transparencyShadow) { + return; + } + + this._transparencyShadow = value; + if (this._shadowGenerator) { + this._shadowGenerator.transparencyShadow = value; + } + } + + private _enableSoftTransparentShadow = false; + /** + * Enables or disables shadows with varying strength based on the transparency + */ + public get enableSoftTransparentShadow() { + return this._enableSoftTransparentShadow; + } + + public set enableSoftTransparentShadow(value: boolean) { + if (value === this._enableSoftTransparentShadow) { + return; + } + + this._enableSoftTransparentShadow = value; + if (this._shadowGenerator) { + this._shadowGenerator.enableSoftTransparentShadow = value; + } + } + + private _useOpacityTextureForTransparentShadow = false; + /** + * If this is true, use the opacity texture's alpha channel for transparent shadows instead of the diffuse one + */ + public get useOpacityTextureForTransparentShadow() { + return this._useOpacityTextureForTransparentShadow; + } + + public set useOpacityTextureForTransparentShadow(value: boolean) { + if (value === this._useOpacityTextureForTransparentShadow) { + return; + } + + this._useOpacityTextureForTransparentShadow = value; + if (this._shadowGenerator) { + this._shadowGenerator.useOpacityTextureForTransparentShadow = value; + } + } + + private _filter = ShadowGenerator.FILTER_PCF; + /** + * The filter to apply to the shadow map. + */ + public get filter() { + return this._filter; + } + + public set filter(value: number) { + if (value === this._filter) { + return; + } + + this._filter = value; + if (this._shadowGenerator) { + this._shadowGenerator.filter = value; + } + } + + private _filteringQuality = ShadowGenerator.QUALITY_HIGH; + /** + * The filtering quality to apply to the filter. + */ + public get filteringQuality() { + return this._filteringQuality; + } + + public set filteringQuality(value: number) { + if (value === this._filteringQuality) { + return; + } + + this._filteringQuality = value; + if (this._shadowGenerator) { + this._shadowGenerator.filteringQuality = value; + } + } + + /** + * The shadow generator. + */ + public readonly shadowGenerator: ShadowGenerator; + + /** + * The shadow map texture. + */ + public readonly outputTexture: FrameGraphTextureHandle; + + protected _shadowGenerator: ShadowGenerator | undefined; + + protected _createShadowGenerator() { + this._shadowGenerator = new ShadowGenerator(this._mapSize, this._light, this._useFloat32TextureType, undefined, this._useRedTextureFormat); + } + + protected _setupShadowGenerator() { + this._shadowGenerator?.dispose(); + this._shadowGenerator = undefined; + if (this._light !== undefined) { + this._createShadowGenerator(); + const shadowGenerator = this._shadowGenerator as ShadowGenerator | undefined; + if (shadowGenerator === undefined) { + return; + } + shadowGenerator.bias = this._bias; + shadowGenerator.normalBias = this._normalBias; + shadowGenerator.darkness = this._darkness; + shadowGenerator.transparencyShadow = this._transparencyShadow; + shadowGenerator.enableSoftTransparentShadow = this._enableSoftTransparentShadow; + shadowGenerator.useOpacityTextureForTransparentShadow = this._useOpacityTextureForTransparentShadow; + shadowGenerator.filter = this._filter; + shadowGenerator.filteringQuality = this._filteringQuality; + shadowGenerator.getShadowMap()!._disableEngineStages = true; + (this.shadowGenerator as WritableObject) = shadowGenerator; + } + } + + public override isReady(): boolean { + return !!this._shadowGenerator && !!this._shadowGenerator.getShadowMap()?.isReadyForRendering(); + } + + private _engine: AbstractEngine; + private _scene: Scene; + + /** + * 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: string, frameGraph: FrameGraph, scene: Scene) { + super(name, frameGraph); + + this._engine = scene.getEngine(); + this._scene = scene; + + this.outputTexture = this._frameGraph.textureManager.createDanglingHandle(); + } + + public record() { + if (this.light === undefined || this.objectList === undefined) { + throw new Error(`FrameGraphShadowGeneratorTask ${this.name}: light and objectList are required`); + } + + const shadowMap = this._frameGraph.textureManager.importTexture(`${this.name} shadowmap`, this._shadowGenerator!.getShadowMap()!.getInternalTexture()!); + + this._frameGraph.textureManager.resolveDanglingHandle(this.outputTexture, shadowMap); + + const pass = this._frameGraph.addPass(this.name); + + pass.setExecuteFunc((_context) => { + if (!this.light.isEnabled() || !this.light.shadowEnabled) { + return; + } + + const shadowMap = this._shadowGenerator!.getShadowMap()!; + + shadowMap.renderList = this.objectList.meshes; + shadowMap.particleSystemList = this.objectList.particleSystems; + + const currentRenderTarget = this._engine._currentRenderTarget; + + this._scene.incrementRenderId(); + this._scene.resetCachedMaterial(); + + shadowMap.render(); + + if (this._engine._currentRenderTarget !== currentRenderTarget) { + if (!currentRenderTarget) { + this._engine.restoreDefaultFramebuffer(); + } else { + this._engine.bindFramebuffer(currentRenderTarget); + } + } + }); + + const passDisabled = this._frameGraph.addPass(this.name + "_disabled", true); + + passDisabled.setExecuteFunc((_context) => {}); + } + + public override dispose() { + this._shadowGenerator?.dispose(); + this._shadowGenerator = undefined; + } +} diff --git a/packages/dev/core/src/FrameGraph/Tasks/Texture/clearTextureTask.ts b/packages/dev/core/src/FrameGraph/Tasks/Texture/clearTextureTask.ts index a52f92e5ded..f5a4bf4b51c 100644 --- a/packages/dev/core/src/FrameGraph/Tasks/Texture/clearTextureTask.ts +++ b/packages/dev/core/src/FrameGraph/Tasks/Texture/clearTextureTask.ts @@ -64,13 +64,22 @@ export class FrameGraphClearTextureTask extends FrameGraphTask { throw new Error(`FrameGraphClearTextureTask ${this.name}: destinationTexture and depthTexture can't both be undefined.`); } + let textureSamples = 0; + let depthSamples = 0; + if (this.destinationTexture !== undefined) { + textureSamples = this._frameGraph.textureManager.getTextureDescription(this.destinationTexture).options.samples || 1; this._frameGraph.textureManager.resolveDanglingHandle(this.outputTexture, this.destinationTexture); } if (this.depthTexture !== undefined) { + depthSamples = this._frameGraph.textureManager.getTextureDescription(this.depthTexture).options.samples || 1; this._frameGraph.textureManager.resolveDanglingHandle(this.outputDepthTexture, this.depthTexture); } + if (textureSamples !== depthSamples && textureSamples !== 0 && depthSamples !== 0) { + throw new Error(`FrameGraphClearTextureTask ${this.name}: the depth texture and the output texture must have the same number of samples.`); + } + const pass = this._frameGraph.addRenderPass(this.name); pass.setRenderTarget(this.destinationTexture); diff --git a/packages/dev/core/src/FrameGraph/frameGraph.ts b/packages/dev/core/src/FrameGraph/frameGraph.ts index 692245d0937..79b920c6a9c 100644 --- a/packages/dev/core/src/FrameGraph/frameGraph.ts +++ b/packages/dev/core/src/FrameGraph/frameGraph.ts @@ -9,9 +9,10 @@ import { FrameGraphTextureManager } from "./frameGraphTextureManager"; import { Observable } from "core/Misc/observable"; enum FrameGraphPassType { - Render = 0, - Cull = 1, - Compute = 2, + Normal = 0, + Render = 1, + Cull = 2, + Compute = 3, } /** @@ -76,6 +77,16 @@ export class FrameGraph { this._tasks.push(task); } + /** + * Adds a pass to a task. This method can only be called during a Task.record execution. + * @param name The name of the pass + * @param whenTaskDisabled If true, the pass will be added to the list of passes to execute when the task is disabled (default is false) + * @returns The render pass created + */ + public addPass(name: string, whenTaskDisabled = false): FrameGraphRenderPass { + return this._addPass(name, FrameGraphPassType.Normal, whenTaskDisabled) as FrameGraphRenderPass; + } + /** * Adds a render pass to a task. This method can only be called during a Task.record execution. * @param name The name of the pass diff --git a/packages/dev/core/src/FrameGraph/frameGraphTextureManager.ts b/packages/dev/core/src/FrameGraph/frameGraphTextureManager.ts index 833cd7b51bb..fcc90b66b8e 100644 --- a/packages/dev/core/src/FrameGraph/frameGraphTextureManager.ts +++ b/packages/dev/core/src/FrameGraph/frameGraphTextureManager.ts @@ -566,7 +566,10 @@ export class FrameGraphTextureManager { const textureName = creationOptions.isHistoryTexture ? `${name} ping` : name; - const label = creationOptions.options.labels?.[textureIndex] ?? ""; + let label = creationOptions.options.labels?.[textureIndex] ?? ""; + if (label === textureName) { + label = ""; + } const textureEntry: TextureEntry = { texture, diff --git a/packages/dev/core/src/FrameGraph/index.ts b/packages/dev/core/src/FrameGraph/index.ts index cb9a7f12ae3..bdee7d38396 100644 --- a/packages/dev/core/src/FrameGraph/index.ts +++ b/packages/dev/core/src/FrameGraph/index.ts @@ -21,9 +21,11 @@ export * from "./Tasks/Texture/clearTextureTask"; export * from "./Tasks/Texture/copyToBackbufferColorTask"; export * from "./Tasks/Texture/copyToTextureTask"; +export * from "./Tasks/Rendering/csmShadowGeneratorTask"; export * from "./Tasks/Rendering/cullObjectsTask"; export * from "./Tasks/Rendering/geometryRendererTask"; export * from "./Tasks/Rendering/objectRendererTask"; +export * from "./Tasks/Rendering/shadowGeneratorTask"; export * from "./Tasks/Rendering/taaObjectRendererTask"; export * from "./frameGraph"; diff --git a/packages/dev/core/src/Lights/Shadows/shadowGenerator.ts b/packages/dev/core/src/Lights/Shadows/shadowGenerator.ts index 30dd47313b2..433c20b1d63 100644 --- a/packages/dev/core/src/Lights/Shadows/shadowGenerator.ts +++ b/packages/dev/core/src/Lights/Shadows/shadowGenerator.ts @@ -665,7 +665,7 @@ export class ShadowGenerator implements IShadowGenerator { protected _transparencyShadow = false; - /** Gets or sets the ability to have transparent shadow */ + /** Gets or sets the ability to have transparent shadow */ public get transparencyShadow() { return this._transparencyShadow; } @@ -1288,9 +1288,7 @@ export class ShadowGenerator implements IShadowGenerator { } const camera = this._getCamera(); - if (camera) { - effect.setFloat2("depthValuesSM", this.getLight().getDepthMinZ(camera), this.getLight().getDepthMinZ(camera) + this.getLight().getDepthMaxZ(camera)); - } + effect.setFloat2("depthValuesSM", this.getLight().getDepthMinZ(camera), this.getLight().getDepthMinZ(camera) + this.getLight().getDepthMaxZ(camera)); if (isTransparent && this.enableSoftTransparentShadow) { effect.setFloat2("softTransparentShadowSM", effectiveMesh.visibility * material.alpha, this._opacityTexture?.getAlphaFromRGB ? 1 : 0); @@ -1830,10 +1828,6 @@ export class ShadowGenerator implements IShadowGenerator { } const camera = this._getCamera(); - if (!camera) { - return; - } - const shadowMap = this.getShadowMap(); if (!shadowMap) { diff --git a/packages/dev/core/src/Lights/directionalLight.ts b/packages/dev/core/src/Lights/directionalLight.ts index bb1847bfe75..264266fc8ef 100644 --- a/packages/dev/core/src/Lights/directionalLight.ts +++ b/packages/dev/core/src/Lights/directionalLight.ts @@ -8,6 +8,8 @@ import { Light } from "./light"; import { ShadowLight } from "./shadowLight"; import type { Effect } from "../Materials/effect"; import { RegisterClass } from "../Misc/typeStore"; +import type { Nullable } from "../types"; +import { Constants } from "core/Engines/constants"; Node.AddNodeConstructor("Light_Type_1", (name, scene) => { return () => new DirectionalLight(name, Vector3.Zero(), scene); @@ -202,10 +204,6 @@ export class DirectionalLight extends ShadowLight { protected _setDefaultAutoExtendShadowProjectionMatrix(matrix: Matrix, viewMatrix: Matrix, renderList: Array): void { const activeCamera = this.getScene().activeCamera; - if (!activeCamera) { - return; - } - // Check extends if (this.autoUpdateExtends || this._orthoLeft === Number.MAX_VALUE) { const tempVector3 = Vector3.Zero(); @@ -264,8 +262,8 @@ export class DirectionalLight extends ShadowLight { const xOffset = this._orthoRight - this._orthoLeft; const yOffset = this._orthoTop - this._orthoBottom; - const minZ = this.shadowMinZ !== undefined ? this.shadowMinZ : activeCamera.minZ; - const maxZ = this.shadowMaxZ !== undefined ? this.shadowMaxZ : activeCamera.maxZ; + const minZ = this.shadowMinZ !== undefined ? this.shadowMinZ : activeCamera?.minZ || Constants.ShadowMinZ; + const maxZ = this.shadowMaxZ !== undefined ? this.shadowMaxZ : activeCamera?.maxZ || Constants.ShadowMaxZ; const useReverseDepthBuffer = this.getScene().getEngine().useReverseDepthBuffer; @@ -321,11 +319,11 @@ export class DirectionalLight extends ShadowLight { * Values are fixed on directional lights as it relies on an ortho projection hence the need to convert being * -1 and 1 to 0 and 1 doing (depth + min) / (min + max) -> (depth + 1) / (1 + 1) -> (depth * 0.5) + 0.5. * (when not using reverse depth buffer / NDC half Z range) - * @param activeCamera The camera we are returning the min for + * @param _activeCamera The camera we are returning the min for (not used) * @returns the depth min z */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public override getDepthMinZ(activeCamera: Camera): number { + public override getDepthMinZ(_activeCamera: Nullable): number { const engine = this._scene.getEngine(); return !engine.useReverseDepthBuffer && engine.isNDCHalfZRange ? 0 : 1; } @@ -336,11 +334,11 @@ export class DirectionalLight extends ShadowLight { * Values are fixed on directional lights as it relies on an ortho projection hence the need to convert being * -1 and 1 to 0 and 1 doing (depth + min) / (min + max) -> (depth + 1) / (1 + 1) -> (depth * 0.5) + 0.5. * (when not using reverse depth buffer / NDC half Z range) - * @param activeCamera The camera we are returning the max for + * @param _activeCamera The camera we are returning the max for * @returns the depth max z */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public override getDepthMaxZ(activeCamera: Camera): number { + public override getDepthMaxZ(_activeCamera: Nullable): number { const engine = this._scene.getEngine(); return engine.useReverseDepthBuffer && engine.isNDCHalfZRange ? 0 : 1; } diff --git a/packages/dev/core/src/Lights/shadowLight.ts b/packages/dev/core/src/Lights/shadowLight.ts index 517355b8027..c20a17f2a92 100644 --- a/packages/dev/core/src/Lights/shadowLight.ts +++ b/packages/dev/core/src/Lights/shadowLight.ts @@ -6,6 +6,7 @@ import type { AbstractMesh } from "../Meshes/abstractMesh"; import { Light } from "./light"; import { Axis } from "../Maths/math.axis"; import type { Nullable } from "core/types"; +import { Constants } from "core/Engines/constants"; /** * Interface describing all the common properties and methods a shadow light needs to implement. * This helps both the shadow generator and materials to generate the corresponding shadow maps @@ -107,14 +108,14 @@ export interface IShadowLight extends Light { * @param activeCamera The camera we are returning the min for * @returns the depth min z */ - getDepthMinZ(activeCamera: Camera): number; + getDepthMinZ(activeCamera: Nullable): number; /** * Gets the maxZ used for shadow according to both the scene and the light. * @param activeCamera The camera we are returning the max for * @returns the depth max z */ - getDepthMaxZ(activeCamera: Camera): number; + getDepthMaxZ(activeCamera: Nullable): number; } /** @@ -360,8 +361,8 @@ export abstract class ShadowLight extends Light implements IShadowLight { * @param activeCamera The camera we are returning the min for * @returns the depth min z */ - public getDepthMinZ(activeCamera: Camera): number { - return this.shadowMinZ !== undefined ? this.shadowMinZ : activeCamera.minZ; + public getDepthMinZ(activeCamera: Nullable): number { + return this.shadowMinZ !== undefined ? this.shadowMinZ : activeCamera?.minZ || Constants.ShadowMinZ; } /** @@ -369,8 +370,8 @@ export abstract class ShadowLight extends Light implements IShadowLight { * @param activeCamera The camera we are returning the max for * @returns the depth max z */ - public getDepthMaxZ(activeCamera: Camera): number { - return this.shadowMaxZ !== undefined ? this.shadowMaxZ : activeCamera.maxZ; + public getDepthMaxZ(activeCamera: Nullable): number { + return this.shadowMaxZ !== undefined ? this.shadowMaxZ : activeCamera?.maxZ || Constants.ShadowMaxZ; } /** diff --git a/packages/dev/core/src/Lights/spotLight.ts b/packages/dev/core/src/Lights/spotLight.ts index 5922906a022..7dd11771008 100644 --- a/packages/dev/core/src/Lights/spotLight.ts +++ b/packages/dev/core/src/Lights/spotLight.ts @@ -12,6 +12,7 @@ import { Texture } from "../Materials/Textures/texture"; import type { ProceduralTexture } from "../Materials/Textures/Procedurals/proceduralTexture"; import type { Camera } from "../Cameras/camera"; import { RegisterClass } from "../Misc/typeStore"; +import { Constants } from "core/Engines/constants"; Node.AddNodeConstructor("Light_Type_2", (name, scene) => { return () => new SpotLight(name, Vector3.Zero(), Vector3.Zero(), 0, 0, scene); @@ -478,9 +479,9 @@ export class SpotLight extends ShadowLight { * @param activeCamera The camera we are returning the min for * @returns the depth min z */ - public override getDepthMinZ(activeCamera: Camera): number { + public override getDepthMinZ(activeCamera: Nullable): number { const engine = this._scene.getEngine(); - const minZ = this.shadowMinZ !== undefined ? this.shadowMinZ : activeCamera.minZ; + const minZ = this.shadowMinZ !== undefined ? this.shadowMinZ : (activeCamera?.minZ ?? Constants.ShadowMinZ); return engine.useReverseDepthBuffer && engine.isNDCHalfZRange ? minZ : this._scene.getEngine().isNDCHalfZRange ? 0 : minZ; } @@ -490,9 +491,9 @@ export class SpotLight extends ShadowLight { * @param activeCamera The camera we are returning the max for * @returns the depth max z */ - public override getDepthMaxZ(activeCamera: Camera): number { + public override getDepthMaxZ(activeCamera: Nullable): number { const engine = this._scene.getEngine(); - const maxZ = this.shadowMaxZ !== undefined ? this.shadowMaxZ : activeCamera.maxZ; + const maxZ = this.shadowMaxZ !== undefined ? this.shadowMaxZ : (activeCamera?.maxZ ?? Constants.ShadowMaxZ); return engine.useReverseDepthBuffer && engine.isNDCHalfZRange ? 0 : maxZ; } diff --git a/packages/dev/core/src/Materials/Textures/renderTargetTexture.ts b/packages/dev/core/src/Materials/Textures/renderTargetTexture.ts index ea56bae5d03..9f78d104906 100644 --- a/packages/dev/core/src/Materials/Textures/renderTargetTexture.ts +++ b/packages/dev/core/src/Materials/Textures/renderTargetTexture.ts @@ -492,6 +492,9 @@ export class RenderTargetTexture extends Texture implements IRenderTargetTexture return this._renderTarget?._depthStencilTexture ?? null; } + /** @internal */ + public _disableEngineStages = false; // TODO: remove this when the shadow generator task (frame graph) is reworked (see https://github.com/BabylonJS/Babylon.js/pull/15962#discussion_r1874417607) + /** * Instantiate a render target texture. This is mainly used to render of screen the scene to for instance apply post process * or used a shadow, depth texture... @@ -609,8 +612,10 @@ export class RenderTargetTexture extends Texture implements IRenderTargetTexture this._objectRenderer.onBeforeRenderingManagerRenderObservable.add(() => { // Before clear - for (const step of this._scene!._beforeRenderTargetClearStage) { - step.action(this, this._currentFaceIndex, this._currentLayer); + if (!this._disableEngineStages) { + for (const step of this._scene!._beforeRenderTargetClearStage) { + step.action(this, this._currentFaceIndex, this._currentLayer); + } } // Clear @@ -625,15 +630,19 @@ export class RenderTargetTexture extends Texture implements IRenderTargetTexture } // Before Camera Draw - for (const step of this._scene!._beforeRenderTargetDrawStage) { - step.action(this, this._currentFaceIndex, this._currentLayer); + if (!this._disableEngineStages) { + for (const step of this._scene!._beforeRenderTargetDrawStage) { + step.action(this, this._currentFaceIndex, this._currentLayer); + } } }); this._objectRenderer.onAfterRenderingManagerRenderObservable.add(() => { // After Camera Draw - for (const step of this._scene!._afterRenderTargetDrawStage) { - step.action(this, this._currentFaceIndex, this._currentLayer); + if (!this._disableEngineStages) { + for (const step of this._scene!._afterRenderTargetDrawStage) { + step.action(this, this._currentFaceIndex, this._currentLayer); + } } const saveGenerateMipMaps = this._texture?.generateMipMaps ?? false; @@ -650,8 +659,10 @@ export class RenderTargetTexture extends Texture implements IRenderTargetTexture this._scene!.postProcessManager._finalizeFrame(false, this._renderTarget ?? undefined, this._currentFaceIndex); } - for (const step of this._scene!._afterRenderTargetPostProcessStage) { - step.action(this, this._currentFaceIndex, this._currentLayer); + if (!this._disableEngineStages) { + for (const step of this._scene!._afterRenderTargetPostProcessStage) { + step.action(this, this._currentFaceIndex, this._currentLayer); + } } if (this._texture) { diff --git a/packages/dev/core/src/Misc/copyTextureToTexture.ts b/packages/dev/core/src/Misc/copyTextureToTexture.ts index f0de9348d11..acafd1826a2 100644 --- a/packages/dev/core/src/Misc/copyTextureToTexture.ts +++ b/packages/dev/core/src/Misc/copyTextureToTexture.ts @@ -106,7 +106,7 @@ export class CopyTextureToTexture { * @returns true if "copy" can be called without delay, else false */ public isReady(): boolean { - return this._shadersLoaded && this._effectWrapper.effect.isReady(); + return this._shadersLoaded && this._effectWrapper?.effect?.isReady(); } /** diff --git a/packages/dev/core/src/Misc/filesInput.ts b/packages/dev/core/src/Misc/filesInput.ts index a35cd056162..2d348e1f9cf 100644 --- a/packages/dev/core/src/Misc/filesInput.ts +++ b/packages/dev/core/src/Misc/filesInput.ts @@ -65,6 +65,7 @@ export class FilesInput { * @param onReloadCallback callback called when a reload is requested * @param errorCallback callback call if an error occurs * @param useAppend defines if the file loaded must be appended (true) or have the scene replaced (false, default behavior) + * @param dontInjectRenderLoop defines if the render loop mustn't be injected into engine (default is false). Used only if useAppend is false. */ constructor( engine: AbstractEngine, @@ -76,7 +77,8 @@ export class FilesInput { startingProcessingFilesCallback: Nullable<(files?: File[]) => void>, onReloadCallback: Nullable<(sceneFile: File) => void>, errorCallback: Nullable<(sceneFile: File, scene: Nullable, message: string) => void>, - public readonly useAppend = false + public readonly useAppend = false, + public readonly dontInjectRenderLoop = false ) { this._engine = engine; this._currentScene = scene; @@ -324,9 +326,11 @@ export class FilesInput { if (this.displayLoadingUI) { this._engine.hideLoadingUI(); } - this._engine.runRenderLoop(() => { - this._renderFunction(); - }); + if (!this.dontInjectRenderLoop) { + this._engine.runRenderLoop(() => { + this._renderFunction(); + }); + } }); } else { if (this.displayLoadingUI) { diff --git a/packages/dev/core/src/PostProcesses/postProcess.ts b/packages/dev/core/src/PostProcesses/postProcess.ts index 97659a70a15..64f92fe91b6 100644 --- a/packages/dev/core/src/PostProcesses/postProcess.ts +++ b/packages/dev/core/src/PostProcesses/postProcess.ts @@ -929,15 +929,15 @@ export class PostProcess { /** * Activates the post process by intializing the textures to be used when executed. Notifies onActivateObservable. * When this post process is used in a pipeline, this is call will bind the input texture of this post process to the output of the previous. - * @param camera The camera that will be used in the post process. This camera will be used when calling onActivateObservable. + * @param cameraOrScene The camera that will be used in the post process. This camera will be used when calling onActivateObservable. You can also pass the scene if no camera is available. * @param sourceTexture The source texture to be inspected to get the width and height if not specified in the post process constructor. (default: null) * @param forceDepthStencil If true, a depth and stencil buffer will be generated. (default: false) * @returns The render target wrapper that was bound to be written to. */ - public activate(camera: Nullable, sourceTexture: Nullable = null, forceDepthStencil?: boolean): RenderTargetWrapper { - camera = camera || this._camera; + public activate(cameraOrScene: Nullable | Scene, sourceTexture: Nullable = null, forceDepthStencil?: boolean): RenderTargetWrapper { + const camera = cameraOrScene === null || (cameraOrScene as Camera).cameraRigMode !== undefined ? (cameraOrScene as Camera) || this._camera : null; - const scene = camera.getScene(); + const scene = camera?.getScene() ?? (cameraOrScene as Scene); const engine = scene.getEngine(); const maxSize = engine.getCaps().maxTextureSize; @@ -1003,7 +1003,7 @@ export class PostProcess { this._engine._debugInsertMarker?.(`post process ${this.name} input`); - this.onActivateObservable.notifyObservers(camera); + this.onActivateObservable.notifyObservers(camera!); // Clear if (this.autoClear && (this.alphaMode === Constants.ALPHA_DISABLE || this.forceAutoClearInAlphaMode)) { diff --git a/packages/dev/core/src/PostProcesses/postProcessManager.ts b/packages/dev/core/src/PostProcesses/postProcessManager.ts index f1bcbfbcce9..372c9e2c861 100644 --- a/packages/dev/core/src/PostProcesses/postProcessManager.ts +++ b/packages/dev/core/src/PostProcesses/postProcessManager.ts @@ -120,7 +120,7 @@ export class PostProcessManager { for (let index = 0; index < postProcesses.length; index++) { if (index < postProcesses.length - 1) { - postProcesses[index + 1].activate(this._scene.activeCamera, targetTexture?.texture); + postProcesses[index + 1].activate(this._scene.activeCamera || this._scene, targetTexture?.texture); } else { if (targetTexture) { engine.bindFramebuffer(targetTexture, faceIndex, undefined, undefined, forceFullscreenViewport, lodLevel); diff --git a/packages/dev/core/src/scene.ts b/packages/dev/core/src/scene.ts index abc7c3dc665..246a4e8cb1e 100644 --- a/packages/dev/core/src/scene.ts +++ b/packages/dev/core/src/scene.ts @@ -1130,7 +1130,11 @@ export class Scene implements IAnimatable, IClipPlanesHolder, IAssetContainer { * @returns the computed eye position */ public bindEyePosition(effect: Nullable, variableName = "vEyePosition", isVector3 = false): Vector4 { - const eyePosition = this._forcedViewPosition ? this._forcedViewPosition : this._mirroredCameraPosition ? this._mirroredCameraPosition : this.activeCamera!.globalPosition; + const eyePosition = this._forcedViewPosition + ? this._forcedViewPosition + : this._mirroredCameraPosition + ? this._mirroredCameraPosition + : (this.activeCamera?.globalPosition ?? Vector3.ZeroReadOnly); const invertNormal = this.useRightHandedSystem === (this._mirroredCameraPosition != null); diff --git a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonShadowLightPropertyGridComponent.tsx b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonShadowLightPropertyGridComponent.tsx index 4f863951f26..c246c382200 100644 --- a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonShadowLightPropertyGridComponent.tsx +++ b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonShadowLightPropertyGridComponent.tsx @@ -63,10 +63,19 @@ export class CommonShadowLightPropertyGridComponent extends React.Component 0) { + generator = shadowGenerators.values().next().value as ShadowGenerator | CascadedShadowGenerator; + } + } + + const csmGenerator = generator instanceof CascadedShadowGenerator; + const typeGeneratorOptions = [{ label: "Shadow Generator", value: 0 }]; if (light instanceof DirectionalLight) { diff --git a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/lights/directionalLightPropertyGridComponent.tsx b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/lights/directionalLightPropertyGridComponent.tsx index d57aec5b559..82cad9275e3 100644 --- a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/lights/directionalLightPropertyGridComponent.tsx +++ b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/lights/directionalLightPropertyGridComponent.tsx @@ -37,7 +37,7 @@ export class DirectionalLightPropertyGridComponent extends React.Component { dlh.update(); @@ -47,8 +47,16 @@ export class DirectionalLightPropertyGridComponent extends React.Component 0) { + generator = shadowGenerators.values().next().value as ShadowGenerator | CascadedShadowGenerator; + } + } const hideAutoCalcShadowZBounds = generator instanceof CascadedShadowGenerator; const displayFrustum = (light as any)._displayFrustum ?? false; diff --git a/packages/tools/nodeRenderGraphEditor/public/index.js b/packages/tools/nodeRenderGraphEditor/public/index.js index 5d344a5f227..6d79026d840 100644 --- a/packages/tools/nodeRenderGraphEditor/public/index.js +++ b/packages/tools/nodeRenderGraphEditor/public/index.js @@ -199,6 +199,7 @@ checkBabylonVersionAsync().then(() => { let scene = new BABYLON.Scene(engine); new BABYLON.Camera("camera", new BABYLON.Vector3(0, 0, 0), scene); new BABYLON.HemisphericLight("light #0", new BABYLON.Vector3(0, 1, 0), scene); + new BABYLON.DirectionalLight("light #1", new BABYLON.Vector3(0, 1, 0), scene); nodeRenderGraph = new BABYLON.NodeRenderGraph("node", scene); nodeRenderGraph.setToDefault(); diff --git a/packages/tools/nodeRenderGraphEditor/src/blockTools.ts b/packages/tools/nodeRenderGraphEditor/src/blockTools.ts index 336afdb6f52..8f238cf1146 100644 --- a/packages/tools/nodeRenderGraphEditor/src/blockTools.ts +++ b/packages/tools/nodeRenderGraphEditor/src/blockTools.ts @@ -19,6 +19,9 @@ import { NodeRenderGraphGeometryRendererBlock } from "core/FrameGraph/Node/Block import { NodeRenderGraphCullObjectsBlock } from "core/FrameGraph/Node/Blocks/Rendering/cullObjectsBlock"; import { NodeRenderGraphGUIBlock } from "gui/2D/FrameGraph/renderGraphGUIBlock"; import { NodeRenderGraphTAAObjectRendererBlock } from "core/FrameGraph/Node/Blocks/Rendering/taaObjectRendererBlock"; +import { NodeRenderGraphResourceContainerBlock } from "core/FrameGraph/Node/Blocks/resourceContainerBlock"; +import { NodeRenderGraphShadowGeneratorBlock } from "core/FrameGraph/Node/Blocks/Rendering/shadowGeneratorBlock"; +import { NodeRenderGraphCascadedShadowGeneratorBlock } from "core/FrameGraph/Node/Blocks/Rendering/csmShadowGeneratorBlock"; import type { FrameGraph } from "core/FrameGraph/frameGraph"; /** @@ -35,6 +38,8 @@ export class BlockTools { return new NodeRenderGraphOutputBlock("Output", frameGraph, scene); case "ElbowBlock": return new NodeRenderGraphElbowBlock("", frameGraph, scene); + case "ResourceContainerBlock": + return new NodeRenderGraphResourceContainerBlock("Resources", frameGraph, scene); case "TextureBlock": { return new NodeRenderGraphInputBlock("Texture", frameGraph, scene, NodeRenderGraphBlockConnectionPointTypes.Texture); } @@ -58,6 +63,9 @@ export class BlockTools { case "ObjectListBlock": { return new NodeRenderGraphInputBlock("Object list", frameGraph, scene, NodeRenderGraphBlockConnectionPointTypes.ObjectList); } + case "ShadowLightBlock": { + return new NodeRenderGraphInputBlock("Shadow light", frameGraph, scene, NodeRenderGraphBlockConnectionPointTypes.ShadowLight); + } case "ClearBlock": { return new NodeRenderGraphClearBlock("Clear", frameGraph, scene); } @@ -100,6 +108,12 @@ export class BlockTools { case "ExtractHighlightsBlock": { return new NodeRenderGraphExtractHighlightsPostProcessBlock("Extract Highlights", frameGraph, scene); } + case "ShadowGeneratorBlock": { + return new NodeRenderGraphShadowGeneratorBlock("Shadow Generator", frameGraph, scene); + } + case "CascadedShadowGeneratorBlock": { + return new NodeRenderGraphCascadedShadowGeneratorBlock("Cascaded Shadow Generator", frameGraph, scene); + } } return null; @@ -156,6 +170,13 @@ export class BlockTools { case NodeRenderGraphBlockConnectionPointTypes.TextureLinearVelocity: color = "#c451e5"; break; + case NodeRenderGraphBlockConnectionPointTypes.StorageTexture: + case NodeRenderGraphBlockConnectionPointTypes.ResourceContainer: + case NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator: + case NodeRenderGraphBlockConnectionPointTypes.ShadowLight: + case NodeRenderGraphBlockConnectionPointTypes.StorageBuffer: + color = "#000000"; + break; case NodeRenderGraphBlockConnectionPointTypes.BasedOnInput: color = "#f28e0a"; // Used by the teleport blocks break; @@ -203,6 +224,16 @@ export class BlockTools { return NodeRenderGraphBlockConnectionPointTypes.TextureWorldNormal; case "TextureLinearVelocity": return NodeRenderGraphBlockConnectionPointTypes.TextureLinearVelocity; + case "StorageTexture": + return NodeRenderGraphBlockConnectionPointTypes.StorageTexture; + case "ResourceContainer": + return NodeRenderGraphBlockConnectionPointTypes.ResourceContainer; + case "ShadowGenerator": + return NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator; + case "ShadowLight": + return NodeRenderGraphBlockConnectionPointTypes.ShadowLight; + case "StorageBuffer": + return NodeRenderGraphBlockConnectionPointTypes.StorageBuffer; } return NodeRenderGraphBlockConnectionPointTypes.AutoDetect; @@ -242,6 +273,16 @@ export class BlockTools { return "TextureWorldNormal"; case NodeRenderGraphBlockConnectionPointTypes.TextureLinearVelocity: return "TextureLinearVelocity"; + case NodeRenderGraphBlockConnectionPointTypes.StorageTexture: + return "StorageTexture"; + case NodeRenderGraphBlockConnectionPointTypes.ResourceContainer: + return "ResourceContainer"; + case NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator: + return "ShadowGenerator"; + case NodeRenderGraphBlockConnectionPointTypes.ShadowLight: + return "ShadowLight"; + case NodeRenderGraphBlockConnectionPointTypes.StorageBuffer: + return "StorageBuffer"; } return ""; diff --git a/packages/tools/nodeRenderGraphEditor/src/components/nodeList/nodeListComponent.tsx b/packages/tools/nodeRenderGraphEditor/src/components/nodeList/nodeListComponent.tsx index d76459337f9..3b277315531 100644 --- a/packages/tools/nodeRenderGraphEditor/src/components/nodeList/nodeListComponent.tsx +++ b/packages/tools/nodeRenderGraphEditor/src/components/nodeList/nodeListComponent.tsx @@ -46,6 +46,10 @@ export class NodeListComponent extends React.Component { this._prepareLights(); - }); - - this._onUpdateRequiredObserver = globalState.stateManager.onUpdateRequiredObservable.add(() => { this._createNodeRenderGraph(); }); @@ -91,6 +92,7 @@ export class PreviewManager { await (this._engine as WebGPUEngine).initAsync(); } else { this._engine = new Engine(targetCanvas, true, { forceSRGBBufferSupportState: true }); + this._engine.getCaps().parallelShaderCompile = undefined; } const canvas = this._engine.getRenderingCanvas(); @@ -120,13 +122,15 @@ export class PreviewManager { this._scene = scene; + this._prepareBackgroundHDR(); + this._globalState.filesInput?.dispose(); this._globalState.filesInput = new FilesInput( this._engine, null, (_, scene) => { + this._scene.dispose(); this._initScene(scene); - this._prepareScene(); }, null, null, @@ -136,7 +140,8 @@ export class PreviewManager { () => { this._reset(); }, - false + false, + true ); this._lightParent = new TransformNode("LightParent", this._scene); @@ -150,6 +155,8 @@ export class PreviewManager { } }); + this._prepareScene(); + this._createNodeRenderGraph(); } @@ -170,26 +177,48 @@ export class PreviewManager { light.dispose(); } + // Create a dummy light, which will be used for a ShadowLight input in case no directional light is selected in the UI + const dummyLight = new DirectionalLight("dummy", new Vector3(0, 1, 0), this._scene); + dummyLight.intensity = 0.0; + // Create new lights based on settings if (this._globalState.hemisphericLight) { new HemisphericLight("Hemispheric light", new Vector3(0, 1, 0), this._scene); } + const worldExtends = this._scene.getWorldExtends(); + const diag = worldExtends.max.subtract(worldExtends.min).length(); + + const findLightPosition = (lightDir: Vector3) => { + const bb = new BoundingBox(worldExtends.min, worldExtends.max); + return bb.center.add(lightDir.scale(-diag * 0.5)); + }; + if (this._globalState.directionalLight0) { const dir0 = new DirectionalLight("Directional light #0", new Vector3(0.841626576496605, -0.2193391004130599, -0.49351298337996535), this._scene); - dir0.intensity = 0.9; + dir0.intensity = 0.7; dir0.diffuse = new Color3(0.9294117647058824, 0.9725490196078431, 0.996078431372549); dir0.specular = new Color3(0.9294117647058824, 0.9725490196078431, 0.996078431372549); dir0.parent = this._lightParent; + dir0.shadowMinZ = 0; + dir0.shadowMaxZ = diag; + dir0.position = findLightPosition(dir0.direction); } if (this._globalState.directionalLight1) { const dir1 = new DirectionalLight("Directional light #1", new Vector3(-0.9519937437504213, -0.24389315636999764, -0.1849974057546125), this._scene); - dir1.intensity = 1.2; + dir1.intensity = 0.7; dir1.specular = new Color3(0.9803921568627451, 0.9529411764705882, 0.7725490196078432); dir1.diffuse = new Color3(0.9803921568627451, 0.9529411764705882, 0.7725490196078432); dir1.parent = this._lightParent; + dir1.shadowMinZ = 0; + dir1.shadowMaxZ = diag; + dir1.position = findLightPosition(dir1.direction); } + + this._scene.meshes.forEach((m) => { + m.receiveShadows = true; + }); } private _createNodeRenderGraph() { @@ -236,6 +265,17 @@ export class PreviewManager { this._scene.cameras.length = 0; this._scene.cameraToUseForPointers = null; + const dummyLight = this._scene.getLightByName("dummy") as IShadowLight; + + const directionalLights: DirectionalLight[] = []; + for (const light of this._scene.lights) { + if (light instanceof DirectionalLight && light.name !== "dummy") { + directionalLights.push(light); + } + } + + let curLightIndex = 0; + // Set default external inputs const allInputs = this._nodeRenderGraph.getInputBlocks(); for (const input of allInputs) { @@ -268,6 +308,13 @@ export class PreviewManager { input.value = camera; } else if (input.isObjectList()) { input.value = { meshes: this._scene.meshes, particleSystems: this._scene.particleSystems }; + } else if (input.isShadowLight()) { + if (curLightIndex < directionalLights.length) { + input.value = directionalLights[curLightIndex++]; + curLightIndex = curLightIndex % directionalLights.length; + } else { + input.value = dummyLight; + } } } @@ -320,6 +367,9 @@ export class PreviewManager { await this._nodeRenderGraph.whenReadyAsync(); this._scene.frameGraph = this._nodeRenderGraph.frameGraph; } catch (err) { + if (logErrorTrace) { + (console as any).log(err); + } this._globalState.onLogRequiredObservable.notifyObservers(new LogEntry("From preview manager: " + err, true)); } } @@ -339,6 +389,7 @@ export class PreviewManager { arcRotateCamera.lowerRadiusLimit = null; arcRotateCamera.upperRadiusLimit = null; framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max); + arcRotateCamera.maxZ = worldExtends.max.subtract(worldExtends.min).length() * 2; } arcRotateCamera.pinchPrecision = 200 / arcRotateCamera.radius; @@ -350,41 +401,54 @@ export class PreviewManager { } private _prepareBackgroundHDR() { - this._scene.environmentTexture = this._hdrTexture; - } - - private _prepareScene() { - this._globalState.onIsLoadingChanged.notifyObservers(false); - - this._prepareLights(); - - this._frameCamera(); - this._prepareBackgroundHDR(); - } + this._hdrTexture = null as any; - public static DefaultEnvironmentURL = "https://assets.babylonjs.com/environments/environmentSpecular.env"; + let newHDRTexture: Nullable = null; - private _refreshPreviewMesh(force?: boolean) { switch (this._globalState.envType) { case PreviewType.Room: - this._hdrTexture = new CubeTexture(PreviewManager.DefaultEnvironmentURL, this._scene); - if (this._hdrTexture) { - this._prepareBackgroundHDR(); - } + newHDRTexture = new CubeTexture(PreviewManager.DefaultEnvironmentURL, this._scene); break; case PreviewType.Custom: { const blob = new Blob([this._globalState.envFile], { type: "octet/stream" }); const reader = new FileReader(); reader.onload = (evt) => { const dataurl = evt.target!.result as string; - this._hdrTexture = new CubeTexture(dataurl, this._scene, undefined, false, undefined, undefined, undefined, undefined, undefined, ".env"); - this._prepareBackgroundHDR(); + newHDRTexture = new CubeTexture(dataurl, this._scene, undefined, false, undefined, undefined, undefined, undefined, undefined, ".env"); }; reader.readAsDataURL(blob); break; } } + if (!newHDRTexture) { + return; + } + + this._hdrTexture = newHDRTexture; + + newHDRTexture.onLoadObservable.add(() => { + if (this._hdrTexture !== newHDRTexture) { + // The HDR texture has been changed in the meantime, so we don't need this one anymore + newHDRTexture!.dispose(); + } + }); + + this._hdrTexture = newHDRTexture; + this._scene.environmentTexture = this._hdrTexture; + } + + private _prepareScene() { + this._globalState.onIsLoadingChanged.notifyObservers(false); + + this._prepareLights(); + + this._frameCamera(); + } + + public static DefaultEnvironmentURL = "https://assets.babylonjs.com/environments/environmentSpecular.env"; + + private _refreshPreviewMesh(force?: boolean) { if (this._currentType === this._globalState.previewType && this._currentType !== PreviewType.Custom && !force) { return; } @@ -399,32 +463,27 @@ export class PreviewManager { case PreviewType.Box: SceneLoader.LoadAsync("https://assets.babylonjs.com/meshes/", "roundedCube.glb").then((scene) => { this._initScene(scene); - this._prepareScene(); }); return; case PreviewType.Sphere: SceneLoader.LoadAsync("https://assets.babylonjs.com/meshes/", "previewSphere.glb").then((scene) => { this._initScene(scene); - this._prepareScene(); }); break; case PreviewType.Cylinder: SceneLoader.LoadAsync("https://assets.babylonjs.com/meshes/", "roundedCylinder.glb").then((scene) => { this._initScene(scene); - this._prepareScene(); }); return; case PreviewType.Plane: { SceneLoader.LoadAsync("https://assets.babylonjs.com/meshes/", "highPolyPlane.glb").then((scene) => { this._initScene(scene); - this._prepareScene(); }); break; } case PreviewType.ShaderBall: SceneLoader.LoadAsync("https://assets.babylonjs.com/meshes/", "shaderBall.glb").then((scene) => { this._initScene(scene); - this._prepareScene(); }); return; case PreviewType.Custom: diff --git a/packages/tools/nodeRenderGraphEditor/src/graphEditor.tsx b/packages/tools/nodeRenderGraphEditor/src/graphEditor.tsx index 278967c86d8..d35a376f92b 100644 --- a/packages/tools/nodeRenderGraphEditor/src/graphEditor.tsx +++ b/packages/tools/nodeRenderGraphEditor/src/graphEditor.tsx @@ -31,6 +31,7 @@ import type { InternalTexture } from "core/Materials/Textures/internalTexture"; import { SplitContainer } from "shared-ui-components/split/splitContainer"; import { Splitter } from "shared-ui-components/split/splitter"; import { ControlledSize, SplitDirection } from "shared-ui-components/split/splitContext"; +import type { IShadowLight } from "core/Lights"; interface IGraphEditorProps { globalState: GlobalState; @@ -268,6 +269,8 @@ export class GraphEditor extends React.Component { img.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMSAyMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5WZWN0b3IzPC90aXRsZT48ZyBpZD0iTGF5ZXJfNSIgZGF0YS1uYW1lPSJMYXllciA1Ij48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0zLjU3LDEzLjMxLDkuNSw5Ljg5VjNBNy41MSw3LjUxLDAsMCwwLDMsMTAuNDYsNy4zMiw3LjMyLDAsMCwwLDMuNTcsMTMuMzFaIi8+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTYuNDMsMTUsMTAuNSwxMS42Miw0LjU3LDE1YTcuNDgsNy40OCwwLDAsMCwxMS44NiwwWiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTE4LDEwLjQ2QTcuNTEsNy41MSwwLDAsMCwxMS41LDNWOS44OWw1LjkzLDMuNDJBNy4zMiw3LjMyLDAsMCwwLDE4LDEwLjQ2WiIvPjwvZz48L3N2Zz4="; break; + case NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator: + img.src = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMSAyMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5NYXRyaXg8L3RpdGxlPjxnIGlkPSJMYXllcl81IiBkYXRhLW5hbWU9IkxheWVyIDUiPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTExLjUsNi4xMVY5LjVoMy4zOUE0LjUxLDQuNTEsMCwwLDAsMTEuNSw2LjExWiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTExLjUsMTQuODlhNC41MSw0LjUxLDAsMCwwLDMuMzktMy4zOUgxMS41WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTExLjUsMy4wN3YyQTUuNTQsNS41NCwwLDAsMSwxNS45Miw5LjVoMkE3LjUxLDcuNTEsMCwwLDAsMTEuNSwzLjA3WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTE1LjkyLDExLjVhNS41NCw1LjU0LDAsMCwxLTQuNDIsNC40MnYyYTcuNTEsNy41MSwwLDAsMCw2LjQzLTYuNDNaIi8+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNS4wOCwxMS41aC0yQTcuNTEsNy41MSwwLDAsMCw5LjUsMTcuOTN2LTJBNS41NCw1LjU0LDAsMCwxLDUuMDgsMTEuNVoiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik05LjUsMy4wN0E3LjUxLDcuNTEsMCwwLDAsMy4wNyw5LjVoMkE1LjU0LDUuNTQsMCwwLDEsOS41LDUuMDhaIi8+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNOS41LDExLjVINi4xMUE0LjUxLDQuNTEsMCwwLDAsOS41LDE0Ljg5WiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTkuNSw2LjExQTQuNTEsNC41MSwwLDAsMCw2LjExLDkuNUg5LjVaIi8+PC9nPjwvc3ZnPg=="; + break; + case NodeRenderGraphBlockConnectionPointTypes.ShadowLight: + img.src = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMSAyMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5WZWN0b3IxPC90aXRsZT48ZyBpZD0iTGF5ZXJfNSIgZGF0YS1uYW1lPSJMYXllciA1Ij48Y2lyY2xlIGNsYXNzPSJjbHMtMSIgY3g9IjEwLjUiIGN5PSIxMC41IiByPSI3LjUiLz48L2c+PC9zdmc+"; + break; case NodeRenderGraphBlockConnectionPointTypes.TextureBackBufferDepthStencilAttachment: case NodeRenderGraphBlockConnectionPointTypes.TextureBackBuffer: case NodeRenderGraphBlockConnectionPointTypes.Texture: @@ -40,6 +48,12 @@ export const RegisterNodePortDesign = (stateManager: StateManager) => { img.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMSAyMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5WZWN0b3IxPC90aXRsZT48ZyBpZD0iTGF5ZXJfNSIgZGF0YS1uYW1lPSJMYXllciA1Ij48Y2lyY2xlIGNsYXNzPSJjbHMtMSIgY3g9IjEwLjUiIGN5PSIxMC41IiByPSI3LjUiLz48L2c+PC9zdmc+"; break; + case NodeRenderGraphBlockConnectionPointTypes.ResourceContainer: + img.src = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMSIgaGVpZ2h0PSIyMSIgdmlld0JveD0iMCAwIDIxIDIxIj48Y2lyY2xlIGN4PSI3LjEiIGN5PSIxMy4wOCIgcj0iMy4yNSIgc3R5bGU9ImZpbGw6I2ZmZiIvPjxwYXRoIGQ9Ik0xMC40OSwzQTcuNTIsNy41MiwwLDAsMCwzLDEwYTUuMTMsNS4xMywwLDEsMSw2LDcuODUsNy42MSw3LjYxLDAsMCwwLDEuNTIuMTYsNy41Miw3LjUyLDAsMCwwLDAtMTVaIiBzdHlsZT0iZmlsbDojZmZmIi8+PC9zdmc+"; + img.style.width = "100%"; // it's so that the svg is correctly centered inside the outer circle + img.style.height = "100%"; + break; } }; };