diff --git a/packages/dev/core/src/Engines/abstractEngine.ts b/packages/dev/core/src/Engines/abstractEngine.ts index 1e09a66de1f..3edfbb12072 100644 --- a/packages/dev/core/src/Engines/abstractEngine.ts +++ b/packages/dev/core/src/Engines/abstractEngine.ts @@ -27,7 +27,7 @@ import type { ThinTexture } from "../Materials/Textures/thinTexture"; import type { InternalTextureCreationOptions, TextureSize } from "../Materials/Textures/textureCreationOptions"; import type { EffectFallbacks } from "../Materials/effectFallbacks"; import type { IMaterialContext } from "./IMaterialContext"; -import type { IStencilState } from "../States/IStencilState"; +import type { IStencilStateProperties, IStencilState } from "../States/IStencilState"; import type { DrawWrapper } from "../Materials/drawWrapper"; import type { IDrawContext } from "./IDrawContext"; import type { VertexBuffer } from "../Meshes/buffer"; @@ -1363,7 +1363,7 @@ export abstract class AbstractEngine { force?: boolean, reverseSide?: boolean, cullBackFaces?: boolean, - stencil?: IStencilState, + stencil?: IStencilState | IStencilStateProperties, zOffsetUnits?: number ): void; diff --git a/packages/dev/core/src/Engines/webgpuEngine.ts b/packages/dev/core/src/Engines/webgpuEngine.ts index e82b97a6e8f..1c325b14a99 100644 --- a/packages/dev/core/src/Engines/webgpuEngine.ts +++ b/packages/dev/core/src/Engines/webgpuEngine.ts @@ -3136,6 +3136,7 @@ export class WebGPUEngine extends ThinWebGPUEngine { // We use the MSAA texture format (if available) to determine if it has a stencil aspect or not because, for MSAA depth textures, // the format of the "resolve" texture (gpuDepthStencilWrapper.format) is a single red channel format, not a depth-stencil format. const depthTextureHasStencil = gpuDepthStencilWrapper ? WebGPUTextureHelper.HasStencilAspect(gpuDepthStencilMSAATexture?.format ?? gpuDepthStencilWrapper.format) : false; + const depthTextureHasDepth = gpuDepthStencilWrapper ? WebGPUTextureHelper.HasDepthAspect(gpuDepthStencilMSAATexture?.format ?? gpuDepthStencilWrapper.format) : false; const colorAttachments: (GPURenderPassColorAttachment | null)[] = []; @@ -3240,18 +3241,17 @@ export class WebGPUEngine extends ThinWebGPUEngine { ? { view: depthMSAATextureView ? depthMSAATextureView : depthTextureView!, depthClearValue: mustClearDepth ? (this.useReverseDepthBuffer ? this._clearReverseDepthValue : this._clearDepthValue) : undefined, - depthLoadOp: rtWrapper.depthReadOnly ? undefined : mustClearDepth ? WebGPUConstants.LoadOp.Clear : WebGPUConstants.LoadOp.Load, - depthStoreOp: rtWrapper.depthReadOnly ? undefined : WebGPUConstants.StoreOp.Store, + depthLoadOp: rtWrapper.depthReadOnly || !depthTextureHasDepth ? undefined : mustClearDepth ? WebGPUConstants.LoadOp.Clear : WebGPUConstants.LoadOp.Load, + depthStoreOp: rtWrapper.depthReadOnly || !depthTextureHasDepth ? undefined : WebGPUConstants.StoreOp.Store, depthReadOnly: rtWrapper.depthReadOnly, stencilClearValue: rtWrapper._depthStencilTextureWithStencil && mustClearStencil ? this._clearStencilValue : undefined, - stencilLoadOp: rtWrapper.stencilReadOnly - ? undefined - : !depthTextureHasStencil - ? undefined - : rtWrapper._depthStencilTextureWithStencil && mustClearStencil - ? WebGPUConstants.LoadOp.Clear - : WebGPUConstants.LoadOp.Load, - stencilStoreOp: rtWrapper.stencilReadOnly ? undefined : !depthTextureHasStencil ? undefined : WebGPUConstants.StoreOp.Store, + stencilLoadOp: + rtWrapper.stencilReadOnly || !depthTextureHasStencil + ? undefined + : rtWrapper._depthStencilTextureWithStencil && mustClearStencil + ? WebGPUConstants.LoadOp.Clear + : WebGPUConstants.LoadOp.Load, + stencilStoreOp: rtWrapper.stencilReadOnly || !depthTextureHasStencil ? undefined : WebGPUConstants.StoreOp.Store, stencilReadOnly: rtWrapper.stencilReadOnly, } : undefined, diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/volumetricLightingBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/volumetricLightingBlock.ts new file mode 100644 index 00000000000..ae198e632ae --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/PostProcesses/volumetricLightingBlock.ts @@ -0,0 +1,222 @@ +import type { + Camera, + DirectionalLight, + FrameGraph, + FrameGraphObjectList, + FrameGraphTextureHandle, + NodeRenderGraphBuildState, + NodeRenderGraphConnectionPoint, + Scene, +} from "core/index"; +import { RegisterClass } from "../../../../Misc/typeStore"; +import { editableInPropertyPage, PropertyTypeForEdition } from "../../../../Decorators/nodeDecorator"; +import { NodeRenderGraphBlock } from "../../nodeRenderGraphBlock"; +import { FrameGraphVolumetricLightingTask } from "core/FrameGraph/Tasks/PostProcesses/volumetricLightingTask"; +import { NodeRenderGraphBlockConnectionPointTypes } from "../../Types/nodeRenderGraphTypes"; +import { Vector3 } from "core/Maths/math.vector"; +import { Color3 } from "core/Maths/math.color"; + +/** + * Block that implements the volumetric lighting post process + */ +export class NodeRenderGraphVolumetricLightingBlock extends NodeRenderGraphBlock { + protected override _frameGraphTask: FrameGraphVolumetricLightingTask; + + public override _additionalConstructionParameters: [boolean]; + + /** + * Gets the frame graph task associated with this block + */ + public override get task() { + return this._frameGraphTask; + } + + /** + * Create a new NodeRenderGraphVolumetricLightingBlock + * @param name defines the block name + * @param frameGraph defines the hosting frame graph + * @param scene defines the hosting scene + * @param enableExtinction defines whether to enable extinction coefficients + */ + public constructor(name: string, frameGraph: FrameGraph, scene: Scene, enableExtinction = false) { + super(name, frameGraph, scene); + + this._additionalConstructionParameters = [enableExtinction]; + + this.registerInput("target", NodeRenderGraphBlockConnectionPointTypes.AutoDetect); + this.registerInput("depth", NodeRenderGraphBlockConnectionPointTypes.AutoDetect); + this.registerInput("camera", NodeRenderGraphBlockConnectionPointTypes.Camera); + this.registerInput("lightingVolumeMesh", NodeRenderGraphBlockConnectionPointTypes.ObjectList); + this.registerInput("light", NodeRenderGraphBlockConnectionPointTypes.ShadowLight); + this.registerInput("lightingVolumeTexture", NodeRenderGraphBlockConnectionPointTypes.AutoDetect, true); + + this.target.addExcludedConnectionPointFromAllowedTypes(NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer); + this.lightingVolumeTexture.addExcludedConnectionPointFromAllowedTypes(NodeRenderGraphBlockConnectionPointTypes.TextureAllButBackBuffer); + + this.depth.addExcludedConnectionPointFromAllowedTypes( + NodeRenderGraphBlockConnectionPointTypes.TextureDepthStencilAttachment | NodeRenderGraphBlockConnectionPointTypes.TextureScreenDepth + ); + + this._addDependenciesInput(); + + this.registerOutput("output", NodeRenderGraphBlockConnectionPointTypes.BasedOnInput); + + this.output._typeConnectionSource = () => { + return this.target; + }; + + this._frameGraphTask = new FrameGraphVolumetricLightingTask(name, frameGraph, enableExtinction); + } + + private _createTask(enableExtinction: boolean) { + const sourceSamplingMode = this._frameGraphTask.sourceSamplingMode; + const phaseG = this._frameGraphTask.phaseG; + const extinction = this._frameGraphTask.extinction; + const lightPower = this._frameGraphTask.lightPower; + + this._frameGraphTask.dispose(); + + this._frameGraphTask = new FrameGraphVolumetricLightingTask(this.name, this._frameGraph, enableExtinction); + this._frameGraphTask.sourceSamplingMode = sourceSamplingMode; + this._frameGraphTask.phaseG = phaseG; + this._frameGraphTask.extinction = extinction; + this._frameGraphTask.lightPower = lightPower; + + this._additionalConstructionParameters = [enableExtinction]; + } + + /** Gets or sets the phaseG parameter */ + @editableInPropertyPage("PhaseG", PropertyTypeForEdition.Float, "PROPERTIES", { min: -0.9, max: 0.9 }) + public get phaseG(): number { + return this._frameGraphTask.phaseG; + } + + public set phaseG(value: number) { + this._frameGraphTask.phaseG = value; + } + + /** If extinction coefficients should be used */ + @editableInPropertyPage("Enable extinction", PropertyTypeForEdition.Boolean, "PROPERTIES") + public get enableExtinction(): boolean { + return this._frameGraphTask.enableExtinction; + } + + public set enableExtinction(value: boolean) { + this._createTask(value); + } + + /** Gets or sets the extinction color */ + @editableInPropertyPage("Extinction", PropertyTypeForEdition.Vector3, "PROPERTIES") + public get extinction(): Vector3 { + return this._frameGraphTask.extinction; + } + + public set extinction(value: Vector3) { + this._frameGraphTask.extinction = value; + } + + /** Gets or sets the light power */ + @editableInPropertyPage("Light power", PropertyTypeForEdition.Color3, "PROPERTIES") + public get lightPower(): Color3 { + return this._frameGraphTask.lightPower; + } + + public set lightPower(value: Color3) { + this._frameGraphTask.lightPower = value; + } + + /** + * Gets the current class name + * @returns the class name + */ + public override getClassName() { + return "NodeRenderGraphVolumetricLightingBlock"; + } + + /** + * Gets the target input component + */ + public get target(): NodeRenderGraphConnectionPoint { + return this._inputs[0]; + } + + /** + * Gets the depth texture input component + */ + public get depth(): NodeRenderGraphConnectionPoint { + return this._inputs[1]; + } + + /** + * Gets the camera input component + */ + public get camera(): NodeRenderGraphConnectionPoint { + return this._inputs[2]; + } + + /** + * Gets the lighting volume mesh input component + */ + public get lightingVolumeMesh(): NodeRenderGraphConnectionPoint { + return this._inputs[3]; + } + + /** + * Gets the light input component + */ + public get light(): NodeRenderGraphConnectionPoint { + return this._inputs[4]; + } + + /** + * Gets the lighting volume texture input component + */ + public get lightingVolumeTexture(): NodeRenderGraphConnectionPoint { + return this._inputs[5]; + } + + /** + * Gets the output component + */ + public get output(): NodeRenderGraphConnectionPoint { + return this._outputs[0]; + } + + protected override _buildBlock(state: NodeRenderGraphBuildState) { + super._buildBlock(state); + + this.output.value = this._frameGraphTask.outputTexture; + + this._frameGraphTask.targetTexture = this.target.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.depthTexture = this.depth.connectedPoint?.value as FrameGraphTextureHandle; + this._frameGraphTask.camera = this.camera.connectedPoint?.value as Camera; + this._frameGraphTask.lightingVolumeMesh = this.lightingVolumeMesh.connectedPoint?.value as FrameGraphObjectList; + this._frameGraphTask.light = this.light.connectedPoint?.value as DirectionalLight; + this._frameGraphTask.lightingVolumeTexture = this.lightingVolumeTexture.connectedPoint?.value as FrameGraphTextureHandle; + } + + protected override _dumpPropertiesCode() { + const codes: string[] = []; + codes.push(`${this._codeVariableName}.phaseG = ${this.phaseG};`); + codes.push(`${this._codeVariableName}.extinction = new BABYLON.Vector3(${this.extinction.x}, ${this.extinction.y}, ${this.extinction.z});`); + codes.push(`${this._codeVariableName}.lightPower = new Color3(${this.lightPower.r}, ${this.lightPower.g}, ${this.lightPower.b});`); + return super._dumpPropertiesCode() + codes.join("\n"); + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.phaseG = this.phaseG; + serializationObject.extinction = this.extinction.asArray(); + serializationObject.lightPower = this.lightPower.asArray(); + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.phaseG = serializationObject.phaseG; + this.extinction = Vector3.FromArray(serializationObject.extinction); + this.lightPower = Color3.FromArray(serializationObject.lightPower); + } +} + +RegisterClass("BABYLON.NodeRenderGraphVolumetricLightingBlock", NodeRenderGraphVolumetricLightingBlock); diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/index.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/index.ts index 96fb25fd36c..a623119da4e 100644 --- a/packages/dev/core/src/FrameGraph/Node/Blocks/index.ts +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/index.ts @@ -3,6 +3,7 @@ export * from "./cullObjectsBlock"; export * from "./elbowBlock"; export * from "./executeBlock"; export * from "./inputBlock"; +export * from "./lightingVolumeBlock"; export * from "./outputBlock"; export * from "./resourceContainerBlock"; @@ -33,6 +34,7 @@ export * from "./PostProcesses/ssao2PostProcessBlock"; export * from "./PostProcesses/ssrPostProcessBlock"; export * from "./PostProcesses/taaPostProcessBlock"; export * from "./PostProcesses/tonemapPostProcessBlock"; +export * from "./PostProcesses/volumetricLightingBlock"; export * from "./Rendering/csmShadowGeneratorBlock"; export * from "./Rendering/geometryRendererBlock"; diff --git a/packages/dev/core/src/FrameGraph/Node/Blocks/lightingVolumeBlock.ts b/packages/dev/core/src/FrameGraph/Node/Blocks/lightingVolumeBlock.ts new file mode 100644 index 00000000000..319347bbafa --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Node/Blocks/lightingVolumeBlock.ts @@ -0,0 +1,123 @@ +import type { FrameGraph, FrameGraphShadowGeneratorTask, NodeRenderGraphBuildState, NodeRenderGraphConnectionPoint, Scene } from "core/index"; +import { FrameGraphLightingVolumeTask } from "core/FrameGraph/Tasks/Misc/lightingVolumeTask"; +import { RegisterClass } from "../../../Misc/typeStore"; +import { editableInPropertyPage, PropertyTypeForEdition } from "../../../Decorators/nodeDecorator"; +import { NodeRenderGraphBlock } from "../nodeRenderGraphBlock"; +import { NodeRenderGraphBlockConnectionPointTypes } from "../Types/nodeRenderGraphTypes"; + +/** + * Block that implements the lighting volume + */ +export class NodeRenderGraphLightingVolumeBlock extends NodeRenderGraphBlock { + protected override _frameGraphTask: FrameGraphLightingVolumeTask; + + /** + * Gets the frame graph task associated with this block + */ + public override get task() { + return this._frameGraphTask; + } + + /** + * Create a new NodeRenderGraphLightingVolumeBlock + * @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("shadowGenerator", NodeRenderGraphBlockConnectionPointTypes.ShadowGenerator); + + this._addDependenciesInput(); + + this.registerOutput("output", NodeRenderGraphBlockConnectionPointTypes.ObjectList); + + this._frameGraphTask = new FrameGraphLightingVolumeTask(name, frameGraph); + } + + /** Gets or sets the tesselation parameter */ + @editableInPropertyPage("Tesselation", PropertyTypeForEdition.Int, "PROPERTIES", { min: 1, max: 4096 }) + public get tesselation(): number { + return this._frameGraphTask.lightingVolume.tesselation; + } + + public set tesselation(value: number) { + this._frameGraphTask.lightingVolume.tesselation = value; + } + + /** Gets or sets the refresh frequency parameter */ + @editableInPropertyPage("Refresh frequency", PropertyTypeForEdition.Int, "PROPERTIES") + public get frequency(): number { + return this._frameGraphTask.lightingVolume.frequency; + } + + public set frequency(value: number) { + this._frameGraphTask.lightingVolume.frequency = value; + } + + /** Indicates whether to build the full volume (true) or only the far plane (false). Default is false. */ + @editableInPropertyPage("Build full volume", PropertyTypeForEdition.Boolean, "PROPERTIES") + public get buildFullVolume(): boolean { + return this._frameGraphTask.lightingVolume.buildFullVolume; + } + + public set buildFullVolume(value: boolean) { + this._frameGraphTask.lightingVolume.buildFullVolume = value; + } + + /** + * Gets the current class name + * @returns the class name + */ + public override getClassName() { + return "NodeRenderGraphLightingVolumeBlock"; + } + + /** + * Gets the shadow generator input component + */ + public get shadowGenerator(): NodeRenderGraphConnectionPoint { + return this._inputs[0]; + } + + /** + * Gets the output component + */ + public get output(): NodeRenderGraphConnectionPoint { + return this._outputs[0]; + } + + protected override _buildBlock(state: NodeRenderGraphBuildState) { + super._buildBlock(state); + + this.output.value = this._frameGraphTask.outputMeshLightingVolume; + + this._frameGraphTask.shadowGenerator = this.shadowGenerator.connectedPoint?.value as FrameGraphShadowGeneratorTask; + } + + protected override _dumpPropertiesCode() { + const codes: string[] = []; + codes.push(`${this._codeVariableName}.tesselation = ${this.tesselation};`); + codes.push(`${this._codeVariableName}.frequency = ${this.frequency};`); + codes.push(`${this._codeVariableName}.buildFullVolume = ${this.buildFullVolume};`); + return super._dumpPropertiesCode() + codes.join("\n"); + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.tesselation = this.tesselation; + serializationObject.frequency = this.frequency; + serializationObject.buildFullVolume = this.buildFullVolume; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.tesselation = serializationObject.tesselation; + this.frequency = serializationObject.frequency; + this.buildFullVolume = !!serializationObject.buildFullVolume; + } +} + +RegisterClass("BABYLON.NodeRenderGraphLightingVolumeBlock", NodeRenderGraphLightingVolumeBlock); diff --git a/packages/dev/core/src/FrameGraph/Tasks/Misc/lightingVolumeTask.ts b/packages/dev/core/src/FrameGraph/Tasks/Misc/lightingVolumeTask.ts new file mode 100644 index 00000000000..75b662a4abc --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Tasks/Misc/lightingVolumeTask.ts @@ -0,0 +1,86 @@ +import type { FrameGraph, FrameGraphObjectList, FrameGraphShadowGeneratorTask } from "core/index"; +import { LightingVolume } from "core/Lights/lightingVolume"; +import { DirectionalLight } from "core/Lights/directionalLight"; +import { FrameGraphTask } from "../../frameGraphTask"; + +/** + * Task used to create a lighting volume from a directional light's shadow generator. + */ +export class FrameGraphLightingVolumeTask extends FrameGraphTask { + /** + * The shadow generator used to create the lighting volume. + */ + public shadowGenerator: FrameGraphShadowGeneratorTask; + + /** + * The output object list containing the lighting volume mesh. + */ + public readonly outputMeshLightingVolume: FrameGraphObjectList; + + /** + * The lighting volume created by this task. + */ + public readonly lightingVolume: LightingVolume; + + public override get name() { + return this._name; + } + + public override set name(name: string) { + this._name = name; + if (this.lightingVolume) { + this.lightingVolume.name = name; + } + } + + /** + * Creates a new FrameGraphLightingVolumeTask. + * @param name Name of the task. + * @param frameGraph The frame graph instance. + */ + constructor(name: string, frameGraph: FrameGraph) { + super(name, frameGraph); + + this.lightingVolume = new LightingVolume(name, frameGraph.scene); + + this.outputMeshLightingVolume = { + meshes: [this.lightingVolume.mesh], + particleSystems: [], + }; + } + + public override isReady() { + return this.lightingVolume.isReady(); + } + + public record() { + if (this.shadowGenerator === undefined) { + throw new Error(`FrameGraphLightingVolumeTask ${this.name}: shadowGenerator is required`); + } + + const light = this.shadowGenerator.light; + + if (!(light instanceof DirectionalLight)) { + throw new Error(`FrameGraphLightingVolumeTask ${this.name}: light must be a directional light`); + } + + this.lightingVolume.shadowGenerator = this.shadowGenerator.shadowGenerator; + + const pass = this._frameGraph.addObjectListPass(this.name); + + pass.setObjectList(this.outputMeshLightingVolume); + pass.setExecuteFunc(() => { + this.lightingVolume.update(); + }); + + const passDisabled = this._frameGraph.addObjectListPass(this.name + "_disabled", true); + + passDisabled.setObjectList(this.outputMeshLightingVolume); + passDisabled.setExecuteFunc(() => {}); + } + + public override dispose() { + super.dispose(); + this.lightingVolume.dispose(); + } +} diff --git a/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/postProcessTask.ts b/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/postProcessTask.ts index ca11b2a7643..ccc993e474e 100644 --- a/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/postProcessTask.ts +++ b/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/postProcessTask.ts @@ -5,9 +5,9 @@ import type { FrameGraphRenderPass, FrameGraphRenderContext, EffectWrapper, - IStencilState, IViewportLike, Nullable, + IStencilStateProperties, } from "core/index"; import { Constants } from "core/Engines/constants"; import { FrameGraphTask } from "../../frameGraphTask"; @@ -37,7 +37,7 @@ export class FrameGraphPostProcessTask extends FrameGraphTask { /** * The stencil state to use for the post process (optional). */ - public stencilState?: IStencilState; + public stencilState?: IStencilStateProperties; /** * The depth attachment texture to use for the post process (optional). diff --git a/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/volumetricLightingBlendVolumeTask.ts b/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/volumetricLightingBlendVolumeTask.ts new file mode 100644 index 00000000000..4a9c8da2861 --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/volumetricLightingBlendVolumeTask.ts @@ -0,0 +1,84 @@ +import type { AbstractEngine, Camera, EffectWrapperCreationOptions, FrameGraph, FrameGraphRenderPass, FrameGraphTextureHandle, Nullable } from "core/index"; +import { Vector3, Matrix } from "core/Maths/math.vector"; +import { Constants } from "core/Engines/constants"; +import { ThinPassPostProcess } from "core/PostProcesses/thinPassPostProcess"; +import { FrameGraphPostProcessTask } from "./postProcessTask"; + +import "core/Shaders/volumetricLightingBlendVolume.fragment"; +import "core/ShadersWGSL/volumetricLightingBlendVolume.fragment"; + +/** + * @internal + */ +class VolumetricLightingBlendVolumeThinPostProcess extends ThinPassPostProcess { + public camera: Camera; + + public outputTextureWidth = 0; + + public outputTextureHeight = 0; + + public extinction = new Vector3(0, 0, 0); + + public enableExtinction = false; + + private _invProjection: Matrix; + + constructor(name: string, engine: Nullable = null, enableExtinction = false, options?: EffectWrapperCreationOptions) { + super(name, engine, { + ...options, + fragmentShader: "volumetricLightingBlendVolume", + samplers: ["depthSampler"], + uniforms: ["invProjection", "outputTextureSize", "extinction"], + defines: enableExtinction ? ["#define DUAL_SOURCE_BLENDING", "#define USE_EXTINCTION"] : undefined, + }); + + this._invProjection = new Matrix(); + this.alphaMode = enableExtinction ? Constants.ALPHA_DUAL_SRC0_ADD_SRC1xDST : Constants.ALPHA_ADD; + } + + public override bind(noDefaultBindings = false) { + super.bind(noDefaultBindings); + + const effect = this.drawWrapper.effect!; + + this._invProjection.copyFrom(this.camera.getProjectionMatrix()); + this._invProjection.invert(); + + effect.setMatrix("invProjection", this._invProjection); + effect.setFloat2("outputTextureSize", this.outputTextureWidth, this.outputTextureHeight); + effect.setVector3("extinction", this.extinction); + } +} + +/** + * @internal + */ +export class FrameGraphVolumetricLightingBlendVolumeTask extends FrameGraphPostProcessTask { + public override readonly postProcess: VolumetricLightingBlendVolumeThinPostProcess; + + public depthTexture: FrameGraphTextureHandle; + + public camera: Camera; + + constructor(name: string, frameGraph: FrameGraph, enableExtinction = false) { + super(name, frameGraph, new VolumetricLightingBlendVolumeThinPostProcess(name, frameGraph.engine, enableExtinction)); + } + + public override record(skipCreationOfDisabledPasses = false): FrameGraphRenderPass { + if (this.sourceTexture === undefined || this.depthTexture === undefined || this.camera === undefined) { + throw new Error(`FrameGraphVolumetricLightingBlendVolumeTask "${this.name}": sourceTexture, depthTexture and camera are required`); + } + + const pass = super.record(skipCreationOfDisabledPasses, undefined, (context) => { + this.postProcess.camera = this.camera; + context.bindTextureHandle(this._postProcessDrawWrapper.effect!, "depthSampler", this.depthTexture); + }); + + pass.addDependencies(this.depthTexture); + + this.postProcess.outputTextureWidth = this._outputWidth; + this.postProcess.outputTextureHeight = this._outputHeight; + + return pass; + } +} diff --git a/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/volumetricLightingTask.ts b/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/volumetricLightingTask.ts new file mode 100644 index 00000000000..e308e63e89b --- /dev/null +++ b/packages/dev/core/src/FrameGraph/Tasks/PostProcesses/volumetricLightingTask.ts @@ -0,0 +1,310 @@ +import type { Camera, DirectionalLight, FrameGraph, FrameGraphObjectList, FrameGraphTextureHandle } from "core/index"; +import { FrameGraphVolumetricLightingBlendVolumeTask } from "./volumetricLightingBlendVolumeTask"; +import { Matrix, TmpVectors, Vector2, Vector3, Vector4 } from "core/Maths/math.vector"; +import { Color3, Color4 } from "core/Maths/math.color"; +import { FrameGraphTask } from "../../frameGraphTask"; +import { Constants } from "core/Engines/constants"; +import { FrameGraphClearTextureTask } from "../Texture/clearTextureTask"; +import { FrameGraphObjectRendererTask } from "../Rendering/objectRendererTask"; +import { ShaderMaterial } from "core/Materials/shaderMaterial"; +import { ShaderLanguage } from "core/Materials/shaderLanguage"; + +import "core/Shaders/volumetricLightingRenderVolume.vertex"; +import "core/Shaders/volumetricLightingRenderVolume.fragment"; +import "core/ShadersWGSL/volumetricLightingRenderVolume.vertex"; +import "core/ShadersWGSL/volumetricLightingRenderVolume.fragment"; + +const InvViewProjectionMatrix = new Matrix(); + +/** + * A frame graph task that performs volumetric lighting. + */ +export class FrameGraphVolumetricLightingTask extends FrameGraphTask { + /** + * The target texture to which the volumetric lighting will be applied. + */ + public targetTexture: FrameGraphTextureHandle; + + /** + * The sampling mode to use when blending the volumetric lighting texture with targetTexture. + */ + public sourceSamplingMode = Constants.TEXTURE_BILINEAR_SAMPLINGMODE; + + /** + * The depth texture used for volumetric lighting calculations. + * It must be the depth texture used to generate targetTexture. + */ + public depthTexture: FrameGraphTextureHandle; + + /** + * The camera used for volumetric lighting calculations. + */ + public camera: Camera; + + /** + * The mesh representing the lighting volume. + * This is the mesh that will be rendered to create the volumetric lighting effect. + */ + public lightingVolumeMesh: FrameGraphObjectList; + + /** + * The directional light used for volumetric lighting. + */ + public light: DirectionalLight; + + /** + * The lighting volume texture (optional). + * If not provided, a new texture will be created, which the same size, format and type as targetTexture. + * This is the texture that will store the volumetric lighting information, before being blended to targetTexture. + */ + public lightingVolumeTexture?: FrameGraphTextureHandle; + + private _extinctionPhaseG = new Vector4(0, 0, 0, 0); + + /** + * The phase G parameter for the volumetric lighting effect (default: 0). + * This parameter controls the anisotropy of the scattering. + * A value of 0 means isotropic scattering, while a value of 1 means forward scattering and -1 means backward scattering. + */ + public get phaseG() { + return this._extinctionPhaseG.w; + } + + public set phaseG(value: number) { + this._extinctionPhaseG.w = value; + this._renderLightingVolumeMaterial.setVector4("extinctionPhaseG", this._extinctionPhaseG); + } + + /** + * Whether to enable extinction in the volumetric lighting effect (default: false). + * Read-only property set in the constructor. + */ + public readonly enableExtinction: boolean; + + /** + * The extinction coefficient for the volumetric lighting effect (default: (0, 0, 0) - no extinction). + * This parameter controls how much light is absorbed and scattered as it travels through the medium. + */ + public get extinction() { + return this._blendLightingVolumeTask.postProcess.extinction; + } + + public set extinction(value: Vector3) { + this._extinctionPhaseG.x = Math.max(value.x, 1e-6); + this._extinctionPhaseG.y = Math.max(value.y, 1e-6); + this._extinctionPhaseG.z = Math.max(value.z, 1e-6); + this._renderLightingVolumeMaterial.setVector4("extinctionPhaseG", this._extinctionPhaseG); + this._blendLightingVolumeTask.postProcess.extinction.copyFromFloats(this._extinctionPhaseG.x, this._extinctionPhaseG.y, this._extinctionPhaseG.z); + } + + private _lightPower = new Color3(1, 1, 1); + + /** + * The light power/color for the volumetric lighting effect (default: (1, 1, 1)). + * This parameter controls the intensity and color of the light used for volumetric lighting. + */ + public get lightPower() { + return this._lightPower; + } + + public set lightPower(value: Color3) { + this._lightPower.copyFrom(value); + this._renderLightingVolumeMaterial.setColor3("lightPower", this._lightPower); + } + + public override get name() { + return this._name; + } + + public override set name(name: string) { + this._name = name; + if (this._renderLightingVolumeMaterial) { + this._renderLightingVolumeMaterial.name = `${name} - render lighting volume`; + } + if (this._clearLightingVolumeTextureTask) { + this._clearLightingVolumeTextureTask.name = `${name} - clear lighting volume texture`; + } + if (this._renderLightingVolumeTask) { + this._renderLightingVolumeTask.name = `${name} - render lighting volume`; + } + if (this._blendLightingVolumeTask) { + this._blendLightingVolumeTask.name = `${name} - blend lighting volume`; + } + } + + /** + * The output texture of the task. + */ + public readonly outputTexture: FrameGraphTextureHandle; + + private readonly _clearLightingVolumeTextureTask: FrameGraphClearTextureTask; + private readonly _renderLightingVolumeTask: FrameGraphObjectRendererTask; + private readonly _blendLightingVolumeTask: FrameGraphVolumetricLightingBlendVolumeTask; + private _renderLightingVolumeMaterial: ShaderMaterial; + + /** + * Creates a new FrameGraphVolumetricLightingTask. + * @param name The name of the task. + * @param frameGraph The frame graph to which the task belongs. + * @param enableExtinction Whether to enable extinction in the volumetric lighting effect (default: false). If you don't plan to set extinction to something different than (0, 0, 0), you can disable this to save some performance. + */ + constructor(name: string, frameGraph: FrameGraph, enableExtinction = false) { + super(name, frameGraph); + + this.enableExtinction = enableExtinction; + + const isWebGPU = this._frameGraph.engine.isWebGPU; + + this._renderLightingVolumeMaterial = new ShaderMaterial(`${name} - render lighting volume`, this._frameGraph.scene, "volumetricLightingRenderVolume", { + attributes: ["position"], + uniformBuffers: ["Scene", "Mesh"], + uniforms: ["world", "viewProjection", "vEyePosition", "lightDir", "invViewProjection", "outputTextureSize", "extinctionPhaseG", "lightPower"], + samplers: ["depthTexture"], + defines: enableExtinction ? ["USE_EXTINCTION"] : [], + shaderLanguage: isWebGPU ? ShaderLanguage.WGSL : ShaderLanguage.GLSL, + needAlphaBlending: true, + }); + this._renderLightingVolumeMaterial.backFaceCulling = false; + this._renderLightingVolumeMaterial.alphaMode = Constants.ALPHA_ADD; + this._renderLightingVolumeMaterial.depthFunction = Constants.ALWAYS; + this._renderLightingVolumeMaterial.stencil.enabled = this.enableExtinction; + this._renderLightingVolumeMaterial.stencil.func = Constants.ALWAYS; + this._renderLightingVolumeMaterial.stencil.backFunc = Constants.ALWAYS; + this._renderLightingVolumeMaterial.stencil.funcRef = 1; + this._renderLightingVolumeMaterial.onBindObservable.add(() => { + this._renderLightingVolumeMaterial.bindEyePosition(this._renderLightingVolumeMaterial.getEffect()); + }); + + this._clearLightingVolumeTextureTask = new FrameGraphClearTextureTask(`${name} - clear lighting volume texture`, frameGraph); + this._renderLightingVolumeTask = new FrameGraphObjectRendererTask(`${name} - render lighting volume`, frameGraph, frameGraph.scene); + this._blendLightingVolumeTask = new FrameGraphVolumetricLightingBlendVolumeTask(`${name} - blend lighting volume`, frameGraph, enableExtinction); + + this.onTexturesAllocatedObservable.add(() => { + this._renderLightingVolumeMaterial.setInternalTexture("depthTexture", frameGraph.textureManager.getTextureFromHandle(this.depthTexture)!); + }); + + this.outputTexture = this._frameGraph.textureManager.createDanglingHandle(); + + // Triggers the setters to set the uniforms + this.phaseG = this._extinctionPhaseG.w; + this.extinction = new Vector3(this.extinction.x, this.extinction.y, this.extinction.z); + this.lightPower = this._lightPower; + } + + public override isReady() { + return ( + this._renderLightingVolumeMaterial.isReady() && + this._clearLightingVolumeTextureTask.isReady() && + this._renderLightingVolumeMaterial.isReady() && + this._blendLightingVolumeTask.isReady() + ); + } + + public override record(skipCreationOfDisabledPasses = false) { + if (this.targetTexture === undefined || this.depthTexture === undefined || this.camera === undefined || this.lightingVolumeMesh === undefined || this.light === undefined) { + throw new Error(`FrameGraphVolumetricLightingTask "${this.name}": targetTexture, depthTexture, camera, lightingVolumeMesh and light are required`); + } + if (!this.lightingVolumeMesh.meshes || this.lightingVolumeMesh.meshes.length === 0) { + throw new Error(`FrameGraphVolumetricLightingTask "${this.name}": lightingVolumeMesh is empty`); + } + + this._frameGraph.textureManager.resolveDanglingHandle(this.outputTexture, this.targetTexture); + + const textureManager = this._frameGraph.textureManager; + + const targetTextureCreationOptions = textureManager.getTextureCreationOptions(this.targetTexture); + const targetTextureSamples = targetTextureCreationOptions.options.samples || 1; + + let lightingVolumeTexture = this.lightingVolumeTexture; + if (!lightingVolumeTexture) { + targetTextureCreationOptions.options.labels = ["InScattering"]; + targetTextureCreationOptions.options.samples = this.enableExtinction ? targetTextureSamples : 1; + + lightingVolumeTexture = textureManager.createRenderTargetTexture(`${this.name} - lighting volume texture`, targetTextureCreationOptions); + } + + this.lightingVolumeMesh.meshes[0].material = this._renderLightingVolumeMaterial; + + const volumeTextureSize = textureManager.getTextureAbsoluteDimensions(lightingVolumeTexture); + + this._renderLightingVolumeMaterial.setVector2("outputTextureSize", new Vector2(volumeTextureSize.width, volumeTextureSize.height)); + + const pass = this._frameGraph.addPass(this.name); + + pass.setExecuteFunc(() => { + this.camera.getTransformationMatrix().invertToRef(InvViewProjectionMatrix); + + this._renderLightingVolumeMaterial.setMatrix("invViewProjection", InvViewProjectionMatrix); + this._renderLightingVolumeMaterial.setVector3("lightDir", this.light.direction.normalizeToRef(TmpVectors.Vector3[0])); + }); + + const ligthingVolumeTextureCreationOptions = textureManager.getTextureCreationOptions(lightingVolumeTexture); + + ligthingVolumeTextureCreationOptions.options.formats = [ + this._frameGraph.engine.isWebGPU ? Constants.TEXTUREFORMAT_STENCIL8 : Constants.TEXTUREFORMAT_DEPTH24UNORM_STENCIL8, + ]; + ligthingVolumeTextureCreationOptions.options.labels = ["InScatteringStencil"]; + ligthingVolumeTextureCreationOptions.options.samples = targetTextureSamples; + + const lightingVolumeStencilTexture = this.enableExtinction + ? textureManager.createRenderTargetTexture(`${this.name} - lighting volume stencil texture`, ligthingVolumeTextureCreationOptions) + : undefined; + + this._clearLightingVolumeTextureTask.clearColor = true; + this._clearLightingVolumeTextureTask.clearStencil = this.enableExtinction; + this._clearLightingVolumeTextureTask.color = new Color4(0, 0, 0, 1); + this._clearLightingVolumeTextureTask.stencilValue = 0; + this._clearLightingVolumeTextureTask.targetTexture = lightingVolumeTexture; + this._clearLightingVolumeTextureTask.depthTexture = lightingVolumeStencilTexture; + this._clearLightingVolumeTextureTask.record(true); + + this._renderLightingVolumeTask.targetTexture = this._clearLightingVolumeTextureTask.outputTexture; + this._renderLightingVolumeTask.depthTexture = this.enableExtinction ? this._clearLightingVolumeTextureTask.outputDepthTexture : undefined; + this._renderLightingVolumeTask.objectList = this.lightingVolumeMesh; + this._renderLightingVolumeTask.camera = this.camera; + this._renderLightingVolumeTask.disableImageProcessing = true; + this._renderLightingVolumeTask.depthTest = false; + this._renderLightingVolumeTask.record(true); + + this._blendLightingVolumeTask.sourceTexture = this._renderLightingVolumeTask.outputTexture; + this._blendLightingVolumeTask.depthAttachmentTexture = this.enableExtinction ? this._renderLightingVolumeTask.outputDepthTexture : undefined; + this._blendLightingVolumeTask.sourceSamplingMode = this.sourceSamplingMode; + this._blendLightingVolumeTask.targetTexture = this.targetTexture; + this._blendLightingVolumeTask.depthTexture = this.depthTexture; + this._blendLightingVolumeTask.camera = this.camera; + this._blendLightingVolumeTask.depthTest = false; + this._blendLightingVolumeTask.stencilState = { + enabled: this.enableExtinction, + + funcRef: 1, + + mask: 0xff, + funcMask: 0xff, + + func: Constants.EQUAL, + opStencilFail: Constants.KEEP, + opDepthFail: Constants.KEEP, + opStencilDepthPass: Constants.KEEP, + + backFunc: Constants.EQUAL, + backOpStencilFail: Constants.KEEP, + backOpDepthFail: Constants.KEEP, + backOpStencilDepthPass: Constants.KEEP, + }; + this._blendLightingVolumeTask.record(true); + + if (!skipCreationOfDisabledPasses) { + const disabledPass = this._frameGraph.addPass(this.name + "_disabled", true); + + disabledPass.setExecuteFunc(() => {}); + } + } + + public override dispose() { + this._renderLightingVolumeMaterial?.dispose(); + this._clearLightingVolumeTextureTask.dispose(); + this._renderLightingVolumeTask.dispose(); + this._blendLightingVolumeTask.dispose(); + super.dispose(); + } +} diff --git a/packages/dev/core/src/FrameGraph/Tasks/Rendering/objectRendererTask.ts b/packages/dev/core/src/FrameGraph/Tasks/Rendering/objectRendererTask.ts index f254e678156..de0e3e5f3f6 100644 --- a/packages/dev/core/src/FrameGraph/Tasks/Rendering/objectRendererTask.ts +++ b/packages/dev/core/src/FrameGraph/Tasks/Rendering/objectRendererTask.ts @@ -304,7 +304,9 @@ export class FrameGraphObjectRendererTask extends FrameGraphTask { const depthTextureDescription = this._frameGraph.textureManager.getTextureDescription(this.depthTexture); if (depthTextureDescription.options.samples !== outputTextureDescription.options.samples) { - throw new Error(`FrameGraphObjectRendererTask ${this.name}: the depth texture and the output texture must have the same number of samples`); + throw new Error( + `FrameGraphObjectRendererTask ${this.name}: the depth texture "${depthTextureDescription.options.labels?.[0] ?? "noname"}" (${depthTextureDescription.options.samples} samples) and the output texture "${outputTextureDescription.options.labels?.[0] ?? "noname"}" (${outputTextureDescription.options.samples} samples) must have the same number of samples` + ); } depthEnabled = true; diff --git a/packages/dev/core/src/FrameGraph/Tasks/Texture/clearTextureTask.ts b/packages/dev/core/src/FrameGraph/Tasks/Texture/clearTextureTask.ts index 0177f90aca1..4f82a8cc7bb 100644 --- a/packages/dev/core/src/FrameGraph/Tasks/Texture/clearTextureTask.ts +++ b/packages/dev/core/src/FrameGraph/Tasks/Texture/clearTextureTask.ts @@ -89,7 +89,9 @@ export class FrameGraphClearTextureTask extends FrameGraphTask { } if (textureSamples !== depthSamples && textureSamples !== 0 && depthSamples !== 0) { - throw new Error(`FrameGraphClearTextureTask ${this.name}: the depth texture and the target texture must have the same number of samples.`); + throw new Error( + `FrameGraphClearTextureTask ${this.name}: the depth texture (${depthSamples} samples) and the target texture (${textureSamples} samples) must have the same number of samples.` + ); } const attachments = this._frameGraph.engine.buildTextureLayout( diff --git a/packages/dev/core/src/FrameGraph/frameGraphRenderContext.ts b/packages/dev/core/src/FrameGraph/frameGraphRenderContext.ts index f651265e73d..20fefdbc4cf 100644 --- a/packages/dev/core/src/FrameGraph/frameGraphRenderContext.ts +++ b/packages/dev/core/src/FrameGraph/frameGraphRenderContext.ts @@ -13,6 +13,7 @@ import type { InternalTexture, UtilityLayerRenderer, IStencilState, + IStencilStateProperties, } from "core/index"; import { Constants } from "../Engines/constants"; import { EffectRenderer } from "../Materials/effectRenderer"; @@ -236,7 +237,7 @@ export class FrameGraphRenderContext extends FrameGraphContext { public applyFullScreenEffect( drawWrapper: DrawWrapper, customBindings?: () => void, - stencilState?: IStencilState, + stencilState?: IStencilState | IStencilStateProperties, disableColorWrite?: boolean, drawBackFace?: boolean, depthTest?: boolean, diff --git a/packages/dev/core/src/FrameGraph/frameGraphTask.ts b/packages/dev/core/src/FrameGraph/frameGraphTask.ts index 8381f2b9ce4..655f02eea28 100644 --- a/packages/dev/core/src/FrameGraph/frameGraphTask.ts +++ b/packages/dev/core/src/FrameGraph/frameGraphTask.ts @@ -204,10 +204,10 @@ export abstract class FrameGraphTask { throw new Error(`The output texture of the task "${this.name}" is different when it is enabled or disabled.`); } } - if (outputDepthTexture !== disabledOutputDepthTexture) { + if (outputDepthTexture !== disabledOutputDepthTexture && disabledOutputDepthTexture !== null) { throw new Error(`The output depth texture of the task "${this.name}" is different when it is enabled or disabled.`); } - if (outputObjectList !== disabledOutputObjectList) { + if (outputObjectList !== disabledOutputObjectList && disabledOutputObjectList !== null) { throw new Error(`The output object list of the task "${this.name}" is different when it is enabled or disabled.`); } } diff --git a/packages/dev/core/src/FrameGraph/index.ts b/packages/dev/core/src/FrameGraph/index.ts index d569d5dce12..4018b02b0ca 100644 --- a/packages/dev/core/src/FrameGraph/index.ts +++ b/packages/dev/core/src/FrameGraph/index.ts @@ -16,6 +16,7 @@ export * from "./Tasks/Layers/highlightLayerTask"; export * from "./Tasks/Misc/computeShaderTask"; export * from "./Tasks/Misc/cullObjectsTask"; export * from "./Tasks/Misc/executeTask"; +export * from "./Tasks/Misc/lightingVolumeTask"; export * from "./Tasks/PostProcesses/anaglyphTask"; export * from "./Tasks/PostProcesses/blackAndWhiteTask"; @@ -41,6 +42,7 @@ export * from "./Tasks/PostProcesses/ssao2RenderingPipelineTask"; export * from "./Tasks/PostProcesses/ssrRenderingPipelineTask"; export * from "./Tasks/PostProcesses/taaTask"; export * from "./Tasks/PostProcesses/tonemapTask"; +export * from "./Tasks/PostProcesses/volumetricLightingTask"; export * from "./Tasks/Texture/clearTextureTask"; export * from "./Tasks/Texture/copyToBackbufferColorTask"; diff --git a/packages/dev/core/src/Lights/lightingVolume.ts b/packages/dev/core/src/Lights/lightingVolume.ts index aa013fb8806..d393a904e8d 100644 --- a/packages/dev/core/src/Lights/lightingVolume.ts +++ b/packages/dev/core/src/Lights/lightingVolume.ts @@ -81,7 +81,7 @@ export class LightingVolume { private _buildFullVolume = false; /** - * Indicates whether to build the full volume (true) or only the far plane (false). Default is false. + * Indicates whether to build the full volume (true) or only the near plane (false). Default is false. */ public get buildFullVolume() { return this._buildFullVolume; @@ -93,7 +93,10 @@ export class LightingVolume { } this._buildFullVolume = value; this._createGeometry(); - this._createComputeShader(); + if (this._engine.isWebGPU) { + this._createComputeShader(); + } + this._firstUpdate = true; } /** @@ -220,9 +223,9 @@ export class LightingVolume { this._uBuffer.updateMatrix("invViewProjMatrix", InvViewProjMatrix); this._uBuffer.update(); - this._engine._debugPushGroup?.(`Generate lighting volume (${this._name})`); + this._engine._debugPushGroup?.(`Update lighting volume (${this._name})`, 1); this._cs.dispatch(dispatchSize, dispatchSize, 1); - this._engine._debugPopGroup?.(); + this._engine._debugPopGroup?.(1); this._firstUpdate = false; } else { @@ -364,8 +367,14 @@ export class LightingVolume { for (let y = 0; y < numTesselation + 1; ++y) { for (let x = 0; x < numTesselation + 1; ++x) { let depth = depthValues[Math.floor(mapSize * Math.floor(stepY) + x * step) * factor]; - if (!this._buildFullVolume && (depth === 1 || y === 0 || x === 0 || y === numTesselation || x === numTesselation)) { - depth = 0; + if (!this._buildFullVolume) { + if (y === 0 || x === 0 || y === numTesselation || x === numTesselation) { + posIndex += 3; + continue; + } + if (depth === 1) { + depth = 0; + } } TmpVec3.set((x - halfTesselation) / halfTesselation, (y - halfTesselation) / halfTesselation, -1 + 2 * depth); @@ -510,7 +519,7 @@ export class LightingVolume { indices.push(2, 3, 1); } - // Tesselate the far plane + // Tesselate the near plane let y = min.y; for (let iy = 0; iy <= numTesselation; ++iy) { let x = min.x; diff --git a/packages/dev/core/src/Shaders/volumetricLightingBlendVolume.fragment.fx b/packages/dev/core/src/Shaders/volumetricLightingBlendVolume.fragment.fx new file mode 100644 index 00000000000..370fe9fc6e9 --- /dev/null +++ b/packages/dev/core/src/Shaders/volumetricLightingBlendVolume.fragment.fx @@ -0,0 +1,28 @@ +varying vec2 vUV; + +uniform sampler2D textureSampler; +uniform sampler2D depthSampler; + +uniform mat4 invProjection; +uniform vec2 outputTextureSize; +#ifdef USE_EXTINCTION + uniform vec3 extinction; +#endif + +#define CUSTOM_FRAGMENT_DEFINITIONS + +void main(void) { + gl_FragColor = texture2D(textureSampler, vUV); + +#ifdef USE_EXTINCTION + float depth = texelFetch(depthSampler, ivec2(gl_FragCoord.xy), 0).r; + vec4 ndc = vec4((gl_FragCoord.xy / outputTextureSize) * 2. - 1., depth * 2. - 1., 1.0); + + vec4 viewPos = invProjection * ndc; + viewPos = viewPos / viewPos.w; + + float eyeDist = length(viewPos); + + gl_FragColor2 = vec4(exp(-extinction * eyeDist), 1.0); +#endif +} diff --git a/packages/dev/core/src/Shaders/volumetricLightingRenderVolume.fragment.fx b/packages/dev/core/src/Shaders/volumetricLightingRenderVolume.fragment.fx new file mode 100644 index 00000000000..95cc9a61561 --- /dev/null +++ b/packages/dev/core/src/Shaders/volumetricLightingRenderVolume.fragment.fx @@ -0,0 +1,48 @@ +#include<__decl__sceneFragment> + +uniform mat4 invViewProjection; +uniform vec3 lightDir; // must be normalized +uniform vec2 outputTextureSize; +uniform vec4 extinctionPhaseG; +uniform vec3 lightPower; + +uniform sampler2D depthTexture; + +varying vec4 vWorldPos; + +float henyeyGreenstein(float g, float cosTheta) { + float denom = 1.0 + g * g - 2.0 * g * cosTheta; + return 1.0 / (4.0 * 3.14159265) * (1.0 - g * g) / (denom * sqrt(max(denom, 0.0))); +} + +vec3 integrateDirectional(float eyeDist, vec3 viewDir, vec3 lightDir) { + float phaseG = extinctionPhaseG.w; +#ifdef USE_EXTINCTION + vec3 extinction = extinctionPhaseG.xyz; + return henyeyGreenstein(phaseG, dot(viewDir, lightDir)) * (vec3(1.0) - exp(-extinction * eyeDist)) / extinction; +#else + return vec3(henyeyGreenstein(phaseG, dot(viewDir, lightDir))) * vec3(eyeDist); +#endif +} + +void main(void) { + float depth = texelFetch(depthTexture, ivec2(gl_FragCoord.xy), 0).r; + vec4 worldPos = vWorldPos; + + if (gl_FragCoord.z > depth) { + vec4 ndc = vec4((gl_FragCoord.xy / outputTextureSize) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + + worldPos = invViewProjection * ndc; + worldPos = worldPos / worldPos.w; + } + + vec3 viewDir = worldPos.xyz - vEyePosition.xyz; + float eyeDist = length(viewDir); + + viewDir = viewDir / eyeDist; + + float fSign = gl_FrontFacing ? 1.0 : -1.0; + vec3 integral = integrateDirectional(eyeDist, -viewDir, lightDir); + + gl_FragColor = vec4(lightPower * integral * fSign, 1.0); +} diff --git a/packages/dev/core/src/Shaders/volumetricLightingRenderVolume.vertex.fx b/packages/dev/core/src/Shaders/volumetricLightingRenderVolume.vertex.fx new file mode 100644 index 00000000000..e2036137d47 --- /dev/null +++ b/packages/dev/core/src/Shaders/volumetricLightingRenderVolume.vertex.fx @@ -0,0 +1,13 @@ +#include<__decl__sceneVertex> +#include<__decl__meshVertex> + +attribute vec3 position; + +varying vec4 vWorldPos; + +void main(void) { + vec4 worldPos = world * vec4(position, 1.0); + + vWorldPos = worldPos; + gl_Position = viewProjection * worldPos; +} diff --git a/packages/dev/core/src/ShadersWGSL/volumetricLightingBlendVolume.fragment.fx b/packages/dev/core/src/ShadersWGSL/volumetricLightingBlendVolume.fragment.fx new file mode 100644 index 00000000000..bf4bed27f11 --- /dev/null +++ b/packages/dev/core/src/ShadersWGSL/volumetricLightingBlendVolume.fragment.fx @@ -0,0 +1,30 @@ +varying vUV: vec2f; + +var textureSamplerSampler: sampler; +var textureSampler: texture_2d; +var depthSampler: texture_2d; + +uniform invProjection: mat4x4; +uniform outputTextureSize: vec2f; +#ifdef USE_EXTINCTION + uniform extinction: vec3f; +#endif + +#define CUSTOM_FRAGMENT_DEFINITIONS + +@fragment +fn main(input: FragmentInputs) -> FragmentOutputs { + fragmentOutputs.color = textureSample(textureSampler, textureSamplerSampler, input.vUV); + +#ifdef USE_EXTINCTION + let depth = textureLoad(depthSampler, vec2u(fragmentInputs.position.xy), 0).r; + let ndc = vec4f((fragmentInputs.position.xy / uniforms.outputTextureSize) * 2. - 1., depth, 1.0); + + var viewPos = uniforms.invProjection * ndc; + viewPos = viewPos / viewPos.w; + + let eyeDist = length(viewPos); + + fragmentOutputs.color2 = vec4f(exp(-uniforms.extinction * eyeDist), 1.0); +#endif +} diff --git a/packages/dev/core/src/ShadersWGSL/volumetricLightingRenderVolume.fragment.fx b/packages/dev/core/src/ShadersWGSL/volumetricLightingRenderVolume.fragment.fx new file mode 100644 index 00000000000..c3dd913b47e --- /dev/null +++ b/packages/dev/core/src/ShadersWGSL/volumetricLightingRenderVolume.fragment.fx @@ -0,0 +1,51 @@ +#include +#include + +uniform invViewProjection: mat4x4; +uniform lightDir: vec3f; // must be normalized +uniform outputTextureSize: vec2f; +uniform extinctionPhaseG: vec4f; +uniform lightPower: vec3f; + +var depthTexture: texture_2d; + +varying vWorldPos: vec4f; + +fn henyeyGreenstein(g: f32, cosTheta: f32) -> f32 { + let denom = 1 + g * g - 2 * g * cosTheta; + return 1.0 / (4.0 * 3.14159265) * (1.0 - g * g) / (denom * sqrt(max(denom, 0.0))); +} + +fn integrateDirectional(eyeDist: f32, viewDir: vec3f, lightDir: vec3f) -> vec3f { + let phaseG = uniforms.extinctionPhaseG.w; +#ifdef USE_EXTINCTION + let extinction = uniforms.extinctionPhaseG.xyz; + return henyeyGreenstein(phaseG, dot(viewDir, lightDir)) * (vec3f(1.0) - exp(-extinction * eyeDist)) / extinction; +#else + return vec3f(henyeyGreenstein(phaseG, dot(viewDir, lightDir))) * vec3f(eyeDist); +#endif +} + +@fragment +fn main(input: FragmentInputs) -> FragmentOutputs { + var depth = textureLoad(depthTexture, vec2u(fragmentInputs.position.xy), 0).r; + + var worldPos = fragmentInputs.vWorldPos; + + if (fragmentInputs.position.z > depth) { + let ndc = vec4f((fragmentInputs.position.xy / uniforms.outputTextureSize) * 2. - 1., depth, 1.0); + + worldPos = uniforms.invViewProjection * ndc; + worldPos = worldPos / worldPos.w; + } + + var viewDir = worldPos.xyz - scene.vEyePosition.xyz; + let eyeDist = length(viewDir); + + viewDir = viewDir / eyeDist; + + let fSign = select(-1.0, 1.0, fragmentInputs.frontFacing); + let integral = integrateDirectional(eyeDist, -viewDir, uniforms.lightDir); + + fragmentOutputs.color = vec4f(uniforms.lightPower * integral * fSign, 1.0); +} diff --git a/packages/dev/core/src/ShadersWGSL/volumetricLightingRenderVolume.vertex.fx b/packages/dev/core/src/ShadersWGSL/volumetricLightingRenderVolume.vertex.fx new file mode 100644 index 00000000000..ae4916b6b8a --- /dev/null +++ b/packages/dev/core/src/ShadersWGSL/volumetricLightingRenderVolume.vertex.fx @@ -0,0 +1,14 @@ +#include +#include + +attribute position : vec3f; + +varying vWorldPos: vec4f; + +@vertex +fn main(input : VertexInputs) -> FragmentInputs { + let worldPos = mesh.world * vec4f(vertexInputs.position, 1.0); + + vertexOutputs.vWorldPos = worldPos; + vertexOutputs.position = scene.viewProjection * worldPos; +} diff --git a/packages/dev/core/src/States/IStencilState.ts b/packages/dev/core/src/States/IStencilState.ts index ff71cef878f..9a2895b2a81 100644 --- a/packages/dev/core/src/States/IStencilState.ts +++ b/packages/dev/core/src/States/IStencilState.ts @@ -1,23 +1,69 @@ -/** @internal */ -export interface IStencilState { +/** + * Interface defining the properties of the stencil state. + */ +export interface IStencilStateProperties { + /** + * Whether the stencil test is enabled or not. + */ enabled: boolean; + /** + * The stencil mask to use for writing. + */ mask: number; // write mask + /** + * The stencil mask to use for reading. + */ funcMask: number; // read mask + /** + * The reference value to use for the stencil test. + */ funcRef: number; // Front stencil operations + /** + * The stencil comparison function to use for front faces. + */ func: number; + /** + * The operation to perform when both the stencil and depth tests pass for front faces. + */ opStencilDepthPass: number; + /** + * The operation to perform when the stencil test fails for front faces. + */ opStencilFail: number; + /** + * The operation to perform when the stencil test passes but the depth test fails for front faces. + */ opDepthFail: number; // Back stencil operations + /** + * The stencil comparison function to use for back faces. + */ backFunc: number; + /** + * The operation to perform when both the stencil and depth tests pass for back faces. + */ backOpStencilDepthPass: number; + /** + * The operation to perform when the stencil test fails for back faces. + */ backOpStencilFail: number; + /** + * The operation to perform when the stencil test passes but the depth test fails for back faces. + */ backOpDepthFail: number; +} +/** + * Interface defining the stencil state. + */ +export interface IStencilState extends IStencilStateProperties { + /** + * Resets the stencil state to default values. + */ reset(): void; } diff --git a/packages/tools/nodeRenderGraphEditor/public/index.js b/packages/tools/nodeRenderGraphEditor/public/index.js index 6223cde3d68..e3c72513bb9 100644 --- a/packages/tools/nodeRenderGraphEditor/public/index.js +++ b/packages/tools/nodeRenderGraphEditor/public/index.js @@ -218,7 +218,11 @@ checkBabylonVersionAsync().then(() => { let engine; if (useWebGPU && (await BABYLON.WebGPUEngine.IsSupportedAsync)) { - engine = new BABYLON.WebGPUEngine(canvas); + engine = new BABYLON.WebGPUEngine(canvas, { + enableGPUDebugMarkers: true, + enableAllFeatures: true, + setMaximumLimits: true, + }); await engine.initAsync(); } else { localStorage.setItem("Engine", 0); diff --git a/packages/tools/nodeRenderGraphEditor/src/blockTools.ts b/packages/tools/nodeRenderGraphEditor/src/blockTools.ts index a36a01c00b6..a67f16f7eb9 100644 --- a/packages/tools/nodeRenderGraphEditor/src/blockTools.ts +++ b/packages/tools/nodeRenderGraphEditor/src/blockTools.ts @@ -43,6 +43,8 @@ import { NodeRenderGraphFilterPostProcessBlock } from "core/FrameGraph/Node/Bloc import { NodeRenderGraphTonemapPostProcessBlock } from "core/FrameGraph/Node/Blocks/PostProcesses/tonemapPostProcessBlock"; import { NodeRenderGraphSSAO2PostProcessBlock } from "core/FrameGraph/Node/Blocks/PostProcesses/ssao2PostProcessBlock"; import { NodeRenderGraphComputeShaderBlock } from "core/FrameGraph/Node/Blocks/computeShaderBlock"; +import { NodeRenderGraphVolumetricLightingBlock } from "core/FrameGraph/Node/Blocks/PostProcesses/volumetricLightingBlock"; +import { NodeRenderGraphLightingVolumeBlock } from "core/FrameGraph/Node/Blocks/lightingVolumeBlock"; /** * Static class for BlockTools @@ -195,6 +197,12 @@ export class BlockTools { case "ComputeShaderBlock": { return new NodeRenderGraphComputeShaderBlock("Compute Shader", frameGraph, scene); } + case "VolumetricLightingBlock": { + return new NodeRenderGraphVolumetricLightingBlock("Volumetric Lighting", frameGraph, scene); + } + case "LightingVolumeBlock": { + return new NodeRenderGraphLightingVolumeBlock("Lighting Volume", frameGraph, scene); + } } return null; diff --git a/packages/tools/nodeRenderGraphEditor/src/components/nodeList/nodeListComponent.tsx b/packages/tools/nodeRenderGraphEditor/src/components/nodeList/nodeListComponent.tsx index 2a8bc088079..5854fe368bd 100644 --- a/packages/tools/nodeRenderGraphEditor/src/components/nodeList/nodeListComponent.tsx +++ b/packages/tools/nodeRenderGraphEditor/src/components/nodeList/nodeListComponent.tsx @@ -71,6 +71,8 @@ export class NodeListComponent extends React.Component