DDGI => DUGI (U for Unified, because no longer diffuse-only etc.) renamed

This commit is contained in:
Benjamin Rosseaux 2026-06-26 15:27:20 +02:00
commit cce7fa8096
53 changed files with 1856 additions and 1855 deletions

View file

@ -507,7 +507,7 @@ PasVulkan currently supports the following Global Illumination techniques:
* However, the feature may eventually be removed due to the complexity and performance challenges involved in implementing it correctly and efficiently, especially since it relies on geometry shaders to voxelize the scene geometry. Geometry shaders are fundamentally unsuitable for real-time use and have always been discouraged, as they are slow and inefficient across all GPU generations. Other voxelization methods, such as compute shader-based approaches, are likewise impractical in real-time contexts, as they take too long to rebuild the voxel grid every frame, and as of now, no alternative voxelization methods are implemented in PasVulkan, although that said, the feature may be retained in the future if a suitable and efficient solution is found, and otherwise, the feature will be removed, just as simple as that.
4. **Ray Traced Global Illumination (RTGI):**
* It is planned to be implemented in the future, but it is not yet implemented. DDGI (Dynamic Diffuse Global Illumination) will be used as the starting point for the implementation, as it is a well-known and widely used technique for real-time ray traced global illumination. But however not in exactly the same way as DDGI, but rather in a more changed and extended way, as it will have more features and will be more flexible and efficient than DDGI in its original form. But let's see how it will turn out in the end.
* It is planned to be implemented in the future, but it is not yet implemented. DUGI (Dynamic Unified Global Illumination) will be used as the starting point for the implementation, as it is a well-known and widely used technique for real-time ray traced global illumination. But however not in exactly the same way as DUGI, but rather in a more changed and extended way, as it will have more features and will be more flexible and efficient than DUGI in its original form. But let's see how it will turn out in the end.
### Summary

View file

@ -2733,7 +2733,7 @@ type TpvScene3DPlanets=class;
fVoxelizationDescriptorSets:array[0..MaxInFlightFrames-1] of TpvVulkanDescriptorSet;
fGrassFragmentShaderStage:TpvVulkanPipelineShaderStage;
fDescriptorSetLayout:TpvVulkanDescriptorSetLayout;
fEmptyDescriptorSetLayout:TpvVulkanDescriptorSetLayout; // 0-binding placeholder so the vertex-path terrain pipeline layout can host the DDGI set at the same fixed set index (4) as the mesh-shader path
fEmptyDescriptorSetLayout:TpvVulkanDescriptorSetLayout; // 0-binding placeholder so the vertex-path terrain pipeline layout can host the DUGI set at the same fixed set index (4) as the mesh-shader path
fDescriptorPool:TpvVulkanDescriptorPool;
fDescriptorSets:array[0..MaxInFlightFrames-1] of TpvVulkanDescriptorSet;
fIBLDescriptors:array[0..MaxInFlightFrames-1] of TpvScene3DRendererIBLDescriptor;
@ -3840,20 +3840,20 @@ type TVector3Array=TpvDynamicArray<TpvVector3>;
TIndexArray=TpvDynamicArray<TpvUInt32>;
// --- Ray-traced GI helpers for the planet pipelines -----------------------------------------------------------------
// The planet pipelines wire a dedicated set 4 + a 'kind' frag-variant segment for the RT-based GI mode (DDGI). These
// The planet pipelines wire a dedicated set 4 + a 'kind' frag-variant segment for the RT-based GI mode (DUGI). These
// helpers select the active mode's descriptor set layout / set / frag-variant name so each call site stays mode-agnostic.
// They return nil / '' for the non-RT GI modes (CRH, VCT, ...).
function PlanetRTGIActive(const aRendererInstance:TObject):boolean;
begin
result:=TpvScene3DRendererInstance(aRendererInstance).Renderer.GlobalIlluminationMode in
[TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination];
[TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination];
end;
function PlanetRTGIDescriptorSetLayout(const aRendererInstance:TObject):TpvVulkanDescriptorSetLayout;
begin
case TpvScene3DRendererInstance(aRendererInstance).Renderer.GlobalIlluminationMode of
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
result:=TpvScene3DRendererInstance(aRendererInstance).GlobalIlluminationDDGIDescriptorSetLayout;
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
result:=TpvScene3DRendererInstance(aRendererInstance).GlobalIlluminationDUGIDescriptorSetLayout;
end;
else begin
result:=nil;
@ -3864,8 +3864,8 @@ end;
function PlanetRTGIDescriptorSet(const aRendererInstance:TObject;const aInFlightFrameIndex:TpvSizeInt):TpvVulkanDescriptorSet;
begin
case TpvScene3DRendererInstance(aRendererInstance).Renderer.GlobalIlluminationMode of
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
result:=TpvScene3DRendererInstance(aRendererInstance).GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex];
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
result:=TpvScene3DRendererInstance(aRendererInstance).GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex];
end;
else begin
result:=nil;
@ -3873,12 +3873,12 @@ begin
end;
end;
// Frag-variant 'kind' name segment ('ddgi_' / ''), e.g. planet_renderpass_raytracing_<kind>frag.spv.
// Frag-variant 'kind' name segment ('dugi_' / ''), e.g. planet_renderpass_raytracing_<kind>frag.spv.
function PlanetRTGIKind(const aRendererInstance:TObject):TpvUTF8String;
begin
case TpvScene3DRendererInstance(aRendererInstance).Renderer.GlobalIlluminationMode of
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
result:='ddgi_';
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
result:='dugi_';
end;
else begin
result:='';
@ -3886,12 +3886,12 @@ begin
end;
end;
// Frag-variant name suffix segment ('_ddgi' / ''), used by the water passes (planet_water..._<suffix>_frag.spv).
// Frag-variant name suffix segment ('_dugi' / ''), used by the water passes (planet_water..._<suffix>_frag.spv).
function PlanetRTGISuffix(const aRendererInstance:TObject):TpvUTF8String;
begin
case TpvScene3DRendererInstance(aRendererInstance).Renderer.GlobalIlluminationMode of
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
result:='_ddgi';
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
result:='_dugi';
end;
else begin
result:='';
@ -27611,12 +27611,12 @@ begin
TpvScene3DPlanet.TRenderPass.TMode.ReflectiveShadowMap:begin
// The non-raytraced DDGI RSM-backend (trace) producer wants raw albedo in the RSM (it re-lights it itself); render the
// The non-raytraced DUGI RSM-backend (trace) producer wants raw albedo in the RSM (it re-lights it itself); render the
// albedo output variant in that case, otherwise (radiance hints, OR the RSM VPL splat producer which re-emits the stored
// flux directly) the lit flux variant.
if (TpvScene3DRenderer(fRenderer).GlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination) and
if (TpvScene3DRenderer(fRenderer).GlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination) and
(not TpvScene3D(fScene3D).RaytracingActive) and
(not TpvScene3DRendererInstance(fRendererInstance).GlobalIlluminationDDGIUseRSMSplat) then begin
(not TpvScene3DRendererInstance(fRendererInstance).GlobalIlluminationDUGIUseRSMSplat) then begin
RSMInfix:='rsm_albedo_';
end else begin
RSMInfix:='rsm_';
@ -27653,9 +27653,9 @@ begin
else begin
// DDGI (RT-based GI): the 'ddgi_' Kind selects the planet_renderpass DDGI frag variant (samples the probe field at
// set 4). Only for the RT GI mode (DDGI) — never CRH/VCT. Reset to '' before the grass frag below,
// since the grass DDGI variant is wired in a separate step.
// DUGI (RT-based GI): the 'dugi_' Kind selects the planet_renderpass DUGI frag variant (samples the probe field at
// set 4). Only for the RT GI mode (DUGI) — never CRH/VCT. Reset to '' before the grass frag below,
// since the grass DUGI variant is wired in a separate step.
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
Kind:=PlanetRTGIKind(fRendererInstance); // matches the same condition used for the set-4 pipeline-layout + draw bind below, so they stay consistent
@ -27682,7 +27682,7 @@ begin
FreeAndNil(Stream);
end;
// Grass DDGI frag variant — same condition as the terrain frag / pipeline layout / draw bind, so they stay consistent.
// Grass DUGI frag variant — same condition as the terrain frag / pipeline layout / draw bind, so they stay consistent.
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
Kind:=PlanetRTGIKind(fRendererInstance);
@ -27870,7 +27870,7 @@ begin
fDescriptorSetLayout.Initialize;
// Empty 0-binding placeholder so the vertex-path terrain pipeline layout can host the DDGI set at the same fixed set
// Empty 0-binding placeholder so the vertex-path terrain pipeline layout can host the DUGI set at the same fixed set
// index (4) as the mesh-shader path (whose set 3 is the terrain-mesh SSBO; the vertex path has no set 3).
fEmptyDescriptorSetLayout:=TpvVulkanDescriptorSetLayout.Create(fVulkanDevice);
fEmptyDescriptorSetLayout.Initialize;
@ -27915,11 +27915,11 @@ begin
fPlanetPipelineLayout.AddDescriptorSetLayout(TpvScene3D(fScene3D).GlobalVulkanDescriptorSetLayout); // set 0 = global scene descriptor set
fPlanetPipelineLayout.AddDescriptorSetLayout(fDescriptorSetLayout); // set 1 = global planet descriptor set
fPlanetPipelineLayout.AddDescriptorSetLayout(TpvScene3D(fScene3D).PlanetDescriptorSetLayout); // set 2 = per planet descriptor set
// RT GI (DDGI): the DDGI frag samples the probe field at the fixed set 4; fill set 3 with the empty placeholder here.
// RT GI (DUGI): the DUGI frag samples the probe field at the fixed set 4; fill set 3 with the empty placeholder here.
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
fPlanetPipelineLayout.AddDescriptorSetLayout(fEmptyDescriptorSetLayout); // set 3 = empty placeholder (terrain-mesh SSBO slot, unused in the vertex path)
fPlanetPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DDGI probe field
fPlanetPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DUGI probe field
end else if fMode=TpvScene3DPlanet.TRenderPass.TMode.Voxelization then begin
fPlanetPipelineLayout.AddDescriptorSetLayout(fEmptyDescriptorSetLayout); // set 3 = empty placeholder (terrain-mesh SSBO slot, unused in the vertex+geometry path)
fPlanetPipelineLayout.AddDescriptorSetLayout(fVoxelizationDescriptorSetLayout); // set 4 = cascaded voxel cone tracing volume
@ -27935,10 +27935,10 @@ begin
fGrassPipelineLayout.AddDescriptorSetLayout(fDescriptorSetLayout); // set 1 = global planet descriptor set
fGrassPipelineLayout.AddDescriptorSetLayout(TpvScene3D(fScene3D).PlanetDescriptorSetLayout); // set 2 = per planet descriptor set
fGrassPipelineLayout.AddDescriptorSetLayout(TpvScene3D(fScene3D).PlanetGrassCullAndMeshGenerationDescriptorSetLayout); // set 3 = grass cull / mesh-gen
// RT GI (DDGI): the DDGI grass frag samples the probe field at the fixed set 4.
// RT GI (DUGI): the DUGI grass frag samples the probe field at the fixed set 4.
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
fGrassPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DDGI probe field
fGrassPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DUGI probe field
end;
fGrassPipelineLayout.Initialize;
fVulkanDevice.DebugUtils.SetObjectName(fGrassPipelineLayout.Handle,VK_OBJECT_TYPE_PIPELINE_LAYOUT,'TpvScene3DPlanet.TRenderPass.fGrassPipelineLayout');
@ -27956,10 +27956,10 @@ begin
fTerrainMeshPipelineLayout.AddDescriptorSetLayout(fDescriptorSetLayout); // Views UBO + pass resources
fTerrainMeshPipelineLayout.AddDescriptorSetLayout(TpvScene3D(fScene3D).PlanetDescriptorSetLayout); // Per planet descriptor set
fTerrainMeshPipelineLayout.AddDescriptorSetLayout(TpvScene3D(fScene3D).PlanetTerrainMeshDescriptorSetLayout); // set 3 = Terrain mesh SSBO
// RT GI (DDGI): the DDGI frag samples the probe field at the fixed set 4 (same index as the vertex path).
// RT GI (DUGI): the DUGI frag samples the probe field at the fixed set 4 (same index as the vertex path).
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
fTerrainMeshPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DDGI probe field
fTerrainMeshPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DUGI probe field
end else if fMode=TpvScene3DPlanet.TRenderPass.TMode.Voxelization then begin
fTerrainMeshPipelineLayout.AddDescriptorSetLayout(fVoxelizationDescriptorSetLayout); // set 4 = cascaded voxel cone tracing volume
end;
@ -28834,8 +28834,8 @@ begin
nil);
end;
// RT GI (DDGI): bind the probe field at the fixed set 4 (matches the DDGI frag variant; same set index for both
// the mesh-shader and vertex terrain pipeline layouts). Only when DDGI is the active GI mode.
// RT GI (DUGI): bind the probe field at the fixed set 4 (matches the DUGI frag variant; same set index for both
// the mesh-shader and vertex terrain pipeline layouts). Only when DUGI is the active GI mode.
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
if UseTerrainMeshShader then begin
@ -28858,7 +28858,7 @@ begin
end;
// Voxelization (cascaded voxel cone tracing): bind the voxel volume at the fixed set 4 (CVCT and RT GI are mutually
// exclusive, so this reuses the same set index as the DDGI path; same set for the mesh-shader and vertex+geometry pipelines).
// exclusive, so this reuses the same set index as the DUGI path; same set for the mesh-shader and vertex+geometry pipelines).
if fMode=TpvScene3DPlanet.TRenderPass.TMode.Voxelization then begin
if UseTerrainMeshShader then begin
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
@ -29144,7 +29144,7 @@ begin
0,
nil);
// RT GI (DDGI): bind the probe field at the fixed set 4 (matches the DDGI grass frag variant). Only when DDGI active.
// RT GI (DUGI): bind the probe field at the fixed set 4 (matches the DUGI grass frag variant). Only when DUGI active.
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
@ -30935,7 +30935,7 @@ begin
end;
fVulkanDevice.DebugUtils.SetObjectName(fUnderwaterVertexShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DPlanet.TWaterRenderPass.fUnderwaterVertexShaderModule');
// RT-based GI (DDGI) frag variant of the underwater fullscreen pass — only the frag differs (DDGI feeds the shore-foam
// RT-based GI (DUGI) frag variant of the underwater fullscreen pass — only the frag differs (DUGI feeds the shore-foam
// ambient); the vertex shader is shared, so this is appended only after the vertex module was loaded with the base name.
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
@ -31026,8 +31026,8 @@ begin
end;
end;
// RT-based GI (DDGI) variant of the main water surface — 'ddgi' segment last, matching compileshaders.sh
// (planet_water[_raytracing][_msaa|_msaa_fast]_ddgi_frag.spv). RT GI only, so it implies RaytracingActive.
// RT-based GI (DUGI) variant of the main water surface — 'dugi' segment last, matching compileshaders.sh
// (planet_water[_raytracing][_msaa|_msaa_fast]_dugi_frag.spv). RT GI only, so it implies RaytracingActive.
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
ShaderFileName:=ShaderFileName+PlanetRTGISuffix(fRendererInstance);
@ -31188,7 +31188,7 @@ begin
fPipelineLayout.AddDescriptorSetLayout(TpvScene3D(fScene3D).PlanetWaterRenderDescriptorSetLayout); // Per render pass descriptor set
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
fPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DDGI probe field (RT GI only, main water surface)
fPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DUGI probe field (RT GI only, main water surface)
end;
fPipelineLayout.Initialize;
fVulkanDevice.DebugUtils.SetObjectName(fPipelineLayout.Handle,VK_OBJECT_TYPE_PIPELINE_LAYOUT,'TpvScene3DPlanet.TWaterRenderPass.fPipelineLayout');
@ -31442,7 +31442,7 @@ begin
fWaterMeshPipelineLayout.AddDescriptorSetLayout(TpvScene3D(fScene3D).PlanetWaterRenderDescriptorSetLayout); // Per render pass descriptor set
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
fWaterMeshPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DDGI probe field (RT GI only, main water surface)
fWaterMeshPipelineLayout.AddDescriptorSetLayout(PlanetRTGIDescriptorSetLayout(fRendererInstance)); // set 4 = DUGI probe field (RT GI only, main water surface)
end;
fWaterMeshPipelineLayout.Initialize;
fVulkanDevice.DebugUtils.SetObjectName(fWaterMeshPipelineLayout.Handle,VK_OBJECT_TYPE_PIPELINE_LAYOUT,'TpvScene3DPlanet.TWaterRenderPass.fWaterMeshPipelineLayout');
@ -31601,7 +31601,7 @@ begin
0,
nil);
// set 4 = DDGI probe field (RT GI only). Bound once here with fPipelineLayout so it covers both the underwater
// set 4 = DUGI probe field (RT GI only). Bound once here with fPipelineLayout so it covers both the underwater
// fullscreen pass and the tessellated water surface (both draw through fPipelineLayout); the mesh-shader water
// draw rebinds it with fWaterMeshPipelineLayout below (the push-constant ranges differ, which disturbs bindings).
if (PlanetRTGIActive(fRendererInstance)) and
@ -31717,7 +31717,7 @@ begin
0,
nil);
// set 4 = DDGI probe field (RT GI only, main water surface)
// set 4 = DUGI probe field (RT GI only, main water surface)
if (PlanetRTGIActive(fRendererInstance)) and
assigned(PlanetRTGIDescriptorSetLayout(fRendererInstance)) then begin
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
@ -31781,7 +31781,7 @@ begin
end;
{$endif}
// set 4 (DDGI probe field) is already bound with fPipelineLayout above (covers underwater + this tessellated surface).
// set 4 (DUGI probe field) is already bound with fPipelineLayout above (covers underwater + this tessellated surface).
aCommandBuffer.CmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,fWaterPipeline.Handle);
if assigned(Planet.fVulkanDevice.BreadcrumbBuffer) then begin

View file

@ -211,12 +211,12 @@ type TpvScene3DRendererAntialiasingMode=
// only that it is used for global illumination instead of shadows.
CascadedVoxelConeTracing,
// Dynamic Diffuse Global Illumination (DDGI, Majercik et al. 2019). A cascaded grid of light probes that are updated each frame by tracing rays
// Dynamic Unified Global Illumination (DUGI — extends Majercik et al. 2019's DDGI). A cascaded grid of light probes that are updated each frame by tracing rays
// against the hardware ray tracing acceleration structure (TLAS). Each probe stores irradiance (either as spherical harmonics or as an octahedral
// atlas, switchable via a shader define) plus an octahedral mean/mean-squared distance term for the Chebyshev visibility test, which is what
// prevents the light leaking that cascaded radiance hints suffer from. The probe grid placement reuses the cascaded radiance hints snapping
// infrastructure. Requires hardware ray tracing support (RaytracingActive).
DynamicDiffuseGlobalIllumination
DynamicUnifiedGlobalIllumination
{
// Possible further options on my todo list for the future:

File diff suppressed because it is too large Load diff

View file

@ -77,10 +77,10 @@ uses SysUtils,
type { TpvScene3DRendererParticleBVH }
// Self-contained, GI-technique-NEUTRAL particle BVH subsystem: owns the per-frame GPU buffers for a per-frame-built LBVH
// over the particle emitters (particles are not in the hardware ray-tracing BLAS). Any consumer software-traces it
// (the DDGI trace now; a pure-path-tracing path later) via particle_bvh_trace.glsl using the emitter + node buffer device
// (the DUGI trace now; a pure-path-tracing path later) via particle_bvh_trace.glsl using the emitter + node buffer device
// addresses — there is no shared descriptor contract. The build pipeline (extract -> AABB -> Morton -> radix sort ->
// Karras hierarchy -> AABB refit) is the separate ParticleBVHComputePass; layouts are in particle_bvh.glsl. Kept entirely
// out of the DDGI (and any other technique's) code so it can be reused without coupling.
// out of the DUGI (and any other technique's) code so it can be reused without coupling.
TpvScene3DRendererParticleBVH=class
public
type TBuffers=array[0..MaxInFlightFrames-1] of TpvVulkanBuffer;
@ -123,7 +123,7 @@ begin
fRenderer:=aRenderer;
// Consumers of the particle BVH. Currently only the DDGI trace software-injects particles; OR future consumers here.
// Consumers of the particle BVH. Currently only the DUGI trace software-injects particles; OR future consumers here.
fActive:=MustBeCreated(fRenderer);
FillChar(fEmitterBuffers,SizeOf(TBuffers),#0);
@ -145,7 +145,7 @@ end;
class function TpvScene3DRendererParticleBVH.MustBeCreated(const aRenderer:TpvScene3DRenderer):boolean;
begin
result:=aRenderer.GlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination;
result:=aRenderer.GlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination;
end;
procedure TpvScene3DRendererParticleBVH.AcquireVolatileResources;

View file

@ -721,8 +721,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -1133,9 +1133,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -569,8 +569,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -895,9 +895,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -98,13 +98,13 @@ type { TpvScene3DRendererPassesForwardRenderPass }
VertexBufferBDA:TpvUInt64;
end;
PDebugLinesPushConstants=^TDebugLinesPushConstants;
// DDGI probe debug overlay (RendererInstance.DebugDDGIProbes): one octahedral sphere per probe over all cascades,
// coloured by the live-sampled directional irradiance. Matches gi_ddgi_probe_debug.vert's push (viewBaseIndex, countViews).
TDDGIProbeDebugPushConstants=packed record
// DUGI probe debug overlay (RendererInstance.DebugDUGIProbes): one octahedral sphere per probe over all cascades,
// coloured by the live-sampled directional irradiance. Matches gi_dugi_probe_debug.vert's push (viewBaseIndex, countViews).
TDUGIProbeDebugPushConstants=packed record
ViewBaseIndex:TpvUInt32;
CountViews:TpvUInt32;
end;
PDDGIProbeDebugPushConstants=^TDDGIProbeDebugPushConstants;
PDUGIProbeDebugPushConstants=^TDUGIProbeDebugPushConstants;
private
fOnSetRenderPassResourcesDone:boolean;
procedure OnSetRenderPassResources(const aCommandBuffer:TpvVulkanCommandBuffer;
@ -154,17 +154,17 @@ type { TpvScene3DRendererPassesForwardRenderPass }
fMeshShaderGraphicsPipelines:array[boolean,TpvScene3D.TMaterial.TAlphaMode] of TpvScene3D.TGraphicsPipelines;
fVulkanGraphicsPipelines:array[boolean,TpvScene3D.TMaterial.TAlphaMode] of TpvScene3D.TGraphicsPipelines;
fVulkanSpaceLinesGraphicsPipeline:TpvVulkanGraphicsPipeline;
fDDGIProbeDebugMeshShader:Boolean; // MeshShaders -> frustum-culled task+mesh path instead of the raw vertex path
fDDGIProbeDebugVertexShaderModule:TpvVulkanShaderModule;
fDDGIProbeDebugTaskShaderModule:TpvVulkanShaderModule;
fDDGIProbeDebugMeshShaderModule:TpvVulkanShaderModule;
fDDGIProbeDebugFragmentShaderModule:TpvVulkanShaderModule;
fVulkanPipelineShaderStageDDGIProbeDebugVertex:TpvVulkanPipelineShaderStage;
fVulkanPipelineShaderStageDDGIProbeDebugTask:TpvVulkanPipelineShaderStage;
fVulkanPipelineShaderStageDDGIProbeDebugMesh:TpvVulkanPipelineShaderStage;
fVulkanPipelineShaderStageDDGIProbeDebugFragment:TpvVulkanPipelineShaderStage;
fVulkanDDGIProbeDebugPipelineLayout:TpvVulkanPipelineLayout;
fVulkanDDGIProbeDebugGraphicsPipeline:TpvVulkanGraphicsPipeline;
fDUGIProbeDebugMeshShader:Boolean; // MeshShaders -> frustum-culled task+mesh path instead of the raw vertex path
fDUGIProbeDebugVertexShaderModule:TpvVulkanShaderModule;
fDUGIProbeDebugTaskShaderModule:TpvVulkanShaderModule;
fDUGIProbeDebugMeshShaderModule:TpvVulkanShaderModule;
fDUGIProbeDebugFragmentShaderModule:TpvVulkanShaderModule;
fVulkanPipelineShaderStageDUGIProbeDebugVertex:TpvVulkanPipelineShaderStage;
fVulkanPipelineShaderStageDUGIProbeDebugTask:TpvVulkanPipelineShaderStage;
fVulkanPipelineShaderStageDUGIProbeDebugMesh:TpvVulkanPipelineShaderStage;
fVulkanPipelineShaderStageDUGIProbeDebugFragment:TpvVulkanPipelineShaderStage;
fVulkanDUGIProbeDebugPipelineLayout:TpvVulkanPipelineLayout;
fVulkanDUGIProbeDebugGraphicsPipeline:TpvVulkanGraphicsPipeline;
fVulkanDebugLinesGraphicsPipeline:TpvVulkanGraphicsPipeline;
fVulkanDebugLinesPipelineLayout:TpvVulkanPipelineLayout;
fDebugLinesVertexShaderModule:TpvVulkanShaderModule;
@ -575,61 +575,61 @@ begin
fVulkanPipelineShaderStageSpaceLinesFragment:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_FRAGMENT_BIT,fSpaceLinesFragmentShaderModule,'main');
// DDGI probe debug overlay: only meaningful (and the DDGI descriptor set only exists) in the DDGI GI mode.
fDDGIProbeDebugMeshShader:=fInstance.Renderer.Scene3D.MeshShaders;
if fInstance.Renderer.GlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination then begin
// DUGI probe debug overlay: only meaningful (and the DUGI descriptor set only exists) in the DUGI GI mode.
fDUGIProbeDebugMeshShader:=fInstance.Renderer.Scene3D.MeshShaders;
if fInstance.Renderer.GlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination then begin
if fDDGIProbeDebugMeshShader then begin
if fDUGIProbeDebugMeshShader then begin
// Frustum-culled task->mesh path: a task workgroup culls a batch of probes, the mesh shader renders one octahedral-sphere band.
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_ddgi_probe_debug_task.spv');
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_dugi_probe_debug_task.spv');
try
fDDGIProbeDebugTaskShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
fDUGIProbeDebugTaskShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
finally
Stream.Free;
end;
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(fDDGIProbeDebugTaskShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DRendererPassesForwardRenderPass.DDGIProbeDebugTaskShaderModule');
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(fDUGIProbeDebugTaskShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DRendererPassesForwardRenderPass.DUGIProbeDebugTaskShaderModule');
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_ddgi_probe_debug_mesh.spv');
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_dugi_probe_debug_mesh.spv');
try
fDDGIProbeDebugMeshShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
fDUGIProbeDebugMeshShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
finally
Stream.Free;
end;
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(fDDGIProbeDebugMeshShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DRendererPassesForwardRenderPass.DDGIProbeDebugMeshShaderModule');
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(fDUGIProbeDebugMeshShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DRendererPassesForwardRenderPass.DUGIProbeDebugMeshShaderModule');
fVulkanPipelineShaderStageDDGIProbeDebugTask:=TpvVulkanPipelineShaderStage.Create(TVkShaderStageFlagBits(VK_SHADER_STAGE_TASK_BIT_EXT),fDDGIProbeDebugTaskShaderModule,'main');
fVulkanPipelineShaderStageDDGIProbeDebugMesh:=TpvVulkanPipelineShaderStage.Create(TVkShaderStageFlagBits(VK_SHADER_STAGE_MESH_BIT_EXT),fDDGIProbeDebugMeshShaderModule,'main');
fVulkanPipelineShaderStageDUGIProbeDebugTask:=TpvVulkanPipelineShaderStage.Create(TVkShaderStageFlagBits(VK_SHADER_STAGE_TASK_BIT_EXT),fDUGIProbeDebugTaskShaderModule,'main');
fVulkanPipelineShaderStageDUGIProbeDebugMesh:=TpvVulkanPipelineShaderStage.Create(TVkShaderStageFlagBits(VK_SHADER_STAGE_MESH_BIT_EXT),fDUGIProbeDebugMeshShaderModule,'main');
end else begin
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_ddgi_probe_debug_vert.spv');
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_dugi_probe_debug_vert.spv');
try
fDDGIProbeDebugVertexShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
fDUGIProbeDebugVertexShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
finally
Stream.Free;
end;
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(fDDGIProbeDebugVertexShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DRendererPassesForwardRenderPass.DDGIProbeDebugVertexShaderModule');
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(fDUGIProbeDebugVertexShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DRendererPassesForwardRenderPass.DUGIProbeDebugVertexShaderModule');
fVulkanPipelineShaderStageDDGIProbeDebugVertex:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_VERTEX_BIT,fDDGIProbeDebugVertexShaderModule,'main');
fVulkanPipelineShaderStageDUGIProbeDebugVertex:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_VERTEX_BIT,fDUGIProbeDebugVertexShaderModule,'main');
end;
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_ddgi_probe_debug_frag.spv');
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_dugi_probe_debug_frag.spv');
try
fDDGIProbeDebugFragmentShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
fDUGIProbeDebugFragmentShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
finally
Stream.Free;
end;
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(fDDGIProbeDebugFragmentShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DRendererPassesForwardRenderPass.DDGIProbeDebugFragmentShaderModule');
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(fDUGIProbeDebugFragmentShaderModule.Handle,VK_OBJECT_TYPE_SHADER_MODULE,'TpvScene3DRendererPassesForwardRenderPass.DUGIProbeDebugFragmentShaderModule');
fVulkanPipelineShaderStageDDGIProbeDebugFragment:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_FRAGMENT_BIT,fDDGIProbeDebugFragmentShaderModule,'main');
fVulkanPipelineShaderStageDUGIProbeDebugFragment:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_FRAGMENT_BIT,fDUGIProbeDebugFragmentShaderModule,'main');
end else begin
fDDGIProbeDebugVertexShaderModule:=nil;
fDDGIProbeDebugTaskShaderModule:=nil;
fDDGIProbeDebugMeshShaderModule:=nil;
fDDGIProbeDebugFragmentShaderModule:=nil;
fVulkanPipelineShaderStageDDGIProbeDebugVertex:=nil;
fVulkanPipelineShaderStageDDGIProbeDebugTask:=nil;
fVulkanPipelineShaderStageDDGIProbeDebugMesh:=nil;
fVulkanPipelineShaderStageDDGIProbeDebugFragment:=nil;
fDUGIProbeDebugVertexShaderModule:=nil;
fDUGIProbeDebugTaskShaderModule:=nil;
fDUGIProbeDebugMeshShaderModule:=nil;
fDUGIProbeDebugFragmentShaderModule:=nil;
fVulkanPipelineShaderStageDUGIProbeDebugVertex:=nil;
fVulkanPipelineShaderStageDUGIProbeDebugTask:=nil;
fVulkanPipelineShaderStageDUGIProbeDebugMesh:=nil;
fVulkanPipelineShaderStageDUGIProbeDebugFragment:=nil;
end;
if fMeshShader then begin
@ -723,13 +723,13 @@ begin
FreeAndNil(fVulkanPipelineShaderStageSpaceLinesFragment);
FreeAndNil(fVulkanPipelineShaderStageDDGIProbeDebugVertex);
FreeAndNil(fVulkanPipelineShaderStageDUGIProbeDebugVertex);
FreeAndNil(fVulkanPipelineShaderStageDDGIProbeDebugTask);
FreeAndNil(fVulkanPipelineShaderStageDUGIProbeDebugTask);
FreeAndNil(fVulkanPipelineShaderStageDDGIProbeDebugMesh);
FreeAndNil(fVulkanPipelineShaderStageDUGIProbeDebugMesh);
FreeAndNil(fVulkanPipelineShaderStageDDGIProbeDebugFragment);
FreeAndNil(fVulkanPipelineShaderStageDUGIProbeDebugFragment);
FreeAndNil(fVulkanPipelineShaderStageDebugLinesVertex);
@ -755,13 +755,13 @@ begin
FreeAndNil(fSpaceLinesFragmentShaderModule);
FreeAndNil(fDDGIProbeDebugVertexShaderModule);
FreeAndNil(fDUGIProbeDebugVertexShaderModule);
FreeAndNil(fDDGIProbeDebugTaskShaderModule);
FreeAndNil(fDUGIProbeDebugTaskShaderModule);
FreeAndNil(fDDGIProbeDebugMeshShaderModule);
FreeAndNil(fDUGIProbeDebugMeshShaderModule);
FreeAndNil(fDDGIProbeDebugFragmentShaderModule);
FreeAndNil(fDUGIProbeDebugFragmentShaderModule);
FreeAndNil(fDebugLinesVertexShaderModule);
@ -1044,8 +1044,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -1061,22 +1061,22 @@ begin
fVulkanSpaceLinesPipelineLayout.AddDescriptorSetLayout(fPassVulkanDescriptorSetLayout);
fVulkanSpaceLinesPipelineLayout.Initialize;
// DDGI probe debug overlay layout: set 0 = global, set 1 = view UBO (binding 0 of the pass set), set 2 = the DDGI probe-field
// descriptor set (ddgiData + irradiance/visibility/glossy), exactly what gi_ddgi_probe_debug.{vert | task+mesh}/.frag read.
// DUGI probe debug overlay layout: set 0 = global, set 1 = view UBO (binding 0 of the pass set), set 2 = the DUGI probe-field
// descriptor set (dugiData + irradiance/visibility/glossy), exactly what gi_dugi_probe_debug.{vert | task+mesh}/.frag read.
// Push is read in VERTEX+FRAGMENT (vertex path) or TASK+MESH+FRAGMENT (mesh-shader path).
if assigned(fVulkanPipelineShaderStageDDGIProbeDebugFragment) then begin
fVulkanDDGIProbeDebugPipelineLayout:=TpvVulkanPipelineLayout.Create(fInstance.Renderer.VulkanDevice);
if fDDGIProbeDebugMeshShader then begin
fVulkanDDGIProbeDebugPipelineLayout.AddPushConstantRange(TVkShaderStageFlags(VK_SHADER_STAGE_TASK_BIT_EXT) or TVkShaderStageFlags(VK_SHADER_STAGE_MESH_BIT_EXT) or TVkShaderStageFlags(VK_SHADER_STAGE_FRAGMENT_BIT),0,SizeOf(TDDGIProbeDebugPushConstants));
if assigned(fVulkanPipelineShaderStageDUGIProbeDebugFragment) then begin
fVulkanDUGIProbeDebugPipelineLayout:=TpvVulkanPipelineLayout.Create(fInstance.Renderer.VulkanDevice);
if fDUGIProbeDebugMeshShader then begin
fVulkanDUGIProbeDebugPipelineLayout.AddPushConstantRange(TVkShaderStageFlags(VK_SHADER_STAGE_TASK_BIT_EXT) or TVkShaderStageFlags(VK_SHADER_STAGE_MESH_BIT_EXT) or TVkShaderStageFlags(VK_SHADER_STAGE_FRAGMENT_BIT),0,SizeOf(TDUGIProbeDebugPushConstants));
end else begin
fVulkanDDGIProbeDebugPipelineLayout.AddPushConstantRange(TVkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT) or TVkShaderStageFlags(VK_SHADER_STAGE_FRAGMENT_BIT),0,SizeOf(TDDGIProbeDebugPushConstants));
fVulkanDUGIProbeDebugPipelineLayout.AddPushConstantRange(TVkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT) or TVkShaderStageFlags(VK_SHADER_STAGE_FRAGMENT_BIT),0,SizeOf(TDUGIProbeDebugPushConstants));
end;
fVulkanDDGIProbeDebugPipelineLayout.AddDescriptorSetLayout(fInstance.Renderer.Scene3D.GlobalVulkanDescriptorSetLayout);
fVulkanDDGIProbeDebugPipelineLayout.AddDescriptorSetLayout(fPassVulkanDescriptorSetLayout);
fVulkanDDGIProbeDebugPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
fVulkanDDGIProbeDebugPipelineLayout.Initialize;
fVulkanDUGIProbeDebugPipelineLayout.AddDescriptorSetLayout(fInstance.Renderer.Scene3D.GlobalVulkanDescriptorSetLayout);
fVulkanDUGIProbeDebugPipelineLayout.AddDescriptorSetLayout(fPassVulkanDescriptorSetLayout);
fVulkanDUGIProbeDebugPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
fVulkanDUGIProbeDebugPipelineLayout.Initialize;
end else begin
fVulkanDDGIProbeDebugPipelineLayout:=nil;
fVulkanDUGIProbeDebugPipelineLayout:=nil;
end;
if fMeshShader then begin
@ -1577,15 +1577,15 @@ begin
end;
// DDGI probe debug overlay pipeline (procedural octahedral spheres, no vertex buffer). Opaque, depth-tested read-only so the
// DUGI probe debug overlay pipeline (procedural octahedral spheres, no vertex buffer). Opaque, depth-tested read-only so the
// probes are occluded by scene geometry; CULL_NONE because the octahedral fold winding is not globally consistent.
if assigned(fVulkanDDGIProbeDebugPipelineLayout) then begin
if assigned(fVulkanDUGIProbeDebugPipelineLayout) then begin
VulkanGraphicsPipeline:=TpvVulkanGraphicsPipeline.Create(fInstance.Renderer.VulkanDevice,
fInstance.Renderer.VulkanPipelineCache,
0,
[],
fVulkanDDGIProbeDebugPipelineLayout,
fVulkanDUGIProbeDebugPipelineLayout,
fVulkanRenderPass,
VulkanRenderPassSubpassIndex,
nil,
@ -1593,13 +1593,13 @@ begin
try
if fDDGIProbeDebugMeshShader then begin
VulkanGraphicsPipeline.AddStage(fVulkanPipelineShaderStageDDGIProbeDebugTask);
VulkanGraphicsPipeline.AddStage(fVulkanPipelineShaderStageDDGIProbeDebugMesh);
if fDUGIProbeDebugMeshShader then begin
VulkanGraphicsPipeline.AddStage(fVulkanPipelineShaderStageDUGIProbeDebugTask);
VulkanGraphicsPipeline.AddStage(fVulkanPipelineShaderStageDUGIProbeDebugMesh);
end else begin
VulkanGraphicsPipeline.AddStage(fVulkanPipelineShaderStageDDGIProbeDebugVertex);
VulkanGraphicsPipeline.AddStage(fVulkanPipelineShaderStageDUGIProbeDebugVertex);
end;
VulkanGraphicsPipeline.AddStage(fVulkanPipelineShaderStageDDGIProbeDebugFragment);
VulkanGraphicsPipeline.AddStage(fVulkanPipelineShaderStageDUGIProbeDebugFragment);
VulkanGraphicsPipeline.InputAssemblyState.Topology:=TVkPrimitiveTopology.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VulkanGraphicsPipeline.InputAssemblyState.PrimitiveRestartEnable:=false;
@ -1658,12 +1658,12 @@ begin
VulkanGraphicsPipeline.FreeMemory;
finally
fVulkanDDGIProbeDebugGraphicsPipeline:=VulkanGraphicsPipeline;
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(VulkanGraphicsPipeline.Handle,VK_OBJECT_TYPE_PIPELINE,'TpvScene3DRendererPassesForwardRenderPass.DDGIProbeDebugGraphicsPipeline');
fVulkanDUGIProbeDebugGraphicsPipeline:=VulkanGraphicsPipeline;
fInstance.Renderer.VulkanDevice.DebugUtils.SetObjectName(VulkanGraphicsPipeline.Handle,VK_OBJECT_TYPE_PIPELINE,'TpvScene3DRendererPassesForwardRenderPass.DUGIProbeDebugGraphicsPipeline');
end;
end else begin
fVulkanDDGIProbeDebugGraphicsPipeline:=nil;
fVulkanDUGIProbeDebugGraphicsPipeline:=nil;
end;
if fMeshShader and assigned(fVulkanDebugLinesPipelineLayout) then begin
@ -1831,11 +1831,11 @@ begin
end;
end;
FreeAndNil(fVulkanSpaceLinesGraphicsPipeline);
FreeAndNil(fVulkanDDGIProbeDebugGraphicsPipeline);
FreeAndNil(fVulkanDUGIProbeDebugGraphicsPipeline);
FreeAndNil(fVulkanDebugLinesGraphicsPipeline);
FreeAndNil(fVulkanPipelineLayout);
FreeAndNil(fVulkanSpaceLinesPipelineLayout);
FreeAndNil(fVulkanDDGIProbeDebugPipelineLayout);
FreeAndNil(fVulkanDUGIProbeDebugPipelineLayout);
FreeAndNil(fVulkanDebugLinesPipelineLayout);
for Index:=0 to fInstance.Renderer.CountInFlightFrames-1 do begin
FreeAndNil(fPassVulkanDescriptorSets[Index]);
@ -1873,9 +1873,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,
@ -1913,8 +1913,8 @@ procedure TpvScene3DRendererPassesForwardRenderPass.Execute(const aCommandBuffer
var InFlightFrameState:TpvScene3DRendererInstance.PInFlightFrameState;
PreviousInFlightFrameIndex:TpvSizeInt;
DebugLinesPushConstants:TDebugLinesPushConstants;
DDGIProbeDebugPushConstants:TDDGIProbeDebugPushConstants;
DDGIProbeDebugDescriptorSets:array[0..2] of TVkDescriptorSet;
DUGIProbeDebugPushConstants:TDUGIProbeDebugPushConstants;
DUGIProbeDebugDescriptorSets:array[0..2] of TVkDescriptorSet;
begin
inherited Execute(aCommandBuffer,aInFlightFrameIndex,aFrameIndex);
@ -2082,58 +2082,58 @@ begin
end;
// DDGI probe debug overlay: one procedural octahedral sphere per probe over all cascades, coloured by the live-sampled
// directional irradiance (ddgiEvaluateIrradiance). Unculled instanced draw; depth-tested read-only so scene geometry occludes.
if assigned(fVulkanDDGIProbeDebugGraphicsPipeline) and
fInstance.DebugDDGIProbes and
// DUGI probe debug overlay: one procedural octahedral sphere per probe over all cascades, coloured by the live-sampled
// directional irradiance (dugiEvaluateIrradiance). Unculled instanced draw; depth-tested read-only so scene geometry occludes.
if assigned(fVulkanDUGIProbeDebugGraphicsPipeline) and
fInstance.DebugDUGIProbes and
(InFlightFrameState^.FinalViewIndex>=0) and
(InFlightFrameState^.CountFinalViews>0) then begin
FrameGraph.VulkanDevice.DebugUtils.CmdBufLabelBegin(aCommandBuffer,'DDGI Probe Debug',[0.2,0.8,1.0,1.0]);
FrameGraph.VulkanDevice.DebugUtils.CmdBufLabelBegin(aCommandBuffer,'DUGI Probe Debug',[0.2,0.8,1.0,1.0]);
aCommandBuffer.CmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,fVulkanDDGIProbeDebugGraphicsPipeline.Handle);
aCommandBuffer.CmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,fVulkanDUGIProbeDebugGraphicsPipeline.Handle);
DDGIProbeDebugPushConstants.ViewBaseIndex:=TpvUInt32(InFlightFrameState^.FinalViewIndex);
DDGIProbeDebugPushConstants.CountViews:=TpvUInt32(InFlightFrameState^.CountFinalViews);
DUGIProbeDebugPushConstants.ViewBaseIndex:=TpvUInt32(InFlightFrameState^.FinalViewIndex);
DUGIProbeDebugPushConstants.CountViews:=TpvUInt32(InFlightFrameState^.CountFinalViews);
if fDDGIProbeDebugMeshShader then begin
aCommandBuffer.CmdPushConstants(fVulkanDDGIProbeDebugPipelineLayout.Handle,
if fDUGIProbeDebugMeshShader then begin
aCommandBuffer.CmdPushConstants(fVulkanDUGIProbeDebugPipelineLayout.Handle,
TVkShaderStageFlags(VK_SHADER_STAGE_TASK_BIT_EXT) or TVkShaderStageFlags(VK_SHADER_STAGE_MESH_BIT_EXT) or TVkShaderStageFlags(VK_SHADER_STAGE_FRAGMENT_BIT),
0,
SizeOf(TDDGIProbeDebugPushConstants),
@DDGIProbeDebugPushConstants);
SizeOf(TDUGIProbeDebugPushConstants),
@DUGIProbeDebugPushConstants);
end else begin
aCommandBuffer.CmdPushConstants(fVulkanDDGIProbeDebugPipelineLayout.Handle,
aCommandBuffer.CmdPushConstants(fVulkanDUGIProbeDebugPipelineLayout.Handle,
TVkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT) or TVkShaderStageFlags(VK_SHADER_STAGE_FRAGMENT_BIT),
0,
SizeOf(TDDGIProbeDebugPushConstants),
@DDGIProbeDebugPushConstants);
SizeOf(TDUGIProbeDebugPushConstants),
@DUGIProbeDebugPushConstants);
end;
DDGIProbeDebugDescriptorSets[0]:=fInstance.Scene3D.GlobalVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DDGIProbeDebugDescriptorSets[1]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DDGIProbeDebugDescriptorSets[2]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DUGIProbeDebugDescriptorSets[0]:=fInstance.Scene3D.GlobalVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DUGIProbeDebugDescriptorSets[1]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DUGIProbeDebugDescriptorSets[2]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanDDGIProbeDebugPipelineLayout.Handle,
fVulkanDUGIProbeDebugPipelineLayout.Handle,
0,
3,
@DDGIProbeDebugDescriptorSets[0],
@DUGIProbeDebugDescriptorSets[0],
0,
nil);
if fDDGIProbeDebugMeshShader then begin
// Task->mesh path: one task workgroup per 32 probes (GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE); each culls + emits visible probes.
if fDUGIProbeDebugMeshShader then begin
// Task->mesh path: one task workgroup per 32 probes (GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE); each culls + emits visible probes.
if assigned(fInstance.Renderer.VulkanDevice.Commands.Commands.CmdDrawMeshTasksEXT) then begin
fInstance.Renderer.VulkanDevice.Commands.Commands.CmdDrawMeshTasksEXT(aCommandBuffer.Handle,
(TpvUInt32(TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade*TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades)+31) shr 5,
(TpvUInt32(TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade*TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades)+31) shr 5,
1,
1);
end;
end else begin
// Vertex path: VertexCount = (GI_DDGI_PROBE_DEBUG_GRID^2)*6 (2 triangles/octahedral grid quad; GRID=16 in the vert).
// Vertex path: VertexCount = (GI_DUGI_PROBE_DEBUG_GRID^2)*6 (2 triangles/octahedral grid quad; GRID=16 in the vert).
// InstanceCount = probes per cascade * cascade count -> gl_InstanceIndex spans every probe across all cascades.
aCommandBuffer.CmdDraw((16*16)*6,
TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade*TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades,
TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade*TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades,
0,
0);
end;

View file

@ -49,7 +49,7 @@
* 11. Make sure the code runs on all platforms with Vulkan support *
* *
******************************************************************************)
unit PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDDGIRSMSplatComputePass;
unit PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDUGIRSMSplatComputePass;
{$i PasVulkan.inc}
{$ifndef fpc}
{$ifdef conditionalexpressions}
@ -77,14 +77,14 @@ uses SysUtils,
PasVulkan.Scene3D.Renderer.Instance,
PasVulkan.Scene3D.Renderer.IBLDescriptor;
type { TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass }
// Non-raytraced DDGI ray-data PRODUCER (Reflective Shadow Map fallback). For hardware without VK_KHR_ray_query it treats
type { TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass }
// Non-raytraced DUGI ray-data PRODUCER (Reflective Shadow Map fallback). For hardware without VK_KHR_ray_query it treats
// a subset of the sun's RSM texels as virtual point lights and splats them into the same per-(ray, probe) ray-data slots
// the ray-query trace pass would have written. It is a drop-in alternative to the trace producer: everything downstream
// (irradiance / visibility / glossy blend, border) depends only on the ray-data, so those stages are unchanged.
// Set 0 = the RSM source (color/normal/depth + the radiance-hints RSM UBO it shares); set 1 = the DDGI field (ddgiData
// Set 0 = the RSM source (color/normal/depth + the radiance-hints RSM UBO it shares); set 1 = the DUGI field (dugiData
// SSBO + the 6 environment cubemaps for sky-on-miss). Dispatch is identical to the trace: one thread per (ray, probe).
TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass=class(TpvFrameGraph.TComputePass)
TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass=class(TpvFrameGraph.TComputePass)
public
type TPushConstants=record
RandomRotation0:TpvVector4; // mat3 column 0 in xyz
@ -106,9 +106,9 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass }
fRSMDescriptorSetLayout:TpvVulkanDescriptorSetLayout;
fRSMDescriptorPool:TpvVulkanDescriptorPool;
fRSMDescriptorSets:array[0..MaxInFlightFrames-1] of TpvVulkanDescriptorSet;
fDDGIDescriptorSetLayout:TpvVulkanDescriptorSetLayout;
fDDGIDescriptorPool:TpvVulkanDescriptorPool;
fDDGIDescriptorSets:array[0..MaxInFlightFrames-1] of TpvVulkanDescriptorSet;
fDUGIDescriptorSetLayout:TpvVulkanDescriptorSetLayout;
fDUGIDescriptorPool:TpvVulkanDescriptorPool;
fDUGIDescriptorSets:array[0..MaxInFlightFrames-1] of TpvVulkanDescriptorSet;
fIBLDescriptors:array[0..MaxInFlightFrames-1] of TpvScene3DRendererIBLDescriptor; // set 1 binding 4 (6 env cubemaps) for sky-on-miss
fPipelineLayout:TpvVulkanPipelineLayout;
fPipeline:TpvVulkanComputePipeline;
@ -126,16 +126,16 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass }
implementation
{ TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass }
{ TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass }
constructor TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass.Create(const aFrameGraph:TpvFrameGraph;const aInstance:TpvScene3DRendererInstance);
constructor TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass.Create(const aFrameGraph:TpvFrameGraph;const aInstance:TpvScene3DRendererInstance);
begin
inherited Create(aFrameGraph);
fInstance:=aInstance;
Name:='GlobalIlluminationDDGIRSMSplatComputePass';
Name:='GlobalIlluminationDUGIRSMSplatComputePass';
// The sun's RSM (rendered by the ReflectiveShadowMapRenderPass): flux/color, encoded normal + used flag, light-space depth.
// AddImageInput makes the frame graph transition them to shader-read and order this pass after the RSM render pass.
@ -159,16 +159,16 @@ begin
end;
destructor TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass.Destroy;
destructor TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass.Destroy;
begin
inherited Destroy;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass.AcquirePersistentResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass.AcquirePersistentResources;
var Stream:TStream;
begin
inherited AcquirePersistentResources;
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_ddgi_rsm_splat_comp.spv');
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_dugi_rsm_splat_comp.spv');
try
fComputeShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
finally
@ -177,14 +177,14 @@ begin
fVulkanPipelineShaderStage:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_COMPUTE_BIT,fComputeShaderModule,'main');
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass.ReleasePersistentResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass.ReleasePersistentResources;
begin
FreeAndNil(fVulkanPipelineShaderStage);
FreeAndNil(fComputeShaderModule);
inherited ReleasePersistentResources;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass.AcquireVolatileResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass.AcquireVolatileResources;
var InFlightFrameIndex:TpvInt32;
begin
@ -205,34 +205,34 @@ begin
fRSMDescriptorSetLayout.AddBinding(3,VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // RSM matrices UBO (shared radiance-hints buffer)
fRSMDescriptorSetLayout.Initialize;
// Set 1 = DDGI resources: ddgiData SSBO (cascade globals + sub-buffer pointers, incl. ray-data) + 6 env cubemaps (sky-on-miss).
// Set 1 = DUGI resources: dugiData SSBO (cascade globals + sub-buffer pointers, incl. ray-data) + 6 env cubemaps (sky-on-miss).
// Plus the previous-frame multi-bounce reads (mirrors the trace pass): octahedral irradiance at binding 2 (octahedral storage
// only; SH irradiance is the master BDA buffer, no image) and the visibility moments at binding 3.
fDDGIDescriptorPool:=TpvVulkanDescriptorPool.Create(fInstance.Renderer.VulkanDevice,
fDUGIDescriptorPool:=TpvVulkanDescriptorPool.Create(fInstance.Renderer.VulkanDevice,
TVkDescriptorPoolCreateFlags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT),
fInstance.Renderer.CountInFlightFrames);
fDDGIDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,fInstance.Renderer.CountInFlightFrames); // binding 0 = ddgiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fDDGIDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames*2); // binding 2 = oct irradiance read + binding 3 = visibility read (multi-bounce)
fDUGIDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,fInstance.Renderer.CountInFlightFrames); // binding 0 = dugiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fDUGIDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames*2); // binding 2 = oct irradiance read + binding 3 = visibility read (multi-bounce)
end else begin
fDDGIDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames); // binding 3 = visibility read only (SH irradiance is a BDA buffer via the master)
fDUGIDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames); // binding 3 = visibility read only (SH irradiance is a BDA buffer via the master)
end;
fDDGIDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,fInstance.Renderer.CountInFlightFrames*6);
fDDGIDescriptorPool.Initialize;
fDUGIDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,fInstance.Renderer.CountInFlightFrames*6);
fDUGIDescriptorPool.Initialize;
fDDGIDescriptorSetLayout:=TpvVulkanDescriptorSetLayout.Create(fInstance.Renderer.VulkanDevice);
fDDGIDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // ddgiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fDDGIDescriptorSetLayout.AddBinding(2,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // oct irradiance read (multi-bounce)
fDUGIDescriptorSetLayout:=TpvVulkanDescriptorSetLayout.Create(fInstance.Renderer.VulkanDevice);
fDUGIDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // dugiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fDUGIDescriptorSetLayout.AddBinding(2,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // oct irradiance read (multi-bounce)
end;
fDDGIDescriptorSetLayout.AddBinding(3,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // visibility moments read (multi-bounce)
fDDGIDescriptorSetLayout.AddBinding(4,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,6,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // 6 env cubemaps (sky-on-miss)
fDDGIDescriptorSetLayout.Initialize;
fDUGIDescriptorSetLayout.AddBinding(3,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // visibility moments read (multi-bounce)
fDUGIDescriptorSetLayout.AddBinding(4,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,6,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // 6 env cubemaps (sky-on-miss)
fDUGIDescriptorSetLayout.Initialize;
fPipelineLayout:=TpvVulkanPipelineLayout.Create(fInstance.Renderer.VulkanDevice);
fPipelineLayout.AddPushConstantRange(TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),0,SizeOf(TPushConstants));
fPipelineLayout.AddDescriptorSetLayout(fRSMDescriptorSetLayout); // set 0 = RSM source
fPipelineLayout.AddDescriptorSetLayout(fDDGIDescriptorSetLayout); // set 1 = DDGI field + env cubemaps
fPipelineLayout.AddDescriptorSetLayout(fDUGIDescriptorSetLayout); // set 1 = DUGI field + env cubemaps
fPipelineLayout.Initialize;
fPipeline:=TpvVulkanComputePipeline.Create(fInstance.Renderer.VulkanDevice,fInstance.Renderer.VulkanPipelineCache,0,fVulkanPipelineShaderStage,fPipelineLayout,nil,0);
@ -256,26 +256,26 @@ begin
[],[fInstance.GlobalIlluminationRadianceHintsRSMUniformBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false);
fRSMDescriptorSets[InFlightFrameIndex].Flush;
fDDGIDescriptorSets[InFlightFrameIndex]:=TpvVulkanDescriptorSet.Create(fDDGIDescriptorPool,fDDGIDescriptorSetLayout);
fDDGIDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(0,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),[],[fInstance.GlobalIlluminationDDGIMasterBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false); // binding 0 = ddgiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fDDGIDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(2,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIIrradianceOctImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 2 = oct irradiance read (multi-bounce)
fDUGIDescriptorSets[InFlightFrameIndex]:=TpvVulkanDescriptorSet.Create(fDUGIDescriptorPool,fDUGIDescriptorSetLayout);
fDUGIDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(0,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),[],[fInstance.GlobalIlluminationDUGIMasterBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false); // binding 0 = dugiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fDUGIDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(2,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIIrradianceOctImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 2 = oct irradiance read (multi-bounce)
end;
fDDGIDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(3,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIVisibilityMomentsImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 3 = visibility moments read (multi-bounce)
fDDGIDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(4,0,6,TVkDescriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER),
fDUGIDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(3,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIVisibilityMomentsImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 3 = visibility moments read (multi-bounce)
fDUGIDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(4,0,6,TVkDescriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER),
[fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.GGXDescriptorImageInfo,
fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.CharlieDescriptorImageInfo,
fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.LambertianDescriptorImageInfo,
fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.GGXDescriptorImageInfo,
fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.CharlieDescriptorImageInfo,
fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.LambertianDescriptorImageInfo],[],[],false);
fDDGIDescriptorSets[InFlightFrameIndex].Flush;
fDUGIDescriptorSets[InFlightFrameIndex].Flush;
// The IBL descriptor keeps set 1 binding 4 (the 6 env cubemaps) in sync with the active scene/atmosphere each frame, so
// sky-on-miss matches the trace producer. Same wiring as the DDGI trace pass.
fIBLDescriptors[InFlightFrameIndex]:=TpvScene3DRendererIBLDescriptor.Create(fInstance.Renderer.VulkanDevice,fDDGIDescriptorSets[InFlightFrameIndex],4,fInstance.Renderer.ClampedSampler.Handle);
// sky-on-miss matches the trace producer. Same wiring as the DUGI trace pass.
fIBLDescriptors[InFlightFrameIndex]:=TpvScene3DRendererIBLDescriptor.Create(fInstance.Renderer.VulkanDevice,fDUGIDescriptorSets[InFlightFrameIndex],4,fInstance.Renderer.ClampedSampler.Handle);
fIBLDescriptors[InFlightFrameIndex].SetFrom(fInstance.Scene3D,fInstance,InFlightFrameIndex);
fIBLDescriptors[InFlightFrameIndex].Update(true);
@ -283,24 +283,24 @@ begin
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass.ReleaseVolatileResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass.ReleaseVolatileResources;
var InFlightFrameIndex:TpvInt32;
begin
FreeAndNil(fPipeline);
FreeAndNil(fPipelineLayout);
for InFlightFrameIndex:=0 to fInstance.Renderer.CountInFlightFrames-1 do begin
FreeAndNil(fIBLDescriptors[InFlightFrameIndex]);
FreeAndNil(fDDGIDescriptorSets[InFlightFrameIndex]);
FreeAndNil(fDUGIDescriptorSets[InFlightFrameIndex]);
FreeAndNil(fRSMDescriptorSets[InFlightFrameIndex]);
end;
FreeAndNil(fDDGIDescriptorSetLayout);
FreeAndNil(fDDGIDescriptorPool);
FreeAndNil(fDUGIDescriptorSetLayout);
FreeAndNil(fDUGIDescriptorPool);
FreeAndNil(fRSMDescriptorSetLayout);
FreeAndNil(fRSMDescriptorPool);
inherited ReleaseVolatileResources;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass.Update(const aUpdateInFlightFrameIndex,aUpdateFrameIndex:TpvSizeInt);
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass.Update(const aUpdateInFlightFrameIndex,aUpdateFrameIndex:TpvSizeInt);
begin
inherited Update(aUpdateInFlightFrameIndex,aUpdateFrameIndex);
if assigned(fIBLDescriptors[aUpdateInFlightFrameIndex]) then begin
@ -309,8 +309,8 @@ begin
end;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIRSMSplatComputePass.Execute(const aCommandBuffer:TpvVulkanCommandBuffer;const aInFlightFrameIndex,aFrameIndex:TpvSizeInt);
const TotalProbes=TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades*TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIRSMSplatComputePass.Execute(const aCommandBuffer:TpvVulkanCommandBuffer;const aInFlightFrameIndex,aFrameIndex:TpvSizeInt);
const TotalProbes=TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades*TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade;
var PushConstants:TPushConstants;
DescriptorSets:array[0..1] of TVkDescriptorSet;
Quaternion:TpvQuaternion;
@ -333,20 +333,20 @@ begin
PushConstants.RandomRotation2:=TpvVector4.InlineableCreate(RotationMatrix.RawComponents[2,0],RotationMatrix.RawComponents[2,1],RotationMatrix.RawComponents[2,2],0.0);
PushConstants.Params.x:=TpvUInt32(aFrameIndex);
PushConstants.Params.y:=TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades;
PushConstants.Params.z:=TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade;
PushConstants.Params.w:=TpvScene3DRendererInstance.GlobalIlluminationDDGIRaysPerProbe;
PushConstants.Params.y:=TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades;
PushConstants.Params.z:=TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade;
PushConstants.Params.w:=TpvScene3DRendererInstance.GlobalIlluminationDUGIRaysPerProbe;
// Multi-bounce feedback strength + relocation-offset gate: 0 / first-frame on this slot's first frame (the previous probe
// field is uninitialized garbage and the relocation offset is not written yet), else full. Shared with the probe-update pass.
if fInstance.GlobalIlluminationDDGIFirstFrames[aInFlightFrameIndex] then begin
if fInstance.GlobalIlluminationDUGIFirstFrames[aInFlightFrameIndex] then begin
PushConstants.Blend:=TpvVector4.InlineableCreate(0.97,0.0,1.0,0.0);
end else begin
PushConstants.Blend:=TpvVector4.InlineableCreate(0.97,1.0,0.0,0.0);
end;
// Particle LBVH (software-traced, no hardware RT): alive count + emitter/node buffer addresses, zero when inactive. The splat
// injects particles through the same shared gi_ddgi_particle_inject.glsl as the trace producer.
// injects particles through the same shared gi_dugi_particle_inject.glsl as the trace producer.
ParticleEmitterAddress:=0;
ParticleNodeAddress:=0;
ParticleCount:=0;
@ -366,12 +366,12 @@ begin
PushConstants.ParticleBVH.z:=TpvUInt32(ParticleNodeAddress and TpvUInt64($ffffffff));
PushConstants.ParticleBVH.w:=TpvUInt32(ParticleNodeAddress shr 32);
// Make the host/transfer writes visible to the compute shader: the ddgiData buffer's per-frame cascade globals (SSBO read)
// Make the host/transfer writes visible to the compute shader: the dugiData buffer's per-frame cascade globals (SSBO read)
// and the shared RSM matrices UBO (uniform read).
BufferMemoryBarriers[0]:=TVkBufferMemoryBarrier.Create(TVkAccessFlags(VK_ACCESS_HOST_WRITE_BIT) or TVkAccessFlags(VK_ACCESS_TRANSFER_WRITE_BIT),
TVkAccessFlags(VK_ACCESS_SHADER_READ_BIT),
VK_QUEUE_FAMILY_IGNORED,VK_QUEUE_FAMILY_IGNORED,
fInstance.GlobalIlluminationDDGIMasterBuffers[aInFlightFrameIndex].Handle,0,VK_WHOLE_SIZE);
fInstance.GlobalIlluminationDUGIMasterBuffers[aInFlightFrameIndex].Handle,0,VK_WHOLE_SIZE);
BufferMemoryBarriers[1]:=TVkBufferMemoryBarrier.Create(TVkAccessFlags(VK_ACCESS_HOST_WRITE_BIT) or TVkAccessFlags(VK_ACCESS_TRANSFER_WRITE_BIT),
TVkAccessFlags(VK_ACCESS_UNIFORM_READ_BIT),
VK_QUEUE_FAMILY_IGNORED,VK_QUEUE_FAMILY_IGNORED,
@ -381,13 +381,13 @@ begin
0,0,nil,2,@BufferMemoryBarriers[0],0,nil);
DescriptorSets[0]:=fRSMDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE,fPipelineLayout.Handle,0,2,@DescriptorSets[0],0,nil);
aCommandBuffer.CmdPushConstants(fPipelineLayout.Handle,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),0,SizeOf(TPushConstants),@PushConstants);
// Splat: one thread per (ray, probe). local_size_x = 32, same dispatch as the trace producer.
aCommandBuffer.CmdBindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE,fPipeline.Handle);
aCommandBuffer.CmdDispatch((TpvScene3DRendererInstance.GlobalIlluminationDDGIRaysPerProbe+31) shr 5,TotalProbes,1);
aCommandBuffer.CmdDispatch((TpvScene3DRendererInstance.GlobalIlluminationDUGIRaysPerProbe+31) shr 5,TotalProbes,1);
// Publish the ray-data writes to the probe-update passes (they read the ray-data buffer). The frame graph orders the passes;
// this memory barrier makes the writes visible.

View file

@ -49,7 +49,7 @@
* 11. Make sure the code runs on all platforms with Vulkan support *
* *
******************************************************************************)
unit PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDDGIStageComputePass;
unit PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDUGIStageComputePass;
{$i PasVulkan.inc}
{$ifndef fpc}
{$ifdef conditionalexpressions}
@ -76,22 +76,22 @@ uses SysUtils,
PasVulkan.Scene3D.Renderer,
PasVulkan.Scene3D.Renderer.Instance;
type { TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass }
// ONE DDGI probe BLEND/update stage as its own frame-graph compute pass. The technique-agnostic ProbeUpdate CORE
type { TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass }
// ONE DUGI probe BLEND/update stage as its own frame-graph compute pass. The technique-agnostic ProbeUpdate CORE
// (RTXGI's ProbeBlendingCS analog) is split into one pass per shader stage — irradiance, visibility, border, and (when
// GlobalIlluminationDDGIProbeRelocation is on) relocation + classification — so each shader gets its own GPU timer and
// GlobalIlluminationDUGIProbeRelocation is on) relocation + classification — so each shader gets its own GPU timer and
// shows up as a separate per-pass entry in the F8 profiler overlay. The stages chain linearly through explicit frame
// graph dependencies; every pass publishes its writes with a memory barrier so the next stage sees them, and the LAST
// stage additionally publishes to the fragment shading stages and flips the shared firstFrames flag. All stages share
// the same set-1 descriptor layout (UBO + irradiance[OCT] + visibility images) and push-constant layout — they differ
// only in which shader/pipeline they bind and the dispatch dimensions. Ray-data / probe-data / SH-irradiance are BDA
// buffers reached through the master push constant.
TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass=class(TpvFrameGraph.TComputePass)
TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass=class(TpvFrameGraph.TComputePass)
public
type TStage=
(
Irradiance, // one thread per probe; integrates the random rays into the irradiance (SH buffer or OCT atlas)
GlossyRadiance, // one workgroup per probe; integrates the rays into the octahedral GLOSSY prefiltered-radiance atlas (only when GlobalIlluminationDDGIGlossyRadiance)
GlossyRadiance, // one workgroup per probe; integrates the rays into the octahedral GLOSSY prefiltered-radiance atlas (only when GlobalIlluminationDUGIGlossyRadiance)
Visibility, // one workgroup per probe; integrates hit distances into the octahedral mean/mean^2/sky atlas
Border, // one workgroup per probe; copies the octahedral guard bands
Relocation, // one thread per probe; RTXGI-style offset out of geometry (relocation only)
@ -103,8 +103,8 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass }
RandomRotation2:TpvVector4; // mat3 column 2 in xyz
Params:TpvUInt32Vector4; // x = frameIndex, y = countCascades, z = probesPerCascade, w = raysPerProbe
Blend:TpvVector4; // x = hysteresis, z = firstFrame (1 = ignore the uninitialized previous probe data); y/w unused here
EmissiveGIParticleCount:TpvVector4; // unused by the update stages; present only to byte-match the shared gi_ddgi_pushconstants.glsl block
ParticleBVH:TpvUInt32Vector4; // unused by the update stages; present only to byte-match the shared gi_ddgi_pushconstants.glsl block
EmissiveGIParticleCount:TpvVector4; // unused by the update stages; present only to byte-match the shared gi_dugi_pushconstants.glsl block
ParticleBVH:TpvUInt32Vector4; // unused by the update stages; present only to byte-match the shared gi_dugi_pushconstants.glsl block
end;
PPushConstants=^TPushConstants;
private
@ -132,9 +132,9 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass }
implementation
{ TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass }
{ TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass }
constructor TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass.Create(const aFrameGraph:TpvFrameGraph;const aInstance:TpvScene3DRendererInstance;const aStage:TStage;const aFinalStage:boolean);
constructor TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass.Create(const aFrameGraph:TpvFrameGraph;const aInstance:TpvScene3DRendererInstance;const aStage:TStage;const aFinalStage:boolean);
begin
inherited Create(aFrameGraph);
fInstance:=aInstance;
@ -142,57 +142,57 @@ begin
fFinalStage:=aFinalStage;
case fStage of
TStage.Irradiance:begin
Name:='GlobalIlluminationDDGIIrradianceUpdateComputePass';
Name:='GlobalIlluminationDUGIIrradianceUpdateComputePass';
end;
TStage.GlossyRadiance:begin
Name:='GlobalIlluminationDDGIGlossyRadianceUpdateComputePass';
Name:='GlobalIlluminationDUGIGlossyRadianceUpdateComputePass';
end;
TStage.Visibility:begin
Name:='GlobalIlluminationDDGIVisibilityUpdateComputePass';
Name:='GlobalIlluminationDUGIVisibilityUpdateComputePass';
end;
TStage.Border:begin
Name:='GlobalIlluminationDDGIBorderUpdateComputePass';
Name:='GlobalIlluminationDUGIBorderUpdateComputePass';
end;
TStage.Relocation:begin
Name:='GlobalIlluminationDDGIRelocationComputePass';
Name:='GlobalIlluminationDUGIRelocationComputePass';
end;
TStage.Classification:begin
Name:='GlobalIlluminationDDGIClassificationComputePass';
Name:='GlobalIlluminationDUGIClassificationComputePass';
end;
else begin
Name:='GlobalIlluminationDDGIStageComputePass';
Name:='GlobalIlluminationDUGIStageComputePass';
end;
end;
end;
destructor TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass.Destroy;
destructor TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass.Destroy;
begin
inherited Destroy;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass.AcquirePersistentResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass.AcquirePersistentResources;
var ShaderName:TpvUTF8String;
Stream:TStream;
begin
inherited AcquirePersistentResources;
case fStage of
TStage.Irradiance:begin
ShaderName:='gi_ddgi_irradiance_update_comp.spv';
ShaderName:='gi_dugi_irradiance_update_comp.spv';
end;
TStage.GlossyRadiance:begin
ShaderName:='gi_ddgi_glossy_update_comp.spv';
ShaderName:='gi_dugi_glossy_update_comp.spv';
end;
TStage.Visibility:begin
ShaderName:='gi_ddgi_visibility_update_comp.spv';
ShaderName:='gi_dugi_visibility_update_comp.spv';
end;
TStage.Border:begin
ShaderName:='gi_ddgi_border_update_comp.spv';
ShaderName:='gi_dugi_border_update_comp.spv';
end;
TStage.Relocation:begin
ShaderName:='gi_ddgi_relocation_comp.spv';
ShaderName:='gi_dugi_relocation_comp.spv';
end;
TStage.Classification:begin
ShaderName:='gi_ddgi_classification_comp.spv';
ShaderName:='gi_dugi_classification_comp.spv';
end;
else begin
ShaderName:='';
@ -207,14 +207,14 @@ begin
fVulkanPipelineShaderStage:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_COMPUTE_BIT,fComputeShaderModule,'main');
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass.ReleasePersistentResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass.ReleasePersistentResources;
begin
FreeAndNil(fVulkanPipelineShaderStage);
FreeAndNil(fComputeShaderModule);
inherited ReleasePersistentResources;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass.AcquireVolatileResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass.AcquireVolatileResources;
var InFlightFrameIndex:TpvInt32;
begin
@ -225,29 +225,29 @@ begin
fVulkanDescriptorPool:=TpvVulkanDescriptorPool.Create(fInstance.Renderer.VulkanDevice,
TVkDescriptorPoolCreateFlags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT),
fInstance.Renderer.CountInFlightFrames);
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,fInstance.Renderer.CountInFlightFrames); // binding 0 = ddgiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,fInstance.Renderer.CountInFlightFrames); // binding 0 = dugiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames*3); // binding 2 = oct irradiance + binding 3 = visibility moments + binding 4 = visibility sky
end else begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames*2); // binding 3 = visibility moments + binding 4 = visibility sky (SH irradiance is a BDA buffer via the master); ray-data + probe-data are BDA too
end;
if TpvScene3DRendererInstance.GlobalIlluminationDDGIGlossyRadiance then begin
if TpvScene3DRendererInstance.GlobalIlluminationDUGIGlossyRadiance then begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames); // binding 5 = glossy prefiltered-radiance atlas (only the glossy + border stages declare it)
end;
fVulkanDescriptorPool.Initialize;
// Set 1 = DDGI resources used by the blend: UBO, irradiance (OCT only), visibility. Same shared layout the gi_ddgi_*.comp
// Set 1 = DUGI resources used by the blend: UBO, irradiance (OCT only), visibility. Same shared layout the gi_dugi_*.comp
// shaders declare (set 1). Ray-data / probe-data / SH-irradiance are BDA buffers reached via the master push constant. The
// relocation/classification stages only touch the UBO + the master (they declare neither image), but they share this
// superset layout — extra layout bindings unused by a shader are valid.
fVulkanDescriptorSetLayout:=TpvVulkanDescriptorSetLayout.Create(fInstance.Renderer.VulkanDevice);
fVulkanDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // binding 0 = ddgiData SSBO (cascade globals + sub-buffer pointers)
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fVulkanDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // binding 0 = dugiData SSBO (cascade globals + sub-buffer pointers)
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorSetLayout.AddBinding(2,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // oct irradiance; SH irradiance is a BDA buffer via the master
end;
fVulkanDescriptorSetLayout.AddBinding(3,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // visibility moments (RG32F)
fVulkanDescriptorSetLayout.AddBinding(4,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // visibility sky (R8); only the visibility/border stages declare it
if TpvScene3DRendererInstance.GlobalIlluminationDDGIGlossyRadiance then begin
if TpvScene3DRendererInstance.GlobalIlluminationDUGIGlossyRadiance then begin
fVulkanDescriptorSetLayout.AddBinding(5,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // glossy prefiltered-radiance atlas; only the glossy + border stages declare it (superset layout, valid for the rest)
end;
fVulkanDescriptorSetLayout.Initialize;
@ -257,7 +257,7 @@ begin
// The update shaders address their resources at set 1 (shared layout with the trace shaders). Set 0 is unused here, so
// the global scene set layout fills the slot and no descriptor set is bound there (the shaders never touch set 0).
fPipelineLayout.AddDescriptorSetLayout(fInstance.Scene3D.GlobalVulkanDescriptorSetLayout); // set 0 = unused placeholder slot
fPipelineLayout.AddDescriptorSetLayout(fVulkanDescriptorSetLayout); // set 1 = DDGI update resources
fPipelineLayout.AddDescriptorSetLayout(fVulkanDescriptorSetLayout); // set 1 = DUGI update resources
fPipelineLayout.Initialize;
fPipeline:=TpvVulkanComputePipeline.Create(fInstance.Renderer.VulkanDevice,fInstance.Renderer.VulkanPipelineCache,0,fVulkanPipelineShaderStage,fPipelineLayout,nil,0);
@ -265,19 +265,19 @@ begin
for InFlightFrameIndex:=0 to fInstance.Renderer.CountInFlightFrames-1 do begin
fVulkanDescriptorSets[InFlightFrameIndex]:=TpvVulkanDescriptorSet.Create(fVulkanDescriptorPool,fVulkanDescriptorSetLayout);
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(0,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),[],[fInstance.GlobalIlluminationDDGIMasterBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false); // binding 0 = ddgiData SSBO
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(0,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),[],[fInstance.GlobalIlluminationDUGIMasterBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false); // binding 0 = dugiData SSBO
// binding 1 (ray-data) + SH irradiance are BDA buffers reached via the master push constant; binding 2 = oct irradiance only.
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(2,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIIrradianceOctImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIIrradianceOctImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
end;
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(3,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIVisibilityMomentsImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 3 = visibility moments (RG32F)
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIVisibilityMomentsImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 3 = visibility moments (RG32F)
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(4,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIVisibilitySkyImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 4 = visibility sky (R8)
if TpvScene3DRendererInstance.GlobalIlluminationDDGIGlossyRadiance then begin
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIVisibilitySkyImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 4 = visibility sky (R8)
if TpvScene3DRendererInstance.GlobalIlluminationDUGIGlossyRadiance then begin
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(5,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIGlossyImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 5 = glossy prefiltered-radiance atlas
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIGlossyImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false); // binding 5 = glossy prefiltered-radiance atlas
end;
fVulkanDescriptorSets[InFlightFrameIndex].Flush;
@ -285,7 +285,7 @@ begin
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass.ReleaseVolatileResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass.ReleaseVolatileResources;
var InFlightFrameIndex:TpvInt32;
begin
FreeAndNil(fPipeline);
@ -298,8 +298,8 @@ begin
inherited ReleaseVolatileResources;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGIStageComputePass.Execute(const aCommandBuffer:TpvVulkanCommandBuffer;const aInFlightFrameIndex,aFrameIndex:TpvSizeInt);
const TotalProbes=TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades*TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGIStageComputePass.Execute(const aCommandBuffer:TpvVulkanCommandBuffer;const aInFlightFrameIndex,aFrameIndex:TpvSizeInt);
const TotalProbes=TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades*TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade;
// Convergence warmup: for a slot's first WarmupFrames updates, ramp the temporal hysteresis from WarmupStartHysteresis
// up to SteadyHysteresis, so freshly (re)initialized probes settle in a few frames instead of ~100 (less startup flicker).
WarmupFrames=16;
@ -316,7 +316,7 @@ begin
inherited Execute(aCommandBuffer,aInFlightFrameIndex,aFrameIndex);
IsFirstFrame:=fInstance.GlobalIlluminationDDGIFirstFrames[aInFlightFrameIndex];
IsFirstFrame:=fInstance.GlobalIlluminationDUGIFirstFrames[aInFlightFrameIndex];
// Reconstruct the same per-frame rotation the trace used, so the directions the blend weights against match the traced
// rays (deterministic from the frame index).
@ -326,9 +326,9 @@ begin
PushConstants.RandomRotation1:=TpvVector4.InlineableCreate(RotationMatrix.RawComponents[1,0],RotationMatrix.RawComponents[1,1],RotationMatrix.RawComponents[1,2],0.0);
PushConstants.RandomRotation2:=TpvVector4.InlineableCreate(RotationMatrix.RawComponents[2,0],RotationMatrix.RawComponents[2,1],RotationMatrix.RawComponents[2,2],0.0);
PushConstants.Params.x:=TpvUInt32(aFrameIndex);
PushConstants.Params.y:=TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades;
PushConstants.Params.z:=TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade;
PushConstants.Params.w:=TpvScene3DRendererInstance.GlobalIlluminationDDGIRaysPerProbe;
PushConstants.Params.y:=TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades;
PushConstants.Params.z:=TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade;
PushConstants.Params.w:=TpvScene3DRendererInstance.GlobalIlluminationDUGIRaysPerProbe;
// x = temporal hysteresis; z = firstFrame flag (this slot's probe data is still uninitialized -> discard the previous data
// in the temporal blend this frame). Shared first-frame state with the trace pass; flipped false by the final stage below.
if IsFirstFrame then begin
@ -349,12 +349,12 @@ begin
// Workgroup model per stage:
// - one workgroup per probe (octahedral tile, local_size = OCT x OCT, gl_WorkGroupID.x = probe): glossy / visibility / border,
// AND the irradiance stage in OCTAHEDRAL storage mode (gi_ddgi_irradiance_update.comp's OCT path is per-texel-per-probe).
// AND the irradiance stage in OCTAHEDRAL storage mode (gi_dugi_irradiance_update.comp's OCT path is per-texel-per-probe).
// - one thread per probe (local_size_x = 64, gl_GlobalInvocationID.x = probe): irradiance in SH storage mode, relocation,
// classification.
// The irradiance stage thus depends on the storage mode -> NOT a fixed stage set (the SH dispatch starved the OCT path before).
if (fStage in [TStage.GlossyRadiance,TStage.Visibility,TStage.Border]) or
(TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral and (fStage=TStage.Irradiance)) then begin
(TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral and (fStage=TStage.Irradiance)) then begin
aCommandBuffer.CmdDispatch(TotalProbes,1,1);
end else begin
aCommandBuffer.CmdDispatch((TotalProbes+63) shr 6,1,1);
@ -389,7 +389,7 @@ begin
// This slot's probe data has now been written once -> subsequent frames blend against it normally. Only the final stage
// flips it (so every stage this frame saw the pre-flip value); the trace pass ran before all of them this frame.
if fFinalStage then begin
fInstance.GlobalIlluminationDDGIFirstFrames[aInFlightFrameIndex]:=false;
fInstance.GlobalIlluminationDUGIFirstFrames[aInFlightFrameIndex]:=false;
end;
end;

View file

@ -49,7 +49,7 @@
* 11. Make sure the code runs on all platforms with Vulkan support *
* *
******************************************************************************)
unit PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDDGITraceComputePass;
unit PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDUGITraceComputePass;
{$i PasVulkan.inc}
{$ifndef fpc}
{$ifdef conditionalexpressions}
@ -78,14 +78,14 @@ uses SysUtils,
PasVulkan.Scene3D.Renderer.Instance,
PasVulkan.Scene3D.Renderer.IBLDescriptor;
const TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePassMaxPlanetTextures=32; // per-planet blend/grass map array size (set 2), indexed by planet object index
const TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePassMaxPlanetTextures=32; // per-planet blend/grass map array size (set 2), indexed by planet object index
type { TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass }
// DDGI ray-tracing PRODUCER pass: traces GI_DDGI_RAYS_PER_PROBE rays per probe against the scene TLAS (via the shared
type { TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass }
// DUGI ray-tracing PRODUCER pass: traces GI_DUGI_RAYS_PER_PROBE rays per probe against the scene TLAS (via the shared
// gi_rt_gather.glsl layer) and writes the shaded radiance + distance into the ray-data image. It is the swappable
// "trace technique" half of DDGI (RTXGI's ProbeTraceRGS analog); everything downstream depends only on the ray-data
// "trace technique" half of DUGI (RTXGI's ProbeTraceRGS analog); everything downstream depends only on the ray-data
// image, not on how it was produced. The technique-agnostic blend lives in the separate ProbeUpdate compute pass.
TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass=class(TpvFrameGraph.TComputePass)
TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass=class(TpvFrameGraph.TComputePass)
public
type TPushConstants=record
RandomRotation0:TpvVector4; // mat3 column 0 in xyz
@ -93,7 +93,7 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass }
RandomRotation2:TpvVector4; // mat3 column 2 in xyz
Params:TpvUInt32Vector4; // x = frameIndex, y = countCascades, z = probesPerCascade, w = raysPerProbe
Blend:TpvVector4; // y = multi-bounce feedback strength (0 on a slot's first frame); x/z unused by the trace (the update owns them)
EmissiveGIParticleCount:TpvVector4; // x = global GI emissive scale, y = global GI emissive max, z = particle count — must match gi_ddgi_pushconstants.glsl
EmissiveGIParticleCount:TpvVector4; // x = global GI emissive scale, y = global GI emissive max, z = particle count — must match gi_dugi_pushconstants.glsl
ParticleBVH:TpvUInt32Vector4; // particle LBVH device addresses: xy = emitter buffer (uvec2), zw = node buffer (uvec2); 0 when inactive
end;
PPushConstants=^TPushConstants;
@ -126,25 +126,25 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass }
implementation
{ TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass }
{ TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass }
constructor TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass.Create(const aFrameGraph:TpvFrameGraph;const aInstance:TpvScene3DRendererInstance);
constructor TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass.Create(const aFrameGraph:TpvFrameGraph;const aInstance:TpvScene3DRendererInstance);
begin
inherited Create(aFrameGraph);
fInstance:=aInstance;
Name:='GlobalIlluminationDDGITraceComputePass';
Name:='GlobalIlluminationDUGITraceComputePass';
end;
destructor TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass.Destroy;
destructor TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass.Destroy;
begin
inherited Destroy;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass.AcquirePersistentResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass.AcquirePersistentResources;
var Stream:TStream;
begin
inherited AcquirePersistentResources;
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_ddgi_trace_comp.spv');
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_dugi_trace_comp.spv');
try
fComputeShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
finally
@ -153,14 +153,14 @@ begin
fVulkanPipelineShaderStage:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_COMPUTE_BIT,fComputeShaderModule,'main');
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass.ReleasePersistentResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass.ReleasePersistentResources;
begin
FreeAndNil(fVulkanPipelineShaderStage);
FreeAndNil(fComputeShaderModule);
inherited ReleasePersistentResources;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass.AcquireVolatileResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass.AcquireVolatileResources;
var InFlightFrameIndex:TpvInt32;
begin
@ -169,8 +169,8 @@ begin
fVulkanDescriptorPool:=TpvVulkanDescriptorPool.Create(fInstance.Renderer.VulkanDevice,
TVkDescriptorPoolCreateFlags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT),
fInstance.Renderer.CountInFlightFrames);
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,fInstance.Renderer.CountInFlightFrames); // binding 0 = ddgiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,fInstance.Renderer.CountInFlightFrames); // binding 0 = dugiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames*2); // binding 2 = oct irradiance read + binding 3 = visibility read
end else begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames); // binding 3 = visibility read only (SH irradiance is a BDA buffer via the master)
@ -178,11 +178,11 @@ begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,fInstance.Renderer.CountInFlightFrames*6);
fVulkanDescriptorPool.Initialize;
// Set 1 = DDGI resources used by the trace: UBO, irradiance (read for multi-bounce), visibility (read for multi-bounce),
// Set 1 = DUGI resources used by the trace: UBO, irradiance (read for multi-bounce), visibility (read for multi-bounce),
// 6 environment cubemaps (sky-on-miss). Ray-data is now a BDA buffer via the master push constant (binding 1 freed).
fVulkanDescriptorSetLayout:=TpvVulkanDescriptorSetLayout.Create(fInstance.Renderer.VulkanDevice);
fVulkanDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // binding 0 = ddgiData SSBO (cascade globals + sub-buffer pointers)
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fVulkanDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // binding 0 = dugiData SSBO (cascade globals + sub-buffer pointers)
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorSetLayout.AddBinding(2,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // oct irradiance read (multi-bounce); SH irradiance is a BDA buffer via the master
end;
fVulkanDescriptorSetLayout.AddBinding(3,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]);
@ -192,20 +192,20 @@ begin
// Set 2 = per-planet octahedral blend/grass maps (bindless, partially bound), indexed by planet object index.
fPlanetTexturesDescriptorSetLayout:=TpvVulkanDescriptorSetLayout.Create(fInstance.Renderer.VulkanDevice,0,true);
fPlanetTexturesDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePassMaxPlanetTextures,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[],TVkDescriptorBindingFlags(VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT));
fPlanetTexturesDescriptorSetLayout.AddBinding(1,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePassMaxPlanetTextures,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[],TVkDescriptorBindingFlags(VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT));
fPlanetTexturesDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePassMaxPlanetTextures,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[],TVkDescriptorBindingFlags(VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT));
fPlanetTexturesDescriptorSetLayout.AddBinding(1,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePassMaxPlanetTextures,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[],TVkDescriptorBindingFlags(VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT));
fPlanetTexturesDescriptorSetLayout.Initialize;
fPlanetTexturesDescriptorPool:=TpvVulkanDescriptorPool.Create(fInstance.Renderer.VulkanDevice,
TVkDescriptorPoolCreateFlags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT),
fInstance.Renderer.CountInFlightFrames);
fPlanetTexturesDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,fInstance.Renderer.CountInFlightFrames*2*TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePassMaxPlanetTextures);
fPlanetTexturesDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,fInstance.Renderer.CountInFlightFrames*2*TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePassMaxPlanetTextures);
fPlanetTexturesDescriptorPool.Initialize;
fPipelineLayout:=TpvVulkanPipelineLayout.Create(fInstance.Renderer.VulkanDevice);
fPipelineLayout.AddPushConstantRange(TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),0,SizeOf(TPushConstants));
fPipelineLayout.AddDescriptorSetLayout(fInstance.Scene3D.GlobalVulkanDescriptorSetLayout); // set 0 = global scene (TLAS, lights, materials, textures)
fPipelineLayout.AddDescriptorSetLayout(fVulkanDescriptorSetLayout); // set 1 = DDGI trace resources
fPipelineLayout.AddDescriptorSetLayout(fVulkanDescriptorSetLayout); // set 1 = DUGI trace resources
fPipelineLayout.AddDescriptorSetLayout(fPlanetTexturesDescriptorSetLayout); // set 2 = per-planet blend/grass maps
fPipelineLayout.Initialize;
@ -214,15 +214,15 @@ begin
for InFlightFrameIndex:=0 to fInstance.Renderer.CountInFlightFrames-1 do begin
fVulkanDescriptorSets[InFlightFrameIndex]:=TpvVulkanDescriptorSet.Create(fVulkanDescriptorPool,fVulkanDescriptorSetLayout);
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(0,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),[],[fInstance.GlobalIlluminationDDGIMasterBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false); // binding 0 = ddgiData SSBO
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(0,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),[],[fInstance.GlobalIlluminationDUGIMasterBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false); // binding 0 = dugiData SSBO
// Particle LBVH is reached by device address pushed in the push constants (BDA) — no descriptor binding here.
// binding 1 (ray-data) + SH irradiance are BDA buffers reached via the master push constant; binding 2 = oct irradiance only.
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(2,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIIrradianceOctImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIIrradianceOctImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
end;
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(3,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIVisibilityMomentsImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIVisibilityMomentsImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(4,0,6,TVkDescriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER),
[fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.GGXDescriptorImageInfo,
fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.CharlieDescriptorImageInfo,
@ -243,7 +243,7 @@ begin
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass.ReleaseVolatileResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass.ReleaseVolatileResources;
var InFlightFrameIndex:TpvInt32;
begin
FreeAndNil(fPipeline);
@ -262,7 +262,7 @@ begin
inherited ReleaseVolatileResources;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass.Update(const aUpdateInFlightFrameIndex,aUpdateFrameIndex:TpvSizeInt);
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass.Update(const aUpdateInFlightFrameIndex,aUpdateFrameIndex:TpvSizeInt);
var Planets:TpvScene3DPlanets;
Planet:TpvScene3DPlanet;
PlanetIndex,Count,Capacity:TpvSizeInt;
@ -286,7 +286,7 @@ begin
Planets:=TpvScene3DPlanets(fInstance.Scene3D.Planets);
Planets.Lock.AcquireRead;
try
for PlanetIndex:=0 to Min(Planets.Count,TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePassMaxPlanetTextures)-1 do begin
for PlanetIndex:=0 to Min(Planets.Count,TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePassMaxPlanetTextures)-1 do begin
Planet:=Planets.Items[PlanetIndex];
Data:=Planet.InFlightFrameDataList[aUpdateInFlightFrameIndex];
if Planet.Ready and assigned(Data) and assigned(Data.BlendMapImage) and assigned(Data.GrassMapImage) then begin
@ -313,8 +313,8 @@ begin
end;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceComputePass.Execute(const aCommandBuffer:TpvVulkanCommandBuffer;const aInFlightFrameIndex,aFrameIndex:TpvSizeInt);
const TotalProbes=TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades*TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceComputePass.Execute(const aCommandBuffer:TpvVulkanCommandBuffer;const aInFlightFrameIndex,aFrameIndex:TpvSizeInt);
const TotalProbes=TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades*TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade;
var PushConstants:TPushConstants;
DescriptorSets:array[0..2] of TVkDescriptorSet;
Quaternion:TpvQuaternion;
@ -337,13 +337,13 @@ begin
PushConstants.RandomRotation2:=TpvVector4.InlineableCreate(RotationMatrix.RawComponents[2,0],RotationMatrix.RawComponents[2,1],RotationMatrix.RawComponents[2,2],0.0);
PushConstants.Params.x:=TpvUInt32(aFrameIndex);
PushConstants.Params.y:=TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades;
PushConstants.Params.z:=TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade;
PushConstants.Params.w:=TpvScene3DRendererInstance.GlobalIlluminationDDGIRaysPerProbe;
PushConstants.Params.y:=TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades;
PushConstants.Params.z:=TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade;
PushConstants.Params.w:=TpvScene3DRendererInstance.GlobalIlluminationDUGIRaysPerProbe;
// Multi-bounce feedback strength: 0 on this slot's first frame (the previous probe field is uninitialized garbage), else
// full. The first-frame state is shared with the probe-update pass (which flips it false after writing the probes).
if fInstance.GlobalIlluminationDDGIFirstFrames[aInFlightFrameIndex] then begin
if fInstance.GlobalIlluminationDUGIFirstFrames[aInFlightFrameIndex] then begin
PushConstants.Blend:=TpvVector4.InlineableCreate(0.97,0.0,1.0,0.0);
end else begin
PushConstants.Blend:=TpvVector4.InlineableCreate(0.97,1.0,0.0,0.0);
@ -373,11 +373,11 @@ begin
PushConstants.ParticleBVH.z:=TpvUInt32(ParticleNodeAddress and TpvUInt64($ffffffff));
PushConstants.ParticleBVH.w:=TpvUInt32(ParticleNodeAddress shr 32);
// Make the host/transfer write of the ddgiData buffer's per-frame cascade globals visible to the compute shader (SSBO read).
// Make the host/transfer write of the dugiData buffer's per-frame cascade globals visible to the compute shader (SSBO read).
BufferMemoryBarrier:=TVkBufferMemoryBarrier.Create(TVkAccessFlags(VK_ACCESS_HOST_WRITE_BIT) or TVkAccessFlags(VK_ACCESS_TRANSFER_WRITE_BIT),
TVkAccessFlags(VK_ACCESS_SHADER_READ_BIT),
VK_QUEUE_FAMILY_IGNORED,VK_QUEUE_FAMILY_IGNORED,
fInstance.GlobalIlluminationDDGIMasterBuffers[aInFlightFrameIndex].Handle,0,VK_WHOLE_SIZE);
fInstance.GlobalIlluminationDUGIMasterBuffers[aInFlightFrameIndex].Handle,0,VK_WHOLE_SIZE);
aCommandBuffer.CmdPipelineBarrier(TVkPipelineStageFlags(VK_PIPELINE_STAGE_HOST_BIT) or TVkPipelineStageFlags(VK_PIPELINE_STAGE_TRANSFER_BIT),
TVkPipelineStageFlags(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT),
0,0,nil,1,@BufferMemoryBarrier,0,nil);
@ -390,7 +390,7 @@ begin
// Trace: one thread per (ray, probe). local_size_x = 32.
aCommandBuffer.CmdBindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE,fPipeline.Handle);
aCommandBuffer.CmdDispatch((TpvScene3DRendererInstance.GlobalIlluminationDDGIRaysPerProbe+31) shr 5,TotalProbes,1);
aCommandBuffer.CmdDispatch((TpvScene3DRendererInstance.GlobalIlluminationDUGIRaysPerProbe+31) shr 5,TotalProbes,1);
// Publish the ray-data writes to the probe-update pass (it reads the ray-data image). The frame graph orders the passes;
// this memory barrier makes the writes visible (both passes keep the image in VK_IMAGE_LAYOUT_GENERAL).

View file

@ -49,7 +49,7 @@
* 11. Make sure the code runs on all platforms with Vulkan support *
* *
******************************************************************************)
unit PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDDGITraceRSMComputePass;
unit PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDUGITraceRSMComputePass;
{$i PasVulkan.inc}
{$ifndef fpc}
{$ifdef conditionalexpressions}
@ -77,16 +77,16 @@ uses SysUtils,
PasVulkan.Scene3D.Renderer.Instance,
PasVulkan.Scene3D.Renderer.IBLDescriptor;
type { TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass }
// Non-raytraced DDGI ray-data PRODUCER (Reflective Shadow Map fallback). This is the SAME gi_ddgi_trace shader built with
type { TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass }
// Non-raytraced DUGI ray-data PRODUCER (Reflective Shadow Map fallback). This is the SAME gi_dugi_trace shader built with
// the RSM backend (GI_TRACE_BACKEND=2): no hardware ray query — it reads the sun's reflective shadow map (rendered by the
// ReflectiveShadowMapRenderPass) and gathers the lit RSM texels along each probe ray instead of tracing the TLAS. Probe
// iteration / relocation / multi-bounce / particle injection / ray-data encode are byte-for-byte identical to the ray-query
// trace, so the whole probe BLEND/update core downstream is unchanged. Only used when raytracing is unavailable, where the
// engine's global descriptor set is the non-RT variant (lights without the TLAS), which the RSM shader matches.
// Descriptor sets: 0 = global scene (lights/materials/textures, no TLAS), 1 = DDGI resources (ddgiData + previous-frame
// Descriptor sets: 0 = global scene (lights/materials/textures, no TLAS), 1 = DUGI resources (dugiData + previous-frame
// irradiance/visibility reads for multi-bounce + the 6 env cubemaps for sky-on-miss), 2 = the RSM source textures + matrices.
TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass=class(TpvFrameGraph.TComputePass)
TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass=class(TpvFrameGraph.TComputePass)
public
type TPushConstants=record
RandomRotation0:TpvVector4; // mat3 column 0 in xyz
@ -94,7 +94,7 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass }
RandomRotation2:TpvVector4; // mat3 column 2 in xyz
Params:TpvUInt32Vector4; // x = frameIndex, y = countCascades, z = probesPerCascade, w = raysPerProbe
Blend:TpvVector4; // y = multi-bounce feedback strength (0 on a slot's first frame); z = first-frame flag (relocation offset gate)
EmissiveGIParticleCount:TpvVector4; // x = global GI emissive scale, y = global GI emissive max, z = particle count — must match gi_ddgi_pushconstants.glsl
EmissiveGIParticleCount:TpvVector4; // x = global GI emissive scale, y = global GI emissive max, z = particle count — must match gi_dugi_pushconstants.glsl
ParticleBVH:TpvUInt32Vector4; // particle LBVH device addresses: xy = emitter buffer (uvec2), zw = node buffer (uvec2); 0 when inactive
end;
PPushConstants=^TPushConstants;
@ -105,7 +105,7 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass }
fResourceRSMDepth:TpvFrameGraph.TPass.TUsedImageResource;
fComputeShaderModule:TpvVulkanShaderModule;
fVulkanPipelineShaderStage:TpvVulkanPipelineShaderStage;
fVulkanDescriptorSetLayout:TpvVulkanDescriptorSetLayout; // set 1 = DDGI resources
fVulkanDescriptorSetLayout:TpvVulkanDescriptorSetLayout; // set 1 = DUGI resources
fVulkanDescriptorPool:TpvVulkanDescriptorPool;
fVulkanDescriptorSets:array[0..MaxInFlightFrames-1] of TpvVulkanDescriptorSet;
fRSMDescriptorSetLayout:TpvVulkanDescriptorSetLayout; // set 2 = RSM source
@ -128,16 +128,16 @@ type { TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass }
implementation
{ TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass }
{ TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass }
constructor TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass.Create(const aFrameGraph:TpvFrameGraph;const aInstance:TpvScene3DRendererInstance);
constructor TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass.Create(const aFrameGraph:TpvFrameGraph;const aInstance:TpvScene3DRendererInstance);
begin
inherited Create(aFrameGraph);
fInstance:=aInstance;
Name:='GlobalIlluminationDDGITraceRSMComputePass';
Name:='GlobalIlluminationDUGITraceRSMComputePass';
// The sun's RSM (rendered by the ReflectiveShadowMapRenderPass): flux/color, encoded normal + used flag, light-space depth.
// AddImageInput makes the frame graph transition them to shader-read and order this pass after the RSM render pass.
@ -161,17 +161,17 @@ begin
end;
destructor TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass.Destroy;
destructor TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass.Destroy;
begin
inherited Destroy;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass.AcquirePersistentResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass.AcquirePersistentResources;
var Stream:TStream;
begin
inherited AcquirePersistentResources;
// The RSM-backend build of gi_ddgi_trace (GI_TRACE_BACKEND=2): same producer, no ray query.
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_ddgi_trace_rsm_comp.spv');
// The RSM-backend build of gi_dugi_trace (GI_TRACE_BACKEND=2): same producer, no ray query.
Stream:=pvScene3DShaderVirtualFileSystem.GetFile('gi_dugi_trace_rsm_comp.spv');
try
fComputeShaderModule:=TpvVulkanShaderModule.Create(fInstance.Renderer.VulkanDevice,Stream);
finally
@ -180,26 +180,26 @@ begin
fVulkanPipelineShaderStage:=TpvVulkanPipelineShaderStage.Create(VK_SHADER_STAGE_COMPUTE_BIT,fComputeShaderModule,'main');
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass.ReleasePersistentResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass.ReleasePersistentResources;
begin
FreeAndNil(fVulkanPipelineShaderStage);
FreeAndNil(fComputeShaderModule);
inherited ReleasePersistentResources;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass.AcquireVolatileResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass.AcquireVolatileResources;
var InFlightFrameIndex:TpvInt32;
begin
inherited AcquireVolatileResources;
// Set 1 = DDGI resources (mirrors the trace pass): ddgiData SSBO, previous-frame irradiance (octahedral storage only) +
// Set 1 = DUGI resources (mirrors the trace pass): dugiData SSBO, previous-frame irradiance (octahedral storage only) +
// visibility reads for multi-bounce, and the 6 environment cubemaps (sky-on-miss).
fVulkanDescriptorPool:=TpvVulkanDescriptorPool.Create(fInstance.Renderer.VulkanDevice,
TVkDescriptorPoolCreateFlags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT),
fInstance.Renderer.CountInFlightFrames);
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,fInstance.Renderer.CountInFlightFrames); // binding 0 = ddgiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,fInstance.Renderer.CountInFlightFrames); // binding 0 = dugiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames*2); // binding 2 = oct irradiance read + binding 3 = visibility read
end else begin
fVulkanDescriptorPool.AddDescriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,fInstance.Renderer.CountInFlightFrames); // binding 3 = visibility read only (SH irradiance is a BDA buffer via the master)
@ -208,8 +208,8 @@ begin
fVulkanDescriptorPool.Initialize;
fVulkanDescriptorSetLayout:=TpvVulkanDescriptorSetLayout.Create(fInstance.Renderer.VulkanDevice);
fVulkanDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // binding 0 = ddgiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fVulkanDescriptorSetLayout.AddBinding(0,VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // binding 0 = dugiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorSetLayout.AddBinding(2,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]); // oct irradiance read (multi-bounce)
end;
fVulkanDescriptorSetLayout.AddBinding(3,VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),[]);
@ -234,7 +234,7 @@ begin
fPipelineLayout:=TpvVulkanPipelineLayout.Create(fInstance.Renderer.VulkanDevice);
fPipelineLayout.AddPushConstantRange(TVkShaderStageFlags(VK_SHADER_STAGE_COMPUTE_BIT),0,SizeOf(TPushConstants));
fPipelineLayout.AddDescriptorSetLayout(fInstance.Scene3D.GlobalVulkanDescriptorSetLayout); // set 0 = global scene (lights/materials/textures; no TLAS in the non-RT layout)
fPipelineLayout.AddDescriptorSetLayout(fVulkanDescriptorSetLayout); // set 1 = DDGI resources
fPipelineLayout.AddDescriptorSetLayout(fVulkanDescriptorSetLayout); // set 1 = DUGI resources
fPipelineLayout.AddDescriptorSetLayout(fRSMDescriptorSetLayout); // set 2 = RSM source
fPipelineLayout.Initialize;
@ -243,13 +243,13 @@ begin
for InFlightFrameIndex:=0 to fInstance.Renderer.CountInFlightFrames-1 do begin
fVulkanDescriptorSets[InFlightFrameIndex]:=TpvVulkanDescriptorSet.Create(fVulkanDescriptorPool,fVulkanDescriptorSetLayout);
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(0,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),[],[fInstance.GlobalIlluminationDDGIMasterBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false); // binding 0 = ddgiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDDGIStorageOctahedral then begin
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(0,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),[],[fInstance.GlobalIlluminationDUGIMasterBuffers[InFlightFrameIndex].DescriptorBufferInfo],[],false); // binding 0 = dugiData SSBO
if TpvScene3DRendererInstance.GlobalIlluminationDUGIStorageOctahedral then begin
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(2,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIIrradianceOctImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIIrradianceOctImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
end;
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(3,0,1,TVkDescriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDDGIVisibilityMomentsImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
[TVkDescriptorImageInfo.Create(VK_NULL_HANDLE,fInstance.GlobalIlluminationDUGIVisibilityMomentsImages[InFlightFrameIndex].VulkanImageView.Handle,VK_IMAGE_LAYOUT_GENERAL)],[],[],false);
fVulkanDescriptorSets[InFlightFrameIndex].WriteToDescriptorSet(4,0,6,TVkDescriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER),
[fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.GGXDescriptorImageInfo,
fInstance.Renderer.ImageBasedLightingEnvMapCubeMaps.CharlieDescriptorImageInfo,
@ -284,7 +284,7 @@ begin
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass.ReleaseVolatileResources;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass.ReleaseVolatileResources;
var InFlightFrameIndex:TpvInt32;
begin
FreeAndNil(fPipeline);
@ -301,7 +301,7 @@ begin
inherited ReleaseVolatileResources;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass.Update(const aUpdateInFlightFrameIndex,aUpdateFrameIndex:TpvSizeInt);
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass.Update(const aUpdateInFlightFrameIndex,aUpdateFrameIndex:TpvSizeInt);
begin
inherited Update(aUpdateInFlightFrameIndex,aUpdateFrameIndex);
if assigned(fIBLDescriptors[aUpdateInFlightFrameIndex]) then begin
@ -310,8 +310,8 @@ begin
end;
end;
procedure TpvScene3DRendererPassesGlobalIlluminationDDGITraceRSMComputePass.Execute(const aCommandBuffer:TpvVulkanCommandBuffer;const aInFlightFrameIndex,aFrameIndex:TpvSizeInt);
const TotalProbes=TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades*TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade;
procedure TpvScene3DRendererPassesGlobalIlluminationDUGITraceRSMComputePass.Execute(const aCommandBuffer:TpvVulkanCommandBuffer;const aInFlightFrameIndex,aFrameIndex:TpvSizeInt);
const TotalProbes=TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades*TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade;
var PushConstants:TPushConstants;
DescriptorSets:array[0..2] of TVkDescriptorSet;
Quaternion:TpvQuaternion;
@ -334,13 +334,13 @@ begin
PushConstants.RandomRotation2:=TpvVector4.InlineableCreate(RotationMatrix.RawComponents[2,0],RotationMatrix.RawComponents[2,1],RotationMatrix.RawComponents[2,2],0.0);
PushConstants.Params.x:=TpvUInt32(aFrameIndex);
PushConstants.Params.y:=TpvScene3DRendererInstance.CountGlobalIlluminationDDGICascades;
PushConstants.Params.z:=TpvScene3DRendererInstance.GlobalIlluminationDDGIProbesPerCascade;
PushConstants.Params.w:=TpvScene3DRendererInstance.GlobalIlluminationDDGIRaysPerProbe;
PushConstants.Params.y:=TpvScene3DRendererInstance.CountGlobalIlluminationDUGICascades;
PushConstants.Params.z:=TpvScene3DRendererInstance.GlobalIlluminationDUGIProbesPerCascade;
PushConstants.Params.w:=TpvScene3DRendererInstance.GlobalIlluminationDUGIRaysPerProbe;
// Multi-bounce feedback strength + relocation-offset gate: 0 / first-frame on this slot's first frame (the previous probe
// field is uninitialized garbage and the relocation offset is not written yet), else full. Shared with the probe-update pass.
if fInstance.GlobalIlluminationDDGIFirstFrames[aInFlightFrameIndex] then begin
if fInstance.GlobalIlluminationDUGIFirstFrames[aInFlightFrameIndex] then begin
PushConstants.Blend:=TpvVector4.InlineableCreate(0.97,0.0,1.0,0.0);
end else begin
PushConstants.Blend:=TpvVector4.InlineableCreate(0.97,1.0,0.0,0.0);
@ -366,12 +366,12 @@ begin
PushConstants.ParticleBVH.z:=TpvUInt32(ParticleNodeAddress and TpvUInt64($ffffffff));
PushConstants.ParticleBVH.w:=TpvUInt32(ParticleNodeAddress shr 32);
// Make the host/transfer writes visible to the compute shader: the ddgiData cascade globals (SSBO read) and the shared RSM
// Make the host/transfer writes visible to the compute shader: the dugiData cascade globals (SSBO read) and the shared RSM
// matrices UBO (uniform read).
BufferMemoryBarriers[0]:=TVkBufferMemoryBarrier.Create(TVkAccessFlags(VK_ACCESS_HOST_WRITE_BIT) or TVkAccessFlags(VK_ACCESS_TRANSFER_WRITE_BIT),
TVkAccessFlags(VK_ACCESS_SHADER_READ_BIT),
VK_QUEUE_FAMILY_IGNORED,VK_QUEUE_FAMILY_IGNORED,
fInstance.GlobalIlluminationDDGIMasterBuffers[aInFlightFrameIndex].Handle,0,VK_WHOLE_SIZE);
fInstance.GlobalIlluminationDUGIMasterBuffers[aInFlightFrameIndex].Handle,0,VK_WHOLE_SIZE);
BufferMemoryBarriers[1]:=TVkBufferMemoryBarrier.Create(TVkAccessFlags(VK_ACCESS_HOST_WRITE_BIT) or TVkAccessFlags(VK_ACCESS_TRANSFER_WRITE_BIT),
TVkAccessFlags(VK_ACCESS_UNIFORM_READ_BIT),
VK_QUEUE_FAMILY_IGNORED,VK_QUEUE_FAMILY_IGNORED,
@ -388,7 +388,7 @@ begin
// One thread per (ray, probe). local_size_x = 32, same dispatch as the trace producer.
aCommandBuffer.CmdBindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE,fPipeline.Handle);
aCommandBuffer.CmdDispatch((TpvScene3DRendererInstance.GlobalIlluminationDDGIRaysPerProbe+31) shr 5,TotalProbes,1);
aCommandBuffer.CmdDispatch((TpvScene3DRendererInstance.GlobalIlluminationDUGIRaysPerProbe+31) shr 5,TotalProbes,1);
// Publish the ray-data writes to the probe-update passes (they read the ray-data buffer).
FinalMemoryBarrier:=TVkMemoryBarrier.Create(TVkAccessFlags(VK_ACCESS_SHADER_WRITE_BIT),

View file

@ -727,8 +727,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -1139,9 +1139,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -643,8 +643,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -1055,9 +1055,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -671,8 +671,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -1084,9 +1084,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -603,8 +603,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -1047,9 +1047,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -660,8 +660,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -1065,9 +1065,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -78,9 +78,9 @@ uses SysUtils,
type { TpvScene3DRendererPassesParticleBVHComputePass }
// Builds a per-frame GPU LBVH over the particle emitters (particles are not in the hardware ray-tracing BLAS) so that
// gi_ddgi_trace.comp can software-trace them into the DDGI probe irradiance. Pipeline: extract emitters from the billboard
// gi_dugi_trace.comp can software-trace them into the DUGI probe irradiance. Pipeline: extract emitters from the billboard
// vertex buffer -> world AABB -> Morton codes -> LSD radix sort -> Karras hierarchy -> bottom-up AABB refit. Runs before
// the DDGI trace pass (explicit dependency). All buffers live on the renderer instance.
// the DUGI trace pass (explicit dependency). All buffers live on the renderer instance.
TpvScene3DRendererPassesParticleBVHComputePass=class(TpvFrameGraph.TComputePass)
public
const Stages=8; // 0=emit 1=aabb 2=morton 3=radixHistogram 4=radixScan 5=radixScatter 6=hierarchy 7=refit
@ -316,7 +316,7 @@ begin
RunStage(7,(ParticleCount+255) shr 8,0);
StageBarrier;
// Publish the finished node + emitter buffers to the DDGI trace pass (it reads them via its set-1 SSBO bindings).
// Publish the finished node + emitter buffers to the DUGI trace pass (it reads them via its set-1 SSBO bindings).
aCommandBuffer.CmdPipelineBarrier(TVkPipelineStageFlags(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT),
TVkPipelineStageFlags(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT),
0,1,@MemoryBarrier,0,nil,0,nil);

View file

@ -219,12 +219,12 @@ begin
MeshFragmentSpecializationConstants:=fInstance.MeshFragmentSpecializationConstants;
// The non-raytraced DDGI RSM-backend (trace) producer wants the RSM to carry raw albedo (it re-lights it itself), so render the
// The non-raytraced DUGI RSM-backend (trace) producer wants the RSM to carry raw albedo (it re-lights it itself), so render the
// albedo output variant of the fragment shaders in that case; otherwise (radiance hints, OR the RSM VPL splat producer which
// re-emits the stored flux directly) the lit flux variant.
if (fInstance.Renderer.GlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination) and
if (fInstance.Renderer.GlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination) and
(not fInstance.Renderer.Scene3D.RaytracingActive) and
(not fInstance.GlobalIlluminationDDGIUseRSMSplat) then begin
(not fInstance.GlobalIlluminationDUGIUseRSMSplat) then begin
RSMInfix:='rsm_albedo_';
end else begin
RSMInfix:='rsm_';

View file

@ -532,8 +532,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -595,9 +595,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -599,8 +599,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedRadianceHints:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationRadianceHintsDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDDGIDescriptorSetLayout);
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationDUGIDescriptorSetLayout);
end;
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fVulkanPipelineLayout.AddDescriptorSetLayout(fInstance.GlobalIlluminationCascadedVoxelConeTracingDescriptorSetLayout);
@ -1043,9 +1043,9 @@ begin
0,
nil);
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
DescriptorSets[0]:=fPassVulkanDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDDGIDescriptorSets[aInFlightFrameIndex].Handle;
DescriptorSets[1]:=fInstance.GlobalIlluminationDUGIDescriptorSets[aInFlightFrameIndex].Handle;
aCommandBuffer.CmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
fVulkanPipelineLayout.Handle,
1,

View file

@ -1168,7 +1168,7 @@ begin
if fGlobalIlluminationMode=TpvScene3DRendererGlobalIlluminationMode.Auto then begin
if fRaytracingActive and (fVulkanDevice.PhysicalDevice.Properties.deviceType<>VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) then begin
fGlobalIlluminationMode:=TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination;
fGlobalIlluminationMode:=TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination;
end else begin
case TpvVulkanVendorID(fVulkanDevice.PhysicalDevice.Properties.vendorID) of
TpvVulkanVendorID.AMD:begin
@ -1201,14 +1201,14 @@ begin
//fGlobalIlluminationMode:=TpvScene3DRendererGlobalIlluminationMode.EnvironmentMap;
end;
// DDGI normally needs hardware ray tracing to trace the probe rays. When it is not available we KEEP DDGI but drive its
// probe field from a non-raytraced Reflective Shadow Map producer (gi_ddgi_trace built with the RSM backend) instead of the ray-query trace; the
// DUGI normally needs hardware ray tracing to trace the probe rays. When it is not available we KEEP DUGI but drive its
// probe field from a non-raytraced Reflective Shadow Map producer (gi_dugi_trace built with the RSM backend) instead of the ray-query trace; the
// probe blend / shading path is producer-agnostic, so only the producer pass differs (wired in the Instance per RaytracingActive).
case fGlobalIlluminationMode of
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
if not fRaytracingActive then begin
if assigned(pvApplication) then begin
pvApplication.Log(LOG_INFO,'TpvScene3DRenderer','DynamicDiffuseGlobalIllumination without raytracing: using the non-raytraced Reflective Shadow Map fallback producer');
pvApplication.Log(LOG_INFO,'TpvScene3DRenderer','DynamicUnifiedGlobalIllumination without raytracing: using the non-raytraced Reflective Shadow Map fallback producer');
end;
end;
end;
@ -1223,8 +1223,8 @@ begin
TpvScene3DRendererGlobalIlluminationMode.CascadedVoxelConeTracing:begin
fMeshFragGlobalIlluminationTypeName:='globalillumination_cascaded_voxel_cone_tracing_';
end;
TpvScene3DRendererGlobalIlluminationMode.DynamicDiffuseGlobalIllumination:begin
fMeshFragGlobalIlluminationTypeName:='globalillumination_ddgi_';
TpvScene3DRendererGlobalIlluminationMode.DynamicUnifiedGlobalIllumination:begin
fMeshFragGlobalIlluminationTypeName:='globalillumination_dugi_';
end;
else begin
fMeshFragGlobalIlluminationTypeName:='';

View file

@ -22,30 +22,30 @@ DELETEAFTERCOMPILE=1
# Debug mode, if set to 1, debug information will generated and written into the spirv files
DEBUG=1
# DDGI irradiance storage mode for the dynamic diffuse global illumination shaders: 0 = octahedral atlas (1 image),
# DUGI irradiance storage mode for the dynamic diffuse global illumination shaders: 0 = octahedral atlas (1 image),
# 1 = L1 spherical harmonics (3 images), 2 = L2 spherical harmonics (7 images, default — for testing/comparison). This MUST match
# GlobalIlluminationDDGIStorageMode in PasVulkan.Scene3D.Renderer.Instance.pas, otherwise the descriptor layouts / image
# counts / view types of the DDGI compute shaders and the globalillumination_ddgi mesh fragment variant won't match the
# GlobalIlluminationDUGIStorageMode in PasVulkan.Scene3D.Renderer.Instance.pas, otherwise the descriptor layouts / image
# counts / view types of the DUGI compute shaders and the globalillumination_dugi mesh fragment variant won't match the
# Pascal side. The define is always passed explicitly (the shader's own default differs), so keep the two in sync.
DDGI_STORAGE=0
DDGI_STORAGE_DEFINE="-DGI_DDGI_STORAGE=${DDGI_STORAGE}"
DUGI_STORAGE=0
DUGI_STORAGE_DEFINE="-DGI_DUGI_STORAGE=${DUGI_STORAGE}"
# RTXGI-style probe relocation + classification (0 = off, 1 = on). When on, the trace traces GI_DDGI_FIXED_RAYS fixed rays
# RTXGI-style probe relocation + classification (0 = off, 1 = on). When on, the trace traces GI_DUGI_FIXED_RAYS fixed rays
# for the relocation/classification compute passes, the irradiance/visibility blend integrates only the remaining random
# rays, and the trace/sampling shaders bind the per-probe probe-data image (relocation offset + active state). This MUST
# match GlobalIlluminationDDGIProbeRelocation in PasVulkan.Scene3D.Renderer.Instance.pas, otherwise the descriptor binding
# counts (compute set binding 5 + shading set binding 3) won't line up with the Pascal side. Passed to every DDGI shader.
DDGI_PROBE_RELOCATION=1
DDGI_PROBE_RELOCATION_DEFINE="-DGI_DDGI_PROBE_RELOCATION=${DDGI_PROBE_RELOCATION}"
# match GlobalIlluminationDUGIProbeRelocation in PasVulkan.Scene3D.Renderer.Instance.pas, otherwise the descriptor binding
# counts (compute set binding 5 + shading set binding 3) won't line up with the Pascal side. Passed to every DUGI shader.
DUGI_PROBE_RELOCATION=1
DUGI_PROBE_RELOCATION_DEFINE="-DGI_DUGI_PROBE_RELOCATION=${DUGI_PROBE_RELOCATION}"
# DDGI glossy prefiltered-radiance octahedral atlas (0 = off, 1 = on). Opt-in. When on, the gi_ddgi_glossy_update
# DUGI glossy prefiltered-radiance octahedral atlas (0 = off, 1 = on). Opt-in. When on, the gi_dugi_glossy_update
# compute pass + the glossy atlas binding (compute set 1 binding 5 / shading set binding 5) are built, and the border + the
# mesh/planet DDGI fragment variants get the glossy sampling path. This MUST match GlobalIlluminationDDGIGlossyRadiance in
# PasVulkan.Scene3D.Renderer.Instance.pas. First iteration uses the RGBA16F atlas format (-DGI_DDGI_GLOSSY_RGBA16F); the
# RGB9E5 variant (smaller) is a later option. When off, GLOSSY_DEFINE is empty (plain DDGI, no glossy atlas/binding).
DDGI_GLOSSY=1
if [ "${DDGI_GLOSSY}" = "1" ]; then
GLOSSY_DEFINE="-DGI_DDGI_GLOSSY_RADIANCE -DGI_DDGI_GLOSSY_RGBA16F"
# mesh/planet DUGI fragment variants get the glossy sampling path. This MUST match GlobalIlluminationDUGIGlossyRadiance in
# PasVulkan.Scene3D.Renderer.Instance.pas. First iteration uses the RGBA16F atlas format (-DGI_DUGI_GLOSSY_RGBA16F); the
# RGB9E5 variant (smaller) is a later option. When off, GLOSSY_DEFINE is empty (plain DUGI, no glossy atlas/binding).
DUGI_GLOSSY=1
if [ "${DUGI_GLOSSY}" = "1" ]; then
GLOSSY_DEFINE="-DGI_DUGI_GLOSSY_RADIANCE -DGI_DUGI_GLOSSY_RGBA16F"
else
GLOSSY_DEFINE=""
fi
@ -534,7 +534,7 @@ compileshaderarguments=(
"-V cnn_buffer_to_image.comp -o ${tempPath}/cnn_buffer_to_image_comp.spv"
# Per-frame GPU particle LBVH build (emit -> AABB -> Morton -> radix sort -> Karras hierarchy -> AABB refit), software-traced
# by GI/RT consumers (DDGI now, path tracing later) to inject particles (not in the BLAS). emit reads the vertex buffer via BDA -> 1.2.
# by GI/RT consumers (DUGI now, path tracing later) to inject particles (not in the BLAS). emit reads the vertex buffer via BDA -> 1.2.
"-V particle_bvh_emit.comp --target-env vulkan1.2 -o ${tempPath}/particle_bvh_emit_comp.spv"
"-V particle_bvh_aabb.comp --target-env vulkan1.2 -o ${tempPath}/particle_bvh_aabb_comp.spv"
"-V particle_bvh_morton.comp --target-env vulkan1.2 -o ${tempPath}/particle_bvh_morton_comp.spv"
@ -565,44 +565,44 @@ compileshaderarguments=(
"-V gi_cascaded_radiance_hints_bounce.comp -o ${tempPath}/gi_cascaded_radiance_hints_bounce_comp.spv"
# DDGI (dynamic diffuse global illumination). Storage mode defaults to L1 spherical harmonics (GI_DDGI_STORAGE = 0);
# build the octahedral irradiance variants by adding -DGI_DDGI_STORAGE=1 (and matching the shading variant below).
# gi_ddgi_trace.comp traces rays via ray query (it includes raytracing.glsl), so it needs the ray tracing SPIR-V target.
# DUGI (dynamic diffuse global illumination). Storage mode defaults to L1 spherical harmonics (GI_DUGI_STORAGE = 0);
# build the octahedral irradiance variants by adding -DGI_DUGI_STORAGE=1 (and matching the shading variant below).
# gi_dugi_trace.comp traces rays via ray query (it includes raytracing.glsl), so it needs the ray tracing SPIR-V target.
# RAYTRACING is #defined inside the shader (not via -D) to avoid a macro redefinition clash, so the auto target-env
# logic below (which keys off "-DRAYTRACING") does not trigger here; set the target explicitly.
"-V gi_ddgi_trace.comp --target-env vulkan1.2 ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_ddgi_trace_comp.spv"
# Non-raytraced DDGI producer = the SAME gi_ddgi_trace.comp built with the Reflective Shadow Map backend (GI_TRACE_BACKEND=2):
"-V gi_dugi_trace.comp --target-env vulkan1.2 ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_dugi_trace_comp.spv"
# Non-raytraced DUGI producer = the SAME gi_dugi_trace.comp built with the Reflective Shadow Map backend (GI_TRACE_BACKEND=2):
# no ray query (no ray-tracing SPIR-V target needed), reads the sun's RSM instead of the TLAS. Probe iteration / relocation /
# multi-bounce / particle injection / ray-data encode are identical to the ray-query build. Used as the DDGI fallback when
# multi-bounce / particle injection / ray-data encode are identical to the ray-query build. Used as the DUGI fallback when
# hardware ray query is unavailable.
"-V gi_ddgi_trace.comp --target-env vulkan1.2 ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} -DGI_TRACE_BACKEND=2 -o ${tempPath}/gi_ddgi_trace_rsm_comp.spv"
# gi_ddgi_rsm_splat.comp is the non-raytraced DDGI producer (Reflective Shadow Map VPL splatting), used as the fallback
# when hardware ray query is unavailable. It writes the same ddgiData ray-data contract as the trace, so it needs the
"-V gi_dugi_trace.comp --target-env vulkan1.2 ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} -DGI_TRACE_BACKEND=2 -o ${tempPath}/gi_dugi_trace_rsm_comp.spv"
# gi_dugi_rsm_splat.comp is the non-raytraced DUGI producer (Reflective Shadow Map VPL splatting), used as the fallback
# when hardware ray query is unavailable. It writes the same dugiData ray-data contract as the trace, so it needs the
# buffer_reference SPIR-V target; it does NOT ray-trace, so no ray-tracing target is required.
"-V gi_ddgi_rsm_splat.comp --target-env vulkan1.2 ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_ddgi_rsm_splat_comp.spv"
# irradiance/visibility update read the ray-data via the DDGI master BDA buffer (gi_ddgi_master.glsl) -> need the
"-V gi_dugi_rsm_splat.comp --target-env vulkan1.2 ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_dugi_rsm_splat_comp.spv"
# irradiance/visibility update read the ray-data via the DUGI master BDA buffer (gi_dugi_master.glsl) -> need the
# buffer_reference SPIR-V target even though they don't ray-trace.
"-V gi_ddgi_irradiance_update.comp --target-env vulkan1.2 ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_ddgi_irradiance_update_comp.spv"
"-V gi_ddgi_visibility_update.comp --target-env vulkan1.2 ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_ddgi_visibility_update_comp.spv"
"-V gi_dugi_irradiance_update.comp --target-env vulkan1.2 ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_dugi_irradiance_update_comp.spv"
"-V gi_dugi_visibility_update.comp --target-env vulkan1.2 ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_dugi_visibility_update_comp.spv"
# Border also copies the glossy atlas guard band when glossy is on (binding 5); GLOSSY_DEFINE gates that to match the
# Pascal descriptor layout (which adds binding 5 only when GlobalIlluminationDDGIGlossyRadiance).
"-V gi_ddgi_border_update.comp ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_ddgi_border_update_comp.spv"
# Pascal descriptor layout (which adds binding 5 only when GlobalIlluminationDUGIGlossyRadiance).
"-V gi_dugi_border_update.comp ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_dugi_border_update_comp.spv"
# Glossy prefiltered-radiance update. Always built (like the relocation/classification comps) with the RGBA16F atlas
# format the Pascal side uses; only dispatched when GlobalIlluminationDDGIGlossyRadiance is true (the matching toggle). Reads
# the ray-data via the DDGI master BDA buffer -> needs the buffer_reference SPIR-V target.
"-V gi_ddgi_glossy_update.comp --target-env vulkan1.2 ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} -DGI_DDGI_GLOSSY_RGBA16F -o ${tempPath}/gi_ddgi_glossy_update_comp.spv"
# format the Pascal side uses; only dispatched when GlobalIlluminationDUGIGlossyRadiance is true (the matching toggle). Reads
# the ray-data via the DUGI master BDA buffer -> needs the buffer_reference SPIR-V target.
"-V gi_dugi_glossy_update.comp --target-env vulkan1.2 ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} -DGI_DUGI_GLOSSY_RGBA16F -o ${tempPath}/gi_dugi_glossy_update_comp.spv"
# Probe relocation + classification (RTXGI-style). Traces fixed rays via ray query (includes raytracing.glsl), hence the
# explicit ray-tracing SPIR-V target like the DDGI trace. Built with the same DDGI_PROBE_RELOCATION_DEFINE as the rest;
# only dispatched when GlobalIlluminationDDGIProbeRelocation is true on the Pascal side (the matching toggle).
"-V gi_ddgi_relocation.comp --target-env vulkan1.2 ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_ddgi_relocation_comp.spv"
"-V gi_ddgi_classification.comp --target-env vulkan1.2 ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_ddgi_classification_comp.spv"
# DDGI probe debug visualization (RendererInstance.DebugDDGIProbes). Procedural octahedral sphere per probe, instanced over
# all cascades, coloured by the probe's directional irradiance via ddgiEvaluateIrradiance (same storage mode as the rest).
"-V gi_ddgi_probe_debug.vert --target-env vulkan1.2 -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_ddgi_probe_debug_vert.spv"
"-V gi_ddgi_probe_debug.frag --target-env vulkan1.2 -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_ddgi_probe_debug_frag.spv"
# explicit ray-tracing SPIR-V target like the DUGI trace. Built with the same DUGI_PROBE_RELOCATION_DEFINE as the rest;
# only dispatched when GlobalIlluminationDUGIProbeRelocation is true on the Pascal side (the matching toggle).
"-V gi_dugi_relocation.comp --target-env vulkan1.2 ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_dugi_relocation_comp.spv"
"-V gi_dugi_classification.comp --target-env vulkan1.2 ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} -o ${tempPath}/gi_dugi_classification_comp.spv"
# DUGI probe debug visualization (RendererInstance.DebugDUGIProbes). Procedural octahedral sphere per probe, instanced over
# all cascades, coloured by the probe's directional irradiance via dugiEvaluateIrradiance (same storage mode as the rest).
"-V gi_dugi_probe_debug.vert --target-env vulkan1.2 -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_dugi_probe_debug_vert.spv"
"-V gi_dugi_probe_debug.frag --target-env vulkan1.2 -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_dugi_probe_debug_frag.spv"
# Frustum-culled mesh-shader variant (task -> mesh) of the probe debug overlay; needs the mesh-shader SPIR-V target like the other mesh/task shaders.
"-V gi_ddgi_probe_debug.task --target-env vulkan1.2 -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_ddgi_probe_debug_task.spv"
"-V gi_ddgi_probe_debug.mesh --target-env vulkan1.2 -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_ddgi_probe_debug_mesh.spv"
"-V gi_dugi_probe_debug.task --target-env vulkan1.2 -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_dugi_probe_debug_task.spv"
"-V gi_dugi_probe_debug.mesh --target-env vulkan1.2 -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/gi_dugi_probe_debug_mesh.spv"
"-V voxel_visualization.vert -o ${tempPath}/voxel_visualization_vert.spv"
"-V voxel_visualization.frag -o ${tempPath}/voxel_visualization_frag.spv"
@ -768,10 +768,10 @@ compileshaderarguments=(
"-V planet_water.frag -DUNDERWATER -o ${tempPath}/planet_water_underwater_frag.spv"
"-V planet_water.frag -DUNDERWATER -DUSE_BUFFER_REFERENCE -o ${tempPath}/planet_water_underwater_bufref_frag.spv"
"-V planet_water.frag -DUNDERWATER -DRAYTRACING -o ${tempPath}/planet_water_underwater_raytracing_frag.spv"
# DDGI (RT-based GI) variant of the underwater fullscreen pass — RT only, 'ddgi' segment last; DDGI feeds the shore-foam
# ambient term here (the underwater base color is refracted scene color, already lit). WATER_CAUSTICS gets no DDGI variant:
# DUGI (RT-based GI) variant of the underwater fullscreen pass — RT only, 'dugi' segment last; DUGI feeds the shore-foam
# ambient term here (the underwater base color is refracted scene color, already lit). WATER_CAUSTICS gets no DUGI variant:
# that pass is purely additive refracted-sun light with no diffuse/ambient term for the probe field to feed.
"-V planet_water.frag -DUNDERWATER -DRAYTRACING -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_water_underwater_raytracing_ddgi_frag.spv"
"-V planet_water.frag -DUNDERWATER -DRAYTRACING -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_water_underwater_raytracing_dugi_frag.spv"
"-V planet_water.vert -DWATER_CAUSTICS -o ${tempPath}/planet_water_caustics_vert.spv"
"-V planet_water.vert -DWATER_CAUSTICS -DUSE_BUFFER_REFERENCE -o ${tempPath}/planet_water_caustics_bufref_vert.spv"
@ -800,16 +800,16 @@ compileshaderarguments=(
"-V planet_renderpass.frag -DRAYTRACING -DVELOCITY -o ${tempPath}/planet_renderpass_raytracing_velocity_frag.spv"
"-V planet_renderpass.frag -DRAYTRACING -DWIREFRAME -o ${tempPath}/planet_renderpass_raytracing_wireframe_frag.spv"
"-V planet_renderpass.frag -DRAYTRACING -DWIREFRAME -DVELOCITY -o ${tempPath}/planet_renderpass_raytracing_wireframe_velocity_frag.spv"
# DDGI (RT-based global illumination) variants — only for RT GI modes, hence combined with raytracing_/bufref_; the 'ddgi_'
# Kind segment sits last (matches the Planet.pas naming, Kind:='ddgi_'). Built per DDGI storage mode (DDGI_STORAGE_DEFINE).
"-V planet_renderpass.frag -DRAYTRACING -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_raytracing_ddgi_frag.spv"
"-V planet_renderpass.frag -DRAYTRACING -DVELOCITY -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_raytracing_velocity_ddgi_frag.spv"
"-V planet_renderpass.frag -DRAYTRACING -DWIREFRAME -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_raytracing_wireframe_ddgi_frag.spv"
"-V planet_renderpass.frag -DRAYTRACING -DWIREFRAME -DVELOCITY -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_raytracing_wireframe_velocity_ddgi_frag.spv"
"-V planet_renderpass.frag -DUSE_BUFFER_REFERENCE -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_bufref_ddgi_frag.spv"
"-V planet_renderpass.frag -DUSE_BUFFER_REFERENCE -DVELOCITY -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_bufref_velocity_ddgi_frag.spv"
"-V planet_renderpass.frag -DUSE_BUFFER_REFERENCE -DWIREFRAME -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_bufref_wireframe_ddgi_frag.spv"
"-V planet_renderpass.frag -DUSE_BUFFER_REFERENCE -DWIREFRAME -DVELOCITY -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_bufref_wireframe_velocity_ddgi_frag.spv"
# DUGI (RT-based global illumination) variants — only for RT GI modes, hence combined with raytracing_/bufref_; the 'dugi_'
# Kind segment sits last (matches the Planet.pas naming, Kind:='dugi_'). Built per DUGI storage mode (DUGI_STORAGE_DEFINE).
"-V planet_renderpass.frag -DRAYTRACING -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_raytracing_dugi_frag.spv"
"-V planet_renderpass.frag -DRAYTRACING -DVELOCITY -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_raytracing_velocity_dugi_frag.spv"
"-V planet_renderpass.frag -DRAYTRACING -DWIREFRAME -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_raytracing_wireframe_dugi_frag.spv"
"-V planet_renderpass.frag -DRAYTRACING -DWIREFRAME -DVELOCITY -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_raytracing_wireframe_velocity_dugi_frag.spv"
"-V planet_renderpass.frag -DUSE_BUFFER_REFERENCE -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_bufref_dugi_frag.spv"
"-V planet_renderpass.frag -DUSE_BUFFER_REFERENCE -DVELOCITY -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_bufref_velocity_dugi_frag.spv"
"-V planet_renderpass.frag -DUSE_BUFFER_REFERENCE -DWIREFRAME -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_bufref_wireframe_dugi_frag.spv"
"-V planet_renderpass.frag -DUSE_BUFFER_REFERENCE -DWIREFRAME -DVELOCITY -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_renderpass_bufref_wireframe_velocity_dugi_frag.spv"
"-V planet_renderpass.frag -DREFLECTIVESHADOWMAPOUTPUT -o ${tempPath}/planet_renderpass_rsm_frag.spv"
"-V planet_renderpass.frag -DREFLECTIVESHADOWMAPOUTPUT -DVELOCITY -o ${tempPath}/planet_renderpass_velocity_rsm_frag.spv"
"-V planet_renderpass.frag -DREFLECTIVESHADOWMAPOUTPUT -DWIREFRAME -o ${tempPath}/planet_renderpass_wireframe_rsm_frag.spv"
@ -822,7 +822,7 @@ compileshaderarguments=(
"-V planet_renderpass.frag -DREFLECTIVESHADOWMAPOUTPUT -DRAYTRACING -DVELOCITY -o ${tempPath}/planet_renderpass_raytracing_velocity_rsm_frag.spv"
"-V planet_renderpass.frag -DREFLECTIVESHADOWMAPOUTPUT -DRAYTRACING -DWIREFRAME -o ${tempPath}/planet_renderpass_raytracing_wireframe_rsm_frag.spv"
"-V planet_renderpass.frag -DREFLECTIVESHADOWMAPOUTPUT -DRAYTRACING -DWIREFRAME -DVELOCITY -o ${tempPath}/planet_renderpass_raytracing_wireframe_velocity_rsm_frag.spv"
# Reflective shadow map ALBEDO output variants (non-raytraced DDGI RSM-backend producer): raw albedo, no lighting. Only the
# Reflective shadow map ALBEDO output variants (non-raytraced DUGI RSM-backend producer): raw albedo, no lighting. Only the
# non-velocity / non-wireframe TopLevelKind variants the ReflectiveShadowMap planet pass actually loads.
"-V planet_renderpass.frag -DREFLECTIVESHADOWMAPOUTPUT -DRSMALBEDO -o ${tempPath}/planet_renderpass_rsm_albedo_frag.spv"
"-V planet_renderpass.frag -DREFLECTIVESHADOWMAPOUTPUT -DUSE_BUFFER_REFERENCE -DRSMALBEDO -o ${tempPath}/planet_renderpass_bufref_rsm_albedo_frag.spv"
@ -926,15 +926,15 @@ compileshaderarguments=(
"-V planet_grass.frag -DRAYTRACING -DVELOCITY -o ${tempPath}/planet_grass_raytracing_velocity_frag.spv"
"-V planet_grass.frag -DRAYTRACING -DWIREFRAME -o ${tempPath}/planet_grass_raytracing_wireframe_frag.spv"
"-V planet_grass.frag -DRAYTRACING -DWIREFRAME -DVELOCITY -o ${tempPath}/planet_grass_raytracing_wireframe_velocity_frag.spv"
# DDGI (RT-based GI) variants — only for RT GI modes; 'ddgi_' Kind segment last (matches Planet.pas). Per DDGI storage mode.
"-V planet_grass.frag -DRAYTRACING -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_raytracing_ddgi_frag.spv"
"-V planet_grass.frag -DRAYTRACING -DVELOCITY -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_raytracing_velocity_ddgi_frag.spv"
"-V planet_grass.frag -DRAYTRACING -DWIREFRAME -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_raytracing_wireframe_ddgi_frag.spv"
"-V planet_grass.frag -DRAYTRACING -DWIREFRAME -DVELOCITY -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_raytracing_wireframe_velocity_ddgi_frag.spv"
"-V planet_grass.frag -DUSE_BUFFER_REFERENCE -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_bufref_ddgi_frag.spv"
"-V planet_grass.frag -DUSE_BUFFER_REFERENCE -DVELOCITY -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_bufref_velocity_ddgi_frag.spv"
"-V planet_grass.frag -DUSE_BUFFER_REFERENCE -DWIREFRAME -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_bufref_wireframe_ddgi_frag.spv"
"-V planet_grass.frag -DUSE_BUFFER_REFERENCE -DWIREFRAME -DVELOCITY -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_bufref_wireframe_velocity_ddgi_frag.spv"
# DUGI (RT-based GI) variants — only for RT GI modes; 'dugi_' Kind segment last (matches Planet.pas). Per DUGI storage mode.
"-V planet_grass.frag -DRAYTRACING -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_raytracing_dugi_frag.spv"
"-V planet_grass.frag -DRAYTRACING -DVELOCITY -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_raytracing_velocity_dugi_frag.spv"
"-V planet_grass.frag -DRAYTRACING -DWIREFRAME -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_raytracing_wireframe_dugi_frag.spv"
"-V planet_grass.frag -DRAYTRACING -DWIREFRAME -DVELOCITY -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_raytracing_wireframe_velocity_dugi_frag.spv"
"-V planet_grass.frag -DUSE_BUFFER_REFERENCE -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_bufref_dugi_frag.spv"
"-V planet_grass.frag -DUSE_BUFFER_REFERENCE -DVELOCITY -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_bufref_velocity_dugi_frag.spv"
"-V planet_grass.frag -DUSE_BUFFER_REFERENCE -DWIREFRAME -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_bufref_wireframe_dugi_frag.spv"
"-V planet_grass.frag -DUSE_BUFFER_REFERENCE -DWIREFRAME -DVELOCITY -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_grass_bufref_wireframe_velocity_dugi_frag.spv"
#"-V planet_grass.frag -DREFLECTIVESHADOWMAPOUTPUT -o ${tempPath}/planet_grass_rsm_frag.spv" # unused: BDA always active
#"-V planet_grass.frag -DREFLECTIVESHADOWMAPOUTPUT -DVELOCITY -o ${tempPath}/planet_grass_velocity_rsm_frag.spv" # unused: BDA always active
#"-V planet_grass.frag -DREFLECTIVESHADOWMAPOUTPUT -DWIREFRAME -o ${tempPath}/planet_grass_wireframe_rsm_frag.spv" # unused: BDA always active
@ -947,7 +947,7 @@ compileshaderarguments=(
"-V planet_grass.frag -DREFLECTIVESHADOWMAPOUTPUT -DRAYTRACING -DVELOCITY -o ${tempPath}/planet_grass_raytracing_velocity_rsm_frag.spv"
"-V planet_grass.frag -DREFLECTIVESHADOWMAPOUTPUT -DRAYTRACING -DWIREFRAME -o ${tempPath}/planet_grass_raytracing_wireframe_rsm_frag.spv"
"-V planet_grass.frag -DREFLECTIVESHADOWMAPOUTPUT -DRAYTRACING -DWIREFRAME -DVELOCITY -o ${tempPath}/planet_grass_raytracing_wireframe_velocity_rsm_frag.spv"
# Reflective shadow map ALBEDO output variants (non-raytraced DDGI RSM-backend producer): raw albedo, no lighting.
# Reflective shadow map ALBEDO output variants (non-raytraced DUGI RSM-backend producer): raw albedo, no lighting.
"-V planet_grass.frag -DREFLECTIVESHADOWMAPOUTPUT -DUSE_BUFFER_REFERENCE -DRSMALBEDO -o ${tempPath}/planet_grass_bufref_rsm_albedo_frag.spv"
"-V planet_grass.frag -DREFLECTIVESHADOWMAPOUTPUT -DRAYTRACING -DRSMALBEDO -o ${tempPath}/planet_grass_raytracing_rsm_albedo_frag.spv"
@ -1160,12 +1160,12 @@ addPlanetWaterFragmentVariants(){
addPlanetWaterFragmentVariants "planet_water" "-DTESSELLATION"
# DDGI (RT-based global illumination) variants of the main water surface — only the raytracing path gets GI (DDGI is RT only),
# and only the main surface (UNDERWATER / WATER_CAUSTICS deliberately excluded). The 'ddgi' segment sits last, matching the
# Planet.pas name assembly (planet_water[_raytracing][_msaa|_msaa_fast]_ddgi_frag.spv). Built per DDGI storage mode.
addShader "-V planet_water.frag -DTESSELLATION -DRAYTRACING -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_water_raytracing_ddgi_frag.spv"
addShader "-V planet_water.frag -DTESSELLATION -DRAYTRACING -DMSAA -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_water_raytracing_msaa_ddgi_frag.spv"
addShader "-V planet_water.frag -DTESSELLATION -DRAYTRACING -DMSAA -DMSAA_FAST -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_water_raytracing_msaa_fast_ddgi_frag.spv"
# DUGI (RT-based global illumination) variants of the main water surface — only the raytracing path gets GI (DUGI is RT only),
# and only the main surface (UNDERWATER / WATER_CAUSTICS deliberately excluded). The 'dugi' segment sits last, matching the
# Planet.pas name assembly (planet_water[_raytracing][_msaa|_msaa_fast]_dugi_frag.spv). Built per DUGI storage mode.
addShader "-V planet_water.frag -DTESSELLATION -DRAYTRACING -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_water_raytracing_dugi_frag.spv"
addShader "-V planet_water.frag -DTESSELLATION -DRAYTRACING -DMSAA -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_water_raytracing_msaa_dugi_frag.spv"
addShader "-V planet_water.frag -DTESSELLATION -DRAYTRACING -DMSAA -DMSAA_FAST -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE} -o ${tempPath}/planet_water_raytracing_msaa_fast_dugi_frag.spv"
#############################################
# Mesh shaders #
@ -1279,7 +1279,7 @@ addMeshFragmentShadingGlobalIlluminationVariants(){
# Cascaded voxel cone tracing
addMeshFragmentShadingAntialiasingVariants "${1}_globalillumination_cascaded_voxel_cone_tracing" "$2 -DGLOBAL_ILLUMINATION_CASCADED_VOXEL_CONE_TRACING"
addMeshFragmentShadingAntialiasingVariants "${1}_globalillumination_ddgi" "$2 -DGLOBAL_ILLUMINATION_DDGI ${DDGI_STORAGE_DEFINE} ${DDGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE}"
addMeshFragmentShadingAntialiasingVariants "${1}_globalillumination_dugi" "$2 -DGLOBAL_ILLUMINATION_DUGI ${DUGI_STORAGE_DEFINE} ${DUGI_PROBE_RELOCATION_DEFINE} ${GLOSSY_DEFINE}"
}
@ -1344,7 +1344,7 @@ addMeshFragmentPassTargetVariants(){
# The reflective shadow map stuff
addMeshFragmentReflectiveShadowMapVariants "${1}_rsm" "$2 -DDECALS -DLIGHTS -DSHADOWS -DREFLECTIVESHADOWMAPOUTPUT"
# Reflective shadow map ALBEDO output (for the non-raytraced DDGI RSM-backend producer): outputs the raw albedo and skips
# Reflective shadow map ALBEDO output (for the non-raytraced DUGI RSM-backend producer): outputs the raw albedo and skips
# all lighting (the producer re-lights the albedo itself, so the probe field is not double-lit).
addMeshFragmentReflectiveShadowMapVariants "${1}_rsm_albedo" "$2 -DDECALS -DLIGHTS -DSHADOWS -DREFLECTIVESHADOWMAPOUTPUT -DRSMALBEDO"

View file

@ -1,6 +1,6 @@
#version 460 core
// DDGI octahedral border (guard band) update pass.
// DUGI octahedral border (guard band) update pass.
//
// Each probe tile in an octahedral atlas is stored with a one-texel guard band so that a linear sampler produces correct
// results when its 2x2 footprint straddles the tile edge. This pass fills that guard band from the interior texels using
@ -9,41 +9,41 @@
#extension GL_EXT_nonuniform_qualifier : require
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_buffer_reference : require // for the DDGIMaster type in the shared push-constant block (the field is unused here)
#extension GL_EXT_buffer_reference : require // for the DUGIMaster type in the shared push-constant block (the field is unused here)
/* clang-format off */
// Storage mode (GI_DDGI_STORAGE) and its *_VALUE defines come from global_illumination_ddgi.glsl.
// Storage mode (GI_DUGI_STORAGE) and its *_VALUE defines come from global_illumination_dugi.glsl.
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 1
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
// ddgiData (SSBO) comes via global_illumination_ddgi.glsl; border doesn't use it, but the SSBO's buffer_reference members need the extension above
#include "global_illumination_dugi.glsl"
// dugiData (SSBO) comes via global_illumination_dugi.glsl; border doesn't use it, but the SSBO's buffer_reference members need the extension above
// One workgroup per probe; threads cover the full (bordered) visibility tile, which is the larger of the two tiles.
layout(local_size_x = GI_DDGI_VISIBILITY_OCT_FULL, local_size_y = GI_DDGI_VISIBILITY_OCT_FULL, local_size_z = 1) in;
layout(local_size_x = GI_DUGI_VISIBILITY_OCT_FULL, local_size_y = GI_DUGI_VISIBILITY_OCT_FULL, local_size_z = 1) in;
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_OCT_VALUE
layout(set = 1, binding = 2, rgba16f) uniform image2D uDDGIIrradianceOct;
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_OCT_VALUE
layout(set = 1, binding = 2, rgba16f) uniform image2D uDUGIIrradianceOct;
#endif
layout(set = 1, binding = 3, rg32f) uniform image2D uDDGIVisibilityMoments; // x = mean dist, y = mean dist^2
layout(set = 1, binding = 4, r8) uniform image2D uDDGIVisibilitySky; // x = sky visibility (0..1)
#if defined(GI_DDGI_GLOSSY_RADIANCE)
layout(set = 1, binding = 3, rg32f) uniform image2D uDUGIVisibilityMoments; // x = mean dist, y = mean dist^2
layout(set = 1, binding = 4, r8) uniform image2D uDUGIVisibilitySky; // x = sky visibility (0..1)
#if defined(GI_DUGI_GLOSSY_RADIANCE)
// Glossy prefiltered-radiance atlas; border-copied here too. RGB9E5 copies the raw shared-exponent uint bits (no
// decode needed for a guard-band copy), RGBA16F copies the vec4. Format must match the glossy update + the Pascal image.
#ifdef GI_DDGI_GLOSSY_RGB9E5
layout(set = 1, binding = 5, r32ui) uniform uimage2D uDDGIGlossyRadiance;
#ifdef GI_DUGI_GLOSSY_RGB9E5
layout(set = 1, binding = 5, r32ui) uniform uimage2D uDUGIGlossyRadiance;
#else
layout(set = 1, binding = 5, rgba16f) uniform image2D uDDGIGlossyRadiance;
layout(set = 1, binding = 5, rgba16f) uniform image2D uDUGIGlossyRadiance;
#endif
#endif
// Must match the shared push constant layout the pass pushes for all DDGI stages (only params is used here, but the
// Must match the shared push constant layout the pass pushes for all DUGI stages (only params is used here, but the
// preceding fields must be present so params sits at the correct offset).
#include "gi_ddgi_pushconstants.glsl"
#include "gi_dugi_pushconstants.glsl"
// Tile-local source interior texel for a border texel t in [0, full-1], using the octahedral wrap rule. Returns false
// for interior texels (which are left untouched).
bool ddgiBorderSource(const in ivec2 t, const in int interiorSize, out ivec2 src){
bool dugiBorderSource(const in ivec2 t, const in int interiorSize, out ivec2 src){
int F = interiorSize + 2;
bool isLeftRight = (t.x == 0) || (t.x == (F - 1));
bool isTopBottom = (t.y == 0) || (t.y == (F - 1));
@ -75,46 +75,46 @@ void main(){
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade);
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade);
ivec2 t = ivec2(gl_LocalInvocationID.xy);
// Visibility atlas (full tile = VIS_FULL).
{
ivec2 fullOrigin = ddgiProbeTileOrigin(probeCoord, cascadeIndex, GI_DDGI_VISIBILITY_OCT_FULL) - ivec2(1);
ivec2 fullOrigin = dugiProbeTileOrigin(probeCoord, cascadeIndex, GI_DUGI_VISIBILITY_OCT_FULL) - ivec2(1);
ivec2 src;
if(ddgiBorderSource(t, GI_DDGI_VISIBILITY_OCT_SIZE, src)){
vec2 moments = imageLoad(uDDGIVisibilityMoments, fullOrigin + src).xy;
float sky = imageLoad(uDDGIVisibilitySky, fullOrigin + src).x;
imageStore(uDDGIVisibilityMoments, fullOrigin + t, vec4(moments, 0.0, 0.0));
imageStore(uDDGIVisibilitySky, fullOrigin + t, vec4(sky, 0.0, 0.0, 0.0));
if(dugiBorderSource(t, GI_DUGI_VISIBILITY_OCT_SIZE, src)){
vec2 moments = imageLoad(uDUGIVisibilityMoments, fullOrigin + src).xy;
float sky = imageLoad(uDUGIVisibilitySky, fullOrigin + src).x;
imageStore(uDUGIVisibilityMoments, fullOrigin + t, vec4(moments, 0.0, 0.0));
imageStore(uDUGIVisibilitySky, fullOrigin + t, vec4(sky, 0.0, 0.0, 0.0));
}
}
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_OCT_VALUE
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_OCT_VALUE
// Irradiance atlas (full tile = IRR_FULL); only the threads inside that smaller tile participate.
if(all(lessThan(t, ivec2(GI_DDGI_IRRADIANCE_OCT_FULL)))){
ivec2 fullOrigin = ddgiProbeTileOrigin(probeCoord, cascadeIndex, GI_DDGI_IRRADIANCE_OCT_FULL) - ivec2(1);
if(all(lessThan(t, ivec2(GI_DUGI_IRRADIANCE_OCT_FULL)))){
ivec2 fullOrigin = dugiProbeTileOrigin(probeCoord, cascadeIndex, GI_DUGI_IRRADIANCE_OCT_FULL) - ivec2(1);
ivec2 src;
if(ddgiBorderSource(t, GI_DDGI_IRRADIANCE_OCT_SIZE, src)){
vec3 value = imageLoad(uDDGIIrradianceOct, fullOrigin + src).rgb;
imageStore(uDDGIIrradianceOct, fullOrigin + t, vec4(value, 1.0));
if(dugiBorderSource(t, GI_DUGI_IRRADIANCE_OCT_SIZE, src)){
vec3 value = imageLoad(uDUGIIrradianceOct, fullOrigin + src).rgb;
imageStore(uDUGIIrradianceOct, fullOrigin + t, vec4(value, 1.0));
}
}
#endif
#if defined(GI_DDGI_GLOSSY_RADIANCE)
#if defined(GI_DUGI_GLOSSY_RADIANCE)
// Glossy atlas guard band (full tile = GLOSSY_OCT_FULL); only the threads inside that tile participate.
if(all(lessThan(t, ivec2(GI_DDGI_GLOSSY_OCT_FULL)))){
ivec2 fullOrigin = ddgiProbeTileOrigin(probeCoord, cascadeIndex, GI_DDGI_GLOSSY_OCT_FULL) - ivec2(1);
if(all(lessThan(t, ivec2(GI_DUGI_GLOSSY_OCT_FULL)))){
ivec2 fullOrigin = dugiProbeTileOrigin(probeCoord, cascadeIndex, GI_DUGI_GLOSSY_OCT_FULL) - ivec2(1);
ivec2 src;
if(ddgiBorderSource(t, GI_DDGI_GLOSSY_OCT_SIZE, src)){
#ifdef GI_DDGI_GLOSSY_RGB9E5
uint value = imageLoad(uDDGIGlossyRadiance, fullOrigin + src).x;
imageStore(uDDGIGlossyRadiance, fullOrigin + t, uvec4(value, 0u, 0u, 0u));
if(dugiBorderSource(t, GI_DUGI_GLOSSY_OCT_SIZE, src)){
#ifdef GI_DUGI_GLOSSY_RGB9E5
uint value = imageLoad(uDUGIGlossyRadiance, fullOrigin + src).x;
imageStore(uDUGIGlossyRadiance, fullOrigin + t, uvec4(value, 0u, 0u, 0u));
#else
vec3 value = imageLoad(uDDGIGlossyRadiance, fullOrigin + src).rgb;
imageStore(uDDGIGlossyRadiance, fullOrigin + t, vec4(value, 1.0));
vec3 value = imageLoad(uDUGIGlossyRadiance, fullOrigin + src).rgb;
imageStore(uDUGIGlossyRadiance, fullOrigin + t, vec4(value, 1.0));
#endif
}
}

View file

@ -1,6 +1,6 @@
#version 460 core
// DDGI probe classification pass (RTXGI-style, read-only consumer of the trace's fixed rays — no re-tracing).
// DUGI probe classification pass (RTXGI-style, read-only consumer of the trace's fixed rays — no re-tracing).
//
// One thread per probe. From the fixed rays' signed distances (negative = backface) written by the trace it marks probes
// that mostly see backfaces (i.e. are inside geometry) as INACTIVE, so the shading gather skips them — the robust anti-leak
@ -15,10 +15,10 @@ layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 1
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
// ddgiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_ddgi.glsl
#include "global_illumination_dugi.glsl"
// dugiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_dugi.glsl
#include "gi_ddgi_pushconstants.glsl"
#include "gi_dugi_pushconstants.glsl"
void main(){
uint globalProbeIndex = gl_GlobalInvocationID.x;
@ -29,38 +29,38 @@ void main(){
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade);
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade);
// Hoist the master->sub-pointer derefs once.
DDGIRayDataBuffer rayData = ddgiData.rayData;
DDGIProbeDataBuffer probeDataBuf = ddgiData.probeData;
DUGIRayDataBuffer rayData = dugiData.rayData;
DUGIProbeDataBuffer probeDataBuf = dugiData.probeData;
int backfaceCount = 0;
for(int i = 0; i < GI_DDGI_FIXED_RAYS; i++){
if(ddgiLoadRay(rayData, globalProbeIndex, uint(i), pushConstants.params.w).a < 0.0){
for(int i = 0; i < GI_DUGI_FIXED_RAYS; i++){
if(dugiLoadRay(rayData, globalProbeIndex, uint(i), pushConstants.params.w).a < 0.0){
backfaceCount++;
}
}
float backfaceFraction = float(backfaceCount) / float(GI_DDGI_FIXED_RAYS);
float backfaceFraction = float(backfaceCount) / float(GI_DUGI_FIXED_RAYS);
vec4 probeData = ddgiLoadProbeDataBuffer(probeDataBuf, probeCoord, cascadeIndex);
vec4 probeData = dugiLoadProbeDataBuffer(probeDataBuf, probeCoord, cascadeIndex);
// Classification hysteresis: a deadband around the threshold stops a borderline probe from flip-flopping ACTIVE<->INACTIVE
// every frame (which pops it in/out of the shading gather -> flicker). Only flip when the backface fraction is clearly
// over/under; inside the band keep the previous state. On this slot's first frame / a probe that just toroidally scrolled
// in there is no valid previous state (w is uninitialized garbage / for another cell), so fall back to the plain threshold.
bool noHistory = (pushConstants.blend.z > 0.5) || ddgiProbeScrolledIn(probeCoord, cascadeIndex);
bool noHistory = (pushConstants.blend.z > 0.5) || dugiProbeScrolledIn(probeCoord, cascadeIndex);
float state;
if(backfaceFraction > (GI_DDGI_PROBE_BACKFACE_THRESHOLD + GI_DDGI_PROBE_BACKFACE_HYSTERESIS)){
state = GI_DDGI_PROBE_STATE_INACTIVE; // clearly inside geometry
}else if(backfaceFraction < (GI_DDGI_PROBE_BACKFACE_THRESHOLD - GI_DDGI_PROBE_BACKFACE_HYSTERESIS)){
state = GI_DDGI_PROBE_STATE_ACTIVE; // clearly in open space
if(backfaceFraction > (GI_DUGI_PROBE_BACKFACE_THRESHOLD + GI_DUGI_PROBE_BACKFACE_HYSTERESIS)){
state = GI_DUGI_PROBE_STATE_INACTIVE; // clearly inside geometry
}else if(backfaceFraction < (GI_DUGI_PROBE_BACKFACE_THRESHOLD - GI_DUGI_PROBE_BACKFACE_HYSTERESIS)){
state = GI_DUGI_PROBE_STATE_ACTIVE; // clearly in open space
}else{
state = noHistory ? ((backfaceFraction > GI_DDGI_PROBE_BACKFACE_THRESHOLD) ? GI_DDGI_PROBE_STATE_INACTIVE : GI_DDGI_PROBE_STATE_ACTIVE)
state = noHistory ? ((backfaceFraction > GI_DUGI_PROBE_BACKFACE_THRESHOLD) ? GI_DUGI_PROBE_STATE_INACTIVE : GI_DUGI_PROBE_STATE_ACTIVE)
: probeData.w; // in the deadband: hold the previous classification
}
ddgiStoreProbeDataBuffer(probeDataBuf, probeCoord, cascadeIndex, vec4(probeData.xyz, state)); // preserve offset (xyz), owned by the relocation pass
dugiStoreProbeDataBuffer(probeDataBuf, probeCoord, cascadeIndex, vec4(probeData.xyz, state)); // preserve offset (xyz), owned by the relocation pass
}

View file

@ -1,12 +1,12 @@
#ifndef GI_DDGI_DATA_GLSL
#define GI_DDGI_DATA_GLSL
#ifndef GI_DUGI_DATA_GLSL
#define GI_DUGI_DATA_GLSL
// =====================================================================================================================
// DDGI data block — the unified per-in-flight DDGI field data.
// DUGI data block — the unified per-in-flight DUGI field data.
//
// ONE std430 readonly SSBO `ddgiData`, bound at the DDGI set's binding 0 (compute set 1 / mesh set 2 / planet set 4 — via
// ONE std430 readonly SSBO `dugiData`, bound at the DUGI set's binding 0 (compute set 1 / mesh set 2 / planet set 4 — via
// GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET/BINDING), shared identically by the compute passes and the fragment consumers. It
// holds the cascade globals (the former uboGlobalIlluminationDDGIData) followed by the device-address pointers to the
// holds the cascade globals (the former uboGlobalIlluminationDUGIData) followed by the device-address pointers to the
// point-access sub-buffers (ray-data, probe-data, SH-irradiance, age). Those sub-buffers stay buffer_reference (their
// addresses live in this block); the bilinear-sampled atlases (visibility moments/sky, OCT irradiance) remain
// descriptor-bound sampled images and are NOT part of this block.
@ -15,96 +15,96 @@
// pointers reference ARE written, through the (non-readonly) buffer_reference handles read out of here (callers launder
// the handle into a local first, so the readonly memory qualifier is not dropped on the accessor argument).
//
// Requires GL_EXT_buffer_reference and the GI_DDGI_* dimension constants — it is included from global_illumination_ddgi.glsl
// AFTER those constants (and only when GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET is defined, i.e. a DDGI shader with the
// Requires GL_EXT_buffer_reference and the GI_DUGI_* dimension constants — it is included from global_illumination_dugi.glsl
// AFTER those constants (and only when GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET is defined, i.e. a DUGI shader with the
// extension enabled), so it is never pulled into a constants-only includer.
// =====================================================================================================================
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DDGIRayDataBuffer { vec4 data[]; }; // rgb = shaded radiance, a = distance (signed for fixed rays); idx = globalProbe*raysPerProbe + ray
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DDGIProbeDataBuffer { vec4 data[]; }; // xyz = relocation offset, w = state; idx = physical probe slot + cascade*probesPerCascade
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DUGIRayDataBuffer { vec4 data[]; }; // rgb = shaded radiance, a = distance (signed for fixed rays); idx = globalProbe*raysPerProbe + ray
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DUGIProbeDataBuffer { vec4 data[]; }; // xyz = relocation offset, w = state; idx = physical probe slot + cascade*probesPerCascade
// SH-irradiance: ONE contiguous element (DDGI_SH_IMAGE_COUNT packed vec4) per probe, so a probe's whole SH is a single linear
// SH-irradiance: ONE contiguous element (DUGI_SH_IMAGE_COUNT packed vec4) per probe, so a probe's whole SH is a single linear
// load/store (better coalescing than indexing individual vec4 by probe*COUNT+i). Indexed by the probe linear index.
#ifdef DDGI_SH_IMAGE_COUNT
struct DDGISHProbe { vec4 c[DDGI_SH_IMAGE_COUNT]; };
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DDGIIrradianceSHBuffer { DDGISHProbe probes[]; };
#ifdef DUGI_SH_IMAGE_COUNT
struct DUGISHProbe { vec4 c[DUGI_SH_IMAGE_COUNT]; };
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DUGIIrradianceSHBuffer { DUGISHProbe probes[]; };
#else
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DDGIIrradianceSHBuffer { vec4 data[]; }; // OCT mode: unused placeholder (irradiance is a sampled image there)
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DUGIIrradianceSHBuffer { vec4 data[]; }; // OCT mode: unused placeholder (irradiance is a sampled image there)
#endif
// Per-probe convergence age (frames since (re)init, capped at the warmup length): a plain uint count, point-access only (the
// visibility update writes it, the irradiance update reads it for the warmup hysteresis ramp; never sampled by shading).
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DDGIAgeBuffer { uint age[]; };
layout(buffer_reference, std430, buffer_reference_align = 16) buffer DUGIAgeBuffer { uint age[]; };
#ifdef GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET
// The unified data block: cascade globals (all vec4/ivec4 -> std140 == std430) followed by the BDA sub-buffer pointers. Bound
// to the per-in-flight data buffer (CPU writes the globals each frame; the sub-pointers are written once). Layout must match
// TGlobalIlluminationDDGIDataBufferData on the Pascal side.
layout(set = GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET, binding = GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING, std430) readonly buffer DDGIData {
vec4 ddgiCascadeAABBMin[GI_DDGI_CASCADES]; // xyz = world space min corner of the probe lattice
vec4 ddgiCascadeAABBMax[GI_DDGI_CASCADES]; // xyz = world space max corner of the probe lattice
vec4 ddgiCascadeAABBScale[GI_DDGI_CASCADES]; // xyz = 1.0 / (max - min)
vec4 ddgiCascadeCellSizes[GI_DDGI_CASCADES]; // xyz = world space spacing between adjacent probes, w = max probe ray distance
vec4 ddgiCascadeAABBCenter[GI_DDGI_CASCADES]; // xyz = AABB center (for cascade fade computation)
vec4 ddgiCascadeAABBFadeStart[GI_DDGI_CASCADES]; // xyz = distance from center where this cascade begins to fade out
vec4 ddgiCascadeAABBFadeEnd[GI_DDGI_CASCADES]; // xyz = distance from center where this cascade is fully faded out
ivec4 ddgiCascadeProbeScroll[GI_DDGI_CASCADES]; // xyz = base cell offset floor(AABBMin/cellSize) (this frame), w = scrolling enabled (1) / disabled (0)
ivec4 ddgiCascadeProbeScrollPrev[GI_DDGI_CASCADES]; // xyz = base cell offset of the previous update of this in-flight slot (for re-initializing scrolled-in probes)
DDGIRayDataBuffer rayData; // -> ray-data sub-buffer
DDGIProbeDataBuffer probeData; // -> probe-data sub-buffer (null when relocation off)
DDGIIrradianceSHBuffer irradianceSH; // -> SH-irradiance sub-buffer (null in octahedral storage mode)
DDGIAgeBuffer age; // -> per-probe convergence age sub-buffer
} ddgiData;
// TGlobalIlluminationDUGIDataBufferData on the Pascal side.
layout(set = GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET, binding = GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING, std430) readonly buffer DUGIData {
vec4 dugiCascadeAABBMin[GI_DUGI_CASCADES]; // xyz = world space min corner of the probe lattice
vec4 dugiCascadeAABBMax[GI_DUGI_CASCADES]; // xyz = world space max corner of the probe lattice
vec4 dugiCascadeAABBScale[GI_DUGI_CASCADES]; // xyz = 1.0 / (max - min)
vec4 dugiCascadeCellSizes[GI_DUGI_CASCADES]; // xyz = world space spacing between adjacent probes, w = max probe ray distance
vec4 dugiCascadeAABBCenter[GI_DUGI_CASCADES]; // xyz = AABB center (for cascade fade computation)
vec4 dugiCascadeAABBFadeStart[GI_DUGI_CASCADES]; // xyz = distance from center where this cascade begins to fade out
vec4 dugiCascadeAABBFadeEnd[GI_DUGI_CASCADES]; // xyz = distance from center where this cascade is fully faded out
ivec4 dugiCascadeProbeScroll[GI_DUGI_CASCADES]; // xyz = base cell offset floor(AABBMin/cellSize) (this frame), w = scrolling enabled (1) / disabled (0)
ivec4 dugiCascadeProbeScrollPrev[GI_DUGI_CASCADES]; // xyz = base cell offset of the previous update of this in-flight slot (for re-initializing scrolled-in probes)
DUGIRayDataBuffer rayData; // -> ray-data sub-buffer
DUGIProbeDataBuffer probeData; // -> probe-data sub-buffer (null when relocation off)
DUGIIrradianceSHBuffer irradianceSH; // -> SH-irradiance sub-buffer (null in octahedral storage mode)
DUGIAgeBuffer age; // -> per-probe convergence age sub-buffer
} dugiData;
#endif
// Ray-data linear index: rows of raysPerProbe per probe (matches the old image layout image[y=globalProbe][x=ray]).
uint ddgiRayDataIndex(const in uint globalProbeIndex, const in uint rayIndex, const in uint raysPerProbe){
uint dugiRayDataIndex(const in uint globalProbeIndex, const in uint rayIndex, const in uint raysPerProbe){
return (globalProbeIndex * raysPerProbe) + rayIndex;
}
// The accessors take the sub-buffer reference directly (not ddgiData) so the caller can hoist the single deref into a local
// once per invocation (which also launders the readonly memory qualifier off ddgiData's member), instead of re-reading it.
vec4 ddgiLoadRay(const in DDGIRayDataBuffer aRayData, const in uint globalProbeIndex, const in uint rayIndex, const in uint raysPerProbe){
return aRayData.data[ddgiRayDataIndex(globalProbeIndex, rayIndex, raysPerProbe)];
// The accessors take the sub-buffer reference directly (not dugiData) so the caller can hoist the single deref into a local
// once per invocation (which also launders the readonly memory qualifier off dugiData's member), instead of re-reading it.
vec4 dugiLoadRay(const in DUGIRayDataBuffer aRayData, const in uint globalProbeIndex, const in uint rayIndex, const in uint raysPerProbe){
return aRayData.data[dugiRayDataIndex(globalProbeIndex, rayIndex, raysPerProbe)];
}
void ddgiStoreRay(const in DDGIRayDataBuffer aRayData, const in uint globalProbeIndex, const in uint rayIndex, const in uint raysPerProbe, const in vec4 aValue){
aRayData.data[ddgiRayDataIndex(globalProbeIndex, rayIndex, raysPerProbe)] = aValue;
void dugiStoreRay(const in DUGIRayDataBuffer aRayData, const in uint globalProbeIndex, const in uint rayIndex, const in uint raysPerProbe, const in vec4 aValue){
aRayData.data[dugiRayDataIndex(globalProbeIndex, rayIndex, raysPerProbe)] = aValue;
}
// Probe-data linear index (one vec4 per physical probe slot): matches the old 3D image addressing
// ivec3(probeCoord.xy, probeCoord.z + cascade*GI_DDGI_PROBES_Z) flattened row-major (x fastest).
uint ddgiProbeDataIndex(const in ivec3 probeCoord, const in int cascadeIndex){
return uint(probeCoord.x + (probeCoord.y * GI_DDGI_PROBES_X) + ((probeCoord.z + (cascadeIndex * GI_DDGI_PROBES_Z)) * GI_DDGI_PROBES_X * GI_DDGI_PROBES_Y));
// ivec3(probeCoord.xy, probeCoord.z + cascade*GI_DUGI_PROBES_Z) flattened row-major (x fastest).
uint dugiProbeDataIndex(const in ivec3 probeCoord, const in int cascadeIndex){
return uint(probeCoord.x + (probeCoord.y * GI_DUGI_PROBES_X) + ((probeCoord.z + (cascadeIndex * GI_DUGI_PROBES_Z)) * GI_DUGI_PROBES_X * GI_DUGI_PROBES_Y));
}
vec4 ddgiLoadProbeDataBuffer(const in DDGIProbeDataBuffer aProbeData, const in ivec3 probeCoord, const in int cascadeIndex){
return aProbeData.data[ddgiProbeDataIndex(probeCoord, cascadeIndex)];
vec4 dugiLoadProbeDataBuffer(const in DUGIProbeDataBuffer aProbeData, const in ivec3 probeCoord, const in int cascadeIndex){
return aProbeData.data[dugiProbeDataIndex(probeCoord, cascadeIndex)];
}
void ddgiStoreProbeDataBuffer(const in DDGIProbeDataBuffer aProbeData, const in ivec3 probeCoord, const in int cascadeIndex, const in vec4 aValue){
aProbeData.data[ddgiProbeDataIndex(probeCoord, cascadeIndex)] = aValue;
void dugiStoreProbeDataBuffer(const in DUGIProbeDataBuffer aProbeData, const in ivec3 probeCoord, const in int cascadeIndex, const in vec4 aValue){
aProbeData.data[dugiProbeDataIndex(probeCoord, cascadeIndex)] = aValue;
}
#ifdef DDGI_SH_IMAGE_COUNT
// Whole-probe SH load/store: one contiguous DDGISHProbe element per probe (probe linear index matches ddgiProbeDataIndex).
#ifdef DUGI_SH_IMAGE_COUNT
// Whole-probe SH load/store: one contiguous DUGISHProbe element per probe (probe linear index matches dugiProbeDataIndex).
// Reading/writing the probe as a unit lets the compiler emit wide/coalesced loads instead of COUNT separate indexed reads.
DDGISHProbe ddgiLoadSHProbe(const in DDGIIrradianceSHBuffer aSH, const in ivec3 probeCoord, const in int cascadeIndex){
return aSH.probes[ddgiProbeDataIndex(probeCoord, cascadeIndex)];
DUGISHProbe dugiLoadSHProbe(const in DUGIIrradianceSHBuffer aSH, const in ivec3 probeCoord, const in int cascadeIndex){
return aSH.probes[dugiProbeDataIndex(probeCoord, cascadeIndex)];
}
void ddgiStoreSHProbe(const in DDGIIrradianceSHBuffer aSH, const in ivec3 probeCoord, const in int cascadeIndex, const in DDGISHProbe aProbe){
aSH.probes[ddgiProbeDataIndex(probeCoord, cascadeIndex)] = aProbe;
void dugiStoreSHProbe(const in DUGIIrradianceSHBuffer aSH, const in ivec3 probeCoord, const in int cascadeIndex, const in DUGISHProbe aProbe){
aSH.probes[dugiProbeDataIndex(probeCoord, cascadeIndex)] = aProbe;
}
#endif
// Per-probe age (probe linear index matches ddgiProbeDataIndex).
uint ddgiLoadAge(const in DDGIAgeBuffer aAge, const in ivec3 probeCoord, const in int cascadeIndex){
return aAge.age[ddgiProbeDataIndex(probeCoord, cascadeIndex)];
// Per-probe age (probe linear index matches dugiProbeDataIndex).
uint dugiLoadAge(const in DUGIAgeBuffer aAge, const in ivec3 probeCoord, const in int cascadeIndex){
return aAge.age[dugiProbeDataIndex(probeCoord, cascadeIndex)];
}
void ddgiStoreAge(const in DDGIAgeBuffer aAge, const in ivec3 probeCoord, const in int cascadeIndex, const in uint aValue){
aAge.age[ddgiProbeDataIndex(probeCoord, cascadeIndex)] = aValue;
void dugiStoreAge(const in DUGIAgeBuffer aAge, const in ivec3 probeCoord, const in int cascadeIndex, const in uint aValue){
aAge.age[dugiProbeDataIndex(probeCoord, cascadeIndex)] = aValue;
}
#endif // GI_DDGI_DATA_GLSL
#endif // GI_DUGI_DATA_GLSL

View file

@ -1,12 +1,12 @@
#version 460 core
// DDGI glossy prefiltered-radiance integration pass.
// DUGI glossy prefiltered-radiance integration pass.
//
// Integrates the per-ray radiance from gi_ddgi_trace.comp into each probe's octahedral GLOSSY atlas: like the irradiance
// Integrates the per-ray radiance from gi_dugi_trace.comp into each probe's octahedral GLOSSY atlas: like the irradiance
// octahedral update, but with a SHARP directional kernel (pow(dot, n)) and NO cosine convolution, so the stored value is
// prefiltered *radiance* usable for glossy reflections (sampled along the reflection vector at shading time). One workgroup
// per probe, one thread per interior glossy texel; reuses the visibility-update shared-ray-cache pattern. Independent of the
// irradiance storage mode (SH or OCT). Only built/dispatched when GI_DDGI_GLOSSY_RADIANCE is defined.
// irradiance storage mode (SH or OCT). Only built/dispatched when GI_DUGI_GLOSSY_RADIANCE is defined.
//
// Single fixed prefilter level (v1). The layout is mip-ready: a future mip chain stores blurrier levels below this one and
// the shading sample picks a level from roughness; nothing here changes except writing the level-0 (sharpest) tile.
@ -19,32 +19,32 @@
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 1
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
#include "gi_ddgi_raydata.glsl"
// ddgiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_ddgi.glsl
#include "global_illumination_dugi.glsl"
#include "gi_dugi_raydata.glsl"
// dugiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_dugi.glsl
layout(local_size_x = GI_DDGI_GLOSSY_OCT_SIZE, local_size_y = GI_DDGI_GLOSSY_OCT_SIZE, local_size_z = 1) in;
layout(local_size_x = GI_DUGI_GLOSSY_OCT_SIZE, local_size_y = GI_DUGI_GLOSSY_OCT_SIZE, local_size_z = 1) in;
// Glossy atlas storage. RGB9E5 (default) is written through an R32_UINT alias view (encode/decode the shared-exponent bits);
// the RGBA16F fallback writes vec4 directly. Whichever is built MUST match the Pascal image format + the sampling include.
#ifdef GI_DDGI_GLOSSY_RGB9E5
#ifdef GI_DUGI_GLOSSY_RGB9E5
#include "rgb9e5.glsl"
layout(set = 1, binding = 5, r32ui) uniform uimage2D uDDGIGlossyRadiance;
vec3 ddgiGlossyLoad(const in ivec2 p){ return decodeRGB9E5(imageLoad(uDDGIGlossyRadiance, p).x); }
void ddgiGlossyStore(const in ivec2 p, const in vec3 c){ imageStore(uDDGIGlossyRadiance, p, uvec4(encodeRGB9E5(max(vec3(0.0), c)), 0u, 0u, 0u)); }
layout(set = 1, binding = 5, r32ui) uniform uimage2D uDUGIGlossyRadiance;
vec3 dugiGlossyLoad(const in ivec2 p){ return decodeRGB9E5(imageLoad(uDUGIGlossyRadiance, p).x); }
void dugiGlossyStore(const in ivec2 p, const in vec3 c){ imageStore(uDUGIGlossyRadiance, p, uvec4(encodeRGB9E5(max(vec3(0.0), c)), 0u, 0u, 0u)); }
#else
layout(set = 1, binding = 5, rgba16f) uniform image2D uDDGIGlossyRadiance;
vec3 ddgiGlossyLoad(const in ivec2 p){ return imageLoad(uDDGIGlossyRadiance, p).rgb; }
void ddgiGlossyStore(const in ivec2 p, const in vec3 c){ imageStore(uDDGIGlossyRadiance, p, vec4(max(vec3(0.0), c), 1.0)); }
layout(set = 1, binding = 5, rgba16f) uniform image2D uDUGIGlossyRadiance;
vec3 dugiGlossyLoad(const in ivec2 p){ return imageLoad(uDUGIGlossyRadiance, p).rgb; }
void dugiGlossyStore(const in ivec2 p, const in vec3 c){ imageStore(uDUGIGlossyRadiance, p, vec4(max(vec3(0.0), c), 1.0)); }
#endif
#include "gi_ddgi_pushconstants.glsl"
#include "gi_dugi_pushconstants.glsl"
// Shared cache of this probe's traced rays for the whole workgroup (one workgroup == one probe), loaded cooperatively once:
// direction (recomputed trig) + radiance. Then every glossy texel integrates from LDS instead of re-reading the ray-data
// buffer + recomputing each direction per texel (the RTXGI ProbeBlendingCS optimization, as in the visibility update).
shared vec3 sDDGIGlossyRayDirection[GI_DDGI_RAYS_PER_PROBE];
shared vec3 sDDGIGlossyRayRadiance[GI_DDGI_RAYS_PER_PROBE];
shared vec3 sDUGIGlossyRayDirection[GI_DUGI_RAYS_PER_PROBE];
shared vec3 sDUGIGlossyRayRadiance[GI_DUGI_RAYS_PER_PROBE];
void main(){
uint globalProbeIndex = gl_WorkGroupID.x;
@ -55,10 +55,10 @@ void main(){
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade);
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade);
ivec2 localTexel = ivec2(gl_LocalInvocationID.xy);
vec2 uv = (vec2(localTexel) + vec2(0.5)) / float(GI_DDGI_GLOSSY_OCT_SIZE);
vec2 uv = (vec2(localTexel) + vec2(0.5)) / float(GI_DUGI_GLOSSY_OCT_SIZE);
vec3 texelDirection = octDecode(fma(uv, vec2(2.0), vec2(-1.0)));
mat3 randomRotation = mat3(pushConstants.randomRotation0.xyz, pushConstants.randomRotation1.xyz, pushConstants.randomRotation2.xyz);
@ -66,37 +66,37 @@ void main(){
// Cooperatively load this probe's rays (direction + radiance) into shared memory ONCE for the whole workgroup; the
// GLOSSY_OCT_SIZE^2 threads cover the <=128 rays, then barrier so each texel integrates from LDS.
DDGIRayDataBuffer rayData = ddgiData.rayData; // hoist the master->sub-pointer deref out of the load
uint localIndex = (uint(gl_LocalInvocationID.y) * GI_DDGI_GLOSSY_OCT_SIZE) + uint(gl_LocalInvocationID.x);
for(uint r = GI_DDGI_RAY_START + localIndex; r < raysPerProbe; r += uint(GI_DDGI_GLOSSY_OCT_SIZE * GI_DDGI_GLOSSY_OCT_SIZE)){
sDDGIGlossyRayDirection[r] = ddgiTraceRayDirection(r, randomRotation);
sDDGIGlossyRayRadiance[r] = ddgiLoadRay(rayData, globalProbeIndex, r, raysPerProbe).rgb;
DUGIRayDataBuffer rayData = dugiData.rayData; // hoist the master->sub-pointer deref out of the load
uint localIndex = (uint(gl_LocalInvocationID.y) * GI_DUGI_GLOSSY_OCT_SIZE) + uint(gl_LocalInvocationID.x);
for(uint r = GI_DUGI_RAY_START + localIndex; r < raysPerProbe; r += uint(GI_DUGI_GLOSSY_OCT_SIZE * GI_DUGI_GLOSSY_OCT_SIZE)){
sDUGIGlossyRayDirection[r] = dugiTraceRayDirection(r, randomRotation);
sDUGIGlossyRayRadiance[r] = dugiLoadRay(rayData, globalProbeIndex, r, raysPerProbe).rgb;
}
barrier();
// Sharp directional prefilter: weighted average of incoming radiance in a Phong-like lobe around this texel direction.
const float sharpness = GI_DDGI_GLOSSY_SHARPNESS;
const float sharpness = GI_DUGI_GLOSSY_SHARPNESS;
vec3 sum = vec3(0.0);
float sumWeight = 0.0;
for(uint r = GI_DDGI_RAY_START; r < raysPerProbe; r++){
float weight = pow(max(0.0, dot(texelDirection, sDDGIGlossyRayDirection[r])), sharpness);
for(uint r = GI_DUGI_RAY_START; r < raysPerProbe; r++){
float weight = pow(max(0.0, dot(texelDirection, sDUGIGlossyRayDirection[r])), sharpness);
if(weight > 0.0){
sum += sDDGIGlossyRayRadiance[r] * weight;
sum += sDUGIGlossyRayRadiance[r] * weight;
sumWeight += weight;
}
}
vec3 glossy = (sumWeight > 1e-6) ? (sum / sumWeight) : vec3(0.0);
ivec2 atlasTexel = ddgiProbeTileOrigin(probeCoord, cascadeIndex, GI_DDGI_GLOSSY_OCT_FULL) + localTexel;
vec3 previous = ddgiGlossyLoad(atlasTexel);
ivec2 atlasTexel = dugiProbeTileOrigin(probeCoord, cascadeIndex, GI_DUGI_GLOSSY_OCT_FULL) + localTexel;
vec3 previous = dugiGlossyLoad(atlasTexel);
// Temporal hysteresis blend, with the same first-frame / toroidal-scroll-in / per-probe age warmup logic as the irradiance
// and visibility updates. The age is owned (written) by the visibility update; this pass only reads it (one frame behind,
// irrelevant for a warmup ramp), exactly like the irradiance update — so glossy must run before visibility in the chain.
bool firstFrame = (pushConstants.blend.z > 0.5) || ddgiProbeScrolledIn(probeCoord, cascadeIndex);
DDGIAgeBuffer ageBuffer = ddgiData.age;
float hysteresis = ddgiWarmupHysteresis(float(ddgiLoadAge(ageBuffer, probeCoord, cascadeIndex)));
bool firstFrame = (pushConstants.blend.z > 0.5) || dugiProbeScrolledIn(probeCoord, cascadeIndex);
DUGIAgeBuffer ageBuffer = dugiData.age;
float hysteresis = dugiWarmupHysteresis(float(dugiLoadAge(ageBuffer, probeCoord, cascadeIndex)));
// NaN-safe discard: a zero blend weight is not enough, because mix(cur, NaN, 0.0) = cur + NaN*0.0 = NaN; select the fresh value.
bool discardPrevious = firstFrame || any(isnan(previous)) || any(isinf(previous));
ddgiGlossyStore(atlasTexel, discardPrevious ? glossy : mix(glossy, previous, hysteresis));
dugiGlossyStore(atlasTexel, discardPrevious ? glossy : mix(glossy, previous, hysteresis));
}

View file

@ -1,11 +1,11 @@
#version 460 core
// DDGI irradiance integration pass.
// DUGI irradiance integration pass.
//
// Integrates the per-ray radiance produced by gi_ddgi_trace.comp into each probe's irradiance representation, blended
// Integrates the per-ray radiance produced by gi_dugi_trace.comp into each probe's irradiance representation, blended
// temporally against the previous frame's data (hysteresis). Two storage variants, selected at compile time:
// GI_DDGI_STORAGE_SH : irradiance as L1 RGB spherical harmonics in three RGBA16F 3D images (one thread per probe).
// GI_DDGI_STORAGE_OCT : irradiance as an octahedral tile in one RGBA16F 2D atlas (one thread per interior texel).
// GI_DUGI_STORAGE_SH : irradiance as L1 RGB spherical harmonics in three RGBA16F 3D images (one thread per probe).
// GI_DUGI_STORAGE_OCT : irradiance as an octahedral tile in one RGBA16F 2D atlas (one thread per interior texel).
#extension GL_EXT_nonuniform_qualifier : require
#extension GL_GOOGLE_include_directive : require
@ -13,50 +13,50 @@
/* clang-format off */
// Storage mode (GI_DDGI_STORAGE), GI_DDGI_STORAGE_IS_SH and the DDGI_SH_* type aliases come from this include.
// Storage mode (GI_DUGI_STORAGE), GI_DUGI_STORAGE_IS_SH and the DUGI_SH_* type aliases come from this include.
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 1
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
#include "gi_ddgi_raydata.glsl"
// ddgiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_ddgi.glsl
#include "global_illumination_dugi.glsl"
#include "gi_dugi_raydata.glsl"
// dugiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_dugi.glsl
#if GI_DDGI_STORAGE_IS_SH
#if GI_DUGI_STORAGE_IS_SH
#include "sphericalharmonics.glsl"
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; // one thread per probe
#else
layout(local_size_x = GI_DDGI_IRRADIANCE_OCT_SIZE, local_size_y = GI_DDGI_IRRADIANCE_OCT_SIZE, local_size_z = 1) in; // one thread per interior texel
layout(local_size_x = GI_DUGI_IRRADIANCE_OCT_SIZE, local_size_y = GI_DUGI_IRRADIANCE_OCT_SIZE, local_size_z = 1) in; // one thread per interior texel
#endif
// Per-ray radiance + hit distance from the trace pass.
#if GI_DDGI_STORAGE_IS_SH
// RGB SH now lives in the master's irradianceSH BDA buffer (DDGI_SH_IMAGE_COUNT packed vec4 per probe); no image binding.
#if GI_DUGI_STORAGE_IS_SH
// RGB SH now lives in the master's irradianceSH BDA buffer (DUGI_SH_IMAGE_COUNT packed vec4 per probe); no image binding.
#else
layout(set = 1, binding = 2, rgba16f) uniform image2D uDDGIIrradianceOct;
layout(set = 1, binding = 2, rgba16f) uniform image2D uDUGIIrradianceOct;
#endif
#include "gi_ddgi_pushconstants.glsl"
#include "gi_dugi_pushconstants.glsl"
// Per-probe convergence-warmup hysteresis ramp: reads the per-probe age the visibility update wrote into its own BDA buffer.
// The probe-update chain runs irradiance before visibility, so this reads the previous frame's age (one frame behind —
// irrelevant for a warmup ramp); the per-stage compute barrier serializes that read against this frame's visibility write.
float ddgiProbeAgeHysteresis(const in ivec3 probeCoord, const in int cascadeIndex){
DDGIAgeBuffer ageBuffer = ddgiData.age;
return ddgiWarmupHysteresis(float(ddgiLoadAge(ageBuffer, probeCoord, cascadeIndex)));
float dugiProbeAgeHysteresis(const in ivec3 probeCoord, const in int cascadeIndex){
DUGIAgeBuffer ageBuffer = dugiData.age;
return dugiWarmupHysteresis(float(dugiLoadAge(ageBuffer, probeCoord, cascadeIndex)));
}
const float GI_DDGI_FOUR_PI = 12.566370614359172;
const float GI_DUGI_FOUR_PI = 12.566370614359172;
#if GI_DDGI_STORAGE_IS_SH
#if GI_DUGI_STORAGE_IS_SH
// Packing of the RGB SH coefficients into the DDGI_SH_IMAGE_COUNT packed vec4 of the master's irradianceSH BDA buffer.
// Packing of the RGB SH coefficients into the DUGI_SH_IMAGE_COUNT packed vec4 of the master's irradianceSH BDA buffer.
// L1 (4 coeffs) uses 3 vec4, L2 (9 coeffs) uses 7; the first three are identical between the two, the L2 ones append c4..c8
// in the same interleaved pattern. (Same packing as the old SH image set, just sourced from / written to the buffer.)
DDGI_SH_TYPE ddgiLoadSH(const in ivec3 probeCoord, const in int cascadeIndex){
DDGIIrradianceSHBuffer shBuf = ddgiData.irradianceSH; // hoist the master->sub-pointer deref once
DDGISHProbe p = ddgiLoadSHProbe(shBuf, probeCoord, cascadeIndex); // one contiguous load of the whole probe
DUGI_SH_TYPE dugiLoadSH(const in ivec3 probeCoord, const in int cascadeIndex){
DUGIIrradianceSHBuffer shBuf = dugiData.irradianceSH; // hoist the master->sub-pointer deref once
DUGISHProbe p = dugiLoadSHProbe(shBuf, probeCoord, cascadeIndex); // one contiguous load of the whole probe
vec4 a = p.c[0]; vec4 b = p.c[1]; vec4 c = p.c[2];
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_L2_VALUE
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_L2_VALUE
vec4 d = p.c[3]; vec4 e = p.c[4]; vec4 f = p.c[5]; vec4 g = p.c[6];
return SHC3CoefficientsL2Create(vec3(a.x, a.y, a.z), vec3(a.w, b.x, b.y), vec3(b.z, b.w, c.x), vec3(c.y, c.z, c.w),
vec3(d.x, d.y, d.z), vec3(d.w, e.x, e.y), vec3(e.z, e.w, f.x), vec3(f.y, f.z, f.w),
@ -66,19 +66,19 @@ DDGI_SH_TYPE ddgiLoadSH(const in ivec3 probeCoord, const in int cascadeIndex){
#endif
}
void ddgiStoreSH(const in ivec3 probeCoord, const in int cascadeIndex, const in DDGI_SH_TYPE sh){
DDGIIrradianceSHBuffer shBuf = ddgiData.irradianceSH; // hoist the master->sub-pointer deref once
DDGISHProbe p;
void dugiStoreSH(const in ivec3 probeCoord, const in int cascadeIndex, const in DUGI_SH_TYPE sh){
DUGIIrradianceSHBuffer shBuf = dugiData.irradianceSH; // hoist the master->sub-pointer deref once
DUGISHProbe p;
p.c[0] = vec4(sh.coefficients[0].xyz, sh.coefficients[1].x);
p.c[1] = vec4(sh.coefficients[1].yz, sh.coefficients[2].xy);
p.c[2] = vec4(sh.coefficients[2].z, sh.coefficients[3].xyz);
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_L2_VALUE
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_L2_VALUE
p.c[3] = vec4(sh.coefficients[4].xyz, sh.coefficients[5].x);
p.c[4] = vec4(sh.coefficients[5].yz, sh.coefficients[6].xy);
p.c[5] = vec4(sh.coefficients[6].z, sh.coefficients[7].xyz);
p.c[6] = vec4(sh.coefficients[8].xyz, 0.0);
#endif
ddgiStoreSHProbe(shBuf, probeCoord, cascadeIndex, p); // one contiguous store of the whole probe
dugiStoreSHProbe(shBuf, probeCoord, cascadeIndex, p); // one contiguous store of the whole probe
}
void main(){
@ -90,44 +90,44 @@ void main(){
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade);
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade);
mat3 randomRotation = mat3(pushConstants.randomRotation0.xyz, pushConstants.randomRotation1.xyz, pushConstants.randomRotation2.xyz);
uint raysPerProbe = pushConstants.params.w;
DDGIRayDataBuffer rayData = ddgiData.rayData; // hoist the master->sub-pointer deref out of the ray loop
DDGI_SH_TYPE sh = DDGI_SH_ZERO();
for(uint r = GI_DDGI_RAY_START; r < raysPerProbe; r++){
vec3 rayDirection = ddgiTraceRayDirection(r, randomRotation);
vec3 radiance = ddgiLoadRay(rayData, globalProbeIndex, r, raysPerProbe).rgb;
sh = DDGI_SH_ADD(sh, DDGI_SH_PROJECT(rayDirection, radiance));
DUGIRayDataBuffer rayData = dugiData.rayData; // hoist the master->sub-pointer deref out of the ray loop
DUGI_SH_TYPE sh = DUGI_SH_ZERO();
for(uint r = GI_DUGI_RAY_START; r < raysPerProbe; r++){
vec3 rayDirection = dugiTraceRayDirection(r, randomRotation);
vec3 radiance = dugiLoadRay(rayData, globalProbeIndex, r, raysPerProbe).rgb;
sh = DUGI_SH_ADD(sh, DUGI_SH_PROJECT(rayDirection, radiance));
}
// Monte-Carlo estimator over the uniform sphere: c_lm = (4*pi / N) * sum(L * Y_lm), where N is the number of rays actually
// integrated (the random rays). With relocation enabled the first GI_DDGI_FIXED_RAYS rays are fixed/unshaded and skipped
// (GI_DDGI_RAY_START > 0), so divide by the integrated count, not the full raysPerProbe (else the irradiance is too dark).
sh = DDGI_SH_MUL(sh, GI_DDGI_FOUR_PI / float(raysPerProbe - GI_DDGI_RAY_START));
// integrated (the random rays). With relocation enabled the first GI_DUGI_FIXED_RAYS rays are fixed/unshaded and skipped
// (GI_DUGI_RAY_START > 0), so divide by the integrated count, not the full raysPerProbe (else the irradiance is too dark).
sh = DUGI_SH_MUL(sh, GI_DUGI_FOUR_PI / float(raysPerProbe - GI_DUGI_RAY_START));
// The probe buffer is not cleared on allocation, so guard against uninitialized NaN/Inf memory which would otherwise
// lock in permanently through the temporal hysteresis blend; on the first (non-finite) read just take the new value.
DDGI_SH_TYPE previous = ddgiLoadSH(probeCoord, cascadeIndex);
DUGI_SH_TYPE previous = dugiLoadSH(probeCoord, cascadeIndex);
// Discard stale previous SH: firstFrame (blend.z) = uninitialized garbage; or the probe just toroidally scrolled into
// the volume (its stored SH belongs to a different world cell). Either way take the freshly projected SH.
bool firstFrame = (pushConstants.blend.z > 0.5) || ddgiProbeScrolledIn(probeCoord, cascadeIndex);
float hysteresis = ddgiProbeAgeHysteresis(probeCoord, cascadeIndex);
bool firstFrame = (pushConstants.blend.z > 0.5) || dugiProbeScrolledIn(probeCoord, cascadeIndex);
float hysteresis = dugiProbeAgeHysteresis(probeCoord, cascadeIndex);
vec3 finiteCheck = ((previous.coefficients[0] + previous.coefficients[1]) + previous.coefficients[2]) + previous.coefficients[3];
if(firstFrame || any(isnan(finiteCheck)) || any(isinf(finiteCheck))){
ddgiStoreSH(probeCoord, cascadeIndex, sh);
dugiStoreSH(probeCoord, cascadeIndex, sh);
}else{
// Lower the hysteresis when the field's mean radiance (SH DC band) changed a lot -> fast re-convergence on lighting changes.
hysteresis = ddgiAdaptiveHysteresis(hysteresis, sh.coefficients[0], previous.coefficients[0]);
ddgiStoreSH(probeCoord, cascadeIndex, DDGI_SH_LERP(sh, previous, hysteresis));
hysteresis = dugiAdaptiveHysteresis(hysteresis, sh.coefficients[0], previous.coefficients[0]);
dugiStoreSH(probeCoord, cascadeIndex, DUGI_SH_LERP(sh, previous, hysteresis));
}
}
#else // GI_DDGI_STORAGE_OCT
#else // GI_DUGI_STORAGE_OCT
void main(){
uint globalProbeIndex = gl_WorkGroupID.x;
@ -138,25 +138,25 @@ void main(){
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade);
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade);
ivec2 localTexel = ivec2(gl_LocalInvocationID.xy);
// Direction this interior texel represents (octahedral mapping of the [0,1]^2 tile).
vec2 uv = (vec2(localTexel) + vec2(0.5)) / float(GI_DDGI_IRRADIANCE_OCT_SIZE);
vec2 uv = (vec2(localTexel) + vec2(0.5)) / float(GI_DUGI_IRRADIANCE_OCT_SIZE);
vec3 texelDirection = octDecode(fma(uv, vec2(2.0), vec2(-1.0)));
mat3 randomRotation = mat3(pushConstants.randomRotation0.xyz, pushConstants.randomRotation1.xyz, pushConstants.randomRotation2.xyz);
uint raysPerProbe = pushConstants.params.w;
DDGIRayDataBuffer rayData = ddgiData.rayData; // hoist the master->sub-pointer deref out of the ray loop
DUGIRayDataBuffer rayData = dugiData.rayData; // hoist the master->sub-pointer deref out of the ray loop
vec3 sum = vec3(0.0);
float sumWeight = 0.0;
for(uint r = GI_DDGI_RAY_START; r < raysPerProbe; r++){
vec3 rayDirection = ddgiTraceRayDirection(r, randomRotation);
for(uint r = GI_DUGI_RAY_START; r < raysPerProbe; r++){
vec3 rayDirection = dugiTraceRayDirection(r, randomRotation);
float weight = max(0.0, dot(texelDirection, rayDirection));
if(weight > 0.0){
sum += ddgiLoadRay(rayData, globalProbeIndex, r, raysPerProbe).rgb * weight;
sum += dugiLoadRay(rayData, globalProbeIndex, r, raysPerProbe).rgb * weight;
sumWeight += weight;
}
}
@ -165,18 +165,18 @@ void main(){
// feeding back PI*A would amplify the feedback loop (open-sky scenes blow up). So the stored/feedback value stays A.
vec3 irradiance = (sumWeight > 1e-6) ? (sum / sumWeight) : vec3(0.0);
ivec2 atlasTexel = ddgiProbeTileOrigin(probeCoord, cascadeIndex, GI_DDGI_IRRADIANCE_OCT_FULL) + localTexel;
vec3 previous = imageLoad(uDDGIIrradianceOct, atlasTexel).rgb;
ivec2 atlasTexel = dugiProbeTileOrigin(probeCoord, cascadeIndex, GI_DUGI_IRRADIANCE_OCT_FULL) + localTexel;
vec3 previous = imageLoad(uDUGIIrradianceOct, atlasTexel).rgb;
// Guard against uninitialized NaN/Inf (images are not cleared) and discard stale history: firstFrame (blend.z) =
// uninitialized garbage, or the probe just toroidally scrolled into the volume (stored data is for a different world
// cell) — in both cases take the freshly computed value instead of blending against it.
bool firstFrame = (pushConstants.blend.z > 0.5) || ddgiProbeScrolledIn(probeCoord, cascadeIndex);
float hysteresis = ddgiProbeAgeHysteresis(probeCoord, cascadeIndex);
bool firstFrame = (pushConstants.blend.z > 0.5) || dugiProbeScrolledIn(probeCoord, cascadeIndex);
float hysteresis = dugiProbeAgeHysteresis(probeCoord, cascadeIndex);
// Lower the hysteresis when this texel's irradiance changed a lot -> fast re-convergence on lighting changes (per texel here).
// NaN-safe discard: a zero blend weight is not enough, because mix(cur, NaN, 0.0) = cur + NaN*0.0 = NaN; select the fresh value.
bool discardPrevious = firstFrame || any(isnan(previous)) || any(isinf(previous));
imageStore(uDDGIIrradianceOct, atlasTexel, vec4(discardPrevious ? irradiance : mix(irradiance, previous, ddgiAdaptiveHysteresis(hysteresis, irradiance, previous)), 1.0));
imageStore(uDUGIIrradianceOct, atlasTexel, vec4(discardPrevious ? irradiance : mix(irradiance, previous, dugiAdaptiveHysteresis(hysteresis, irradiance, previous)), 1.0));
}

View file

@ -1,68 +1,68 @@
#ifndef GI_DDGI_MULTIBOUNCE_GLSL
#define GI_DDGI_MULTIBOUNCE_GLSL
#ifndef GI_DUGI_MULTIBOUNCE_GLSL
#define GI_DUGI_MULTIBOUNCE_GLSL
// =====================================================================================================================
// Shared previous-frame probe-field reads for the DDGI producers (ray-query trace + RSM backends + RSM splat).
// Shared previous-frame probe-field reads for the DUGI producers (ray-query trace + RSM backends + RSM splat).
//
// Read-only views of the irradiance/visibility field this in-flight slot still holds from the PREVIOUS frame — the update
// stages overwrite these images only AFTER the producer runs — used as the multi-bounce feedback term, plus the per-probe
// relocation offset. These back the probe-sampling function prototypes declared by global_illumination_ddgi.glsl
// (ddgiEvaluateIrradiance / ddgiSampleVisibility / ddgiLoadProbeData / ddgiLoadIrradianceSH), so the in-header
// ddgiSampleIrradiance(...) resolves against them.
// relocation offset. These back the probe-sampling function prototypes declared by global_illumination_dugi.glsl
// (dugiEvaluateIrradiance / dugiSampleVisibility / dugiLoadProbeData / dugiLoadIrradianceSH), so the in-header
// dugiSampleIrradiance(...) resolves against them.
//
// Prerequisites (the includer must set these up first):
// - GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET defined (the DDGI resource set; reads sit at its binding 2 and 3)
// - #include "global_illumination_ddgi.glsl" (with GLOBAL_ILLUMINATION_DDGI_SAMPLE) BEFORE this file, for the GI_DDGI_*
// dimension constants, the storage-mode SH aliases, the octahedral addressing helpers and the ddgiData SSBO accessors
// - GL_EXT_buffer_reference enabled (for the SH / probe-data BDA sub-buffers reached through the ddgiData master)
// - GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET defined (the DUGI resource set; reads sit at its binding 2 and 3)
// - #include "global_illumination_dugi.glsl" (with GLOBAL_ILLUMINATION_DUGI_SAMPLE) BEFORE this file, for the GI_DUGI_*
// dimension constants, the storage-mode SH aliases, the octahedral addressing helpers and the dugiData SSBO accessors
// - GL_EXT_buffer_reference enabled (for the SH / probe-data BDA sub-buffers reached through the dugiData master)
//
// Octahedral storage (GI_DDGI_STORAGE_OCT) reads the irradiance atlas at binding 2; SH storage reads the master's
// Octahedral storage (GI_DUGI_STORAGE_OCT) reads the irradiance atlas at binding 2; SH storage reads the master's
// irradianceSH BDA buffer instead (no image binding). Visibility is always the binding-3 mean/mean^2 atlas.
// =====================================================================================================================
#if GI_DDGI_MULTIBOUNCE
#if GI_DUGI_MULTIBOUNCE
#if GI_DDGI_STORAGE_IS_SH
// SH multi-bounce read comes from the master's irradianceSH BDA buffer; ddgiLoadIrradianceSH is defined further below.
#if GI_DUGI_STORAGE_IS_SH
// SH multi-bounce read comes from the master's irradianceSH BDA buffer; dugiLoadIrradianceSH is defined further below.
#else
layout(set = GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET, binding = 2, rgba16f) uniform readonly image2D uDDGIIrradianceOctRead;
layout(set = GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET, binding = 2, rgba16f) uniform readonly image2D uDUGIIrradianceOctRead;
vec3 ddgiEvaluateIrradiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 normal){
vec2 uv = ddgiProbeOctUV(probeCoord, cascadeIndex, normal, GI_DDGI_IRRADIANCE_OCT_SIZE, GI_DDGI_IRRADIANCE_OCT_FULL);
ivec2 texel = ivec2(uv * vec2(ddgiAtlasSize(GI_DDGI_IRRADIANCE_OCT_FULL)));
return max(vec3(0.0), imageLoad(uDDGIIrradianceOctRead, texel).rgb);
vec3 dugiEvaluateIrradiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 normal){
vec2 uv = dugiProbeOctUV(probeCoord, cascadeIndex, normal, GI_DUGI_IRRADIANCE_OCT_SIZE, GI_DUGI_IRRADIANCE_OCT_FULL);
ivec2 texel = ivec2(uv * vec2(dugiAtlasSize(GI_DUGI_IRRADIANCE_OCT_FULL)));
return max(vec3(0.0), imageLoad(uDUGIIrradianceOctRead, texel).rgb);
}
#endif
layout(set = GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET, binding = 3, rg32f) uniform readonly image2D uDDGIVisibilityMomentsRead; // x = mean dist, y = mean dist^2
layout(set = GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET, binding = 3, rg32f) uniform readonly image2D uDUGIVisibilityMomentsRead; // x = mean dist, y = mean dist^2
vec3 ddgiSampleVisibility(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 direction){
vec2 uv = ddgiProbeOctUV(probeCoord, cascadeIndex, direction, GI_DDGI_VISIBILITY_OCT_SIZE, GI_DDGI_VISIBILITY_OCT_FULL);
ivec2 texel = ivec2(uv * vec2(ddgiAtlasSize(GI_DDGI_VISIBILITY_OCT_FULL))); // point sample is fine for the secondary feedback term
vec3 dugiSampleVisibility(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 direction){
vec2 uv = dugiProbeOctUV(probeCoord, cascadeIndex, direction, GI_DUGI_VISIBILITY_OCT_SIZE, GI_DUGI_VISIBILITY_OCT_FULL);
ivec2 texel = ivec2(uv * vec2(dugiAtlasSize(GI_DUGI_VISIBILITY_OCT_FULL))); // point sample is fine for the secondary feedback term
// The multibounce gather discards the sky-visibility term, so only the distance moments are read here (z = 0); the sky atlas
// is therefore not bound in the producers, avoiding a clash with the env cubemaps at binding 4.
return vec3(imageLoad(uDDGIVisibilityMomentsRead, texel).xy, 0.0); // x = mean dist, y = mean dist^2, z = sky (unused here)
return vec3(imageLoad(uDUGIVisibilityMomentsRead, texel).xy, 0.0); // x = mean dist, y = mean dist^2, z = sky (unused here)
}
#endif // GI_DDGI_MULTIBOUNCE
#endif // GI_DUGI_MULTIBOUNCE
#if GI_DDGI_PROBE_RELOCATION
#if GI_DUGI_PROBE_RELOCATION
// Probe data (xyz = relocation offset, w = state) lives in the master's probe-data BDA buffer (written by the relocation/
// classification passes). Used for the relocated probe/ray origin and by the probe-sampling functions (multi-bounce).
vec4 ddgiLoadProbeData(const in ivec3 probeCoord, const in int cascadeIndex){
DDGIProbeDataBuffer pd = ddgiData.probeData; // launder through a local (readonly master field -> non-readonly ref) + hoist
return ddgiLoadProbeDataBuffer(pd, probeCoord, cascadeIndex);
vec4 dugiLoadProbeData(const in ivec3 probeCoord, const in int cascadeIndex){
DUGIProbeDataBuffer pd = dugiData.probeData; // launder through a local (readonly master field -> non-readonly ref) + hoist
return dugiLoadProbeDataBuffer(pd, probeCoord, cascadeIndex);
}
#endif
#if GI_DDGI_MULTIBOUNCE && GI_DDGI_STORAGE_IS_SH
#if GI_DUGI_MULTIBOUNCE && GI_DUGI_STORAGE_IS_SH
// Previous-frame SH irradiance (multi-bounce feedback) from the master's irradianceSH BDA buffer (same packing the update
// pass writes; one contiguous load of the whole probe).
DDGI_SH_TYPE ddgiLoadIrradianceSH(const in ivec3 probeCoord, const in int cascadeIndex){
DDGIIrradianceSHBuffer shBuf = ddgiData.irradianceSH; // hoist the master->sub-pointer deref once
DDGISHProbe p = ddgiLoadSHProbe(shBuf, probeCoord, cascadeIndex);
DUGI_SH_TYPE dugiLoadIrradianceSH(const in ivec3 probeCoord, const in int cascadeIndex){
DUGIIrradianceSHBuffer shBuf = dugiData.irradianceSH; // hoist the master->sub-pointer deref once
DUGISHProbe p = dugiLoadSHProbe(shBuf, probeCoord, cascadeIndex);
vec4 a = p.c[0]; vec4 b = p.c[1]; vec4 c = p.c[2];
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_L2_VALUE
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_L2_VALUE
vec4 d = p.c[3]; vec4 e = p.c[4]; vec4 f = p.c[5]; vec4 g = p.c[6];
return SHC3CoefficientsL2Create(vec3(a.x, a.y, a.z), vec3(a.w, b.x, b.y), vec3(b.z, b.w, c.x), vec3(c.y, c.z, c.w),
vec3(d.x, d.y, d.z), vec3(d.w, e.x, e.y), vec3(e.z, e.w, f.x), vec3(f.y, f.z, f.w),
@ -73,4 +73,4 @@ DDGI_SH_TYPE ddgiLoadIrradianceSH(const in ivec3 probeCoord, const in int cascad
}
#endif
#endif // GI_DDGI_MULTIBOUNCE_GLSL
#endif // GI_DUGI_MULTIBOUNCE_GLSL

View file

@ -1,8 +1,8 @@
#ifndef GI_DDGI_PARTICLE_INJECT_GLSL
#define GI_DDGI_PARTICLE_INJECT_GLSL
#ifndef GI_DUGI_PARTICLE_INJECT_GLSL
#define GI_DUGI_PARTICLE_INJECT_GLSL
// =====================================================================================================================
// Shared particle-LBVH injection for the DDGI producers (ray-query trace + RSM backends + RSM splat).
// Shared particle-LBVH injection for the DUGI producers (ray-query trace + RSM backends + RSM splat).
//
// Particles are NOT in any hardware ray-tracing BLAS (too many, too dynamic), so each producer software-traces the per-frame
// GPU-built particle LBVH and injects the result into the probe ray: a closest OPAQUE particle nearer than the current
@ -11,7 +11,7 @@
// by the closest opaque distance (so the visibility moments see the opaque particle too).
//
// Prerequisites (the includer must set these up first):
// - #include "gi_ddgi_pushconstants.glsl" (uses pushConstants.particleBVH device addresses + emissiveGIParticleCount.z)
// - #include "gi_dugi_pushconstants.glsl" (uses pushConstants.particleBVH device addresses + emissiveGIParticleCount.z)
// - GL_EXT_buffer_reference / GL_EXT_buffer_reference_uvec2 enabled (the LBVH is reached descriptor-free by device address)
//
// Does nothing when the alive particle count is 0 (the Pascal side pushes 0 then), so the call is always safe.
@ -20,7 +20,7 @@
#include "particle_bvh.glsl" // ParticleBVHEmitterRef / ParticleBVHNodeRef structs (BDA)
#include "particle_bvh_trace.glsl" // particleBVHClosestOpaque / particleBVHAdditiveEmission
void ddgiInjectParticles(const in vec3 origin, const in vec3 direction, const in float tMin, const in float tMaxMiss,
void dugiInjectParticles(const in vec3 origin, const in vec3 direction, const in float tMin, const in float tMaxMiss,
inout vec3 radiance, inout bool hit, inout bool backface, inout float hitDistance){
uint particleCount = uint(pushConstants.emissiveGIParticleCount.z);
@ -48,4 +48,4 @@ void ddgiInjectParticles(const in vec3 origin, const in vec3 direction, const in
radiance += particleBVHAdditiveEmission(particleNodes, particleEmitters, origin, direction, tMin, particleBound, particleCount);
}
#endif // GI_DDGI_PARTICLE_INJECT_GLSL
#endif // GI_DUGI_PARTICLE_INJECT_GLSL

View file

@ -1,10 +1,10 @@
#version 460 core
// DDGI probe debug visualization — fragment shader.
// DUGI probe debug visualization — fragment shader.
//
// Colors each octahedral-sphere fragment with the probe's irradiance in the fragment's OUTWARD (sphere-normal) direction,
// using the SAME live DDGI sampling the renderer uses: ddgiEvaluateIrradiance() resolves the octahedral atlas OR the SH (L1/L2)
// representation depending on the build-time GI_DDGI_STORAGE constant, so the debug spheres show exactly "what is active".
// using the SAME live DUGI sampling the renderer uses: dugiEvaluateIrradiance() resolves the octahedral atlas OR the SH (L1/L2)
// representation depending on the build-time GI_DUGI_STORAGE constant, so the debug spheres show exactly "what is active".
// One probe -> one octahedral sphere whose surface is the probe's directional irradiance.
#extension GL_GOOGLE_include_directive : enable
@ -19,8 +19,8 @@
#include "octahedral.glsl"
#define DDGI_DESCRIPTOR_SET 2
#include "global_illumination_ddgi_sampling.glsl" // ddgiData @ set 2 binding 0 + irradiance/visibility + ddgiEvaluateIrradiance()
#define DUGI_DESCRIPTOR_SET 2
#include "global_illumination_dugi_sampling.glsl" // dugiData @ set 2 binding 0 + irradiance/visibility + dugiEvaluateIrradiance()
layout(location = 0) in vec3 inDirection; // outward direction at this fragment (interpolated sphere normal), world space
layout(location = 1) flat in ivec3 inProbeCoord; // probe grid coordinate within its cascade
@ -30,7 +30,7 @@ layout(location = 3) flat in float inActive; // relocation state: 1 = acti
layout(location = 0) out vec4 outFragColor;
void main(){
vec3 irradiance = ddgiEvaluateIrradiance(inProbeCoord, inCascadeIndex, normalize(inDirection));
vec3 irradiance = dugiEvaluateIrradiance(inProbeCoord, inCascadeIndex, normalize(inDirection));
// Inactive (relocation-deactivated) probes are dimmed + tinted red so the debug view shows which probes the renderer skips.
if(inActive < 0.5){
irradiance = mix(irradiance * 0.1, vec3(0.25, 0.0, 0.0), 0.5);

View file

@ -1,12 +1,12 @@
#version 460 core
// DDGI probe debug visualization — mesh shader (frustum-culled mesh-shader path).
// DUGI probe debug visualization — mesh shader (frustum-culled mesh-shader path).
//
// One mesh workgroup renders ONE band of ONE visible probe's octahedral sphere. The task shader appended the visible global
// probe indices to the payload and emitted GI_DDGI_PROBE_DEBUG_BANDS workgroups per probe; here gl_WorkGroupID.x decodes to
// probe indices to the payload and emitted GI_DUGI_PROBE_DEBUG_BANDS workgroups per probe; here gl_WorkGroupID.x decodes to
// (probeSlot, band): probeSlot = id / BANDS, band = id % BANDS. The 16x16 octahedral grid (512 triangles) is split into BANDS
// horizontal bands so each meshlet stays within the 256-primitive / 256-vertex limit. The fragment shader is shared with the
// vertex path, so the per-vertex outputs match gi_ddgi_probe_debug.vert exactly (direction + probe coord/cascade/active state).
// vertex path, so the per-vertex outputs match gi_dugi_probe_debug.vert exactly (direction + probe coord/cascade/active state).
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_mesh_shader : enable
@ -18,25 +18,25 @@
/* clang-format off */
#define GI_DDGI_PROBE_DEBUG_GRID 16
#define GI_DDGI_PROBE_DEBUG_BANDS 2
#define GI_DDGI_PROBE_DEBUG_BAND_ROWS (GI_DDGI_PROBE_DEBUG_GRID / GI_DDGI_PROBE_DEBUG_BANDS)
#define GI_DDGI_PROBE_DEBUG_BAND_VERTICES ((GI_DDGI_PROBE_DEBUG_GRID + 1) * (GI_DDGI_PROBE_DEBUG_BAND_ROWS + 1))
#define GI_DDGI_PROBE_DEBUG_BAND_PRIMITIVES (GI_DDGI_PROBE_DEBUG_GRID * GI_DDGI_PROBE_DEBUG_BAND_ROWS * 2)
#define GI_DDGI_PROBE_DEBUG_MESH_GROUP_SIZE 128
#define GI_DDGI_PROBE_DEBUG_RADIUS_FACTOR 0.125
#define GI_DUGI_PROBE_DEBUG_GRID 16
#define GI_DUGI_PROBE_DEBUG_BANDS 2
#define GI_DUGI_PROBE_DEBUG_BAND_ROWS (GI_DUGI_PROBE_DEBUG_GRID / GI_DUGI_PROBE_DEBUG_BANDS)
#define GI_DUGI_PROBE_DEBUG_BAND_VERTICES ((GI_DUGI_PROBE_DEBUG_GRID + 1) * (GI_DUGI_PROBE_DEBUG_BAND_ROWS + 1))
#define GI_DUGI_PROBE_DEBUG_BAND_PRIMITIVES (GI_DUGI_PROBE_DEBUG_GRID * GI_DUGI_PROBE_DEBUG_BAND_ROWS * 2)
#define GI_DUGI_PROBE_DEBUG_MESH_GROUP_SIZE 128
#define GI_DUGI_PROBE_DEBUG_RADIUS_FACTOR 0.125
#define GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE 32 // must match gi_ddgi_probe_debug.task
#define GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE 32 // must match gi_dugi_probe_debug.task
#include "octahedral.glsl"
// ddgiData (cascade globals) + probe<->grid<->world helpers. No GLOBAL_ILLUMINATION_DDGI_SAMPLE -> data block + helpers only.
// dugiData (cascade globals) + probe<->grid<->world helpers. No GLOBAL_ILLUMINATION_DUGI_SAMPLE -> data block + helpers only.
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 2
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
#include "global_illumination_dugi.glsl"
layout(local_size_x = GI_DDGI_PROBE_DEBUG_MESH_GROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
layout(triangles, max_vertices = GI_DDGI_PROBE_DEBUG_BAND_VERTICES, max_primitives = GI_DDGI_PROBE_DEBUG_BAND_PRIMITIVES) out;
layout(local_size_x = GI_DUGI_PROBE_DEBUG_MESH_GROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
layout(triangles, max_vertices = GI_DUGI_PROBE_DEBUG_BAND_VERTICES, max_primitives = GI_DUGI_PROBE_DEBUG_BAND_PRIMITIVES) out;
struct View {
mat4 viewMatrix;
@ -56,7 +56,7 @@ layout(push_constant) uniform PushConstants {
struct TaskPayload {
uint count;
uint probeInstanceIndices[GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE];
uint probeInstanceIndices[GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE];
};
taskPayloadSharedEXT TaskPayload payload;
@ -68,23 +68,23 @@ layout(location = 3) flat out float outActive[];
void main(){
uint workGroupIndex = gl_WorkGroupID.x;
uint probeSlot = workGroupIndex / uint(GI_DDGI_PROBE_DEBUG_BANDS);
uint band = workGroupIndex - (probeSlot * uint(GI_DDGI_PROBE_DEBUG_BANDS));
uint probeSlot = workGroupIndex / uint(GI_DUGI_PROBE_DEBUG_BANDS);
uint band = workGroupIndex - (probeSlot * uint(GI_DUGI_PROBE_DEBUG_BANDS));
uint globalProbe = payload.probeInstanceIndices[probeSlot];
int cascadeIndex = int(globalProbe) / GI_DDGI_PROBES_PER_CASCADE;
int localProbe = int(globalProbe) - (cascadeIndex * GI_DDGI_PROBES_PER_CASCADE);
ivec3 probeCoord = ddgiProbeCoordFromIndex(localProbe);
int cascadeIndex = int(globalProbe) / GI_DUGI_PROBES_PER_CASCADE;
int localProbe = int(globalProbe) - (cascadeIndex * GI_DUGI_PROBES_PER_CASCADE);
ivec3 probeCoord = dugiProbeCoordFromIndex(localProbe);
vec3 probeWorld = ddgiProbeGridToWorld(probeCoord, cascadeIndex);
vec3 cellSize = ddgiData.ddgiCascadeCellSizes[cascadeIndex].xyz;
float radius = GI_DDGI_PROBE_DEBUG_RADIUS_FACTOR * min(cellSize.x, min(cellSize.y, cellSize.z));
vec3 probeWorld = dugiProbeGridToWorld(probeCoord, cascadeIndex);
vec3 cellSize = dugiData.dugiCascadeCellSizes[cascadeIndex].xyz;
float radius = GI_DUGI_PROBE_DEBUG_RADIUS_FACTOR * min(cellSize.x, min(cellSize.y, cellSize.z));
float probeActive = 1.0;
#if GI_DDGI_PROBE_RELOCATION
ivec3 physProbeCoord = ddgiProbePhysicalCoord(probeCoord, cascadeIndex);
vec4 probeData = ddgiData.probeData.data[ddgiProbeDataIndex(physProbeCoord, cascadeIndex)];
#if GI_DUGI_PROBE_RELOCATION
ivec3 physProbeCoord = dugiProbePhysicalCoord(probeCoord, cascadeIndex);
vec4 probeData = dugiData.probeData.data[dugiProbeDataIndex(physProbeCoord, cascadeIndex)];
probeWorld += probeData.xyz;
probeActive = probeData.w;
#endif
@ -92,16 +92,16 @@ void main(){
uint viewIndex = pushConstants.viewBaseIndex + uint(gl_ViewIndex);
mat4 viewProjectionMatrix = uView.views[viewIndex].projectionMatrix * uView.views[viewIndex].viewMatrix;
SetMeshOutputsEXT(uint(GI_DDGI_PROBE_DEBUG_BAND_VERTICES), uint(GI_DDGI_PROBE_DEBUG_BAND_PRIMITIVES));
SetMeshOutputsEXT(uint(GI_DUGI_PROBE_DEBUG_BAND_VERTICES), uint(GI_DUGI_PROBE_DEBUG_BAND_PRIMITIVES));
uint bandRowStart = band * uint(GI_DDGI_PROBE_DEBUG_BAND_ROWS);
uint bandRowStart = band * uint(GI_DUGI_PROBE_DEBUG_BAND_ROWS);
// Vertices: the band's grid points (columns 0..GRID, rows bandRowStart..bandRowStart+BAND_ROWS), octahedral -> sphere.
for(uint vertexIndex = gl_LocalInvocationIndex; vertexIndex < uint(GI_DDGI_PROBE_DEBUG_BAND_VERTICES); vertexIndex += uint(GI_DDGI_PROBE_DEBUG_MESH_GROUP_SIZE)){
uint localRow = vertexIndex / uint(GI_DDGI_PROBE_DEBUG_GRID + 1);
uint col = vertexIndex - (localRow * uint(GI_DDGI_PROBE_DEBUG_GRID + 1));
for(uint vertexIndex = gl_LocalInvocationIndex; vertexIndex < uint(GI_DUGI_PROBE_DEBUG_BAND_VERTICES); vertexIndex += uint(GI_DUGI_PROBE_DEBUG_MESH_GROUP_SIZE)){
uint localRow = vertexIndex / uint(GI_DUGI_PROBE_DEBUG_GRID + 1);
uint col = vertexIndex - (localRow * uint(GI_DUGI_PROBE_DEBUG_GRID + 1));
uint row = bandRowStart + localRow;
vec3 direction = normalize(octUnsignedDecode(vec2(uvec2(col, row)) / vec2(GI_DDGI_PROBE_DEBUG_GRID)));
vec3 direction = normalize(octUnsignedDecode(vec2(uvec2(col, row)) / vec2(GI_DUGI_PROBE_DEBUG_GRID)));
vec3 worldPosition = probeWorld + (direction * radius);
gl_MeshVerticesEXT[vertexIndex].gl_Position = viewProjectionMatrix * vec4(worldPosition, 1.0);
outDirection[vertexIndex] = direction;
@ -111,14 +111,14 @@ void main(){
}
// Primitives: 2 triangles per quad over the band's GRID x BAND_ROWS quads (indices into the local vertex array above).
for(uint primitiveIndex = gl_LocalInvocationIndex; primitiveIndex < uint(GI_DDGI_PROBE_DEBUG_BAND_PRIMITIVES); primitiveIndex += uint(GI_DDGI_PROBE_DEBUG_MESH_GROUP_SIZE)){
for(uint primitiveIndex = gl_LocalInvocationIndex; primitiveIndex < uint(GI_DUGI_PROBE_DEBUG_BAND_PRIMITIVES); primitiveIndex += uint(GI_DUGI_PROBE_DEBUG_MESH_GROUP_SIZE)){
uint quad = primitiveIndex >> 1u;
uint triangle = primitiveIndex & 1u;
uint quadRow = quad / uint(GI_DDGI_PROBE_DEBUG_GRID);
uint quadCol = quad - (quadRow * uint(GI_DDGI_PROBE_DEBUG_GRID));
uint v00 = (quadRow * uint(GI_DDGI_PROBE_DEBUG_GRID + 1)) + quadCol;
uint quadRow = quad / uint(GI_DUGI_PROBE_DEBUG_GRID);
uint quadCol = quad - (quadRow * uint(GI_DUGI_PROBE_DEBUG_GRID));
uint v00 = (quadRow * uint(GI_DUGI_PROBE_DEBUG_GRID + 1)) + quadCol;
uint v10 = v00 + 1u;
uint v01 = v00 + uint(GI_DDGI_PROBE_DEBUG_GRID + 1);
uint v01 = v00 + uint(GI_DUGI_PROBE_DEBUG_GRID + 1);
uint v11 = v01 + 1u;
gl_PrimitiveTriangleIndicesEXT[primitiveIndex] = (triangle == 0u) ? uvec3(v00, v10, v11) : uvec3(v00, v11, v01);
}

View file

@ -1,12 +1,12 @@
#version 460 core
// DDGI probe debug visualization — task shader (frustum-culled mesh-shader path).
// DUGI probe debug visualization — task shader (frustum-culled mesh-shader path).
//
// One task workgroup processes GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE probes (one thread each), spanning all cascades via the
// One task workgroup processes GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE probes (one thread each), spanning all cascades via the
// global probe index. Each thread places its probe (cascade globals + relocation offset, exactly like the vertex path),
// frustum-culls the probe's bounding sphere against every active view, and — if visible in any — appends the global probe
// index to the task payload. Finally the workgroup emits 2 mesh workgroups per visible probe (the octahedral sphere is split
// into GI_DDGI_PROBE_DEBUG_BANDS bands so each meshlet stays within the 256-primitive limit). The mesh shader renders one band.
// into GI_DUGI_PROBE_DEBUG_BANDS bands so each meshlet stays within the 256-primitive limit). The mesh shader renders one band.
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_mesh_shader : enable
@ -19,18 +19,18 @@
/* clang-format off */
#define GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE 32
#define GI_DDGI_PROBE_DEBUG_BANDS 2
#define GI_DDGI_PROBE_DEBUG_RADIUS_FACTOR 0.125
#define GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE 32
#define GI_DUGI_PROBE_DEBUG_BANDS 2
#define GI_DUGI_PROBE_DEBUG_RADIUS_FACTOR 0.125
// ddgiData (cascade globals) + probe<->grid<->world helpers. No GLOBAL_ILLUMINATION_DDGI_SAMPLE -> data block + helpers only.
// dugiData (cascade globals) + probe<->grid<->world helpers. No GLOBAL_ILLUMINATION_DUGI_SAMPLE -> data block + helpers only.
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 2
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
#include "global_illumination_dugi.glsl"
#include "frustum.glsl"
layout(local_size_x = GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
layout(local_size_x = GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
struct View {
mat4 viewMatrix;
@ -50,7 +50,7 @@ layout(push_constant) uniform PushConstants {
struct TaskPayload {
uint count; // number of visible probes appended
uint probeInstanceIndices[GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE]; // global probe indices (cascade*probesPerCascade + localProbe)
uint probeInstanceIndices[GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE]; // global probe indices (cascade*probesPerCascade + localProbe)
};
taskPayloadSharedEXT TaskPayload payload;
@ -73,22 +73,22 @@ void main(){
barrier();
uint globalProbe = (gl_WorkGroupID.x * GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE) + localInvocationIndex;
uint totalProbes = uint(GI_DDGI_PROBES_PER_CASCADE) * uint(GI_DDGI_CASCADES);
uint globalProbe = (gl_WorkGroupID.x * GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE) + localInvocationIndex;
uint totalProbes = uint(GI_DUGI_PROBES_PER_CASCADE) * uint(GI_DUGI_CASCADES);
if(globalProbe < totalProbes){
int cascadeIndex = int(globalProbe) / GI_DDGI_PROBES_PER_CASCADE;
int localProbe = int(globalProbe) - (cascadeIndex * GI_DDGI_PROBES_PER_CASCADE);
ivec3 probeCoord = ddgiProbeCoordFromIndex(localProbe);
int cascadeIndex = int(globalProbe) / GI_DUGI_PROBES_PER_CASCADE;
int localProbe = int(globalProbe) - (cascadeIndex * GI_DUGI_PROBES_PER_CASCADE);
ivec3 probeCoord = dugiProbeCoordFromIndex(localProbe);
vec3 probeWorld = ddgiProbeGridToWorld(probeCoord, cascadeIndex);
vec3 cellSize = ddgiData.ddgiCascadeCellSizes[cascadeIndex].xyz;
float radius = GI_DDGI_PROBE_DEBUG_RADIUS_FACTOR * min(cellSize.x, min(cellSize.y, cellSize.z));
vec3 probeWorld = dugiProbeGridToWorld(probeCoord, cascadeIndex);
vec3 cellSize = dugiData.dugiCascadeCellSizes[cascadeIndex].xyz;
float radius = GI_DUGI_PROBE_DEBUG_RADIUS_FACTOR * min(cellSize.x, min(cellSize.y, cellSize.z));
#if GI_DDGI_PROBE_RELOCATION
ivec3 physProbeCoord = ddgiProbePhysicalCoord(probeCoord, cascadeIndex);
probeWorld += ddgiData.probeData.data[ddgiProbeDataIndex(physProbeCoord, cascadeIndex)].xyz;
#if GI_DUGI_PROBE_RELOCATION
ivec3 physProbeCoord = dugiProbePhysicalCoord(probeCoord, cascadeIndex);
probeWorld += dugiData.probeData.data[dugiProbeDataIndex(physProbeCoord, cascadeIndex)].xyz;
#endif
bool visible = false;
@ -103,7 +103,7 @@ void main(){
if(visible){
uint idx = atomicAdd(payload.count, 1u);
if(idx < GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE){
if(idx < GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE){
payload.probeInstanceIndices[idx] = globalProbe;
}
}
@ -112,8 +112,8 @@ void main(){
barrier();
// 2 (GI_DDGI_PROBE_DEBUG_BANDS) mesh workgroups per visible probe; the mesh shader derives (probeSlot, band) from gl_WorkGroupID.
uint visibleProbes = min(payload.count, uint(GI_DDGI_PROBE_DEBUG_TASK_GROUP_SIZE));
EmitMeshTasksEXT(visibleProbes * uint(GI_DDGI_PROBE_DEBUG_BANDS), 1u, 1u);
// 2 (GI_DUGI_PROBE_DEBUG_BANDS) mesh workgroups per visible probe; the mesh shader derives (probeSlot, band) from gl_WorkGroupID.
uint visibleProbes = min(payload.count, uint(GI_DUGI_PROBE_DEBUG_TASK_GROUP_SIZE));
EmitMeshTasksEXT(visibleProbes * uint(GI_DUGI_PROBE_DEBUG_BANDS), 1u, 1u);
}

View file

@ -1,12 +1,12 @@
#version 460 core
// DDGI probe debug visualization — vertex shader.
// DUGI probe debug visualization — vertex shader.
//
// Fully procedural octahedral sphere (no vertex/index buffer): GI_DDGI_PROBE_DEBUG_GRID x GI_DDGI_PROBE_DEBUG_GRID quad grid,
// Fully procedural octahedral sphere (no vertex/index buffer): GI_DUGI_PROBE_DEBUG_GRID x GI_DUGI_PROBE_DEBUG_GRID quad grid,
// each grid vertex -> octahedral UV -> octDecode -> unit sphere direction. Instanced per probe over ALL cascades, unculled:
// gl_InstanceIndex -> (cascadeIndex, localProbe) -> probe grid coord -> probe world position (via ddgiData helpers).
// gl_InstanceIndex -> (cascadeIndex, localProbe) -> probe grid coord -> probe world position (via dugiData helpers).
// Vertex world = probeWorld + dir * radius, radius = 0.125 * min cascade cell size. Outputs the outward direction + the probe
// coord/cascade so the fragment shader colours the sphere with that probe's directional irradiance (ddgiEvaluateIrradiance).
// coord/cascade so the fragment shader colours the sphere with that probe's directional irradiance (dugiEvaluateIrradiance).
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_multiview : enable
@ -17,16 +17,16 @@
/* clang-format off */
#define GI_DDGI_PROBE_DEBUG_GRID 16
#define GI_DDGI_PROBE_DEBUG_RADIUS_FACTOR 0.125
#define GI_DUGI_PROBE_DEBUG_GRID 16
#define GI_DUGI_PROBE_DEBUG_RADIUS_FACTOR 0.125
#include "octahedral.glsl"
// ddgiData (cascade AABBMin / CellSizes / ProbeScroll) + the probe<->grid<->world helpers. No GLOBAL_ILLUMINATION_DDGI_SAMPLE
// dugiData (cascade AABBMin / CellSizes / ProbeScroll) + the probe<->grid<->world helpers. No GLOBAL_ILLUMINATION_DUGI_SAMPLE
// here -> only the data block + helpers, no image samplers (those are fragment-side).
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 2
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
#include "global_illumination_dugi.glsl"
struct View {
mat4 viewMatrix;
@ -63,7 +63,7 @@ void main(){
//
// 0xe24 = 3,2,0,2,1,0 packs the per-vertex quad-corner index (output order 0,1,2, 0,2,3),
// 0xb4 = 0b10110100 packs the corner -> (x,y) UV bits for corners 0..3.
const uint countQuadPointsInOneDirection = uint(GI_DDGI_PROBE_DEBUG_GRID);
const uint countQuadPointsInOneDirection = uint(GI_DUGI_PROBE_DEBUG_GRID);
uint vertexIndex = uint(gl_VertexIndex);
uint quadIndex = vertexIndex / 6u;
uint quadVertexIndex = (0xe24u >> ((vertexIndex - (quadIndex * 6u)) << 1u)) & 3u;
@ -78,23 +78,23 @@ void main(){
// --- Per-probe placement: gl_InstanceIndex spans all cascades ---
int instanceIndex = gl_InstanceIndex;
int cascadeIndex = instanceIndex / GI_DDGI_PROBES_PER_CASCADE;
int localProbe = instanceIndex - (cascadeIndex * GI_DDGI_PROBES_PER_CASCADE);
ivec3 probeCoord = ddgiProbeCoordFromIndex(localProbe);
int cascadeIndex = instanceIndex / GI_DUGI_PROBES_PER_CASCADE;
int localProbe = instanceIndex - (cascadeIndex * GI_DUGI_PROBES_PER_CASCADE);
ivec3 probeCoord = dugiProbeCoordFromIndex(localProbe);
vec3 probeWorld = ddgiProbeGridToWorld(probeCoord, cascadeIndex);
vec3 cellSize = ddgiData.ddgiCascadeCellSizes[cascadeIndex].xyz;
float radius = GI_DDGI_PROBE_DEBUG_RADIUS_FACTOR * min(cellSize.x, min(cellSize.y, cellSize.z));
vec3 probeWorld = dugiProbeGridToWorld(probeCoord, cascadeIndex);
vec3 cellSize = dugiData.dugiCascadeCellSizes[cascadeIndex].xyz;
float radius = GI_DUGI_PROBE_DEBUG_RADIUS_FACTOR * min(cellSize.x, min(cellSize.y, cellSize.z));
float probeActive = 1.0;
#if GI_DDGI_PROBE_RELOCATION
// Place each sphere at the probe's ACTUAL relocated position (gi_ddgi_relocation.comp pushes probes out of geometry, up to
// GI_DDGI_PROBE_MAX_OFFSET*cellSize). The per-probe data is indexed by the PHYSICAL (toroidal) slot, exactly like the shading
#if GI_DUGI_PROBE_RELOCATION
// Place each sphere at the probe's ACTUAL relocated position (gi_dugi_relocation.comp pushes probes out of geometry, up to
// GI_DUGI_PROBE_MAX_OFFSET*cellSize). The per-probe data is indexed by the PHYSICAL (toroidal) slot, exactly like the shading
// gather. w = state (1 active, 0 inactive/inside-geometry -> dimmed by the fragment shader). probeData is null when off, so guard.
ivec3 physProbeCoord = ddgiProbePhysicalCoord(probeCoord, cascadeIndex);
// Index the probe-data BDA sub-buffer directly (ddgiData is readonly here, so we can't pass the buffer reference to the
// non-readonly ddgiLoadProbeDataBuffer helper) — same access pattern as ddgiLoadIrradianceSH in the sampling include.
vec4 probeData = ddgiData.probeData.data[ddgiProbeDataIndex(physProbeCoord, cascadeIndex)];
ivec3 physProbeCoord = dugiProbePhysicalCoord(probeCoord, cascadeIndex);
// Index the probe-data BDA sub-buffer directly (dugiData is readonly here, so we can't pass the buffer reference to the
// non-readonly dugiLoadProbeDataBuffer helper) — same access pattern as dugiLoadIrradianceSH in the sampling include.
vec4 probeData = dugiData.probeData.data[dugiProbeDataIndex(physProbeCoord, cascadeIndex)];
probeWorld += probeData.xyz;
probeActive = probeData.w;
#endif

View file

@ -1,11 +1,11 @@
#ifndef GI_DDGI_PUSHCONSTANTS_GLSL
#define GI_DDGI_PUSHCONSTANTS_GLSL
#ifndef GI_DUGI_PUSHCONSTANTS_GLSL
#define GI_DUGI_PUSHCONSTANTS_GLSL
// Shared push-constant block for all DDGI compute passes — the trace PRODUCER and the per-stage update CORE (irradiance,
// visibility, border, relocation, classification). Holds only the transient per-frame parameters; the DDGI field data
// (cascade globals + the sub-buffer pointers) is reached through the `ddgiData` SSBO (gi_ddgi_data.glsl) at the set's
// Shared push-constant block for all DUGI compute passes — the trace PRODUCER and the per-stage update CORE (irradiance,
// visibility, border, relocation, classification). Holds only the transient per-frame parameters; the DUGI field data
// (cascade globals + the sub-buffer pointers) is reached through the `dugiData` SSBO (gi_dugi_data.glsl) at the set's
// binding 0, NOT the push. Must byte-match TPushConstants on the Pascal side
// (PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDDGITraceComputePass / ...DDGIStageComputePass).
// (PasVulkan.Scene3D.Renderer.Passes.GlobalIlluminationDUGITraceComputePass / ...DUGIStageComputePass).
layout(push_constant) uniform PushConstants {
vec4 randomRotation0; // per-frame ray rotation, mat3 column 0 (xyz)
vec4 randomRotation1; // mat3 column 1 (xyz)
@ -16,4 +16,4 @@ layout(push_constant) uniform PushConstants {
uvec4 particleBVH; // particle LBVH device addresses (trace only): xy = emitter buffer (uvec2), zw = node buffer (uvec2); 0 when inactive
} pushConstants;
#endif // GI_DDGI_PUSHCONSTANTS_GLSL
#endif // GI_DUGI_PUSHCONSTANTS_GLSL

View file

@ -1,28 +1,28 @@
#ifndef GI_DDGI_RAYDATA_GLSL
#define GI_DDGI_RAYDATA_GLSL
#ifndef GI_DUGI_RAYDATA_GLSL
#define GI_DUGI_RAYDATA_GLSL
// =====================================================================================================================
// DDGI ray-data contract — the single modular seam between any trace PRODUCER (compute + ray-query, SDF tracing, or a
// DUGI ray-data contract — the single modular seam between any trace PRODUCER (compute + ray-query, SDF tracing, or a
// ray-generation/closest-hit RT-pipeline producer) and the technique-agnostic CONSUMERS (irradiance/visibility blend,
// relocation, classification). Mirrors RTXGI's split of ProbeTraceRGS (engine-side, swappable) vs ProbeBlendingCS (SDK).
//
// Ray-data image layout: image2D [ rayIndex x globalProbeIndex ], RGBA16F: rgb = radiance towards the probe, a = distance.
// - rays [0, GI_DDGI_FIXED_RAYS) FIXED rays (only when relocation is enabled): unrotated directions;
// - rays [0, GI_DUGI_FIXED_RAYS) FIXED rays (only when relocation is enabled): unrotated directions;
// a = SIGNED RAW distance (negative = backface hit), rgb = 0 (not blended).
// Consumed by the relocation + classification passes.
// - rays [GI_DDGI_FIXED_RAYS, raysPerProbe) RANDOM rays: per-frame-rotated directions; a = distance, backface-shortened
// - rays [GI_DUGI_FIXED_RAYS, raysPerProbe) RANDOM rays: per-frame-rotated directions; a = distance, backface-shortened
// and clamped to the local visibility scale; rgb = shaded radiance.
// Consumed by the irradiance + visibility blend.
//
// Requires global_illumination_ddgi.glsl included first (GI_DDGI_* defines, ddgiSphericalFibonacci, ddgiRayDirection,
// GI_DDGI_RAY_START, GI_DDGI_VISIBILITY_MAX_DISTANCE_SCALE). Backend-agnostic: encodes from plain hit primitives, so it
// Requires global_illumination_dugi.glsl included first (GI_DUGI_* defines, dugiSphericalFibonacci, dugiRayDirection,
// GI_DUGI_RAY_START, GI_DUGI_VISIBILITY_MAX_DISTANCE_SCALE). Backend-agnostic: encodes from plain hit primitives, so it
// does not depend on the ray-query gather layer.
// =====================================================================================================================
// Is ray rayIndex a fixed (relocation/classification) ray rather than a random (blended) ray?
bool ddgiRayIsFixed(const in uint rayIndex){
#if GI_DDGI_PROBE_RELOCATION
return rayIndex < uint(GI_DDGI_FIXED_RAYS);
bool dugiRayIsFixed(const in uint rayIndex){
#if GI_DUGI_PROBE_RELOCATION
return rayIndex < uint(GI_DUGI_FIXED_RAYS);
#else
return false;
#endif
@ -30,13 +30,13 @@ bool ddgiRayIsFixed(const in uint rayIndex){
// Split-aware ray direction. Producer AND consumers MUST use this so they agree on directions:
// fixed rays -> unrotated spherical Fibonacci over the fixed set; random rays -> the per-frame-rotated set.
vec3 ddgiTraceRayDirection(const in uint rayIndex, const in mat3 randomRotation){
#if GI_DDGI_PROBE_RELOCATION
if(rayIndex < uint(GI_DDGI_FIXED_RAYS)){
return ddgiSphericalFibonacci(float(rayIndex), float(GI_DDGI_FIXED_RAYS));
vec3 dugiTraceRayDirection(const in uint rayIndex, const in mat3 randomRotation){
#if GI_DUGI_PROBE_RELOCATION
if(rayIndex < uint(GI_DUGI_FIXED_RAYS)){
return dugiSphericalFibonacci(float(rayIndex), float(GI_DUGI_FIXED_RAYS));
}
#endif
return ddgiRayDirection(int(rayIndex), randomRotation);
return dugiRayDirection(int(rayIndex), randomRotation);
}
// Encode one traced ray into the ray-data image value, applying the fixed-vs-random distance convention above.
@ -44,7 +44,7 @@ vec3 ddgiTraceRayDirection(const in uint rayIndex, const in mat3 randomRotation)
// aHit/aBackface/aHitDistance : closest-hit result from any backend; aHit=false => sky/miss.
// aMissDistance : the distance to store on a miss (typically the ray tMax).
// aCellSize : cascade cell size, for the local visibility distance clamp.
vec4 ddgiEncodeRayData(const in uint rayIndex,
vec4 dugiEncodeRayData(const in uint rayIndex,
const in vec3 aShadedRadiance,
const in bool aHit,
const in bool aBackface,
@ -52,8 +52,8 @@ vec4 ddgiEncodeRayData(const in uint rayIndex,
const in float aMissDistance,
const in float aCellSize){
#if GI_DDGI_PROBE_RELOCATION
if(rayIndex < uint(GI_DDGI_FIXED_RAYS)){
#if GI_DUGI_PROBE_RELOCATION
if(rayIndex < uint(GI_DUGI_FIXED_RAYS)){
// Fixed ray: only the geometry distance matters (consumed by the relocation + classification passes). Store it SIGNED
// (negative = backface hit) and UNCLAMPED, with no shading and no backface shortening; the probe blend skips it.
return vec4(0.0, 0.0, 0.0, aHit ? (aBackface ? -aHitDistance : aHitDistance) : aMissDistance);
@ -72,8 +72,8 @@ vec4 ddgiEncodeRayData(const in uint rayIndex,
// Clamp the stored visibility distance to a local scale (~1.5 * cell size, mirroring RTXGI's probeMaxRayDistance) so far
// hits and sky misses don't inflate the mean/mean^2 statistics and mask nearby thin-slab occluders. The radiance is
// unaffected (it still gathers light from the full ray length); only this depth channel is clamped.
storedDistance = min(storedDistance, GI_DDGI_VISIBILITY_MAX_DISTANCE_SCALE * aCellSize);
storedDistance = min(storedDistance, GI_DUGI_VISIBILITY_MAX_DISTANCE_SCALE * aCellSize);
return vec4(aShadedRadiance, storedDistance);
}
#endif // GI_DDGI_RAYDATA_GLSL
#endif // GI_DUGI_RAYDATA_GLSL

View file

@ -1,11 +1,11 @@
#version 460 core
// DDGI probe relocation pass (RTXGI-style, read-only consumer of the trace's fixed rays — no re-tracing).
// DUGI probe relocation pass (RTXGI-style, read-only consumer of the trace's fixed rays — no re-tracing).
//
// One thread per probe. Reads the SIGNED distances of the GI_DDGI_FIXED_RAYS fixed rays that the trace pass wrote into the
// One thread per probe. Reads the SIGNED distances of the GI_DUGI_FIXED_RAYS fixed rays that the trace pass wrote into the
// ray-data image (negative = backface hit), reconstructs their (unrotated) directions, and from the closest/farthest
// front/back faces computes a world-space relocation OFFSET that pushes a probe out of geometry it is embedded in (or away
// from a too-close surface), clamped to GI_DDGI_PROBE_MAX_OFFSET * cellSize (ellipsoid). Writes only the offset (xyz) of the
// from a too-close surface), clamped to GI_DUGI_PROBE_MAX_OFFSET * cellSize (ellipsoid). Writes only the offset (xyz) of the
// probe-data image and preserves the state (w), which the classification pass owns. Runs after the trace, before the
// classification pass.
@ -17,16 +17,16 @@ layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
// UBO + probe addressing + toroidal helpers + fixed-ray / relocation tunables. No TLAS / gather needed (no tracing here).
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 1
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
#include "gi_ddgi_raydata.glsl"
// ddgiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_ddgi.glsl
#include "global_illumination_dugi.glsl"
#include "gi_dugi_raydata.glsl"
// dugiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_dugi.glsl
// Ray data written by the trace this frame: rgb = radiance (0 for fixed rays), a = distance. For the FIXED rays (index <
// GI_DDGI_FIXED_RAYS) the distance is SIGNED (negative = backface) and UNCLAMPED. Binding 1 in the shared DDGI compute set.
// GI_DUGI_FIXED_RAYS) the distance is SIGNED (negative = backface) and UNCLAMPED. Binding 1 in the shared DUGI compute set.
// Probe data: xyz = relocation offset (this pass), w = state (classification pass). Read-modify-write to preserve w.
#include "gi_ddgi_pushconstants.glsl"
#include "gi_dugi_pushconstants.glsl"
void main(){
uint globalProbeIndex = gl_GlobalInvocationID.x;
@ -37,19 +37,19 @@ void main(){
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade); // physical storage slot
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade); // physical storage slot
float cellSize = ddgiData.ddgiCascadeCellSizes[cascadeIndex].x;
float cellSize = dugiData.dugiCascadeCellSizes[cascadeIndex].x;
// Hoist the master->sub-pointer derefs once.
DDGIRayDataBuffer rayData = ddgiData.rayData;
DDGIProbeDataBuffer probeDataBuf = ddgiData.probeData;
DUGIRayDataBuffer rayData = dugiData.rayData;
DUGIProbeDataBuffer probeDataBuf = dugiData.probeData;
vec4 probeData = ddgiLoadProbeDataBuffer(probeDataBuf, probeCoord, cascadeIndex);
vec4 probeData = dugiLoadProbeDataBuffer(probeDataBuf, probeCoord, cascadeIndex);
// Start from the previously accumulated offset, except when it is invalid: the image is not cleared on allocation, so on
// this slot's first frame (blend.z) it is uninitialized garbage; and a probe that just toroidally scrolled in carries an
// offset for a different world cell. In both cases restart from zero.
bool firstFrame = (pushConstants.blend.z > 0.5) || ddgiProbeScrolledIn(probeCoord, cascadeIndex);
bool firstFrame = (pushConstants.blend.z > 0.5) || dugiProbeScrolledIn(probeCoord, cascadeIndex);
vec3 offset = firstFrame ? vec3(0.0) : probeData.xyz;
float closestBackfaceDist = 1e30; vec3 closestBackfaceDir = vec3(0.0);
@ -57,9 +57,9 @@ void main(){
float farthestFrontfaceDist = 0.0; vec3 farthestFrontfaceDir = vec3(0.0);
int backfaceCount = 0;
for(int i = 0; i < GI_DDGI_FIXED_RAYS; i++){
float signedDist = ddgiLoadRay(rayData, globalProbeIndex, uint(i), pushConstants.params.w).a;
vec3 dir = ddgiTraceRayDirection(uint(i), mat3(1.0));
for(int i = 0; i < GI_DUGI_FIXED_RAYS; i++){
float signedDist = dugiLoadRay(rayData, globalProbeIndex, uint(i), pushConstants.params.w).a;
vec3 dir = dugiTraceRayDirection(uint(i), mat3(1.0));
if(signedDist < 0.0){
// Backface hit (probe behind this surface).
float d = -signedDist;
@ -81,8 +81,8 @@ void main(){
}
}
float backfaceFraction = float(backfaceCount) / float(GI_DDGI_FIXED_RAYS);
float minFrontface = GI_DDGI_PROBE_MIN_FRONTFACE * cellSize;
float backfaceFraction = float(backfaceCount) / float(GI_DUGI_FIXED_RAYS);
float minFrontface = GI_DUGI_PROBE_MIN_FRONTFACE * cellSize;
// RTXGI ProbeRelocationCS controller: compute a CANDIDATE offset (relative to the current one) and only commit it if it
// stays inside the max-offset ellipsoid. The three branches are mutually exclusive; the sentinel means "no move this
@ -90,7 +90,7 @@ void main(){
// what makes the relocation converge to a stable minimal offset instead of oscillating.
vec3 fullOffset = vec3(1e27); // sentinel = no candidate
if((backfaceFraction > GI_DDGI_PROBE_BACKFACE_THRESHOLD) && (closestBackfaceDist < 1e29)){
if((backfaceFraction > GI_DUGI_PROBE_BACKFACE_THRESHOLD) && (closestBackfaceDist < 1e29)){
// Probe is inside geometry: push it out past the closest backface, leaving half a min-frontface of clearance.
fullOffset = offset + (closestBackfaceDir * (closestBackfaceDist + (minFrontface * 0.5)));
}else if(closestFrontfaceDist < minFrontface){
@ -109,15 +109,15 @@ void main(){
}
}
// Commit the candidate only if it stays inside the |offset / cellSize| <= GI_DDGI_PROBE_MAX_OFFSET ellipsoid; otherwise
// Commit the candidate only if it stays inside the |offset / cellSize| <= GI_DUGI_PROBE_MAX_OFFSET ellipsoid; otherwise
// keep the previous offset (clamping to the boundary instead would leave the probe sitting on the rim and jittering). A
// probe that would need to move further than this to escape geometry stays put and gets marked INACTIVE by classification.
if(fullOffset.x < 1e26){
vec3 normalizedOffset = fullOffset / cellSize;
if(dot(normalizedOffset, normalizedOffset) < (GI_DDGI_PROBE_MAX_OFFSET * GI_DDGI_PROBE_MAX_OFFSET)){
if(dot(normalizedOffset, normalizedOffset) < (GI_DUGI_PROBE_MAX_OFFSET * GI_DUGI_PROBE_MAX_OFFSET)){
offset = fullOffset;
}
}
ddgiStoreProbeDataBuffer(probeDataBuf, probeCoord, cascadeIndex, vec4(offset, probeData.w)); // preserve state (w), owned by the classification pass
dugiStoreProbeDataBuffer(probeDataBuf, probeCoord, cascadeIndex, vec4(offset, probeData.w)); // preserve state (w), owned by the classification pass
}

View file

@ -1,8 +1,8 @@
#version 460 core
// DDGI probe ray-data PRODUCER — non-raytraced Reflective Shadow Map (RSM) fallback.
// DUGI probe ray-data PRODUCER — non-raytraced Reflective Shadow Map (RSM) fallback.
//
// A drop-in replacement for gi_ddgi_trace.comp for hardware without VK_KHR_ray_query. Instead of tracing rays against the
// A drop-in replacement for gi_dugi_trace.comp for hardware without VK_KHR_ray_query. Instead of tracing rays against the
// scene TLAS, it treats a subset of the sun's RSM texels as virtual point lights (VPLs) and "splats" them into the same
// per-(ray, probe) ray-data slots the ray-query trace would have written — so every downstream consumer (irradiance /
// visibility / glossy blend, border, relocation, classification) stays byte-for-byte identical and needs no change.
@ -16,8 +16,8 @@
// the probe aligns with this ray's deterministic spherical-Fibonacci direction, weighted by a Phong lobe around it.
//
// Extension: multi-bounce and particle injection (the "later stages" above) are now implemented, and the probe position is
// relocated (ddgiLoadProbeData) like the trace — all shared with the trace via gi_ddgi_multibounce.glsl and
// gi_ddgi_particle_inject.glsl. It still reads the RSM flux directly (no lights / no global descriptor set).
// relocated (dugiLoadProbeData) like the trace — all shared with the trace via gi_dugi_multibounce.glsl and
// gi_dugi_particle_inject.glsl. It still reads the RSM flux directly (no lights / no global descriptor set).
#extension GL_EXT_nonuniform_qualifier : require
#extension GL_GOOGLE_include_directive : require
@ -50,73 +50,73 @@ layout(set = 0, binding = 3, std140) uniform RSMData {
ivec4 countSamples; // unused here (x = countSamples, y = countOcclusionSamples)
} rsm;
// --- Set 1: DDGI field resources (same set/bindings the trace uses) --------------------------------------------------
// binding 0 = ddgiData SSBO (cascade globals + the BDA sub-buffer pointers, incl. rayData), pulled in by the include below.
// binding 4 = the 6 environment cubemaps (sky on ray miss), mirroring gi_ddgi_trace.comp.
// binding 2 = previous-frame oct irradiance read + binding 3 = visibility read (multi-bounce; declared by gi_ddgi_multibounce.glsl).
// --- Set 1: DUGI field resources (same set/bindings the trace uses) --------------------------------------------------
// binding 0 = dugiData SSBO (cascade globals + the BDA sub-buffer pointers, incl. rayData), pulled in by the include below.
// binding 4 = the 6 environment cubemaps (sky on ray miss), mirroring gi_dugi_trace.comp.
// binding 2 = previous-frame oct irradiance read + binding 3 = visibility read (multi-bounce; declared by gi_dugi_multibounce.glsl).
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 1
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
// Optional multi-bounce (default on): sample the previous frame's irradiance field at each gather hit and feed it back as the
// indirect term. Enables the probe sampling path (GLOBAL_ILLUMINATION_DDGI_SAMPLE) in the DDGI header below.
#ifndef GI_DDGI_MULTIBOUNCE
#define GI_DDGI_MULTIBOUNCE 1
// indirect term. Enables the probe sampling path (GLOBAL_ILLUMINATION_DUGI_SAMPLE) in the DUGI header below.
#ifndef GI_DUGI_MULTIBOUNCE
#define GI_DUGI_MULTIBOUNCE 1
#endif
#if GI_DDGI_MULTIBOUNCE
#define GLOBAL_ILLUMINATION_DDGI_SAMPLE
#if GI_DUGI_MULTIBOUNCE
#define GLOBAL_ILLUMINATION_DUGI_SAMPLE
#endif
#include "global_illumination_ddgi.glsl" // probe field dims + addressing + ddgiData SSBO (via gi_ddgi_data.glsl)
#include "gi_ddgi_raydata.glsl" // shared ray-data contract: ddgiTraceRayDirection / ddgiEncodeRayData / ddgiStoreRay
#include "gi_ddgi_pushconstants.glsl" // the shared DDGI push-constant block (randomRotation / params / blend / ...)
#include "global_illumination_dugi.glsl" // probe field dims + addressing + dugiData SSBO (via gi_dugi_data.glsl)
#include "gi_dugi_raydata.glsl" // shared ray-data contract: dugiTraceRayDirection / dugiEncodeRayData / dugiStoreRay
#include "gi_dugi_pushconstants.glsl" // the shared DUGI push-constant block (randomRotation / params / blend / ...)
layout(set = 1, binding = 4) uniform samplerCube uDDGIEnvironmentMaps[6];
layout(set = 1, binding = 4) uniform samplerCube uDUGIEnvironmentMaps[6];
// Sky diffuse irradiance in a direction (byte-identical to gi_ddgi_trace.comp's ddgiGatherSky): Lambertian env A dual-
// Sky diffuse irradiance in a direction (byte-identical to gi_dugi_trace.comp's dugiGatherSky): Lambertian env A dual-
// blended with the atmosphere-inclusive env B by env B's coverage alpha. Used for ray slots that gather no VPL (a "miss").
vec3 ddgiGatherSky(const in vec3 dir){
vec3 irradiance = textureLod(uDDGIEnvironmentMaps[2], dir, 0.0).xyz; // Lambertian (diffuse irradiance), env A
vec4 envB = textureLod(uDDGIEnvironmentMaps[5], dir, 0.0); // Lambertian, env B (atmosphere); w = coverage
vec3 dugiGatherSky(const in vec3 dir){
vec3 irradiance = textureLod(uDUGIEnvironmentMaps[2], dir, 0.0).xyz; // Lambertian (diffuse irradiance), env A
vec4 envB = textureLod(uDUGIEnvironmentMaps[5], dir, 0.0); // Lambertian, env B (atmosphere); w = coverage
return mix(irradiance, envB.xyz, envB.w);
}
// Shared previous-frame probe-field reads (binding 2/3) + the relocation offset + the SH irradiance loader, and the particle
// LBVH inject — the same machinery the ray-query trace uses, factored out here to avoid duplicating it.
#include "gi_ddgi_multibounce.glsl"
#include "gi_ddgi_particle_inject.glsl"
#include "gi_dugi_multibounce.glsl"
#include "gi_dugi_particle_inject.glsl"
// --- RSM VPL sampling parameters -------------------------------------------------------------------------------------
// Number of RSM texels sampled per ray slot per frame, spread over the whole RSM with a 2D spherical-Fibonacci (golden-
// ratio) pattern so each frame's random rotation re-decorrelates the set. More samples = less noise / fewer missed VPLs,
// at a linear cost. Env/define overridable (kept tunable, default 64).
#ifndef GI_DDGI_RSM_SPLAT_SAMPLES
#define GI_DDGI_RSM_SPLAT_SAMPLES 64
#ifndef GI_DUGI_RSM_SPLAT_SAMPLES
#define GI_DUGI_RSM_SPLAT_SAMPLES 64
#endif
// Phong-lobe exponent of the per-ray-slot gather: w = pow(max(dot(rayDir, dirToVPL), 0), sharpness). Wider (smaller) =
// smoother but more direction-blurred; sharper = crisper VPL direction but more noise/empty slots. ~16 covers roughly one
// spherical-Fibonacci ray spacing for 128 rays. Env/define overridable.
#ifndef GI_DDGI_RSM_SPLAT_SHARPNESS
#define GI_DDGI_RSM_SPLAT_SHARPNESS 16.0
#ifndef GI_DUGI_RSM_SPLAT_SHARPNESS
#define GI_DUGI_RSM_SPLAT_SHARPNESS 16.0
#endif
// Nearest-hit band of the two-pass gather: pass 1 finds the closest VPL in the lobe (a real ray hits only the nearest
// surface), pass 2 keeps only VPLs up to nearestDistance * this factor, so distant lit surfaces behind the nearest hit do
// not bleed in. 1.0 = hard nearest, larger = softer (more samples, smoother, but more far-leak). Env/define overridable.
#ifndef GI_DDGI_RSM_SPLAT_NEAR_BAND
#define GI_DDGI_RSM_SPLAT_NEAR_BAND 1.5
#ifndef GI_DUGI_RSM_SPLAT_NEAR_BAND
#define GI_DUGI_RSM_SPLAT_NEAR_BAND 1.5
#endif
// Optional global energy scale on the gathered VPL radiance, so the fallback can be calibrated against the ray-traced
// look / the RSM flux units without touching the math. Env/define overridable (default 1.0).
#ifndef GI_DDGI_RSM_SPLAT_INTENSITY
#define GI_DDGI_RSM_SPLAT_INTENSITY 1.0
#ifndef GI_DUGI_RSM_SPLAT_INTENSITY
#define GI_DUGI_RSM_SPLAT_INTENSITY 1.0
#endif
const float GI_DDGI_RSM_ONE_OVER_PI = 0.3183098861837907; // 1/PI: Lambertian outgoing radiance = flux / PI
const float GI_DUGI_RSM_ONE_OVER_PI = 0.3183098861837907; // 1/PI: Lambertian outgoing radiance = flux / PI
// 2D low-discrepancy point on the unit square (golden-ratio / spherical-Fibonacci style) for sample i of n.
vec2 ddgiRSMSampleUV(const in int i, const in int n){
vec2 dugiRSMSampleUV(const in int i, const in int n){
const float PHI = 1.6180339887498949; // golden ratio
return vec2(fract((float(i) + 0.5) * (1.0 / float(n))), fract(float(i) * (PHI - 1.0)));
}
@ -135,30 +135,30 @@ void main(){
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade); // physical storage slot
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade); // physical storage slot
// Toroidal scrolling: the iterated slot is physical; its world position comes from the logical lattice coordinate.
ivec3 logicalProbeCoord = ddgiProbeLogicalCoord(probeCoord, cascadeIndex);
vec3 probePosition = ddgiProbeGridToWorld(logicalProbeCoord, cascadeIndex);
#if GI_DDGI_PROBE_RELOCATION
ivec3 logicalProbeCoord = dugiProbeLogicalCoord(probeCoord, cascadeIndex);
vec3 probePosition = dugiProbeGridToWorld(logicalProbeCoord, cascadeIndex);
#if GI_DUGI_PROBE_RELOCATION
// Gather from the relocated probe position (the relocation pass pushed it out of geometry), so the gathered irradiance/
// visibility match the position the shading sampler reads it back at. Skip on this slot's first frame (probe data is still
// uninitialized garbage) until the relocation pass has written a value at least once. blend.z = first-frame flag.
if(pushConstants.blend.z < 0.5){
probePosition += ddgiLoadProbeData(probeCoord, cascadeIndex).xyz;
probePosition += dugiLoadProbeData(probeCoord, cascadeIndex).xyz;
}
#endif
mat3 randomRotation = mat3(pushConstants.randomRotation0.xyz, pushConstants.randomRotation1.xyz, pushConstants.randomRotation2.xyz);
vec3 rayDirection = ddgiTraceRayDirection(rayIndex, randomRotation); // contract: fixed (unrotated) vs random (rotated)
vec3 rayDirection = dugiTraceRayDirection(rayIndex, randomRotation); // contract: fixed (unrotated) vs random (rotated)
float tMax = max(ddgiData.ddgiCascadeCellSizes[cascadeIndex].w, 1e-2);
float cellSize = ddgiData.ddgiCascadeCellSizes[cascadeIndex].x;
float tMax = max(dugiData.dugiCascadeCellSizes[cascadeIndex].w, 1e-2);
float cellSize = dugiData.dugiCascadeCellSizes[cascadeIndex].x;
// Fixed rays (relocation/classification) carry no radiance — and without an actual trace we cannot report their signed
// geometry distance, so the RSM fallback runs with relocation disabled (the Pascal side gates it off). Guard anyway: a
// fixed ray just writes a miss so the encode keeps the contract.
bool isFixed = ddgiRayIsFixed(rayIndex);
bool isFixed = dugiRayIsFixed(rayIndex);
// Gather: accumulate the RSM VPLs that fall within this ray slot's Phong lobe. As a ray *hit*, a Lambertian VPL emits
// radiance flux/PI towards the probe (view-independent), with no 1/d^2 falloff on the stored radiance (that is a directional
@ -179,8 +179,8 @@ void main(){
// Pass 1 (nearest-hit selection): the distance of the closest VPL that passes this slot's lobe + half-space gates.
float nearestDistance = tMax;
for(int sampleIndex = 0; sampleIndex < GI_DDGI_RSM_SPLAT_SAMPLES; sampleIndex++){
vec2 uv = ddgiRSMSampleUV(sampleIndex, GI_DDGI_RSM_SPLAT_SAMPLES);
for(int sampleIndex = 0; sampleIndex < GI_DUGI_RSM_SPLAT_SAMPLES; sampleIndex++){
vec2 uv = dugiRSMSampleUV(sampleIndex, GI_DUGI_RSM_SPLAT_SAMPLES);
vec4 normalUsed = textureLod(uReflectiveShadowMapNormalUsed, uv, 0.0);
if(normalUsed.w < 0.5){
continue; // no surface rasterized into this RSM texel
@ -206,10 +206,10 @@ void main(){
// Pass 2 (accumulate within the near band, 1/d^2-biased towards the nearest). Clamp the band to the cascade reach so it
// never reaches past tMax; with an empty pass 1 (nearestDistance == tMax) this leaves the slot a sky miss.
float bandDistance = min(nearestDistance * GI_DDGI_RSM_SPLAT_NEAR_BAND, tMax);
for(int sampleIndex = 0; sampleIndex < GI_DDGI_RSM_SPLAT_SAMPLES; sampleIndex++){
float bandDistance = min(nearestDistance * GI_DUGI_RSM_SPLAT_NEAR_BAND, tMax);
for(int sampleIndex = 0; sampleIndex < GI_DUGI_RSM_SPLAT_SAMPLES; sampleIndex++){
vec2 uv = ddgiRSMSampleUV(sampleIndex, GI_DDGI_RSM_SPLAT_SAMPLES);
vec2 uv = dugiRSMSampleUV(sampleIndex, GI_DUGI_RSM_SPLAT_SAMPLES);
vec4 normalUsed = textureLod(uReflectiveShadowMapNormalUsed, uv, 0.0);
if(normalUsed.w < 0.5){
@ -236,10 +236,10 @@ void main(){
if(alignment <= 0.0){
continue; // the VPL is not in this ray slot's hemisphere
}
float weight = pow(alignment, GI_DDGI_RSM_SPLAT_SHARPNESS) / max(distanceToVPL * distanceToVPL, 1e-4);
float weight = pow(alignment, GI_DUGI_RSM_SPLAT_SHARPNESS) / max(distanceToVPL * distanceToVPL, 1e-4);
vec3 flux = max(vec3(0.0), textureLod(uReflectiveShadowMapColor, uv, 0.0).xyz);
vec3 radiance = flux * GI_DDGI_RSM_ONE_OVER_PI;
vec3 radiance = flux * GI_DUGI_RSM_ONE_OVER_PI;
radianceSum += radiance * weight;
positionSum += vplPosition * weight;
@ -262,27 +262,27 @@ void main(){
vec3 vplPosition = positionSum / weightSum;
vec3 vplNormal = normalize(normalSum);
hitDistance = distanceSum / weightSum;
#if GI_DDGI_MULTIBOUNCE
#if GI_DUGI_MULTIBOUNCE
// The VPL re-reflects the previous-frame indirect light (multi-bounce). The RSM stores flux, not a separate albedo, so the
// secondary bounce assumes unit albedo (the 1/PI Lambertian normalization only); pushConstants.blend.y is the feedback
// strength that bounds it against runaway, the same feedback term the trace producer applies at its hit.
float skyVisibilityUnused;
vec3 previousFrameIndirect = ddgiSampleIrradiance(vplPosition, vplNormal, -rayDirection, skyVisibilityUnused) * pushConstants.blend.y;
vec3 previousFrameIndirect = dugiSampleIrradiance(vplPosition, vplNormal, -rayDirection, skyVisibilityUnused) * pushConstants.blend.y;
if(any(isnan(previousFrameIndirect)) || any(isinf(previousFrameIndirect))){
previousFrameIndirect = vec3(0.0);
}
radiance += GI_DDGI_RSM_ONE_OVER_PI * previousFrameIndirect;
radiance += GI_DUGI_RSM_ONE_OVER_PI * previousFrameIndirect;
#endif
outRadiance = radiance * GI_DDGI_RSM_SPLAT_INTENSITY;
outRadiance = radiance * GI_DUGI_RSM_SPLAT_INTENSITY;
hit = true;
}else{
outRadiance = ddgiGatherSky(rayDirection);
outRadiance = dugiGatherSky(rayDirection);
hit = false;
}
// Particle LBVH inject (shared with the trace via gi_ddgi_particle_inject.glsl): a closest opaque particle overrides the
// Particle LBVH inject (shared with the trace via gi_dugi_particle_inject.glsl): a closest opaque particle overrides the
// radiance + shortens the distance, transparent/additive particles add emission. tMin = 1e-3, miss bound = tMax.
ddgiInjectParticles(probePosition, rayDirection, 1e-3, tMax, outRadiance, hit, backface, hitDistance);
dugiInjectParticles(probePosition, rayDirection, 1e-3, tMax, outRadiance, hit, backface, hitDistance);
if(any(isnan(outRadiance)) || any(isinf(outRadiance))){
outRadiance = vec3(0.0);
@ -290,7 +290,7 @@ void main(){
}
// Encode per the shared ray-data contract (random ray: shaded radiance + locally-clamped distance; no backface here).
DDGIRayDataBuffer rayData = ddgiData.rayData; // launder through a local (readonly master field -> non-readonly ref)
ddgiStoreRay(rayData, globalProbeIndex, rayIndex, raysPerProbe,
ddgiEncodeRayData(rayIndex, outRadiance, hit, false, hitDistance, tMax, cellSize));
DUGIRayDataBuffer rayData = dugiData.rayData; // launder through a local (readonly master field -> non-readonly ref)
dugiStoreRay(rayData, globalProbeIndex, rayIndex, raysPerProbe,
dugiEncodeRayData(rayIndex, outRadiance, hit, false, hitDistance, tMax, cellSize));
}

View file

@ -1,8 +1,8 @@
#version 460 core
// DDGI probe ray tracing pass.
// DUGI probe ray tracing pass.
//
// For every probe of every cascade it traces GI_DDGI_RAYS_PER_PROBE rays against the scene TLAS and stores, per ray,
// For every probe of every cascade it traces GI_DUGI_RAYS_PER_PROBE rays against the scene TLAS and stores, per ray,
// the shaded radiance towards the probe plus the hit distance. The probe update passes then integrate this ray data
// into the irradiance and visibility probe data structures. Ray directions are generated deterministically (spherical
// Fibonacci rotated by a per-frame random rotation) so the update passes can reconstruct them without a direction buffer.
@ -67,15 +67,15 @@ layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
// the whole dual term is a no-op when no planet/atmosphere is active). Integrating this per missed ray bakes the sky into
// the probe irradiance, correctly occluded (a probe whose rays mostly hit geometry sees little sky).
layout(set = 1, binding = 4) uniform samplerCube uDDGIEnvironmentMaps[6];
layout(set = 1, binding = 4) uniform samplerCube uDUGIEnvironmentMaps[6];
vec3 ddgiGatherSky(const in vec3 dir){
vec3 irradiance = textureLod(uDDGIEnvironmentMaps[2], dir, 0.0).xyz; // Lambertian (diffuse irradiance), env A
vec4 envB = textureLod(uDDGIEnvironmentMaps[5], dir, 0.0); // Lambertian, env B (atmosphere); w = coverage
vec3 dugiGatherSky(const in vec3 dir){
vec3 irradiance = textureLod(uDUGIEnvironmentMaps[2], dir, 0.0).xyz; // Lambertian (diffuse irradiance), env A
vec4 envB = textureLod(uDUGIEnvironmentMaps[5], dir, 0.0); // Lambertian, env B (atmosphere); w = coverage
return mix(irradiance, envB.xyz, envB.w);
}
#define GI_GATHER_SKY(dir) ddgiGatherSky(dir)
#define GI_GATHER_SKY(dir) dugiGatherSky(dir)
// Per-planet blend/grass maps (bindless, indexed by planet object index) on set 2, so planet hits get the full material
// layer blend instead of just the default ground material. RT closest-hit only; the RSM backend reads albedo from the RSM
@ -87,10 +87,10 @@ vec3 ddgiGatherSky(const in vec3 dir){
// Global GI-emissive master regulators, delivered via the push constants. The push block is included further below, so these
// file-scope globals (set in main() before any hit is shaded) let giGatherShadeHit read the values without a signature change.
float ddgiEmissiveScale = 1.0;
float ddgiEmissiveMax = uintBitsToFloat(0x7f800000u);
#define GI_GATHER_EMISSIVE_SCALE ddgiEmissiveScale
#define GI_GATHER_EMISSIVE_MAX ddgiEmissiveMax
float dugiEmissiveScale = 1.0;
float dugiEmissiveMax = uintBitsToFloat(0x7f800000u);
#define GI_GATHER_EMISSIVE_SCALE dugiEmissiveScale
#define GI_GATHER_EMISSIVE_MAX dugiEmissiveMax
#include "gi_rt_gather.glsl"
@ -119,21 +119,21 @@ layout(set = 2, binding = 3, std140) uniform RSMData {
} rsm;
// Number of RSM texels sampled per probe ray (spread over the whole RSM); more = less noise, linear cost. Env/define overridable.
#ifndef GI_DDGI_RSM_SAMPLES
#define GI_DDGI_RSM_SAMPLES 64
#ifndef GI_DUGI_RSM_SAMPLES
#define GI_DUGI_RSM_SAMPLES 64
#endif
// Phong-lobe exponent of the per-ray RSM gather (how tightly a VPL must align with the ray to count). Env/define overridable.
#ifndef GI_DDGI_RSM_SHARPNESS
#define GI_DDGI_RSM_SHARPNESS 16.0
#ifndef GI_DUGI_RSM_SHARPNESS
#define GI_DUGI_RSM_SHARPNESS 16.0
#endif
// Nearest-hit band of the two-pass gather: a real ray hits only the nearest surface, so VPLs farther than nearestDistance *
// this factor are behind it and rejected (else distant lit surfaces leak in). 1.0 = hard nearest, larger = softer. Overridable.
#ifndef GI_DDGI_RSM_NEAR_BAND
#define GI_DDGI_RSM_NEAR_BAND 1.5
#ifndef GI_DUGI_RSM_NEAR_BAND
#define GI_DUGI_RSM_NEAR_BAND 1.5
#endif
// 2D low-discrepancy point on the unit square (golden ratio) for RSM sample i of n.
vec2 ddgiRSMSampleUV(const in int i, const in int n){
vec2 dugiRSMSampleUV(const in int i, const in int n){
const float PHI = 1.6180339887498949;
return vec2(fract((float(i) + 0.5) * (1.0 / float(n))), fract(float(i) * (PHI - 1.0)));
}
@ -157,13 +157,13 @@ GIGatherSurface giTraceClosestHit(const in vec3 origin, const in vec3 direction,
float distanceSum = 0.0;
float weightSum = 0.0;
// Two-pass closest-hit gather (mirrors gi_ddgi_rsm_splat.comp): a real ray hits only the NEAREST surface, so VPLs much
// Two-pass closest-hit gather (mirrors gi_dugi_rsm_splat.comp): a real ray hits only the NEAREST surface, so VPLs much
// farther than the nearest lobe hit are behind it and must not bleed in. Pass 1 finds the nearest lobe VPL distance; pass 2
// accumulates only VPLs within nearestDistance * NEAR_BAND, weighted alignment^sharpness / d^2 (the 1/d^2 is a SELECTION bias
// towards the nearest, NOT a falloff on the stored albedo — it cancels in the normalized average).
float nearestDistance = tMax;
for(int sampleIndex = 0; sampleIndex < GI_DDGI_RSM_SAMPLES; sampleIndex++){
vec2 uv = ddgiRSMSampleUV(sampleIndex, GI_DDGI_RSM_SAMPLES);
for(int sampleIndex = 0; sampleIndex < GI_DUGI_RSM_SAMPLES; sampleIndex++){
vec2 uv = dugiRSMSampleUV(sampleIndex, GI_DUGI_RSM_SAMPLES);
vec4 normalUsed = textureLod(uReflectiveShadowMapNormalUsed, uv, 0.0);
if(normalUsed.w < 0.5){
continue; // no surface rasterized into this RSM texel
@ -187,10 +187,10 @@ GIGatherSurface giTraceClosestHit(const in vec3 origin, const in vec3 direction,
nearestDistance = min(nearestDistance, distanceToVPL);
}
float bandDistance = min(nearestDistance * GI_DDGI_RSM_NEAR_BAND, tMax); // clamp to the cascade reach (empty pass 1 => sky miss)
for(int sampleIndex = 0; sampleIndex < GI_DDGI_RSM_SAMPLES; sampleIndex++){
float bandDistance = min(nearestDistance * GI_DUGI_RSM_NEAR_BAND, tMax); // clamp to the cascade reach (empty pass 1 => sky miss)
for(int sampleIndex = 0; sampleIndex < GI_DUGI_RSM_SAMPLES; sampleIndex++){
vec2 uv = ddgiRSMSampleUV(sampleIndex, GI_DDGI_RSM_SAMPLES);
vec2 uv = dugiRSMSampleUV(sampleIndex, GI_DUGI_RSM_SAMPLES);
vec4 normalUsed = textureLod(uReflectiveShadowMapNormalUsed, uv, 0.0);
if(normalUsed.w < 0.5){
@ -217,7 +217,7 @@ GIGatherSurface giTraceClosestHit(const in vec3 origin, const in vec3 direction,
if(alignment <= 0.0){
continue;
}
float weight = pow(alignment, GI_DDGI_RSM_SHARPNESS) / max(distanceToVPL * distanceToVPL, 1e-4);
float weight = pow(alignment, GI_DUGI_RSM_SHARPNESS) / max(distanceToVPL * distanceToVPL, 1e-4);
vec3 albedo = max(vec3(0.0), textureLod(uReflectiveShadowMapColor, uv, 0.0).xyz);
albedoSum += albedo * weight;
@ -241,29 +241,29 @@ GIGatherSurface giTraceClosestHit(const in vec3 origin, const in vec3 direction,
// Optional multi-bounce: sample the previous frame's irradiance field at each ray hit and feed it back as the indirect
// term, giving "infinite" diffuse bounces. Compile-time toggle (default on) plus a runtime strength (push constant
// blend.y, 0 = effectively first-bounce only). Only the L1 SH storage variant is supported for the feedback read.
#ifndef GI_DDGI_MULTIBOUNCE
#define GI_DDGI_MULTIBOUNCE 1
#ifndef GI_DUGI_MULTIBOUNCE
#define GI_DUGI_MULTIBOUNCE 1
#endif
// DDGI probe field definition (UBO + addressing). With multi-bounce we also enable the probe sampling path.
// DUGI probe field definition (UBO + addressing). With multi-bounce we also enable the probe sampling path.
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 1
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#if GI_DDGI_MULTIBOUNCE
#define GLOBAL_ILLUMINATION_DDGI_SAMPLE
#if GI_DUGI_MULTIBOUNCE
#define GLOBAL_ILLUMINATION_DUGI_SAMPLE
#endif
#include "global_illumination_ddgi.glsl"
#include "gi_ddgi_raydata.glsl" // shared ray-data contract: ddgiTraceRayDirection / ddgiEncodeRayData / ddgiRayIsFixed
// ddgiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_ddgi.glsl
#include "global_illumination_dugi.glsl"
#include "gi_dugi_raydata.glsl" // shared ray-data contract: dugiTraceRayDirection / dugiEncodeRayData / dugiRayIsFixed
// dugiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_dugi.glsl
// Per-ray output (rgb = radiance towards the probe, a = hit distance / signed for fixed rays) now lives in the master's
// ray-data BDA storage buffer (full precision, no image-format cap), written via ddgiStoreRay below.
// ray-data BDA storage buffer (full precision, no image-format cap), written via dugiStoreRay below.
#include "gi_ddgi_pushconstants.glsl"
#include "gi_dugi_pushconstants.glsl"
// Shared previous-frame probe-field reads (set 1 binding 2/3) + the relocation offset + the SH irradiance loader — factored
// out so the non-raytraced RSM producers reuse exactly this machinery instead of duplicating it. (The reads are declared
// read-only here; the update stages overwrite these images only after this trace stage.)
#include "gi_ddgi_multibounce.glsl"
#include "gi_dugi_multibounce.glsl"
void main(){
@ -278,32 +278,32 @@ void main(){
}
// Publish the global GI-emissive master regulators (push) into the file-scope globals the gather's giGatherShadeHit reads.
ddgiEmissiveScale = pushConstants.emissiveGIParticleCount.x;
ddgiEmissiveMax = pushConstants.emissiveGIParticleCount.y;
dugiEmissiveScale = pushConstants.emissiveGIParticleCount.x;
dugiEmissiveMax = pushConstants.emissiveGIParticleCount.y;
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade); // physical storage slot
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade); // physical storage slot
// Toroidal scrolling: the iterated slot is physical; its world position comes from the logical lattice coordinate.
ivec3 logicalProbeCoord = ddgiProbeLogicalCoord(probeCoord, cascadeIndex);
vec3 probePosition = ddgiProbeGridToWorld(logicalProbeCoord, cascadeIndex);
#if GI_DDGI_PROBE_RELOCATION
ivec3 logicalProbeCoord = dugiProbeLogicalCoord(probeCoord, cascadeIndex);
vec3 probePosition = dugiProbeGridToWorld(logicalProbeCoord, cascadeIndex);
#if GI_DUGI_PROBE_RELOCATION
// Trace from the relocated probe position (the relocation pass pushed it out of any geometry it was embedded in), so the
// gathered irradiance/visibility match the position the shading sampler reads it back at. The probe-data image is not
// cleared on allocation, so on this slot's first frame (blend.z) it still holds uninitialized garbage -> skip the offset
// until the relocation pass has written a (clamped) value at least once.
if(pushConstants.blend.z < 0.5){
probePosition += ddgiLoadProbeData(probeCoord, cascadeIndex).xyz;
probePosition += dugiLoadProbeData(probeCoord, cascadeIndex).xyz;
}
#endif
mat3 randomRotation = mat3(pushConstants.randomRotation0.xyz, pushConstants.randomRotation1.xyz, pushConstants.randomRotation2.xyz);
vec3 rayDirection = ddgiTraceRayDirection(rayIndex, randomRotation); // contract: fixed (unrotated) vs random (rotated)
vec3 rayDirection = dugiTraceRayDirection(rayIndex, randomRotation); // contract: fixed (unrotated) vs random (rotated)
float tMin = 1e-3;
float tMax = max(ddgiData.ddgiCascadeCellSizes[cascadeIndex].w, 1e-2);
float cellSize = ddgiData.ddgiCascadeCellSizes[cascadeIndex].x;
float tMax = max(dugiData.dugiCascadeCellSizes[cascadeIndex].w, 1e-2);
float cellSize = dugiData.dugiCascadeCellSizes[cascadeIndex].x;
// Trace via the swappable backend (ray-query by default). Emissive meshes + all analytic lights + ray-traced shadows are
// handled in the gather layer; optional multi-bounce feedback samples the previous frame's irradiance field at the hit.
@ -336,19 +336,19 @@ void main(){
// Shade only the random (blended) rays; fixed rays carry no radiance (the contract encodes only their signed distance).
vec3 radiance = vec3(0.0);
if(!ddgiRayIsFixed(rayIndex)){
if(!dugiRayIsFixed(rayIndex)){
if(surface.hit){
if(surface.backface){
// Backface hit: the probe sits behind/inside this surface (e.g. embedded in a thin ceiling/floor slab). Treat it as
// absorptive (black) so the probe does not accumulate light it should not see — the key fix against emissive panels
// bleeding through thin geometry to the other side. The stored distance is additionally shortened in the ray-data
// encode (gi_ddgi_raydata.glsl) so the visibility (Chebyshev) test registers this slab as a near occluder.
// encode (gi_dugi_raydata.glsl) so the visibility (Chebyshev) test registers this slab as a near occluder.
radiance = vec3(0.0);
}else{
vec3 previousFrameIndirect = vec3(0.0);
#if GI_DDGI_MULTIBOUNCE
#if GI_DUGI_MULTIBOUNCE
float skyVisibilityUnused;
previousFrameIndirect = ddgiSampleIrradiance(surface.position, surface.normal, -rayDirection, skyVisibilityUnused) * pushConstants.blend.y;
previousFrameIndirect = dugiSampleIrradiance(surface.position, surface.normal, -rayDirection, skyVisibilityUnused) * pushConstants.blend.y;
// The probe field is not cleared on allocation; ignore non-finite feedback so NaN/Inf cannot lock into the field.
if(any(isnan(previousFrameIndirect)) || any(isinf(previousFrameIndirect))){
previousFrameIndirect = vec3(0.0);
@ -373,7 +373,7 @@ void main(){
// Encode per the shared ray-data contract (fixed -> signed/unclamped distance, no radiance; random -> shaded radiance +
// backface-shortened, locally-clamped distance).
DDGIRayDataBuffer rayData = ddgiData.rayData; // launder through a local (readonly master field -> non-readonly ref)
ddgiStoreRay(rayData, globalProbeIndex, rayIndex, raysPerProbe,
ddgiEncodeRayData(rayIndex, radiance, effectiveHit, effectiveBackface, effectiveHitDistance, tMax, cellSize));
DUGIRayDataBuffer rayData = dugiData.rayData; // launder through a local (readonly master field -> non-readonly ref)
dugiStoreRay(rayData, globalProbeIndex, rayIndex, raysPerProbe,
dugiEncodeRayData(rayIndex, radiance, effectiveHit, effectiveBackface, effectiveHitDistance, tMax, cellSize));
}

View file

@ -1,8 +1,8 @@
#version 460 core
// DDGI visibility integration pass.
// DUGI visibility integration pass.
//
// Integrates the per-ray hit distances from gi_ddgi_trace.comp into each probe's octahedral visibility tile, storing the
// Integrates the per-ray hit distances from gi_dugi_trace.comp into each probe's octahedral visibility tile, storing the
// mean distance and mean squared distance (RG16F) used by the Chebyshev visibility test in the sampling code. One
// workgroup per probe, one thread per interior visibility texel. Always octahedral, independent of the irradiance
// storage mode.
@ -15,21 +15,21 @@
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET 1
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#include "global_illumination_ddgi.glsl"
#include "gi_ddgi_raydata.glsl"
// ddgiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_ddgi.glsl
#include "global_illumination_dugi.glsl"
#include "gi_dugi_raydata.glsl"
// dugiData (SSBO: cascade globals + sub-buffer pointers) + the sub-buffer accessors come via global_illumination_dugi.glsl
layout(local_size_x = GI_DDGI_VISIBILITY_OCT_SIZE, local_size_y = GI_DDGI_VISIBILITY_OCT_SIZE, local_size_z = 1) in;
layout(local_size_x = GI_DUGI_VISIBILITY_OCT_SIZE, local_size_y = GI_DUGI_VISIBILITY_OCT_SIZE, local_size_z = 1) in;
layout(set = 1, binding = 3, rg32f) uniform image2D uDDGIVisibilityMoments; // x = mean dist, y = mean dist^2 (F32 precision for the Chebyshev test)
layout(set = 1, binding = 4, r8) uniform image2D uDDGIVisibilitySky; // x = sky visibility (0..1)
layout(set = 1, binding = 3, rg32f) uniform image2D uDUGIVisibilityMoments; // x = mean dist, y = mean dist^2 (F32 precision for the Chebyshev test)
layout(set = 1, binding = 4, r8) uniform image2D uDUGIVisibilitySky; // x = sky visibility (0..1)
#include "gi_ddgi_pushconstants.glsl"
#include "gi_dugi_pushconstants.glsl"
// Shared cache of this probe's traced rays for the whole workgroup (one workgroup == one probe): xyz = traced ray
// direction, w = raw hit distance. Loaded cooperatively once, then every octahedral texel integrates from LDS instead of
// re-reading the ray-data buffer + recomputing each direction per texel (the RTXGI ProbeBlendingCS optimization).
shared vec4 sDDGIVisibilityRays[GI_DDGI_RAYS_PER_PROBE];
shared vec4 sDUGIVisibilityRays[GI_DUGI_RAYS_PER_PROBE];
void main(){
uint globalProbeIndex = gl_WorkGroupID.x;
@ -40,27 +40,27 @@ void main(){
int cascadeIndex = int(globalProbeIndex / pushConstants.params.z);
int probeIndexInCascade = int(globalProbeIndex % pushConstants.params.z);
ivec3 probeCoord = ddgiProbeCoordFromIndex(probeIndexInCascade);
ivec3 probeCoord = dugiProbeCoordFromIndex(probeIndexInCascade);
ivec2 localTexel = ivec2(gl_LocalInvocationID.xy);
vec2 uv = (vec2(localTexel) + vec2(0.5)) / float(GI_DDGI_VISIBILITY_OCT_SIZE);
vec2 uv = (vec2(localTexel) + vec2(0.5)) / float(GI_DUGI_VISIBILITY_OCT_SIZE);
vec3 texelDirection = octDecode(fma(uv, vec2(2.0), vec2(-1.0)));
mat3 randomRotation = mat3(pushConstants.randomRotation0.xyz, pushConstants.randomRotation1.xyz, pushConstants.randomRotation2.xyz);
uint raysPerProbe = pushConstants.params.w;
// Sharper directional weighting than for irradiance, so the depth statistics stay local to the texel direction.
const float sharpness = GI_DDGI_VISIBILITY_SHARPNESS;
const float sharpness = GI_DUGI_VISIBILITY_SHARPNESS;
float maxRayDistance = max(ddgiData.ddgiCascadeCellSizes[cascadeIndex].w, 1e-2);
float maxRayDistance = max(dugiData.dugiCascadeCellSizes[cascadeIndex].w, 1e-2);
// Cooperatively load this probe's rays (direction + raw distance) into shared memory ONCE for the whole workgroup. Each
// thread loads a strided subset; the 256 threads (16x16) cover the <=128 rays in a single iteration. Then barrier so every
// texel below reads from LDS instead of the ray-data buffer (256x less global traffic + the direction trig done once).
DDGIRayDataBuffer rayData = ddgiData.rayData; // hoist the master->sub-pointer deref out of the load
uint localIndex = (uint(gl_LocalInvocationID.y) * GI_DDGI_VISIBILITY_OCT_SIZE) + uint(gl_LocalInvocationID.x);
for(uint r = GI_DDGI_RAY_START + localIndex; r < raysPerProbe; r += uint(GI_DDGI_VISIBILITY_OCT_SIZE * GI_DDGI_VISIBILITY_OCT_SIZE)){
sDDGIVisibilityRays[r] = vec4(ddgiTraceRayDirection(r, randomRotation), ddgiLoadRay(rayData, globalProbeIndex, r, raysPerProbe).a);
DUGIRayDataBuffer rayData = dugiData.rayData; // hoist the master->sub-pointer deref out of the load
uint localIndex = (uint(gl_LocalInvocationID.y) * GI_DUGI_VISIBILITY_OCT_SIZE) + uint(gl_LocalInvocationID.x);
for(uint r = GI_DUGI_RAY_START + localIndex; r < raysPerProbe; r += uint(GI_DUGI_VISIBILITY_OCT_SIZE * GI_DUGI_VISIBILITY_OCT_SIZE)){
sDUGIVisibilityRays[r] = vec4(dugiTraceRayDirection(r, randomRotation), dugiLoadRay(rayData, globalProbeIndex, r, raysPerProbe).a);
}
barrier();
@ -68,8 +68,8 @@ void main(){
float sumDist2 = 0.0;
float sumSky = 0.0; // weighted fraction of rays in this direction that escaped to the sky (missed geometry)
float sumWeight = 0.0;
for(uint r = GI_DDGI_RAY_START; r < raysPerProbe; r++){
vec4 ray = sDDGIVisibilityRays[r];
for(uint r = GI_DUGI_RAY_START; r < raysPerProbe; r++){
vec4 ray = sDUGIVisibilityRays[r];
float weight = pow(max(0.0, dot(texelDirection, ray.xyz)), sharpness);
if(weight > 0.0){
float rawDist = ray.w;
@ -86,28 +86,28 @@ void main(){
vec3 moments = (sumWeight > 1e-6) ? vec3(sumDist / sumWeight, sumDist2 / sumWeight, sumSky / sumWeight)
: vec3(maxRayDistance, maxRayDistance * maxRayDistance, 1.0);
ivec2 atlasTexel = ddgiProbeTileOrigin(probeCoord, cascadeIndex, GI_DDGI_VISIBILITY_OCT_FULL) + localTexel;
vec3 previous = vec3(imageLoad(uDDGIVisibilityMoments, atlasTexel).xy, imageLoad(uDDGIVisibilitySky, atlasTexel).x);
ivec2 atlasTexel = dugiProbeTileOrigin(probeCoord, cascadeIndex, GI_DUGI_VISIBILITY_OCT_FULL) + localTexel;
vec3 previous = vec3(imageLoad(uDUGIVisibilityMoments, atlasTexel).xy, imageLoad(uDUGIVisibilitySky, atlasTexel).x);
// Guard against uninitialized NaN/Inf (images are not cleared) and discard stale history: on the firstFrame flag
// (blend.z) the previous data is uninitialized garbage, and on a probe that just toroidally scrolled into the volume
// its stored history belongs to a different world cell — in both cases take the freshly computed value.
bool firstFrame = (pushConstants.blend.z > 0.5) || ddgiProbeScrolledIn(probeCoord, cascadeIndex);
bool firstFrame = (pushConstants.blend.z > 0.5) || dugiProbeScrolledIn(probeCoord, cascadeIndex);
// Per-probe convergence age (frames since (re)init) lives in its own BDA buffer (was the free image w channel): reset on
// firstFrame/scroll-in, else +1 (capped at the warmup length). It drives a faster hysteresis right after init so the probe
// converges in a few frames; the irradiance update reads the same age for its matching ramp. The age is per-probe (same for
// every texel of the tile), so it is computed identically by all threads but written back by only one.
DDGIAgeBuffer ageBuffer = ddgiData.age;
uint probeAge = firstFrame ? 0u : min(ddgiLoadAge(ageBuffer, probeCoord, cascadeIndex) + 1u, uint(GI_DDGI_WARMUP_FRAMES));
float hysteresis = ddgiWarmupHysteresis(float(probeAge));
DUGIAgeBuffer ageBuffer = dugiData.age;
uint probeAge = firstFrame ? 0u : min(dugiLoadAge(ageBuffer, probeCoord, cascadeIndex) + 1u, uint(GI_DUGI_WARMUP_FRAMES));
float hysteresis = dugiWarmupHysteresis(float(probeAge));
// NaN-safe discard: a zero blend weight is not enough, because mix(cur, NaN, 0.0) = cur + NaN*0.0 = NaN; select the fresh value.
bool discardPrevious = firstFrame || any(isnan(previous)) || any(isinf(previous));
vec3 blended = discardPrevious ? moments : mix(moments, previous, hysteresis);
imageStore(uDDGIVisibilityMoments, atlasTexel, vec4(blended.xy, 0.0, 0.0)); // x = mean dist, y = mean dist^2
imageStore(uDDGIVisibilitySky, atlasTexel, vec4(blended.z, 0.0, 0.0, 0.0)); // x = sky visibility
imageStore(uDUGIVisibilityMoments, atlasTexel, vec4(blended.xy, 0.0, 0.0)); // x = mean dist, y = mean dist^2
imageStore(uDUGIVisibilitySky, atlasTexel, vec4(blended.z, 0.0, 0.0, 0.0)); // x = sky visibility
if(gl_LocalInvocationIndex == 0u){
ddgiStoreAge(ageBuffer, probeCoord, cascadeIndex, probeAge);
dugiStoreAge(ageBuffer, probeCoord, cascadeIndex, probeAge);
}
}

View file

@ -2,7 +2,7 @@
#define GI_RT_GATHER_GLSL
// =====================================================================================================================
// Shared ray-traced "probe gather" layer for the ray-traced global illumination techniques (currently DDGI).
// Shared ray-traced "probe gather" layer for the ray-traced global illumination techniques (currently DUGI).
//
// This include encapsulates the common operation a ray-traced GI producer needs: shoot a ray into the scene,
// find the closest hit, and compute the outgoing radiance towards the ray origin at that hit point. The radiance
@ -67,7 +67,7 @@ struct GIGatherSurface {
float hitDistance; // distance from ray origin to hit; negative when the ray missed
bool hit; // true when the ray hit geometry, false on a sky/environment miss
bool backface; // true when the ray hit the back side of the surface (shading normal pointed along the ray before
// it was flipped) — i.e. the ray origin is behind/inside this surface. Used by the DDGI trace to
// it was flipped) — i.e. the ray origin is behind/inside this surface. Used by the DUGI trace to
// treat such hits as occluders (shortened distance) and absorptive (black), preventing leaks.
bool doubleSided; // true when the hit material is double-sided (face culling == None). A "backface" of a double-sided
// surface (foliage, thin sheets) is a legitimate surface, not geometry the probe is embedded in.
@ -436,12 +436,12 @@ vec3 giGatherEvaluateLighting(const in vec3 worldPosition, const in vec3 normal)
// Outgoing radiance towards the ray origin at a gather hit.
// Lo = emission + (albedo / PI) * (directLight + previousFrameIndirect)
// previousFrameIndirect is the irradiance that the caller sampled from the *previous* frame's GI data structure at the
// hit point (the probe field for DDGI). Passing it in here gives multi-bounce ("infinite bounce")
// hit point (the probe field for DUGI). Passing it in here gives multi-bounce ("infinite bounce")
// lighting almost for free; pass vec3(0.0) to disable it.
// ---------------------------------------------------------------------------------------------------------------------
// Global GI-emissive master regulators: a renderer-wide scale (multiplies the per-material factor) and an absolute cap
// (min'd with the per-material max). Each GI producer supplies them from its own source (DDGI: ddgiData) by #defining
// (min'd with the per-material max). Each GI producer supplies them from its own source (DUGI: dugiData) by #defining
// these before including this file; they default to a no-op (scale 1.0, +Inf cap).
#ifndef GI_GATHER_EMISSIVE_SCALE
#define GI_GATHER_EMISSIVE_SCALE 1.0
@ -473,7 +473,7 @@ vec3 giGatherTraceRadiance(const in vec3 origin, const in vec3 direction, const
#endif // RAYTRACING
// ---------------------------------------------------------------------------------------------------------------------
// Swappable closest-hit trace backend. The trace producers (DDGI) call giTraceClosestHit() instead of a fixed
// Swappable closest-hit trace backend. The trace producers (DUGI) call giTraceClosestHit() instead of a fixed
// implementation, so the ray-vs-scene query can be swapped at compile time. The default is the hardware ray-query
// backend (giGatherClosestHit, above); a future SDF backend would #define GI_TRACE_BACKEND = GI_TRACE_BACKEND_SDF and
// provide its own giTraceClosestHit returning a GIGatherSurface. (A ray-generation/closest-hit RT-pipeline producer is a
@ -481,7 +481,7 @@ vec3 giGatherTraceRadiance(const in vec3 origin, const in vec3 direction, const
// ---------------------------------------------------------------------------------------------------------------------
#define GI_TRACE_BACKEND_RAYQUERY 0
#define GI_TRACE_BACKEND_SDF 1
#define GI_TRACE_BACKEND_RSM 2 // non-raytraced Reflective Shadow Map gather; its giTraceClosestHit is provided by the includer (gi_ddgi_trace.comp)
#define GI_TRACE_BACKEND_RSM 2 // non-raytraced Reflective Shadow Map gather; its giTraceClosestHit is provided by the includer (gi_dugi_trace.comp)
#ifndef GI_TRACE_BACKEND
#define GI_TRACE_BACKEND GI_TRACE_BACKEND_RAYQUERY
#endif

View file

@ -1,100 +0,0 @@
#ifndef GLOBAL_ILLUMINATION_DDGI_SAMPLING_GLSL
#define GLOBAL_ILLUMINATION_DDGI_SAMPLING_GLSL
// Shared fragment-side DDGI probe-field sampling. Factors out the descriptor-set declarations (UBO + irradiance + visibility)
// and the per-consumer texelFetch loaders that were otherwise duplicated across mesh.frag / planet_renderpass.frag /
// planet_grass.frag / planet_water.frag.
//
// The including shader must, before the #include:
// - have octahedral.glsl reachable (octEncode, used by ddgiProbeOctUV) — the SH headers are pulled in by
// global_illumination_ddgi.glsl itself under SH storage,
// - #define DDGI_DESCRIPTOR_SET to the descriptor-set index the DDGI probe data is bound to (mesh.frag = 2, planets = 4),
// - only include this in the GLOBAL_ILLUMINATION_DDGI build variant.
//
// (The DDGI compute passes - trace / irradiance update / visibility update - read the probe images as *storage* images via
// imageLoad and therefore keep their own loaders; this include is for the *sampled* fragment-shading consumers only.)
#ifndef DDGI_DESCRIPTOR_SET
#error "global_illumination_ddgi_sampling.glsl: #define DDGI_DESCRIPTOR_SET (the probe-field descriptor set index) before including."
#endif
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET DDGI_DESCRIPTOR_SET
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#define GLOBAL_ILLUMINATION_DDGI_SAMPLE
#include "global_illumination_ddgi.glsl" // pulls in gi_ddgi_data.glsl -> the `ddgiData` SSBO (cascade globals + sub-buffer pointers) at this set's binding 0
// The DDGI data block — cascade globals + the BDA sub-buffer pointers (probe-data, SH-irradiance, ...) — is the std430 SSBO
// `ddgiData` declared at this set's binding 0 by gi_ddgi_data.glsl (via global_illumination_ddgi.glsl above). The fragment
// reads its globals + the probe-data / SH-irradiance pointers from it directly; no separate master UBO any more (the old
// binding 3 is freed).
#if GI_DDGI_STORAGE_IS_SH
// RGB spherical harmonics: one contiguous DDGISHProbe (DDGI_SH_IMAGE_COUNT packed vec4) per probe in the master's
// irradianceSH BDA buffer (no sampler) — loaded as a whole element for coalesced access.
DDGI_SH_TYPE ddgiLoadIrradianceSH(const in ivec3 probeCoord, const in int cascadeIndex){
DDGISHProbe p = ddgiData.irradianceSH.probes[ddgiProbeDataIndex(probeCoord, cascadeIndex)];
vec4 a = p.c[0]; vec4 b = p.c[1]; vec4 c = p.c[2];
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_L2_VALUE
vec4 d = p.c[3]; vec4 e = p.c[4]; vec4 f = p.c[5]; vec4 g = p.c[6];
return SHC3CoefficientsL2Create(vec3(a.x, a.y, a.z), vec3(a.w, b.x, b.y), vec3(b.z, b.w, c.x), vec3(c.y, c.z, c.w),
vec3(d.x, d.y, d.z), vec3(d.w, e.x, e.y), vec3(e.z, e.w, f.x), vec3(f.y, f.z, f.w),
vec3(g.x, g.y, g.z));
#else
return SHC3CoefficientsL1Create(vec3(a.x, a.y, a.z), vec3(a.w, b.x, b.y), vec3(b.z, b.w, c.x), vec3(c.y, c.z, c.w));
#endif
}
#else
layout(set = DDGI_DESCRIPTOR_SET, binding = 1) uniform sampler2D uDDGIIrradianceOct;
vec3 ddgiEvaluateIrradiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 normal){
vec2 uv = ddgiProbeOctUV(probeCoord, cascadeIndex, normal, GI_DDGI_IRRADIANCE_OCT_SIZE, GI_DDGI_IRRADIANCE_OCT_FULL);
// The atlas stores the cosine-weighted MEAN incident radiance A = E/PI; multiply by PI here (split, like RTXGI) to return the
// full irradiance integral E (matches the SH path; shading then applies albedo/PI). The trace's own multibounce read stays raw.
return max(vec3(0.0), textureLod(uDDGIIrradianceOct, uv, 0.0).rgb) * 3.14159265358979;
}
#endif
layout(set = DDGI_DESCRIPTOR_SET, binding = 2) uniform sampler2D uDDGIVisibilityMoments; // x = mean dist, y = mean dist^2 (RG32F)
layout(set = DDGI_DESCRIPTOR_SET, binding = 4) uniform sampler2D uDDGIVisibilitySky; // x = sky visibility (R8, 0..1)
vec3 ddgiSampleVisibility(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 direction){
vec2 uv = ddgiProbeOctUV(probeCoord, cascadeIndex, direction, GI_DDGI_VISIBILITY_OCT_SIZE, GI_DDGI_VISIBILITY_OCT_FULL);
return vec3(textureLod(uDDGIVisibilityMoments, uv, 0.0).xy, textureLod(uDDGIVisibilitySky, uv, 0.0).x); // x = mean dist, y = mean dist^2, z = sky visibility
}
#if GI_DDGI_PROBE_RELOCATION
// Per-probe data (xyz = world-space relocation offset, w = state) lives in the master's probe-data BDA buffer.
vec4 ddgiLoadProbeData(const in ivec3 probeCoord, const in int cascadeIndex){
return ddgiData.probeData.data[ddgiProbeDataIndex(probeCoord, cascadeIndex)];
}
#endif
#if defined(GI_DDGI_GLOSSY_RADIANCE)
// Glossy prefiltered-radiance octahedral atlas, binding 5. RGB9E5 (default) is sampled as a uint texture (it is not
// reliably hardware-linear-filterable) and bilinear-filtered manually with a decode per tap; the RGBA16F fallback uses a
// hardware-bilinear sampler. The guard band (filled by gi_ddgi_border_update.comp) makes the edge taps correct either way.
#include "rgb9e5.glsl"
#ifdef GI_DDGI_GLOSSY_RGB9E5
layout(set = DDGI_DESCRIPTOR_SET, binding = 5) uniform usampler2D uDDGIGlossyRadiance; // R32_UINT alias of the E5B9G9R9 atlas
#else
layout(set = DDGI_DESCRIPTOR_SET, binding = 5) uniform sampler2D uDDGIGlossyRadiance; // RGBA16F atlas
#endif
vec3 ddgiEvaluateGlossyRadiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 reflectionDirection){
vec2 oct = fma(octEncode(normalize(reflectionDirection)), vec2(0.5), vec2(0.5)); // [-1,1] -> [0,1]
vec2 originTexel = vec2(ddgiProbeTileOrigin(probeCoord, cascadeIndex, GI_DDGI_GLOSSY_OCT_FULL));
vec2 texel = originTexel + (oct * float(GI_DDGI_GLOSSY_OCT_SIZE));
#ifdef GI_DDGI_GLOSSY_RGB9E5
vec2 t = texel - vec2(0.5);
ivec2 base = ivec2(floor(t));
vec2 f = t - vec2(base);
vec3 c00 = decodeRGB9E5(texelFetch(uDDGIGlossyRadiance, base + ivec2(0, 0), 0).x);
vec3 c10 = decodeRGB9E5(texelFetch(uDDGIGlossyRadiance, base + ivec2(1, 0), 0).x);
vec3 c01 = decodeRGB9E5(texelFetch(uDDGIGlossyRadiance, base + ivec2(0, 1), 0).x);
vec3 c11 = decodeRGB9E5(texelFetch(uDDGIGlossyRadiance, base + ivec2(1, 1), 0).x);
return max(vec3(0.0), mix(mix(c00, c10, f.x), mix(c01, c11, f.x), f.y));
#else
vec2 uv = texel / vec2(ddgiAtlasSize(GI_DDGI_GLOSSY_OCT_FULL));
return max(vec3(0.0), textureLod(uDDGIGlossyRadiance, uv, 0.0).rgb);
#endif
}
#endif
#endif // GLOBAL_ILLUMINATION_DDGI_SAMPLING_GLSL

View file

@ -1,249 +1,250 @@
#ifndef GLOBAL_ILLUMINATION_DDGI_GLSL
#define GLOBAL_ILLUMINATION_DDGI_GLSL
#ifndef GLOBAL_ILLUMINATION_DUGI_GLSL
#define GLOBAL_ILLUMINATION_DUGI_GLSL
// =====================================================================================================================
// Dynamic Diffuse Global Illumination (DDGI) - shared probe field definitions, addressing and sampling.
// Dynamic Unified Global Illumination (DUGI) - shared probe field definitions, addressing and sampling.
// Extends the DDGI technique with a coarse glossy term and an optional RSM (non-RT) backend.
//
// Based on:
// - "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields", Majercik, Guertin, Nowrouzezahrai,
// McGuire, JCGT 2019. https://jcgt.org/published/0008/02/01/
// - "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields" (DDGI), Majercik, Guertin,
// Nowrouzezahrai, McGuire, JCGT 2019. https://jcgt.org/published/0008/02/01/
// - "Scaling Probe-Based Real-Time Dynamic Global Illumination for Production", Majercik et al. 2021.
//
// This engine variant reuses the cascaded radiance hints snapping infrastructure for probe placement: instead of one
// irradiance volume, we keep GI_DDGI_CASCADES nested probe grids that snap to the camera, so a small per-cascade probe
// irradiance volume, we keep GI_DUGI_CASCADES nested probe grids that snap to the camera, so a small per-cascade probe
// count covers both near and far field. Each probe stores:
// - irradiance, either as L1 spherical harmonics in a 3D volume (GI_DDGI_STORAGE_SH, default) or as an octahedral
// irradiance tile in a 2D atlas (GI_DDGI_STORAGE_OCT) - switchable via the GI_DDGI_STORAGE define.
// - irradiance, either as L1 spherical harmonics in a 3D volume (GI_DUGI_STORAGE_SH, default) or as an octahedral
// irradiance tile in a 2D atlas (GI_DUGI_STORAGE_OCT) - switchable via the GI_DUGI_STORAGE define.
// - visibility, always as an octahedral mean / mean-squared distance tile in a 2D atlas, used for the Chebyshev
// visibility test that prevents the light leaking that plain irradiance volumes (and radiance hints) suffer from.
//
// The probe radiance is gathered by tracing rays against the scene TLAS; see gi_ddgi_trace.comp / gi_ddgi_probe_update.comp.
// The probe radiance is gathered by tracing rays against the scene TLAS; see gi_dugi_trace.comp / gi_dugi_probe_update.comp.
// =====================================================================================================================
#include "octahedral.glsl" // octEncode / octDecode (unit vector <-> [-1,1]^2 signed octahedral mapping)
// --- Storage mode -----------------------------------------------------------------------------------------------------
#define GI_DDGI_STORAGE_OCT_VALUE 0 // octahedral irradiance atlas (1 RGBA16F image)
#define GI_DDGI_STORAGE_SH_VALUE 1 // L1 RGB spherical harmonics (4 coefficients, 3 RGBA16F images)
#define GI_DDGI_STORAGE_L2_VALUE 2 // L2 RGB spherical harmonics (9 coefficients, 7 RGBA16F images)
#ifndef GI_DDGI_STORAGE
#define GI_DDGI_STORAGE GI_DDGI_STORAGE_L2_VALUE
#define GI_DUGI_STORAGE_OCT_VALUE 0 // octahedral irradiance atlas (1 RGBA16F image)
#define GI_DUGI_STORAGE_SH_VALUE 1 // L1 RGB spherical harmonics (4 coefficients, 3 RGBA16F images)
#define GI_DUGI_STORAGE_L2_VALUE 2 // L2 RGB spherical harmonics (9 coefficients, 7 RGBA16F images)
#ifndef GI_DUGI_STORAGE
#define GI_DUGI_STORAGE GI_DUGI_STORAGE_L2_VALUE
#endif
// Convenience define mirroring GI_DDGI_STORAGE for consumers that select via defined()/!defined() (e.g. mesh.frag's
// Convenience define mirroring GI_DUGI_STORAGE for consumers that select via defined()/!defined() (e.g. mesh.frag's
// IBL block, which is kept for octahedral storage but replaced by the SH dominant-light path for both SH storage modes).
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_OCT_VALUE
#define GLOBAL_ILLUMINATION_DDGI_OCT_STORAGE
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_OCT_VALUE
#define GLOBAL_ILLUMINATION_DUGI_OCT_STORAGE
#endif
// Both L1 and L2 are spherical-harmonics storage (3D image triplet/septuplet); octahedral is the odd one out.
#if (GI_DDGI_STORAGE == GI_DDGI_STORAGE_SH_VALUE) || (GI_DDGI_STORAGE == GI_DDGI_STORAGE_L2_VALUE)
#define GI_DDGI_STORAGE_IS_SH 1
#if (GI_DUGI_STORAGE == GI_DUGI_STORAGE_SH_VALUE) || (GI_DUGI_STORAGE == GI_DUGI_STORAGE_L2_VALUE)
#define GI_DUGI_STORAGE_IS_SH 1
#else
#define GI_DDGI_STORAGE_IS_SH 0
#define GI_DUGI_STORAGE_IS_SH 0
#endif
// Storage-order-agnostic spherical-harmonics aliases: the sampling/update/shading code is written once against these
// (DDGI_SH_*), only the per-texel (un)packing of the coefficients into the RGBA16F image set is storage-specific.
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_L2_VALUE
#define DDGI_SH_IMAGE_COUNT 7
#define DDGI_SH_TYPE SHC3CoefficientsL2
#define DDGI_SH_ZERO SHC3CoefficientsL2Zero
#define DDGI_SH_ADD SHC3CoefficientsL2Add
#define DDGI_SH_MUL SHC3CoefficientsL2Mul
#define DDGI_SH_LERP SHC3CoefficientsL2Lerp
#define DDGI_SH_PROJECT ProjectOntoSHC3CoefficientsL2
#define DDGI_SH_SUB SHC3CoefficientsL2Sub
#define DDGI_SH_CONVOLVE_COSINE SHC3CoefficientsL2ConvolveWithCosineLobe
#define DDGI_SH_EVALUATE EvaluateSHC3CoefficientsL2
// (DUGI_SH_*), only the per-texel (un)packing of the coefficients into the RGBA16F image set is storage-specific.
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_L2_VALUE
#define DUGI_SH_IMAGE_COUNT 7
#define DUGI_SH_TYPE SHC3CoefficientsL2
#define DUGI_SH_ZERO SHC3CoefficientsL2Zero
#define DUGI_SH_ADD SHC3CoefficientsL2Add
#define DUGI_SH_MUL SHC3CoefficientsL2Mul
#define DUGI_SH_LERP SHC3CoefficientsL2Lerp
#define DUGI_SH_PROJECT ProjectOntoSHC3CoefficientsL2
#define DUGI_SH_SUB SHC3CoefficientsL2Sub
#define DUGI_SH_CONVOLVE_COSINE SHC3CoefficientsL2ConvolveWithCosineLobe
#define DUGI_SH_EVALUATE EvaluateSHC3CoefficientsL2
// Dominant light direction/intensity live in the L0/L1 bands, so the "approximate" method extracts them from the L1
// reduction (identical to the L1 path); the full L2 detail stays in the residual.
#define DDGI_SH_APPROX_DOMINANT(sh, dir, color) SHC3CoefficientsL1ApproximateDirectionalLight(SHC3CoefficientsL1FromL2(sh), dir, color)
#define DDGI_SH_EXTRACT_DOMINANT SHC3CoefficientsL2ExtractAndSubtractDominantAmbientAndDirectionalLights
#elif GI_DDGI_STORAGE == GI_DDGI_STORAGE_SH_VALUE
#define DDGI_SH_IMAGE_COUNT 3
#define DDGI_SH_TYPE SHC3CoefficientsL1
#define DDGI_SH_ZERO SHC3CoefficientsL1Zero
#define DDGI_SH_ADD SHC3CoefficientsL1Add
#define DDGI_SH_MUL SHC3CoefficientsL1Mul
#define DDGI_SH_LERP SHC3CoefficientsL1Lerp
#define DDGI_SH_PROJECT ProjectOntoSHC3CoefficientsL1
#define DDGI_SH_SUB SHC3CoefficientsL1Sub
#define DDGI_SH_CONVOLVE_COSINE SHC3CoefficientsL1ConvolveWithCosineLobe
#define DDGI_SH_EVALUATE EvaluateSHC3CoefficientsL1
#define DDGI_SH_APPROX_DOMINANT(sh, dir, color) SHC3CoefficientsL1ApproximateDirectionalLight(sh, dir, color)
#define DDGI_SH_EXTRACT_DOMINANT SHC3CoefficientsL1ExtractAndSubtractDominantAmbientAndDirectionalLights
#define DUGI_SH_APPROX_DOMINANT(sh, dir, color) SHC3CoefficientsL1ApproximateDirectionalLight(SHC3CoefficientsL1FromL2(sh), dir, color)
#define DUGI_SH_EXTRACT_DOMINANT SHC3CoefficientsL2ExtractAndSubtractDominantAmbientAndDirectionalLights
#elif GI_DUGI_STORAGE == GI_DUGI_STORAGE_SH_VALUE
#define DUGI_SH_IMAGE_COUNT 3
#define DUGI_SH_TYPE SHC3CoefficientsL1
#define DUGI_SH_ZERO SHC3CoefficientsL1Zero
#define DUGI_SH_ADD SHC3CoefficientsL1Add
#define DUGI_SH_MUL SHC3CoefficientsL1Mul
#define DUGI_SH_LERP SHC3CoefficientsL1Lerp
#define DUGI_SH_PROJECT ProjectOntoSHC3CoefficientsL1
#define DUGI_SH_SUB SHC3CoefficientsL1Sub
#define DUGI_SH_CONVOLVE_COSINE SHC3CoefficientsL1ConvolveWithCosineLobe
#define DUGI_SH_EVALUATE EvaluateSHC3CoefficientsL1
#define DUGI_SH_APPROX_DOMINANT(sh, dir, color) SHC3CoefficientsL1ApproximateDirectionalLight(sh, dir, color)
#define DUGI_SH_EXTRACT_DOMINANT SHC3CoefficientsL1ExtractAndSubtractDominantAmbientAndDirectionalLights
#endif
// Dominant-light extraction method for the SH shading path (mesh.frag), compile-time switchable for comparison.
// When GI_DDGI_SH_APPROXIMATE_DOMINANT is defined (the DEFAULT): SHC3CoefficientsL1ApproximateDirectionalLight + residual
// When GI_DUGI_SH_APPROXIMATE_DOMINANT is defined (the DEFAULT): SHC3CoefficientsL1ApproximateDirectionalLight + residual
// SH with the DC kept (matches the original / HEAD~1 look), applied to both L1 and L2 (L2 extracts from the L1 reduction).
// #undef it (or comment out the line below) to switch to SHC3CoefficientsL{1,2}ExtractAndSubtractDominantAmbientAnd-
// DirectionalLights (separate uniform ambient + DC-zeroed residual + per-direction roughness estimate) — a different fit.
#define GI_DDGI_SH_APPROXIMATE_DOMINANT
#define GI_DUGI_SH_APPROXIMATE_DOMINANT
// SH-storage glossy toggle (mesh.frag): when defined (together with GI_DDGI_GLOSSY_RADIANCE), the SH shading path adds the
// SH-storage glossy toggle (mesh.frag): when defined (together with GI_DUGI_GLOSSY_RADIANCE), the SH shading path adds the
// directional glossy prefiltered-radiance atlas, crossfaded by roughness against the dominant directional light — low
// roughness takes the sharp atlas, high roughness the broad dominant-light specular (see mesh.frag). Comment out for an A/B
// comparison against the dominant-light-only specular. Default ON. Octahedral storage and the diffuse term are unaffected.
#define GI_DDGI_GLOSSY_RESIDUAL
#define GI_DUGI_GLOSSY_RESIDUAL
// --- Probe field dimensions -------------------------------------------------------------------------------------------
#ifndef GI_DDGI_CASCADES
#define GI_DDGI_CASCADES 4
#ifndef GI_DUGI_CASCADES
#define GI_DUGI_CASCADES 4
#endif
#ifndef GI_DDGI_PROBES_X
#define GI_DDGI_PROBES_X 16
#ifndef GI_DUGI_PROBES_X
#define GI_DUGI_PROBES_X 16
#endif
#ifndef GI_DDGI_PROBES_Y
#define GI_DDGI_PROBES_Y 16
#ifndef GI_DUGI_PROBES_Y
#define GI_DUGI_PROBES_Y 16
#endif
#ifndef GI_DDGI_PROBES_Z
#define GI_DDGI_PROBES_Z 16
#ifndef GI_DUGI_PROBES_Z
#define GI_DUGI_PROBES_Z 16
#endif
#define GI_DDGI_PROBES_PER_CASCADE (GI_DDGI_PROBES_X * GI_DDGI_PROBES_Y * GI_DDGI_PROBES_Z)
#define GI_DUGI_PROBES_PER_CASCADE (GI_DUGI_PROBES_X * GI_DUGI_PROBES_Y * GI_DUGI_PROBES_Z)
// Octahedral tile sizes (interior texels; one guard-band texel is added on each side in the atlas for bilinear filtering).
// Default: NPOT interior sizes (6/14) whose BORDERED tile is power-of-two aligned (6+2=8, 14+2=16) — the RTXGI/Wicked/Flax
// convention (less memory, POT atlas tiles). Comment out GI_DDGI_OCT_ALIGNED_BORDER_NPOT_SIZES (and its Pascal {$define}
// convention (less memory, POT atlas tiles). Comment out GI_DUGI_OCT_ALIGNED_BORDER_NPOT_SIZES (and its Pascal {$define}
// counterpart in PasVulkan.Scene3D.Renderer.Instance.pas) for the legacy 8/16 interior (10/18 bordered, NPOT tiles).
#define GI_DDGI_OCT_ALIGNED_BORDER_NPOT_SIZES
#ifndef GI_DDGI_IRRADIANCE_OCT_SIZE
#ifdef GI_DDGI_OCT_ALIGNED_BORDER_NPOT_SIZES
#define GI_DDGI_IRRADIANCE_OCT_SIZE 6
#define GI_DUGI_OCT_ALIGNED_BORDER_NPOT_SIZES
#ifndef GI_DUGI_IRRADIANCE_OCT_SIZE
#ifdef GI_DUGI_OCT_ALIGNED_BORDER_NPOT_SIZES
#define GI_DUGI_IRRADIANCE_OCT_SIZE 6
#else
#define GI_DDGI_IRRADIANCE_OCT_SIZE 8
#define GI_DUGI_IRRADIANCE_OCT_SIZE 8
#endif
#endif
#ifndef GI_DDGI_VISIBILITY_OCT_SIZE
#ifdef GI_DDGI_OCT_ALIGNED_BORDER_NPOT_SIZES
#define GI_DDGI_VISIBILITY_OCT_SIZE 14
#ifndef GI_DUGI_VISIBILITY_OCT_SIZE
#ifdef GI_DUGI_OCT_ALIGNED_BORDER_NPOT_SIZES
#define GI_DUGI_VISIBILITY_OCT_SIZE 14
#else
#define GI_DDGI_VISIBILITY_OCT_SIZE 16
#define GI_DUGI_VISIBILITY_OCT_SIZE 16
#endif
#endif
#define GI_DDGI_IRRADIANCE_OCT_FULL (GI_DDGI_IRRADIANCE_OCT_SIZE + 2)
#define GI_DDGI_VISIBILITY_OCT_FULL (GI_DDGI_VISIBILITY_OCT_SIZE + 2)
#define GI_DUGI_IRRADIANCE_OCT_FULL (GI_DUGI_IRRADIANCE_OCT_SIZE + 2)
#define GI_DUGI_VISIBILITY_OCT_FULL (GI_DUGI_VISIBILITY_OCT_SIZE + 2)
// Glossy-radiance octahedral atlas. Separate from the irradiance atlas because it stores prefiltered *radiance*
// (no cosine convolution), integrated with a sharp directional kernel for glossy reflections. Only allocated/updated/sampled
// when GI_DDGI_GLOSSY_RADIANCE is defined (Pascal GlobalIlluminationDDGIGlossyRadiance, mirrored in compileshaders.sh; the
// when GI_DUGI_GLOSSY_RADIANCE is defined (Pascal GlobalIlluminationDUGIGlossyRadiance, mirrored in compileshaders.sh; the
// toggle is opt-in / default OFF). Sized like the visibility atlas (same 14/16 interior + guard band) for reasonable sharpness.
#ifndef GI_DDGI_GLOSSY_OCT_SIZE
#ifdef GI_DDGI_OCT_ALIGNED_BORDER_NPOT_SIZES
#define GI_DDGI_GLOSSY_OCT_SIZE 14
#ifndef GI_DUGI_GLOSSY_OCT_SIZE
#ifdef GI_DUGI_OCT_ALIGNED_BORDER_NPOT_SIZES
#define GI_DUGI_GLOSSY_OCT_SIZE 14
#else
#define GI_DDGI_GLOSSY_OCT_SIZE 16
#define GI_DUGI_GLOSSY_OCT_SIZE 16
#endif
#endif
#define GI_DDGI_GLOSSY_OCT_FULL (GI_DDGI_GLOSSY_OCT_SIZE + 2)
#define GI_DUGI_GLOSSY_OCT_FULL (GI_DUGI_GLOSSY_OCT_SIZE + 2)
// Directional prefilter sharpness (Phong-like lobe exponent pow(max(dot,0), n)). Bounded by the ray count (~96 random rays):
// too sharp -> too few rays per texel -> noise (temporal accumulation hides some). ~8 is the practical sharp limit here; the
// planned mip chain (v2) adds *blurrier* levels below this for higher roughness, picked by a roughness->LOD at sample time.
#ifndef GI_DDGI_GLOSSY_SHARPNESS
#define GI_DDGI_GLOSSY_SHARPNESS 8.0
#ifndef GI_DUGI_GLOSSY_SHARPNESS
#define GI_DUGI_GLOSSY_SHARPNESS 8.0
#endif
// Storage format of the glossy atlas. Default RGB9E5 (E5B9G9R9 shared-exponent, 4 bytes/texel ~= half of RGBA16F): compute
// read/write goes through an R32_UINT alias view (encodeRGB9E5/decodeRGB9E5 in rgb9e5.glsl), and sampling does a manual
// 4-tap bilinear decode because E5B9G9R9 is not reliably hardware-linear-filterable. Build with -DGI_DDGI_GLOSSY_RGBA16F for
// 4-tap bilinear decode because E5B9G9R9 is not reliably hardware-linear-filterable. Build with -DGI_DUGI_GLOSSY_RGBA16F for
// the RGBA16F fallback (8 bytes, hardware bilinear). Whichever is chosen MUST match the Pascal image format.
#if !defined(GI_DDGI_GLOSSY_RGB9E5) && !defined(GI_DDGI_GLOSSY_RGBA16F)
#define GI_DDGI_GLOSSY_RGB9E5
#if !defined(GI_DUGI_GLOSSY_RGB9E5) && !defined(GI_DUGI_GLOSSY_RGBA16F)
#define GI_DUGI_GLOSSY_RGB9E5
#endif
// Shading-time roughness band for blending the sharp glossy atlas against the broad source: at/below LO take the sharp
// atlas, at/above HI take the broad source (the atlas prefilter sharpness ~ roughness HI, beyond which the broad source
// is already correct). Only used when GI_DDGI_GLOSSY_RADIANCE.
#ifndef GI_DDGI_GLOSSY_ROUGHNESS_LO
#define GI_DDGI_GLOSSY_ROUGHNESS_LO 0.0
// is already correct). Only used when GI_DUGI_GLOSSY_RADIANCE.
#ifndef GI_DUGI_GLOSSY_ROUGHNESS_LO
#define GI_DUGI_GLOSSY_ROUGHNESS_LO 0.0
#endif
#ifndef GI_DDGI_GLOSSY_ROUGHNESS_HI
#define GI_DDGI_GLOSSY_ROUGHNESS_HI 0.45
#ifndef GI_DUGI_GLOSSY_ROUGHNESS_HI
#define GI_DUGI_GLOSSY_ROUGHNESS_HI 0.45
#endif
// Number of rays traced per probe per frame.
#ifndef GI_DDGI_RAYS_PER_PROBE
#define GI_DDGI_RAYS_PER_PROBE 128
#ifndef GI_DUGI_RAYS_PER_PROBE
#define GI_DUGI_RAYS_PER_PROBE 128
#endif
// Temporal blend hysteresis when integrating new ray results into the stored probe data (closer to 1 = more stable / slower).
#ifndef GI_DDGI_HYSTERESIS
#define GI_DDGI_HYSTERESIS 0.97
#ifndef GI_DUGI_HYSTERESIS
#define GI_DUGI_HYSTERESIS 0.97
#endif
// Sharpness exponent applied to the Chebyshev weight; higher values darken leaking transitions more aggressively.
#ifndef GI_DDGI_VISIBILITY_SHARPNESS
#define GI_DDGI_VISIBILITY_SHARPNESS 8.0
#ifndef GI_DUGI_VISIBILITY_SHARPNESS
#define GI_DUGI_VISIBILITY_SHARPNESS 8.0
#endif
// Surface bias when sampling the probe field (mirrors RTXGI probeNormalBias/probeViewBias): the shading point is offset
// along its normal and towards the camera before the probe interpolation + Chebyshev test, which reduces both probe
// self-shadowing and light leaking through thin geometry. Expressed as a fraction of the cascade cell size (probe spacing),
// so it scales with cascade resolution. Tunable; too large makes the GI "slip"/over-darken near edges.
#ifndef GI_DDGI_NORMAL_BIAS
#define GI_DDGI_NORMAL_BIAS 0.3
#ifndef GI_DUGI_NORMAL_BIAS
#define GI_DUGI_NORMAL_BIAS 0.3
#endif
#ifndef GI_DDGI_VIEW_BIAS
#define GI_DDGI_VIEW_BIAS 0.1
#ifndef GI_DUGI_VIEW_BIAS
#define GI_DUGI_VIEW_BIAS 0.1
#endif
// Upper bound for the distance written into the visibility (mean / mean^2) statistics, as a multiple of the cascade cell
// size — mirrors RTXGI's probeMaxRayDistance = length(probeSpacing) * 1.5. Keeps the depth statistics on a local scale so
// far hits / sky misses don't inflate the mean and mask a nearby thin-slab occluder (which would otherwise leak). Only
// the stored DISTANCE is clamped; the ray's radiance still gathers light from the full ray length.
#ifndef GI_DDGI_VISIBILITY_MAX_DISTANCE_SCALE
#define GI_DDGI_VISIBILITY_MAX_DISTANCE_SCALE 1.5
#ifndef GI_DUGI_VISIBILITY_MAX_DISTANCE_SCALE
#define GI_DUGI_VISIBILITY_MAX_DISTANCE_SCALE 1.5
#endif
// --- Probe relocation + classification (RTXGI-style, compile-time toggle) ---------------------------------------------
// When enabled, a per-probe "probe data" image stores xyz = world-space relocation offset (probe pushed out of geometry,
// |offset| <= GI_DDGI_PROBE_MAX_OFFSET * cellSize) and w = state (0 = inactive/inside geometry or empty space -> skipped
// while shading, 1 = active). A dedicated compute pass (gi_ddgi_relocation.comp) traces GI_DDGI_FIXED_RAYS fixed directions
// |offset| <= GI_DUGI_PROBE_MAX_OFFSET * cellSize) and w = state (0 = inactive/inside geometry or empty space -> skipped
// while shading, 1 = active). A dedicated compute pass (gi_dugi_relocation.comp) traces GI_DUGI_FIXED_RAYS fixed directions
// per probe to compute these. The trace origin and the sampler probe world position both add the offset; the sampler skips
// inactive probes. DEFAULT OFF until the Pascal side (probe-data image + relocation pass + descriptor binding) is wired.
#ifndef GI_DDGI_PROBE_RELOCATION
#define GI_DDGI_PROBE_RELOCATION 0
#ifndef GI_DUGI_PROBE_RELOCATION
#define GI_DUGI_PROBE_RELOCATION 0
#endif
#ifndef GI_DDGI_FIXED_RAYS
#define GI_DDGI_FIXED_RAYS 32
#ifndef GI_DUGI_FIXED_RAYS
#define GI_DUGI_FIXED_RAYS 32
#endif
#ifndef GI_DDGI_PROBE_MAX_OFFSET // max relocation offset as a fraction of cell size (RTXGI: 0.45, ellipsoid)
#define GI_DDGI_PROBE_MAX_OFFSET 0.45
#ifndef GI_DUGI_PROBE_MAX_OFFSET // max relocation offset as a fraction of cell size (RTXGI: 0.45, ellipsoid)
#define GI_DUGI_PROBE_MAX_OFFSET 0.45
#endif
#ifndef GI_DDGI_PROBE_MIN_FRONTFACE // keep this much clear space (in cell sizes) in front of a probe
#define GI_DDGI_PROBE_MIN_FRONTFACE 1.0
#ifndef GI_DUGI_PROBE_MIN_FRONTFACE // keep this much clear space (in cell sizes) in front of a probe
#define GI_DUGI_PROBE_MIN_FRONTFACE 1.0
#endif
#ifndef GI_DDGI_PROBE_BACKFACE_THRESHOLD // fixed-ray backface fraction above which a probe counts as inside geometry
#define GI_DDGI_PROBE_BACKFACE_THRESHOLD 0.25
#ifndef GI_DUGI_PROBE_BACKFACE_THRESHOLD // fixed-ray backface fraction above which a probe counts as inside geometry
#define GI_DUGI_PROBE_BACKFACE_THRESHOLD 0.25
#endif
#ifndef GI_DDGI_PROBE_BACKFACE_HYSTERESIS // deadband half-width around the threshold: classification only flips ACTIVE<->
#define GI_DDGI_PROBE_BACKFACE_HYSTERESIS 0.05 // INACTIVE outside [threshold-h, threshold+h], else keeps the previous state
#ifndef GI_DUGI_PROBE_BACKFACE_HYSTERESIS // deadband half-width around the threshold: classification only flips ACTIVE<->
#define GI_DUGI_PROBE_BACKFACE_HYSTERESIS 0.05 // INACTIVE outside [threshold-h, threshold+h], else keeps the previous state
#endif
#define GI_DDGI_PROBE_STATE_INACTIVE 0.0
#define GI_DDGI_PROBE_STATE_ACTIVE 1.0
#define GI_DUGI_PROBE_STATE_INACTIVE 0.0
#define GI_DUGI_PROBE_STATE_ACTIVE 1.0
// Per-probe convergence warmup (always on). Each probe ramps its temporal hysteresis from GI_DDGI_WARMUP_START_HYSTERESIS up
// to GI_DDGI_STEADY_HYSTERESIS over its first GI_DDGI_WARMUP_FRAMES frames of life, so a freshly-initialized or toroidally-
// Per-probe convergence warmup (always on). Each probe ramps its temporal hysteresis from GI_DUGI_WARMUP_START_HYSTERESIS up
// to GI_DUGI_STEADY_HYSTERESIS over its first GI_DUGI_WARMUP_FRAMES frames of life, so a freshly-initialized or toroidally-
// scrolled-in probe converges in a few frames instead of ~100 (kills the scroll-in flicker during fast camera motion). The
// per-probe age (frames since (re)init) lives in its own BDA buffer (DDGIAgeBuffer in gi_ddgi_master.glsl): the visibility
// per-probe age (frames since (re)init) lives in its own BDA buffer (DUGIAgeBuffer in gi_dugi_master.glsl): the visibility
// update owns/increments it (reset on firstFrame / scroll-in), the irradiance update reads it back.
#ifndef GI_DDGI_WARMUP_FRAMES
#define GI_DDGI_WARMUP_FRAMES 16.0
#ifndef GI_DUGI_WARMUP_FRAMES
#define GI_DUGI_WARMUP_FRAMES 16.0
#endif
#ifndef GI_DDGI_WARMUP_START_HYSTERESIS
#define GI_DDGI_WARMUP_START_HYSTERESIS 0.7
#ifndef GI_DUGI_WARMUP_START_HYSTERESIS
#define GI_DUGI_WARMUP_START_HYSTERESIS 0.7
#endif
#ifndef GI_DDGI_STEADY_HYSTERESIS
#define GI_DDGI_STEADY_HYSTERESIS 0.97
#ifndef GI_DUGI_STEADY_HYSTERESIS
#define GI_DUGI_STEADY_HYSTERESIS 0.97
#endif
// Hysteresis for a probe of the given age (frames since (re)init): low right after init, easing up to the steady value.
float ddgiWarmupHysteresis(const in float age){
return mix(GI_DDGI_WARMUP_START_HYSTERESIS, GI_DDGI_STEADY_HYSTERESIS, min(age / GI_DDGI_WARMUP_FRAMES, 1.0));
float dugiWarmupHysteresis(const in float age){
return mix(GI_DUGI_WARMUP_START_HYSTERESIS, GI_DUGI_STEADY_HYSTERESIS, min(age / GI_DUGI_WARMUP_FRAMES, 1.0));
}
// Luminance-adaptive hysteresis ("faster GI transitions", Scaling-DDGI / RTXGI ProbeBlendingCS): when a probe's freshly
// Luminance-adaptive hysteresis ("faster GI transitions", Scaling-DUGI / RTXGI ProbeBlendingCS): when a probe's freshly
// integrated irradiance differs a lot in luminance from its stored (temporally smoothed) value — a real runtime lighting
// change, e.g. a light toggles or a door opens — temporarily LOWER the temporal hysteresis so the probe re-converges in a
// few frames instead of ~100; when the field is stable, keep the high steady hysteresis (noise-free). Complements the
@ -251,49 +252,49 @@ float ddgiWarmupHysteresis(const in float age){
// relativeChange = |Lnew - Lprev| / max(Lnew, Lprev); ramps the hysteresis from base toward a floor across the threshold.
// CAVEAT: the per-frame Monte-Carlo noise of the new estimate (~96 rays) is itself a luminance change, so keep the
// threshold above that noise floor or stable probes will spuriously drop hysteresis and get noisier; the floor bounds it.
#ifndef GI_DDGI_ADAPTIVE_HYSTERESIS
#define GI_DDGI_ADAPTIVE_HYSTERESIS 0 // 0 = off (age-warmup hysteresis only; current default), 1 = on (faster reaction to lighting changes)
#ifndef GI_DUGI_ADAPTIVE_HYSTERESIS
#define GI_DUGI_ADAPTIVE_HYSTERESIS 0 // 0 = off (age-warmup hysteresis only; current default), 1 = on (faster reaction to lighting changes)
#endif
#ifndef GI_DDGI_ADAPTIVE_CHANGE_THRESHOLD // relative luminance change at which adaptation starts; above ~2x it the floor is reached
#define GI_DDGI_ADAPTIVE_CHANGE_THRESHOLD 0.25
#ifndef GI_DUGI_ADAPTIVE_CHANGE_THRESHOLD // relative luminance change at which adaptation starts; above ~2x it the floor is reached
#define GI_DUGI_ADAPTIVE_CHANGE_THRESHOLD 0.25
#endif
#ifndef GI_DDGI_ADAPTIVE_MIN_HYSTERESIS // hysteresis floor the adaptation can pull down to on a large change (still some smoothing)
#define GI_DDGI_ADAPTIVE_MIN_HYSTERESIS 0.5
#ifndef GI_DUGI_ADAPTIVE_MIN_HYSTERESIS // hysteresis floor the adaptation can pull down to on a large change (still some smoothing)
#define GI_DUGI_ADAPTIVE_MIN_HYSTERESIS 0.5
#endif
float ddgiAdaptiveHysteresis(const in float baseHysteresis, const in vec3 newColor, const in vec3 prevColor){
#if GI_DDGI_ADAPTIVE_HYSTERESIS
float dugiAdaptiveHysteresis(const in float baseHysteresis, const in vec3 newColor, const in vec3 prevColor){
#if GI_DUGI_ADAPTIVE_HYSTERESIS
const vec3 lumaWeights = vec3(0.2126, 0.7152, 0.0722);
float newLuma = dot(max(newColor, vec3(0.0)), lumaWeights);
float prevLuma = dot(max(prevColor, vec3(0.0)), lumaWeights);
float relativeChange = abs(newLuma - prevLuma) / (max(newLuma, prevLuma) + 1e-4);
float t = clamp((relativeChange - GI_DDGI_ADAPTIVE_CHANGE_THRESHOLD) / max(GI_DDGI_ADAPTIVE_CHANGE_THRESHOLD, 1e-4), 0.0, 1.0);
return mix(baseHysteresis, min(baseHysteresis, GI_DDGI_ADAPTIVE_MIN_HYSTERESIS), t);
float t = clamp((relativeChange - GI_DUGI_ADAPTIVE_CHANGE_THRESHOLD) / max(GI_DUGI_ADAPTIVE_CHANGE_THRESHOLD, 1e-4), 0.0, 1.0);
return mix(baseHysteresis, min(baseHysteresis, GI_DUGI_ADAPTIVE_MIN_HYSTERESIS), t);
#else
return baseHysteresis;
#endif
}
// First ray index the irradiance/visibility integration uses. With relocation enabled the first GI_DDGI_FIXED_RAYS rays
// First ray index the irradiance/visibility integration uses. With relocation enabled the first GI_DUGI_FIXED_RAYS rays
// are the FIXED rays (unrotated, used only by the relocation + classification passes for geometry sampling), so the probe
// blend skips them — exactly RTXGI's `rayIndex = NUM_FIXED_RAYS` when relocation/classification is enabled.
#if GI_DDGI_PROBE_RELOCATION
#define GI_DDGI_RAY_START uint(GI_DDGI_FIXED_RAYS)
#if GI_DUGI_PROBE_RELOCATION
#define GI_DUGI_RAY_START uint(GI_DUGI_FIXED_RAYS)
#else
#define GI_DDGI_RAY_START 0u
#define GI_DUGI_RAY_START 0u
#endif
const ivec3 uDDGIProbeCounts = ivec3(GI_DDGI_PROBES_X, GI_DDGI_PROBES_Y, GI_DDGI_PROBES_Z);
const ivec3 uDUGIProbeCounts = ivec3(GI_DUGI_PROBES_X, GI_DUGI_PROBES_Y, GI_DUGI_PROBES_Z);
// --- Uniform data -----------------------------------------------------------------------------------------------------
// Mirrors the cascaded radiance hints volume uniform layout (one entry per cascade) so the CPU-side snapping code can be
// shared. AABBMin/Max/Scale/Center are the probe grid bounds in world space; the probes sit on the grid lattice spanning
// the AABB, i.e. probe (i,j,k) is at AABBMin + (i,j,k) * cellSize, with cellSize = (AABBMax-AABBMin)/(probeCounts-1).
// The DDGI data block (cascade globals + the BDA sub-buffer pointers) lives in gi_ddgi_data.glsl as one std430 readonly SSBO
// `ddgiData`, declared at the DDGI set's binding 0 (same set/binding the old globals UBO used). Only pulled in when the DDGI
// set is defined (i.e. a DDGI shader, which has GL_EXT_buffer_reference enabled); constants-only includers skip it. The
// addressing/sampling helpers below read ddgiData.ddgiCascade* exactly as before — only the backing storage changed.
// The DUGI data block (cascade globals + the BDA sub-buffer pointers) lives in gi_dugi_data.glsl as one std430 readonly SSBO
// `dugiData`, declared at the DUGI set's binding 0 (same set/binding the old globals UBO used). Only pulled in when the DUGI
// set is defined (i.e. a DUGI shader, which has GL_EXT_buffer_reference enabled); constants-only includers skip it. The
// addressing/sampling helpers below read dugiData.dugiCascade* exactly as before — only the backing storage changed.
#ifdef GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET
#include "gi_ddgi_data.glsl"
#include "gi_dugi_data.glsl"
#endif
// =====================================================================================================================
@ -305,25 +306,25 @@ const ivec3 uDDGIProbeCounts = ivec3(GI_DDGI_PROBES_X, GI_DDGI_PROBES_Y, GI_DDGI
// Probe spacing is exactly cellSize (= the AABB snap increment), so the lattice stays aligned to the world cell grid as
// the volume snaps/scrolls; probe (i,j,k) sits at AABBMin + (i,j,k)*cellSize (the last probe leaves a one-cell margin
// before AABBMax, which is what the cascade fade band uses).
vec3 ddgiWorldToProbeGrid(const in vec3 worldPosition, const in int cascadeIndex){
return (worldPosition - ddgiData.ddgiCascadeAABBMin[cascadeIndex].xyz) / ddgiData.ddgiCascadeCellSizes[cascadeIndex].xyz;
vec3 dugiWorldToProbeGrid(const in vec3 worldPosition, const in int cascadeIndex){
return (worldPosition - dugiData.dugiCascadeAABBMin[cascadeIndex].xyz) / dugiData.dugiCascadeCellSizes[cascadeIndex].xyz;
}
vec3 ddgiProbeGridToWorld(const in ivec3 probeCoord, const in int cascadeIndex){
return ddgiData.ddgiCascadeAABBMin[cascadeIndex].xyz + (vec3(probeCoord) * ddgiData.ddgiCascadeCellSizes[cascadeIndex].xyz);
vec3 dugiProbeGridToWorld(const in ivec3 probeCoord, const in int cascadeIndex){
return dugiData.dugiCascadeAABBMin[cascadeIndex].xyz + (vec3(probeCoord) * dugiData.dugiCascadeCellSizes[cascadeIndex].xyz);
}
#endif
// Linear probe index within a cascade from integer probe coordinates.
int ddgiProbeIndex(const in ivec3 probeCoord){
return (((probeCoord.z * GI_DDGI_PROBES_Y) + probeCoord.y) * GI_DDGI_PROBES_X) + probeCoord.x;
int dugiProbeIndex(const in ivec3 probeCoord){
return (((probeCoord.z * GI_DUGI_PROBES_Y) + probeCoord.y) * GI_DUGI_PROBES_X) + probeCoord.x;
}
// Inverse of ddgiProbeIndex: integer probe coordinates from a linear index within a cascade.
ivec3 ddgiProbeCoordFromIndex(const in int probeIndex){
int x = probeIndex % GI_DDGI_PROBES_X;
int y = (probeIndex / GI_DDGI_PROBES_X) % GI_DDGI_PROBES_Y;
int z = probeIndex / (GI_DDGI_PROBES_X * GI_DDGI_PROBES_Y);
// Inverse of dugiProbeIndex: integer probe coordinates from a linear index within a cascade.
ivec3 dugiProbeCoordFromIndex(const in int probeIndex){
int x = probeIndex % GI_DUGI_PROBES_X;
int y = (probeIndex / GI_DUGI_PROBES_X) % GI_DUGI_PROBES_Y;
int z = probeIndex / (GI_DUGI_PROBES_X * GI_DUGI_PROBES_Y);
return ivec3(x, y, z);
}
@ -335,32 +336,32 @@ ivec3 ddgiProbeCoordFromIndex(const in int probeIndex){
// A world cell W = baseCell + logical; a physical slot keeps representing the same world cell while it stays inside the
// volume, and only "scrolls in" (gets a new world cell, so its history must be reset) at the leading edges.
#ifdef GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET
ivec3 ddgiProbeBaseCell(const in int cascadeIndex){
return (ddgiData.ddgiCascadeProbeScroll[cascadeIndex].w != 0) ? ddgiData.ddgiCascadeProbeScroll[cascadeIndex].xyz : ivec3(0);
ivec3 dugiProbeBaseCell(const in int cascadeIndex){
return (dugiData.dugiCascadeProbeScroll[cascadeIndex].w != 0) ? dugiData.dugiCascadeProbeScroll[cascadeIndex].xyz : ivec3(0);
}
ivec3 ddgiProbeBaseCellPrev(const in int cascadeIndex){
return (ddgiData.ddgiCascadeProbeScroll[cascadeIndex].w != 0) ? ddgiData.ddgiCascadeProbeScrollPrev[cascadeIndex].xyz : ivec3(0);
ivec3 dugiProbeBaseCellPrev(const in int cascadeIndex){
return (dugiData.dugiCascadeProbeScroll[cascadeIndex].w != 0) ? dugiData.dugiCascadeProbeScrollPrev[cascadeIndex].xyz : ivec3(0);
}
// Physical storage coordinate for a logical probe coordinate (used when sampling/reading the field).
ivec3 ddgiProbePhysicalCoord(const in ivec3 logicalCoord, const in int cascadeIndex){
ivec3 c = uDDGIProbeCounts;
return (((logicalCoord + ddgiProbeBaseCell(cascadeIndex)) % c) + c) % c;
ivec3 dugiProbePhysicalCoord(const in ivec3 logicalCoord, const in int cascadeIndex){
ivec3 c = uDUGIProbeCounts;
return (((logicalCoord + dugiProbeBaseCell(cascadeIndex)) % c) + c) % c;
}
// Logical probe coordinate for a physical storage slot (used by the update passes that iterate physical slots).
ivec3 ddgiProbeLogicalCoord(const in ivec3 physicalCoord, const in int cascadeIndex){
ivec3 c = uDDGIProbeCounts;
return (((physicalCoord - ddgiProbeBaseCell(cascadeIndex)) % c) + c) % c;
ivec3 dugiProbeLogicalCoord(const in ivec3 physicalCoord, const in int cascadeIndex){
ivec3 c = uDUGIProbeCounts;
return (((physicalCoord - dugiProbeBaseCell(cascadeIndex)) % c) + c) % c;
}
// True if a physical slot now maps to a different world cell than at the previous update of this in-flight slot, i.e. it
// just scrolled into the volume and its stored history is stale and must be discarded.
bool ddgiProbeScrolledIn(const in ivec3 physicalCoord, const in int cascadeIndex){
ivec3 c = uDDGIProbeCounts;
ivec3 base = ddgiProbeBaseCell(cascadeIndex);
ivec3 basePrev = ddgiProbeBaseCellPrev(cascadeIndex);
bool dugiProbeScrolledIn(const in ivec3 physicalCoord, const in int cascadeIndex){
ivec3 c = uDUGIProbeCounts;
ivec3 base = dugiProbeBaseCell(cascadeIndex);
ivec3 basePrev = dugiProbeBaseCellPrev(cascadeIndex);
ivec3 worldCell = base + ((((physicalCoord - base) % c) + c) % c);
ivec3 worldCellPrev = basePrev + ((((physicalCoord - basePrev) % c) + c) % c);
return any(notEqual(worldCell, worldCellPrev));
@ -368,7 +369,7 @@ bool ddgiProbeScrolledIn(const in ivec3 physicalCoord, const in int cascadeIndex
#endif
// Evenly distributed direction on the unit sphere (spherical Fibonacci / golden spiral) for ray index i of n.
vec3 ddgiSphericalFibonacci(const in float i, const in float n){
vec3 dugiSphericalFibonacci(const in float i, const in float n){
const float PHI = 1.6180339887498949; // golden ratio
float phi = 6.2831853071795864 * fract(i * (PHI - 1.0));
float cosTheta = 1.0 - ((2.0 * i) + 1.0) * (1.0 / n);
@ -377,95 +378,95 @@ vec3 ddgiSphericalFibonacci(const in float i, const in float n){
}
// The traced direction for a given ray index, rotated by a per-frame random rotation so that, over several frames, the
// whole sphere is covered while only GI_DDGI_RAYS_PER_PROBE rays are traced per frame. Both the trace and update shaders
// whole sphere is covered while only GI_DUGI_RAYS_PER_PROBE rays are traced per frame. Both the trace and update shaders
// call this with the same rotation (passed as a push constant) so they agree on the directions without storing them.
vec3 ddgiRayDirection(const in int rayIndex, const in mat3 randomRotation){
return normalize(randomRotation * ddgiSphericalFibonacci(float(rayIndex), float(GI_DDGI_RAYS_PER_PROBE)));
vec3 dugiRayDirection(const in int rayIndex, const in mat3 randomRotation){
return normalize(randomRotation * dugiSphericalFibonacci(float(rayIndex), float(GI_DUGI_RAYS_PER_PROBE)));
}
// Octahedral atlases pack the probes of a cascade row-major into a 2D grid of tiles. We lay out all cascades vertically
// (one cascade block per GI_DDGI_PROBES_Z*... rows) so a single 2D texture array layer or a tall 2D texture can hold them.
// tilesPerRow chosen as GI_DDGI_PROBES_X * GI_DDGI_PROBES_Y wide is wasteful; instead we use a square-ish layout.
const int GI_DDGI_TILES_PER_ROW = GI_DDGI_PROBES_X; // one row of the atlas holds one X-row of probes
// (one cascade block per GI_DUGI_PROBES_Z*... rows) so a single 2D texture array layer or a tall 2D texture can hold them.
// tilesPerRow chosen as GI_DUGI_PROBES_X * GI_DUGI_PROBES_Y wide is wasteful; instead we use a square-ish layout.
const int GI_DUGI_TILES_PER_ROW = GI_DUGI_PROBES_X; // one row of the atlas holds one X-row of probes
// Top-left interior texel (in full-tile units, i.e. including guard band) of a probe tile inside the atlas for a given
// per-probe full tile size.
ivec2 ddgiProbeTileOrigin(const in ivec3 probeCoord, const in int cascadeIndex, const in int fullTileSize){
ivec2 dugiProbeTileOrigin(const in ivec3 probeCoord, const in int cascadeIndex, const in int fullTileSize){
// Atlas grid coordinate of the tile: x advances with probe.x, y advances with probe.y then probe.z then cascade.
int tileX = probeCoord.x;
int tileY = probeCoord.y + (GI_DDGI_PROBES_Y * (probeCoord.z + (GI_DDGI_PROBES_Z * cascadeIndex)));
int tileY = probeCoord.y + (GI_DUGI_PROBES_Y * (probeCoord.z + (GI_DUGI_PROBES_Z * cascadeIndex)));
return (ivec2(tileX, tileY) * fullTileSize) + ivec2(1); // +1 to skip the guard-band texel
}
// Atlas dimensions in texels for a given per-probe full tile size.
ivec2 ddgiAtlasSize(const in int fullTileSize){
return ivec2(GI_DDGI_PROBES_X, GI_DDGI_PROBES_Y * GI_DDGI_PROBES_Z * GI_DDGI_CASCADES) * fullTileSize;
ivec2 dugiAtlasSize(const in int fullTileSize){
return ivec2(GI_DUGI_PROBES_X, GI_DUGI_PROBES_Y * GI_DUGI_PROBES_Z * GI_DUGI_CASCADES) * fullTileSize;
}
// Normalized [0,1] atlas UV for a direction in a probe's octahedral tile (for sampling with a linear sampler; the guard
// band makes bilinear taps at tile edges correct).
vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 direction, const in int interiorSize, const in int fullTileSize){
vec2 dugiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 direction, const in int interiorSize, const in int fullTileSize){
vec2 oct = fma(octEncode(normalize(direction)), vec2(0.5), vec2(0.5)); // [-1,1] -> [0,1]
vec2 originTexel = vec2(ddgiProbeTileOrigin(probeCoord, cascadeIndex, fullTileSize));
vec2 originTexel = vec2(dugiProbeTileOrigin(probeCoord, cascadeIndex, fullTileSize));
vec2 texel = originTexel + (oct * float(interiorSize));
return texel / vec2(ddgiAtlasSize(fullTileSize));
return texel / vec2(dugiAtlasSize(fullTileSize));
}
// =====================================================================================================================
// Probe data declarations and sampling (only when sampling, i.e. in mesh.frag or the probe update shader)
// =====================================================================================================================
#ifdef GLOBAL_ILLUMINATION_DDGI_SAMPLE
#ifdef GLOBAL_ILLUMINATION_DUGI_SAMPLE
// Irradiance storage.
#if GI_DDGI_STORAGE_IS_SH
// RGB spherical harmonics packed into DDGI_SH_IMAGE_COUNT RGBA16F 3D textures per cascade (L1 = 3, L2 = 7); see the
// consumer's ddgiLoadIrradianceSH for the exact (un)packing. The 3D texture coordinate addresses the probe lattice
#if GI_DUGI_STORAGE_IS_SH
// RGB spherical harmonics packed into DUGI_SH_IMAGE_COUNT RGBA16F 3D textures per cascade (L1 = 3, L2 = 7); see the
// consumer's dugiLoadIrradianceSH for the exact (un)packing. The 3D texture coordinate addresses the probe lattice
// (size = probe counts, with the cascade stacked along Z).
#include "sphericalharmonics.glsl"
// Defined by each consumer against its own resources: the probe update shader loads from a storage image, the
// shading pass loads from a sampled texture. Returns the stored *radiance* SH (L1 or L2) of the probe.
DDGI_SH_TYPE ddgiLoadIrradianceSH(const in ivec3 probeCoord, const in int cascadeIndex);
DUGI_SH_TYPE dugiLoadIrradianceSH(const in ivec3 probeCoord, const in int cascadeIndex);
// Evaluate the diffuse irradiance E(n) for a normal direction: convolve the stored radiance SH with the clamped
// cosine lobe and evaluate it in the normal direction. The caller multiplies by albedo/PI to get outgoing radiance.
vec3 ddgiEvaluateIrradiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 normal){
DDGI_SH_TYPE sh = DDGI_SH_CONVOLVE_COSINE(ddgiLoadIrradianceSH(probeCoord, cascadeIndex));
return max(vec3(0.0), DDGI_SH_EVALUATE(sh, normalize(normal)));
vec3 dugiEvaluateIrradiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 normal){
DUGI_SH_TYPE sh = DUGI_SH_CONVOLVE_COSINE(dugiLoadIrradianceSH(probeCoord, cascadeIndex));
return max(vec3(0.0), DUGI_SH_EVALUATE(sh, normalize(normal)));
}
#else
// Octahedral irradiance atlas (RGBA16F).
vec3 ddgiEvaluateIrradiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 normal);
vec3 dugiEvaluateIrradiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 normal);
#endif
// Visibility octahedral atlas (RGBA16F): x = mean distance, y = mean distance squared (Chebyshev), z = sky visibility
// (fraction of probe rays in that direction that escaped to the sky / missed geometry, used as the IBL occlusion factor).
vec3 ddgiSampleVisibility(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 direction);
vec3 dugiSampleVisibility(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 direction);
#if GI_DDGI_PROBE_RELOCATION
#if GI_DUGI_PROBE_RELOCATION
// Probe data (xyz = world-space relocation offset, w = state) for the physical probe slot. Provided by the consumer
// (sampler3D in the shading path / image3D in the trace), like ddgiSampleVisibility above.
vec4 ddgiLoadProbeData(const in ivec3 probeCoord, const in int cascadeIndex);
// (sampler3D in the shading path / image3D in the trace), like dugiSampleVisibility above.
vec4 dugiLoadProbeData(const in ivec3 probeCoord, const in int cascadeIndex);
#endif
#if defined(GI_DDGI_GLOSSY_RADIANCE)
#if defined(GI_DUGI_GLOSSY_RADIANCE)
// Prefiltered glossy *radiance* (NOT cosine-convolved) for a reflection direction, from the octahedral glossy atlas.
// Provided by the consumer: a (u)sampler2D with manual/HW bilinear in the shading path. See ddgiSampleGlossyRadiance.
vec3 ddgiEvaluateGlossyRadiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 reflectionDirection);
// Provided by the consumer: a (u)sampler2D with manual/HW bilinear in the shading path. See dugiSampleGlossyRadiance.
vec3 dugiEvaluateGlossyRadiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 reflectionDirection);
#endif
// ---------------------------------------------------------------------------------------------------------------------
// Sample the irradiance field at a world position for a surface with the given normal, with Chebyshev visibility
// weighting (the DDGI leak-reduction term) and trilinear + backface weighting. Returns diffuse irradiance.
// weighting (the DUGI leak-reduction term) and trilinear + backface weighting. Returns diffuse irradiance.
// ---------------------------------------------------------------------------------------------------------------------
vec3 ddgiSampleIrradianceInCascade(const in vec3 worldPosition, const in vec3 normal, const in vec3 viewDirection, const in int cascadeIndex, out float skyVisibility){
vec3 gridCoord = ddgiWorldToProbeGrid(worldPosition, cascadeIndex);
vec3 dugiSampleIrradianceInCascade(const in vec3 worldPosition, const in vec3 normal, const in vec3 viewDirection, const in int cascadeIndex, out float skyVisibility){
vec3 gridCoord = dugiWorldToProbeGrid(worldPosition, cascadeIndex);
ivec3 baseProbe = ivec3(floor(gridCoord));
vec3 frac = gridCoord - vec3(baseProbe);
// Surface bias (along normal + towards camera, scaled by the cascade cell size) to reduce probe self-shadowing AND
// light leaking through thin geometry; the Chebyshev distToProbe below is measured from this lifted position.
vec3 biasedPosition = worldPosition + ((normal * GI_DDGI_NORMAL_BIAS) + (viewDirection * GI_DDGI_VIEW_BIAS)) * ddgiData.ddgiCascadeCellSizes[cascadeIndex].x;
vec3 biasedPosition = worldPosition + ((normal * GI_DUGI_NORMAL_BIAS) + (viewDirection * GI_DUGI_VIEW_BIAS)) * dugiData.dugiCascadeCellSizes[cascadeIndex].x;
vec3 sumIrradiance = vec3(0.0);
float sumSkyVisibility = 0.0;
@ -473,15 +474,15 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
for(int i = 0; i < 8; i++){
ivec3 offset = ivec3(i & 1, (i >> 1) & 1, (i >> 2) & 1);
ivec3 probeCoord = clamp(baseProbe + offset, ivec3(0), uDDGIProbeCounts - ivec3(1)); // logical (lattice) coord
ivec3 physProbeCoord = ddgiProbePhysicalCoord(probeCoord, cascadeIndex); // toroidal storage slot for reads
ivec3 probeCoord = clamp(baseProbe + offset, ivec3(0), uDUGIProbeCounts - ivec3(1)); // logical (lattice) coord
ivec3 physProbeCoord = dugiProbePhysicalCoord(probeCoord, cascadeIndex); // toroidal storage slot for reads
vec3 trilinear = mix(vec3(1.0) - frac, frac, vec3(offset));
float weight = trilinear.x * trilinear.y * trilinear.z;
vec3 probeWorld = ddgiProbeGridToWorld(probeCoord, cascadeIndex);
#if GI_DDGI_PROBE_RELOCATION
vec4 probeData = ddgiLoadProbeData(physProbeCoord, cascadeIndex);
vec3 probeWorld = dugiProbeGridToWorld(probeCoord, cascadeIndex);
#if GI_DUGI_PROBE_RELOCATION
vec4 probeData = dugiLoadProbeData(physProbeCoord, cascadeIndex);
if(probeData.w < 0.5){
continue; // inactive probe (classified inside geometry / empty space) — skip it in the gather
}
@ -496,7 +497,7 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
// Chebyshev visibility test against the probe's stored octahedral depth statistics.
float distToProbe = length(probeToPoint);
vec3 vis = ddgiSampleVisibility(physProbeCoord, cascadeIndex, normalize(probeToPoint));
vec3 vis = dugiSampleVisibility(physProbeCoord, cascadeIndex, normalize(probeToPoint));
vec2 moments = vis.xy;
float meanDist = moments.x;
if(distToProbe > meanDist){
@ -515,10 +516,10 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
weight = max(weight, 1e-6);
sumIrradiance += ddgiEvaluateIrradiance(physProbeCoord, cascadeIndex, normal) * weight;
sumIrradiance += dugiEvaluateIrradiance(physProbeCoord, cascadeIndex, normal) * weight;
// Sky visibility for IBL occlusion: how open the surface hemisphere (normal direction) is to the sky at this probe.
sumSkyVisibility += ddgiSampleVisibility(physProbeCoord, cascadeIndex, normal).z * weight;
sumSkyVisibility += dugiSampleVisibility(physProbeCoord, cascadeIndex, normal).z * weight;
sumWeight += weight;
@ -536,11 +537,11 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
// Select cascade by AABB containment with fade-based blending between cascades, then sample. Returns diffuse irradiance;
// skyVisibility (out) is the IBL occlusion factor (1 = fully open to the sky, 0 = enclosed), 1 outside all cascades.
vec3 ddgiSampleIrradiance(const in vec3 worldPosition, const in vec3 normal, const in vec3 viewDirection, out float skyVisibility){
vec3 dugiSampleIrradiance(const in vec3 worldPosition, const in vec3 normal, const in vec3 viewDirection, out float skyVisibility){
int cascadeIndex = 0;
while(((cascadeIndex + 1) < GI_DDGI_CASCADES) &&
(any(lessThan(worldPosition, ddgiData.ddgiCascadeAABBMin[cascadeIndex].xyz)) ||
any(greaterThan(worldPosition, ddgiData.ddgiCascadeAABBMax[cascadeIndex].xyz)))){
while(((cascadeIndex + 1) < GI_DUGI_CASCADES) &&
(any(lessThan(worldPosition, dugiData.dugiCascadeAABBMin[cascadeIndex].xyz)) ||
any(greaterThan(worldPosition, dugiData.dugiCascadeAABBMax[cascadeIndex].xyz)))){
cascadeIndex++;
}
@ -548,16 +549,16 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
float sumSkyVisibility = 0.0;
float sumWeight = 0.0;
float current = 1.0;
for(int c = cascadeIndex; c < GI_DDGI_CASCADES; c++){
for(int c = cascadeIndex; c < GI_DUGI_CASCADES; c++){
float weight;
if(c == (GI_DDGI_CASCADES - 1)){
if(c == (GI_DUGI_CASCADES - 1)){
weight = current;
current = 0.0;
}else if(all(greaterThanEqual(worldPosition, ddgiData.ddgiCascadeAABBMin[c].xyz)) &&
all(lessThanEqual(worldPosition, ddgiData.ddgiCascadeAABBMax[c].xyz))){
vec3 fade = smoothstep(ddgiData.ddgiCascadeAABBFadeStart[c].xyz,
ddgiData.ddgiCascadeAABBFadeEnd[c].xyz,
abs(worldPosition - ddgiData.ddgiCascadeAABBCenter[c].xyz));
}else if(all(greaterThanEqual(worldPosition, dugiData.dugiCascadeAABBMin[c].xyz)) &&
all(lessThanEqual(worldPosition, dugiData.dugiCascadeAABBMax[c].xyz))){
vec3 fade = smoothstep(dugiData.dugiCascadeAABBFadeStart[c].xyz,
dugiData.dugiCascadeAABBFadeEnd[c].xyz,
abs(worldPosition - dugiData.dugiCascadeAABBCenter[c].xyz));
float f = 1.0 - clamp(max(max(fade.x, fade.y), fade.z), 0.0, 1.0);
weight = current * f;
current *= 1.0 - f;
@ -566,7 +567,7 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
}
if(weight > 1e-6){
float cascadeSkyVisibility;
result += ddgiSampleIrradianceInCascade(worldPosition, normal, viewDirection, c, cascadeSkyVisibility) * weight;
result += dugiSampleIrradianceInCascade(worldPosition, normal, viewDirection, c, cascadeSkyVisibility) * weight;
sumSkyVisibility += cascadeSkyVisibility * weight;
sumWeight += weight;
}
@ -578,34 +579,34 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
return result;
}
#if defined(GI_DDGI_GLOSSY_RADIANCE)
#if defined(GI_DUGI_GLOSSY_RADIANCE)
// ---------------------------------------------------------------------------------------------------------------------
// Sample the prefiltered glossy radiance field along a reflection direction. Same probe gather as the irradiance
// path (surface bias, trilinear, relocation skip, normal-based backface wrap, Chebyshev visibility) so it stays leak-
// consistent — only the per-probe lookup samples the glossy atlas along the *reflection* vector instead of evaluating
// cosine-convolved irradiance along the normal. Returns prefiltered radiance (the caller applies the split-sum BRDF).
// ---------------------------------------------------------------------------------------------------------------------
vec3 ddgiSampleGlossyRadianceInCascade(const in vec3 worldPosition, const in vec3 normal, const in vec3 reflectionDirection, const in vec3 viewDirection, const in int cascadeIndex){
vec3 gridCoord = ddgiWorldToProbeGrid(worldPosition, cascadeIndex);
vec3 dugiSampleGlossyRadianceInCascade(const in vec3 worldPosition, const in vec3 normal, const in vec3 reflectionDirection, const in vec3 viewDirection, const in int cascadeIndex){
vec3 gridCoord = dugiWorldToProbeGrid(worldPosition, cascadeIndex);
ivec3 baseProbe = ivec3(floor(gridCoord));
vec3 frac = gridCoord - vec3(baseProbe);
vec3 biasedPosition = worldPosition + ((normal * GI_DDGI_NORMAL_BIAS) + (viewDirection * GI_DDGI_VIEW_BIAS)) * ddgiData.ddgiCascadeCellSizes[cascadeIndex].x;
vec3 biasedPosition = worldPosition + ((normal * GI_DUGI_NORMAL_BIAS) + (viewDirection * GI_DUGI_VIEW_BIAS)) * dugiData.dugiCascadeCellSizes[cascadeIndex].x;
vec3 sumGlossy = vec3(0.0);
float sumWeight = 0.0;
for(int i = 0; i < 8; i++){
ivec3 offset = ivec3(i & 1, (i >> 1) & 1, (i >> 2) & 1);
ivec3 probeCoord = clamp(baseProbe + offset, ivec3(0), uDDGIProbeCounts - ivec3(1));
ivec3 physProbeCoord = ddgiProbePhysicalCoord(probeCoord, cascadeIndex);
ivec3 probeCoord = clamp(baseProbe + offset, ivec3(0), uDUGIProbeCounts - ivec3(1));
ivec3 physProbeCoord = dugiProbePhysicalCoord(probeCoord, cascadeIndex);
vec3 trilinear = mix(vec3(1.0) - frac, frac, vec3(offset));
float weight = trilinear.x * trilinear.y * trilinear.z;
vec3 probeWorld = ddgiProbeGridToWorld(probeCoord, cascadeIndex);
#if GI_DDGI_PROBE_RELOCATION
vec4 probeData = ddgiLoadProbeData(physProbeCoord, cascadeIndex);
vec3 probeWorld = dugiProbeGridToWorld(probeCoord, cascadeIndex);
#if GI_DUGI_PROBE_RELOCATION
vec4 probeData = dugiLoadProbeData(physProbeCoord, cascadeIndex);
if(probeData.w < 0.5){
continue;
}
@ -618,7 +619,7 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
weight *= (wrap * wrap) + 0.2;
float distToProbe = length(probeToPoint);
vec2 moments = ddgiSampleVisibility(physProbeCoord, cascadeIndex, normalize(probeToPoint)).xy;
vec2 moments = dugiSampleVisibility(physProbeCoord, cascadeIndex, normalize(probeToPoint)).xy;
float meanDist = moments.x;
if(distToProbe > meanDist){
float variance = abs((meanDist * meanDist) - moments.y);
@ -634,36 +635,36 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
}
weight = max(weight, 1e-6);
sumGlossy += ddgiEvaluateGlossyRadiance(physProbeCoord, cascadeIndex, reflectionDirection) * weight;
sumGlossy += dugiEvaluateGlossyRadiance(physProbeCoord, cascadeIndex, reflectionDirection) * weight;
sumWeight += weight;
}
return (sumWeight > 0.0) ? (sumGlossy / sumWeight) : vec3(0.0);
}
// Cascade selection + fade blend (same scheme as ddgiSampleIrradiance), returning prefiltered glossy radiance along the
// Cascade selection + fade blend (same scheme as dugiSampleIrradiance), returning prefiltered glossy radiance along the
// reflection vector. Outside all cascades returns 0 (the caller falls back to the broad source / environment specular).
vec3 ddgiSampleGlossyRadiance(const in vec3 worldPosition, const in vec3 normal, const in vec3 reflectionDirection, const in vec3 viewDirection){
vec3 dugiSampleGlossyRadiance(const in vec3 worldPosition, const in vec3 normal, const in vec3 reflectionDirection, const in vec3 viewDirection){
int cascadeIndex = 0;
while(((cascadeIndex + 1) < GI_DDGI_CASCADES) &&
(any(lessThan(worldPosition, ddgiData.ddgiCascadeAABBMin[cascadeIndex].xyz)) ||
any(greaterThan(worldPosition, ddgiData.ddgiCascadeAABBMax[cascadeIndex].xyz)))){
while(((cascadeIndex + 1) < GI_DUGI_CASCADES) &&
(any(lessThan(worldPosition, dugiData.dugiCascadeAABBMin[cascadeIndex].xyz)) ||
any(greaterThan(worldPosition, dugiData.dugiCascadeAABBMax[cascadeIndex].xyz)))){
cascadeIndex++;
}
vec3 result = vec3(0.0);
float sumWeight = 0.0;
float current = 1.0;
for(int c = cascadeIndex; c < GI_DDGI_CASCADES; c++){
for(int c = cascadeIndex; c < GI_DUGI_CASCADES; c++){
float weight;
if(c == (GI_DDGI_CASCADES - 1)){
if(c == (GI_DUGI_CASCADES - 1)){
weight = current;
current = 0.0;
}else if(all(greaterThanEqual(worldPosition, ddgiData.ddgiCascadeAABBMin[c].xyz)) &&
all(lessThanEqual(worldPosition, ddgiData.ddgiCascadeAABBMax[c].xyz))){
vec3 fade = smoothstep(ddgiData.ddgiCascadeAABBFadeStart[c].xyz,
ddgiData.ddgiCascadeAABBFadeEnd[c].xyz,
abs(worldPosition - ddgiData.ddgiCascadeAABBCenter[c].xyz));
}else if(all(greaterThanEqual(worldPosition, dugiData.dugiCascadeAABBMin[c].xyz)) &&
all(lessThanEqual(worldPosition, dugiData.dugiCascadeAABBMax[c].xyz))){
vec3 fade = smoothstep(dugiData.dugiCascadeAABBFadeStart[c].xyz,
dugiData.dugiCascadeAABBFadeEnd[c].xyz,
abs(worldPosition - dugiData.dugiCascadeAABBCenter[c].xyz));
float f = 1.0 - clamp(max(max(fade.x, fade.y), fade.z), 0.0, 1.0);
weight = current * f;
current *= 1.0 - f;
@ -671,7 +672,7 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
break;
}
if(weight > 1e-6){
result += ddgiSampleGlossyRadianceInCascade(worldPosition, normal, reflectionDirection, viewDirection, c) * weight;
result += dugiSampleGlossyRadianceInCascade(worldPosition, normal, reflectionDirection, viewDirection, c) * weight;
sumWeight += weight;
}
if(current < 1e-6){
@ -680,37 +681,37 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
}
return (sumWeight > 0.0) ? (result / sumWeight) : vec3(0.0);
}
#endif // GI_DDGI_GLOSSY_RADIANCE
#endif // GI_DUGI_GLOSSY_RADIANCE
#if GI_DDGI_STORAGE_IS_SH
#if GI_DUGI_STORAGE_IS_SH
// ---------------------------------------------------------------------------------------------------------------------
// Same sampling as ddgiSampleIrradiance* but returning the blended *radiance* SH (L1 or L2, pre cosine-lobe) instead of
// Same sampling as dugiSampleIrradiance* but returning the blended *radiance* SH (L1 or L2, pre cosine-lobe) instead of
// the evaluated diffuse irradiance. The SH-storage shading path uses this to extract a dominant directional light
// (proper specular via the analytic BRDF) plus a residual ambient SH (diffuse), mirroring the cascaded radiance hints.
// ---------------------------------------------------------------------------------------------------------------------
DDGI_SH_TYPE ddgiSampleRadianceSHInCascade(const in vec3 worldPosition, const in vec3 normal, const in vec3 viewDirection, const in int cascadeIndex, out float skyVisibility){
vec3 gridCoord = ddgiWorldToProbeGrid(worldPosition, cascadeIndex);
DUGI_SH_TYPE dugiSampleRadianceSHInCascade(const in vec3 worldPosition, const in vec3 normal, const in vec3 viewDirection, const in int cascadeIndex, out float skyVisibility){
vec3 gridCoord = dugiWorldToProbeGrid(worldPosition, cascadeIndex);
ivec3 baseProbe = ivec3(floor(gridCoord));
vec3 frac = gridCoord - vec3(baseProbe);
// Surface bias (see ddgiSampleIrradianceInCascade): lift along normal + towards camera, scaled by the cell size.
vec3 biasedPosition = worldPosition + ((normal * GI_DDGI_NORMAL_BIAS) + (viewDirection * GI_DDGI_VIEW_BIAS)) * ddgiData.ddgiCascadeCellSizes[cascadeIndex].x;
// Surface bias (see dugiSampleIrradianceInCascade): lift along normal + towards camera, scaled by the cell size.
vec3 biasedPosition = worldPosition + ((normal * GI_DUGI_NORMAL_BIAS) + (viewDirection * GI_DUGI_VIEW_BIAS)) * dugiData.dugiCascadeCellSizes[cascadeIndex].x;
DDGI_SH_TYPE sumSH = DDGI_SH_ZERO();
DUGI_SH_TYPE sumSH = DUGI_SH_ZERO();
float sumSkyVisibility = 0.0;
float sumWeight = 0.0;
for(int i = 0; i < 8; i++){
ivec3 offset = ivec3(i & 1, (i >> 1) & 1, (i >> 2) & 1);
ivec3 probeCoord = clamp(baseProbe + offset, ivec3(0), uDDGIProbeCounts - ivec3(1)); // logical (lattice) coord
ivec3 physProbeCoord = ddgiProbePhysicalCoord(probeCoord, cascadeIndex); // toroidal storage slot for reads
ivec3 probeCoord = clamp(baseProbe + offset, ivec3(0), uDUGIProbeCounts - ivec3(1)); // logical (lattice) coord
ivec3 physProbeCoord = dugiProbePhysicalCoord(probeCoord, cascadeIndex); // toroidal storage slot for reads
vec3 trilinear = mix(vec3(1.0) - frac, frac, vec3(offset));
float weight = trilinear.x * trilinear.y * trilinear.z;
vec3 probeWorld = ddgiProbeGridToWorld(probeCoord, cascadeIndex);
#if GI_DDGI_PROBE_RELOCATION
vec4 probeData = ddgiLoadProbeData(physProbeCoord, cascadeIndex);
vec3 probeWorld = dugiProbeGridToWorld(probeCoord, cascadeIndex);
#if GI_DUGI_PROBE_RELOCATION
vec4 probeData = dugiLoadProbeData(physProbeCoord, cascadeIndex);
if(probeData.w < 0.5){
continue; // inactive probe (classified inside geometry / empty space) — skip it in the gather
}
@ -723,7 +724,7 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
weight *= (wrap * wrap) + 0.2;
float distToProbe = length(probeToPoint);
vec3 vis = ddgiSampleVisibility(physProbeCoord, cascadeIndex, normalize(probeToPoint));
vec3 vis = dugiSampleVisibility(physProbeCoord, cascadeIndex, normalize(probeToPoint));
vec2 moments = vis.xy;
float meanDist = moments.x;
float chebyshev = 1.0;
@ -742,37 +743,37 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
weight = max(weight, 1e-6);
sumSH = DDGI_SH_ADD(sumSH, DDGI_SH_MUL(ddgiLoadIrradianceSH(physProbeCoord, cascadeIndex), weight));
sumSkyVisibility += ddgiSampleVisibility(physProbeCoord, cascadeIndex, normal).z * weight;
sumSH = DUGI_SH_ADD(sumSH, DUGI_SH_MUL(dugiLoadIrradianceSH(physProbeCoord, cascadeIndex), weight));
sumSkyVisibility += dugiSampleVisibility(physProbeCoord, cascadeIndex, normal).z * weight;
sumWeight += weight;
}
skyVisibility = (sumWeight > 0.0) ? clamp(sumSkyVisibility / sumWeight, 0.0, 1.0) : 0.0;
return (sumWeight > 0.0) ? DDGI_SH_MUL(sumSH, 1.0 / sumWeight) : DDGI_SH_ZERO();
return (sumWeight > 0.0) ? DUGI_SH_MUL(sumSH, 1.0 / sumWeight) : DUGI_SH_ZERO();
}
DDGI_SH_TYPE ddgiSampleRadianceSH(const in vec3 worldPosition, const in vec3 normal, const in vec3 viewDirection, out float skyVisibility){
DUGI_SH_TYPE dugiSampleRadianceSH(const in vec3 worldPosition, const in vec3 normal, const in vec3 viewDirection, out float skyVisibility){
int cascadeIndex = 0;
while(((cascadeIndex + 1) < GI_DDGI_CASCADES) &&
(any(lessThan(worldPosition, ddgiData.ddgiCascadeAABBMin[cascadeIndex].xyz)) ||
any(greaterThan(worldPosition, ddgiData.ddgiCascadeAABBMax[cascadeIndex].xyz)))){
while(((cascadeIndex + 1) < GI_DUGI_CASCADES) &&
(any(lessThan(worldPosition, dugiData.dugiCascadeAABBMin[cascadeIndex].xyz)) ||
any(greaterThan(worldPosition, dugiData.dugiCascadeAABBMax[cascadeIndex].xyz)))){
cascadeIndex++;
}
DDGI_SH_TYPE result = DDGI_SH_ZERO();
DUGI_SH_TYPE result = DUGI_SH_ZERO();
float sumSkyVisibility = 0.0;
float sumWeight = 0.0;
float current = 1.0;
for(int c = cascadeIndex; c < GI_DDGI_CASCADES; c++){
for(int c = cascadeIndex; c < GI_DUGI_CASCADES; c++){
float weight;
if(c == (GI_DDGI_CASCADES - 1)){
if(c == (GI_DUGI_CASCADES - 1)){
weight = current;
current = 0.0;
}else if(all(greaterThanEqual(worldPosition, ddgiData.ddgiCascadeAABBMin[c].xyz)) &&
all(lessThanEqual(worldPosition, ddgiData.ddgiCascadeAABBMax[c].xyz))){
vec3 fade = smoothstep(ddgiData.ddgiCascadeAABBFadeStart[c].xyz,
ddgiData.ddgiCascadeAABBFadeEnd[c].xyz,
abs(worldPosition - ddgiData.ddgiCascadeAABBCenter[c].xyz));
}else if(all(greaterThanEqual(worldPosition, dugiData.dugiCascadeAABBMin[c].xyz)) &&
all(lessThanEqual(worldPosition, dugiData.dugiCascadeAABBMax[c].xyz))){
vec3 fade = smoothstep(dugiData.dugiCascadeAABBFadeStart[c].xyz,
dugiData.dugiCascadeAABBFadeEnd[c].xyz,
abs(worldPosition - dugiData.dugiCascadeAABBCenter[c].xyz));
float f = 1.0 - clamp(max(max(fade.x, fade.y), fade.z), 0.0, 1.0);
weight = current * f;
current *= 1.0 - f;
@ -781,7 +782,7 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
}
if(weight > 1e-6){
float cascadeSkyVisibility;
result = DDGI_SH_ADD(result, DDGI_SH_MUL(ddgiSampleRadianceSHInCascade(worldPosition, normal, viewDirection, c, cascadeSkyVisibility), weight));
result = DUGI_SH_ADD(result, DUGI_SH_MUL(dugiSampleRadianceSHInCascade(worldPosition, normal, viewDirection, c, cascadeSkyVisibility), weight));
sumSkyVisibility += cascadeSkyVisibility * weight;
sumWeight += weight;
}
@ -792,8 +793,8 @@ vec2 ddgiProbeOctUV(const in ivec3 probeCoord, const in int cascadeIndex, const
skyVisibility = (sumWeight > 0.0) ? clamp(sumSkyVisibility / sumWeight, 0.0, 1.0) : 1.0;
return result;
}
#endif // GI_DDGI_STORAGE_IS_SH
#endif // GI_DUGI_STORAGE_IS_SH
#endif // GLOBAL_ILLUMINATION_DDGI_SAMPLE
#endif // GLOBAL_ILLUMINATION_DUGI_SAMPLE
#endif // GLOBAL_ILLUMINATION_DDGI_GLSL
#endif // GLOBAL_ILLUMINATION_DUGI_GLSL

View file

@ -0,0 +1,100 @@
#ifndef GLOBAL_ILLUMINATION_DUGI_SAMPLING_GLSL
#define GLOBAL_ILLUMINATION_DUGI_SAMPLING_GLSL
// Shared fragment-side DUGI probe-field sampling. Factors out the descriptor-set declarations (UBO + irradiance + visibility)
// and the per-consumer texelFetch loaders that were otherwise duplicated across mesh.frag / planet_renderpass.frag /
// planet_grass.frag / planet_water.frag.
//
// The including shader must, before the #include:
// - have octahedral.glsl reachable (octEncode, used by dugiProbeOctUV) — the SH headers are pulled in by
// global_illumination_dugi.glsl itself under SH storage,
// - #define DUGI_DESCRIPTOR_SET to the descriptor-set index the DUGI probe data is bound to (mesh.frag = 2, planets = 4),
// - only include this in the GLOBAL_ILLUMINATION_DUGI build variant.
//
// (The DUGI compute passes - trace / irradiance update / visibility update - read the probe images as *storage* images via
// imageLoad and therefore keep their own loaders; this include is for the *sampled* fragment-shading consumers only.)
#ifndef DUGI_DESCRIPTOR_SET
#error "global_illumination_dugi_sampling.glsl: #define DUGI_DESCRIPTOR_SET (the probe-field descriptor set index) before including."
#endif
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_SET DUGI_DESCRIPTOR_SET
#define GLOBAL_ILLUMINATION_VOLUME_UNIFORM_BINDING 0
#define GLOBAL_ILLUMINATION_DUGI_SAMPLE
#include "global_illumination_dugi.glsl" // pulls in gi_dugi_data.glsl -> the `dugiData` SSBO (cascade globals + sub-buffer pointers) at this set's binding 0
// The DUGI data block — cascade globals + the BDA sub-buffer pointers (probe-data, SH-irradiance, ...) — is the std430 SSBO
// `dugiData` declared at this set's binding 0 by gi_dugi_data.glsl (via global_illumination_dugi.glsl above). The fragment
// reads its globals + the probe-data / SH-irradiance pointers from it directly; no separate master UBO any more (the old
// binding 3 is freed).
#if GI_DUGI_STORAGE_IS_SH
// RGB spherical harmonics: one contiguous DUGISHProbe (DUGI_SH_IMAGE_COUNT packed vec4) per probe in the master's
// irradianceSH BDA buffer (no sampler) — loaded as a whole element for coalesced access.
DUGI_SH_TYPE dugiLoadIrradianceSH(const in ivec3 probeCoord, const in int cascadeIndex){
DUGISHProbe p = dugiData.irradianceSH.probes[dugiProbeDataIndex(probeCoord, cascadeIndex)];
vec4 a = p.c[0]; vec4 b = p.c[1]; vec4 c = p.c[2];
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_L2_VALUE
vec4 d = p.c[3]; vec4 e = p.c[4]; vec4 f = p.c[5]; vec4 g = p.c[6];
return SHC3CoefficientsL2Create(vec3(a.x, a.y, a.z), vec3(a.w, b.x, b.y), vec3(b.z, b.w, c.x), vec3(c.y, c.z, c.w),
vec3(d.x, d.y, d.z), vec3(d.w, e.x, e.y), vec3(e.z, e.w, f.x), vec3(f.y, f.z, f.w),
vec3(g.x, g.y, g.z));
#else
return SHC3CoefficientsL1Create(vec3(a.x, a.y, a.z), vec3(a.w, b.x, b.y), vec3(b.z, b.w, c.x), vec3(c.y, c.z, c.w));
#endif
}
#else
layout(set = DUGI_DESCRIPTOR_SET, binding = 1) uniform sampler2D uDUGIIrradianceOct;
vec3 dugiEvaluateIrradiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 normal){
vec2 uv = dugiProbeOctUV(probeCoord, cascadeIndex, normal, GI_DUGI_IRRADIANCE_OCT_SIZE, GI_DUGI_IRRADIANCE_OCT_FULL);
// The atlas stores the cosine-weighted MEAN incident radiance A = E/PI; multiply by PI here (split, like RTXGI) to return the
// full irradiance integral E (matches the SH path; shading then applies albedo/PI). The trace's own multibounce read stays raw.
return max(vec3(0.0), textureLod(uDUGIIrradianceOct, uv, 0.0).rgb) * 3.14159265358979;
}
#endif
layout(set = DUGI_DESCRIPTOR_SET, binding = 2) uniform sampler2D uDUGIVisibilityMoments; // x = mean dist, y = mean dist^2 (RG32F)
layout(set = DUGI_DESCRIPTOR_SET, binding = 4) uniform sampler2D uDUGIVisibilitySky; // x = sky visibility (R8, 0..1)
vec3 dugiSampleVisibility(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 direction){
vec2 uv = dugiProbeOctUV(probeCoord, cascadeIndex, direction, GI_DUGI_VISIBILITY_OCT_SIZE, GI_DUGI_VISIBILITY_OCT_FULL);
return vec3(textureLod(uDUGIVisibilityMoments, uv, 0.0).xy, textureLod(uDUGIVisibilitySky, uv, 0.0).x); // x = mean dist, y = mean dist^2, z = sky visibility
}
#if GI_DUGI_PROBE_RELOCATION
// Per-probe data (xyz = world-space relocation offset, w = state) lives in the master's probe-data BDA buffer.
vec4 dugiLoadProbeData(const in ivec3 probeCoord, const in int cascadeIndex){
return dugiData.probeData.data[dugiProbeDataIndex(probeCoord, cascadeIndex)];
}
#endif
#if defined(GI_DUGI_GLOSSY_RADIANCE)
// Glossy prefiltered-radiance octahedral atlas, binding 5. RGB9E5 (default) is sampled as a uint texture (it is not
// reliably hardware-linear-filterable) and bilinear-filtered manually with a decode per tap; the RGBA16F fallback uses a
// hardware-bilinear sampler. The guard band (filled by gi_dugi_border_update.comp) makes the edge taps correct either way.
#include "rgb9e5.glsl"
#ifdef GI_DUGI_GLOSSY_RGB9E5
layout(set = DUGI_DESCRIPTOR_SET, binding = 5) uniform usampler2D uDUGIGlossyRadiance; // R32_UINT alias of the E5B9G9R9 atlas
#else
layout(set = DUGI_DESCRIPTOR_SET, binding = 5) uniform sampler2D uDUGIGlossyRadiance; // RGBA16F atlas
#endif
vec3 dugiEvaluateGlossyRadiance(const in ivec3 probeCoord, const in int cascadeIndex, const in vec3 reflectionDirection){
vec2 oct = fma(octEncode(normalize(reflectionDirection)), vec2(0.5), vec2(0.5)); // [-1,1] -> [0,1]
vec2 originTexel = vec2(dugiProbeTileOrigin(probeCoord, cascadeIndex, GI_DUGI_GLOSSY_OCT_FULL));
vec2 texel = originTexel + (oct * float(GI_DUGI_GLOSSY_OCT_SIZE));
#ifdef GI_DUGI_GLOSSY_RGB9E5
vec2 t = texel - vec2(0.5);
ivec2 base = ivec2(floor(t));
vec2 f = t - vec2(base);
vec3 c00 = decodeRGB9E5(texelFetch(uDUGIGlossyRadiance, base + ivec2(0, 0), 0).x);
vec3 c10 = decodeRGB9E5(texelFetch(uDUGIGlossyRadiance, base + ivec2(1, 0), 0).x);
vec3 c01 = decodeRGB9E5(texelFetch(uDUGIGlossyRadiance, base + ivec2(0, 1), 0).x);
vec3 c11 = decodeRGB9E5(texelFetch(uDUGIGlossyRadiance, base + ivec2(1, 1), 0).x);
return max(vec3(0.0), mix(mix(c00, c10, f.x), mix(c01, c11, f.x), f.y));
#else
vec2 uv = texel / vec2(dugiAtlasSize(GI_DUGI_GLOSSY_OCT_FULL));
return max(vec3(0.0), textureLod(uDUGIGlossyRadiance, uv, 0.0).rgb);
#endif
}
#endif
#endif // GLOBAL_ILLUMINATION_DUGI_SAMPLING_GLSL

View file

@ -236,10 +236,10 @@ layout(set = 1, binding = 10) uniform sampler2D uRainTextures[]; // 0 = rain tex
#include "global_illumination_voxel_cone_tracing.glsl"
#elif defined(GLOBAL_ILLUMINATION_DDGI)
#elif defined(GLOBAL_ILLUMINATION_DUGI)
#define DDGI_DESCRIPTOR_SET 2
#include "global_illumination_ddgi_sampling.glsl"
#define DUGI_DESCRIPTOR_SET 2
#include "global_illumination_dugi_sampling.glsl"
#endif
@ -497,7 +497,7 @@ void main() {
vec3 voxelEmission = textureFetch(4, vec4(1.0), true).xyz * material.emissiveFactor.xyz * material.emissiveFactor.w * inColor0.xyz;
// GI-only emissive limitation (PASVULKAN_materials_emissive_gi): per-material factor/max (two fp16 packed into the material's
// dispersion/shadow uvec4 .w) scaled by the global master regulator (voxelGridData) and clamped — same policy as the
// DDGI gather (gi_rt_gather.glsl giGatherShadeHit). The voxel feeds only the GI here, so limiting it at injection is correct.
// DUGI gather (gi_rt_gather.glsl giGatherShadeHit). The voxel feeds only the GI here, so limiting it at injection is correct.
vec2 voxelEmissiveGI = unpackHalf2x16(material.dispersionShadowCastMaskShadowReceiveMaskUnused.w);
voxelEmission = min(voxelEmission * (voxelEmissiveGI.x * voxelGridData.emissiveGIScale), vec3(min(voxelEmissiveGI.y, voxelGridData.emissiveGIMax)));
vec4 emissionColor = vec4(voxelEmission, baseColor.w);
@ -932,38 +932,38 @@ void main() {
colorOutput += cvctIndirectSpecularLight(inWorldSpacePosition.xyz, normal.xyz, viewDirection, cvctRoughnessToVoxelConeTracingApertureAngle(perceptualRoughness), 1e+24) * F0Dielectric * specularOcclusion * OneOverPI;
}
}
#elif defined(GLOBAL_ILLUMINATION_DDGI)
#if GI_DDGI_STORAGE_IS_SH
#elif defined(GLOBAL_ILLUMINATION_DUGI)
#if GI_DUGI_STORAGE_IS_SH
// SH storage (L1 or L2): sample the radiance SH field, extract its dominant directional light (shaded analytically
// by doSingleLight) and add the remaining residual SH as diffuse — mirroring the cascaded radiance hints path. The
// environment IBL block below is disabled for this variant (see its #if guard); the specular comes from the dominant
// light, optionally crossfaded with the directional glossy-radiance atlas by roughness when GI_DDGI_GLOSSY_RADIANCE.
// light, optionally crossfaded with the directional glossy-radiance atlas by roughness when GI_DUGI_GLOSSY_RADIANCE.
{
// Roughness crossfade weight (set below when the glossy atlas is built): scales the dominant-light specular; the
// glossy atlas takes the complementary 1-weight. Stays 1.0 (full dominant specular, no atlas) when glossy is off.
float ddgiSpecularWeight = 1.0;
float ddgiSkyVisibility;
DDGI_SH_TYPE ddgiRadianceSH = ddgiSampleRadianceSH(inWorldSpacePosition.xyz, normal.xyz, viewDirection, ddgiSkyVisibility);
float dugiSpecularWeight = 1.0;
float dugiSkyVisibility;
DUGI_SH_TYPE dugiRadianceSH = dugiSampleRadianceSH(inWorldSpacePosition.xyz, normal.xyz, viewDirection, dugiSkyVisibility);
vec3 shDominantDirectionalLightColor, shDominantDirectionalLightDirection;
// INVARIANT: the probe radiance field is split into (dominant directional light) + (residual SH), and the total
// diffuse must stay = the full field's diffuse, i.e. residualDiffuse (added below) + dominantDiffuse (contributed by
// doSingleLight further down) == full-field diffuse. The residual is therefore "field minus dominant", and the
// dominant light's DIFFUSE must always be applied at full strength. Only the dominant's SPECULAR may be scaled (it is,
// by ddgiSpecularWeight via doSingleLight's diffuseSpecularFactors.y, to crossfade against the glossy atlas). Do NOT
// by dugiSpecularWeight via doSingleLight's diffuseSpecularFactors.y, to crossfade against the glossy atlas). Do NOT
// scale the whole dominant light (the 2nd doSingleLight arg / diffuseSpecularFactors.x) by the roughness weight, or
// the dominant's diffuse goes missing and low-roughness surfaces darken.
#ifdef GI_DDGI_SH_APPROXIMATE_DOMINANT
#ifdef GI_DUGI_SH_APPROXIMATE_DOMINANT
// Default (applied to L1 and L2): approximate dominant directional light + residual SH (DC kept).
// The dominant light direction/intensity live in the L0/L1 bands, so the L2 variant extracts them from the L1
// reduction (identical method to L1); the full L2 detail is preserved in the residual below.
#if GI_DDGI_STORAGE == GI_DDGI_STORAGE_L2_VALUE
SHC3CoefficientsL1ApproximateDirectionalLight(SHC3CoefficientsL1FromL2(ddgiRadianceSH), shDominantDirectionalLightDirection, shDominantDirectionalLightColor);
#if GI_DUGI_STORAGE == GI_DUGI_STORAGE_L2_VALUE
SHC3CoefficientsL1ApproximateDirectionalLight(SHC3CoefficientsL1FromL2(dugiRadianceSH), shDominantDirectionalLightDirection, shDominantDirectionalLightColor);
#else
SHC3CoefficientsL1ApproximateDirectionalLight(ddgiRadianceSH, shDominantDirectionalLightDirection, shDominantDirectionalLightColor);
SHC3CoefficientsL1ApproximateDirectionalLight(dugiRadianceSH, shDominantDirectionalLightDirection, shDominantDirectionalLightColor);
#endif
// Residual SH = field minus the extracted dominant light, so it is not double-counted in the diffuse term.
DDGI_SH_TYPE shResidual = DDGI_SH_SUB(ddgiRadianceSH, DDGI_SH_PROJECT(shDominantDirectionalLightDirection, shDominantDirectionalLightColor));
vec3 shResidualDiffuse = max(vec3(0.0), DDGI_SH_EVALUATE(DDGI_SH_CONVOLVE_COSINE(shResidual), normal.xyz));
DUGI_SH_TYPE shResidual = DUGI_SH_SUB(dugiRadianceSH, DUGI_SH_PROJECT(shDominantDirectionalLightDirection, shDominantDirectionalLightColor));
vec3 shResidualDiffuse = max(vec3(0.0), DUGI_SH_EVALUATE(DUGI_SH_CONVOLVE_COSINE(shResidual), normal.xyz));
if(dot(baseColor.xyz, vec3(1.0)) > 1e-6){
colorOutput += shResidualDiffuse * baseColor.xyz * diffuseOcclusion * OneOverPI;
}
@ -971,32 +971,32 @@ void main() {
// Alternative: native extract-and-subtract -> uniform ambient + DC-zeroed residual + dominant light.
vec3 shAmbient;
float shModifiedSqrtRoughness;
DDGI_SH_EXTRACT_DOMINANT(ddgiRadianceSH, shAmbient, shDominantDirectionalLightDirection, shDominantDirectionalLightColor, sqrt(clamp(perceptualRoughness, 0.0, 1.0)), shModifiedSqrtRoughness);
vec3 shResidualDiffuse = max(vec3(0.0), DDGI_SH_EVALUATE(DDGI_SH_CONVOLVE_COSINE(ddgiRadianceSH), normal.xyz));
DUGI_SH_EXTRACT_DOMINANT(dugiRadianceSH, shAmbient, shDominantDirectionalLightDirection, shDominantDirectionalLightColor, sqrt(clamp(perceptualRoughness, 0.0, 1.0)), shModifiedSqrtRoughness);
vec3 shResidualDiffuse = max(vec3(0.0), DUGI_SH_EVALUATE(DUGI_SH_CONVOLVE_COSINE(dugiRadianceSH), normal.xyz));
if(dot(baseColor.xyz, vec3(1.0)) > 1e-6){
colorOutput += fma(shResidualDiffuse, vec3(OneOverPI), max(vec3(0.0), shAmbient)) * baseColor.xyz * diffuseOcclusion;
}
DDGI_SH_TYPE shResidual = ddgiRadianceSH; // extract-and-subtract leaves the residual (DC-zeroed) field in ddgiRadianceSH
DUGI_SH_TYPE shResidual = dugiRadianceSH; // extract-and-subtract leaves the residual (DC-zeroed) field in dugiRadianceSH
#endif
#if defined(GI_DDGI_GLOSSY_RESIDUAL) && defined(GI_DDGI_GLOSSY_RADIANCE) && !defined(REFLECTIVESHADOWMAPOUTPUT)
#if defined(GI_DUGI_GLOSSY_RESIDUAL) && defined(GI_DUGI_GLOSSY_RADIANCE) && !defined(REFLECTIVESHADOWMAPOUTPUT)
// Probe-field specular, crossfaded by roughness against the dominant directional light (doSingleLight below): at low
// roughness the sharp, directional glossy prefiltered-radiance atlas (sampled along the reflection vector) dominates;
// at high roughness the broad dominant-light specular does. ddgiSpecularWeight scales the dominant specular (via
// at high roughness the broad dominant-light specular does. dugiSpecularWeight scales the dominant specular (via
// doSingleLight's diffuseSpecularFactors.y) and this adds the complementary (1 - weight) of the atlas, so the two sum
// to one specular without double-counting. The dominant light's DIFFUSE stays full (diffuseSpecularFactors.x = 1), so
// no indirect diffuse is lost. Routed through the same split-sum BRDF term (getIBLGGXFresnel) as the environment IBL.
{
vec3 ddgiReflectionVector = normalize(reflect(-viewDirection, normal.xyz));
vec3 dugiReflectionVector = normalize(reflect(-viewDirection, normal.xyz));
// Crossfade weight: 0 at low roughness (take the glossy atlas) .. 1 at high roughness (take the dominant light).
ddgiSpecularWeight = smoothstep(GI_DDGI_GLOSSY_ROUGHNESS_LO, GI_DDGI_GLOSSY_ROUGHNESS_HI, perceptualRoughness);
vec3 ddgiGlossyRadiance = ddgiSampleGlossyRadiance(inWorldSpacePosition.xyz, normal.xyz, ddgiReflectionVector, viewDirection);
vec3 ddgiGlossyFresnel = getIBLGGXFresnel(normal.xyz, viewDirection, perceptualRoughness, mix(F0Dielectric, baseColor.xyz, metallic), mix(specularWeight, 1.0, metallic));
colorOutput += ddgiGlossyRadiance * ddgiGlossyFresnel * specularOcclusion * (1.0 - ddgiSpecularWeight);
dugiSpecularWeight = smoothstep(GI_DUGI_GLOSSY_ROUGHNESS_LO, GI_DUGI_GLOSSY_ROUGHNESS_HI, perceptualRoughness);
vec3 dugiGlossyRadiance = dugiSampleGlossyRadiance(inWorldSpacePosition.xyz, normal.xyz, dugiReflectionVector, viewDirection);
vec3 dugiGlossyFresnel = getIBLGGXFresnel(normal.xyz, viewDirection, perceptualRoughness, mix(F0Dielectric, baseColor.xyz, metallic), mix(specularWeight, 1.0, metallic));
colorOutput += dugiGlossyRadiance * dugiGlossyFresnel * specularOcclusion * (1.0 - dugiSpecularWeight);
}
#endif
doSingleLight(shDominantDirectionalLightColor, //
vec3(specularOcclusion), //
vec2(1.0, ddgiSpecularWeight), // diffuse kept full; specular crossfaded against the glossy atlas (1-weight added above)
vec2(1.0, dugiSpecularWeight), // diffuse kept full; specular crossfaded against the glossy atlas (1-weight added above)
-shDominantDirectionalLightDirection, //
normal.xyz, //
baseColor.xyz, //
@ -1019,27 +1019,27 @@ void main() {
0.0);
}
#else
// Octahedral storage: ddgiSampleIrradiance returns the pre-integrated diffuse irradiance E(n) (outgoing diffuse =
// Octahedral storage: dugiSampleIrradiance returns the pre-integrated diffuse irradiance E(n) (outgoing diffuse =
// albedo/PI * E) plus a sky-visibility factor from the probes. The probe field replaces the environment IBL diffuse;
// the environment IBL specular is kept (block below) but occluded by the probe sky-visibility (long-range "is the
// sky actually visible here", which the short-range per-pixel AO misses) combined with that AO.
float ddgiSkyVisibility;
vec3 ddgiIrradiance = ddgiSampleIrradiance(inWorldSpacePosition.xyz, normal.xyz, viewDirection, ddgiSkyVisibility);
float iblWeight = ddgiSkyVisibility;
float dugiSkyVisibility;
vec3 dugiIrradiance = dugiSampleIrradiance(inWorldSpacePosition.xyz, normal.xyz, viewDirection, dugiSkyVisibility);
float iblWeight = dugiSkyVisibility;
if(dot(baseColor.xyz, vec3(1.0)) > 1e-6){
colorOutput += ddgiIrradiance * baseColor.xyz * diffuseOcclusion * OneOverPI;
colorOutput += dugiIrradiance * baseColor.xyz * diffuseOcclusion * OneOverPI;
}
#endif
#endif
#if !defined(REFLECTIVESHADOWMAPOUTPUT)
#if !(defined(GLOBAL_ILLUMINATION_CASCADED_RADIANCE_HINTS) || (defined(GLOBAL_ILLUMINATION_DDGI) && !defined(GLOBAL_ILLUMINATION_DDGI_OCT_STORAGE)))
#if defined(GLOBAL_ILLUMINATION_CASCADED_VOXEL_CONE_TRACING) || defined(GLOBAL_ILLUMINATION_DDGI)
#if !(defined(GLOBAL_ILLUMINATION_CASCADED_RADIANCE_HINTS) || (defined(GLOBAL_ILLUMINATION_DUGI) && !defined(GLOBAL_ILLUMINATION_DUGI_OCT_STORAGE)))
#if defined(GLOBAL_ILLUMINATION_CASCADED_VOXEL_CONE_TRACING) || defined(GLOBAL_ILLUMINATION_DUGI)
// float iblWeight = 1.0; // already declared in the global illumination branch above
#else
float iblWeight = 1.0; // for future sky occulsion
#endif
#if defined(GLOBAL_ILLUMINATION_DDGI)
vec3 iblDiffuse = vec3(0.0); // DDGI replaces the environment IBL diffuse term (the field carries the sky via ray misses); IBL specular is kept but occluded via iblWeight
#if defined(GLOBAL_ILLUMINATION_DUGI)
vec3 iblDiffuse = vec3(0.0); // DUGI replaces the environment IBL diffuse term (the field carries the sky via ray misses); IBL specular is kept but occluded via iblWeight
#else
vec3 iblDiffuse = getIBLDiffuse(normal) * baseColor.xyz;
#endif

View file

@ -2,8 +2,8 @@
#define PARTICLE_BVH_GLSL
// Shared layout for the per-frame GPU-constructed particle LBVH (Morton -> radix sort -> Karras hierarchy -> AABB refit),
// software-traced from gi_ddgi_trace.comp to inject emissive/transparent particles (not in the hardware ray-tracing BLAS)
// into the DDGI probe irradiance. All build shaders bind the same descriptor set (set 0, bindings below); each uses a subset.
// software-traced from gi_dugi_trace.comp to inject emissive/transparent particles (not in the hardware ray-tracing BLAS)
// into the DUGI probe irradiance. All build shaders bind the same descriptor set (set 0, bindings below); each uses a subset.
//
// The array is always processed at the fixed padded size PARTICLE_BVH_CAPACITY (= MaxParticles, a power of two), with the
// [particleCount, PARTICLE_BVH_CAPACITY) tail filled with sentinel Morton keys (0xffffffff) so the sort is dispatch-count-static;
@ -32,7 +32,7 @@ struct ParticleBVHNode {
};
#ifdef PARTICLE_BVH_BINDINGS
// The build-pass descriptor set (set 0). gi_ddgi_trace.comp does NOT include this; it binds emitters+nodes on its own set.
// The build-pass descriptor set (set 0). gi_dugi_trace.comp does NOT include this; it binds emitters+nodes on its own set.
layout(set = 0, binding = 0, std430) buffer ParticleEmitterBuffer {
ParticleEmitter emitters[];

View file

@ -4,7 +4,7 @@
// Descriptor-FREE software traversal of the per-frame GPU particle LBVH, used to inject particles (not in the hardware
// ray-tracing BLAS) into a renderer's lighting. Technique-neutral: a consumer only needs the two buffer device addresses + the
// particle count (however it obtains them — push constant, UBO, a master buffer). No shared descriptor set/binding contract,
// so the DDGI trace (now) and a pure-path-tracing path (later) reuse the exact same code. Requires GL_EXT_buffer_reference(+
// so the DUGI trace (now) and a pure-path-tracing path (later) reuse the exact same code. Requires GL_EXT_buffer_reference(+
// _uvec2) and the structs from particle_bvh.glsl (included WITHOUT PARTICLE_BVH_BINDINGS).
layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer ParticleBVHEmitterRef {

View file

@ -264,7 +264,7 @@ vec3 getVolumeTransmissionRay(vec3 n, vec3 v, float thickness, float ior) {
void doSingleLight(const in vec3 lightColor,
const in vec3 lightLit,
const in vec2 diffuseSpecularFactors, // x = diffuse scale, y = specular (incl. sheen/clearcoat) scale; lets a caller fade one lobe without the other (e.g. DDGI crossfading the dominant-light specular against the glossy atlas while keeping its diffuse). vec2(1.0) = neutral.
const in vec2 diffuseSpecularFactors, // x = diffuse scale, y = specular (incl. sheen/clearcoat) scale; lets a caller fade one lobe without the other (e.g. DUGI crossfading the dominant-light specular against the glossy atlas while keeping its diffuse). vec2(1.0) = neutral.
const in vec3 lightDirection, // Direction from surface point to light
const in vec3 normal,
const in vec3 baseColor,

View file

@ -159,12 +159,12 @@ layout(set = 2, binding = 2) uniform usampler2D uGrassFlagsMap; // GrassFlagsMap
#include "roughness.glsl"
#if defined(GLOBAL_ILLUMINATION_DDGI)
// DDGI probe field for ray-tracing-based global illumination — only for the RT GI modes, never
#if defined(GLOBAL_ILLUMINATION_DUGI)
// DUGI probe field for ray-tracing-based global illumination — only for the RT GI modes, never
// CRH/VCT. GI lives at the fixed dedicated set 4 (the grass pipeline uses sets 0..3: global, mesh-rendering-pass, planet
// textures, grass cull/mesh-gen). Mirrors planet_renderpass.frag / mesh.frag.
#define DDGI_DESCRIPTOR_SET 4
#include "global_illumination_ddgi_sampling.glsl"
#define DUGI_DESCRIPTOR_SET 4
#include "global_illumination_dugi_sampling.glsl"
#endif
vec3 imageLightBasedLightDirection = imageBasedSphericalHarmonicsMetaData.dominantLightDirection.xyz;
@ -385,34 +385,34 @@ void main(){
#include "lighting.glsl"
#undef LIGHTING_IMPLEMENTATION
#if defined(GLOBAL_ILLUMINATION_DDGI)
#if defined(GLOBAL_ILLUMINATION_DUGI)
// RT GI: probe-field diffuse (replaces IBL diffuse); IBL specular kept but occluded by probe sky-visibility · AO.
float ddgiSkyVisibility;
vec3 ddgiIrradiance = ddgiSampleIrradiance(inWorldSpacePosition, normal, viewDirection, ddgiSkyVisibility);
float dugiSkyVisibility;
vec3 dugiIrradiance = dugiSampleIrradiance(inWorldSpacePosition, normal, viewDirection, dugiSkyVisibility);
if(dot(baseColor.xyz, vec3(1.0)) > 1e-6){
colorOutput += ddgiIrradiance * baseColor.xyz * diffuseOcclusion * OneOverPI;
colorOutput += dugiIrradiance * baseColor.xyz * diffuseOcclusion * OneOverPI;
}
vec3 iblDiffuse = vec3(0.0);
float giIBLWeight = ddgiSkyVisibility;
float giIBLWeight = dugiSkyVisibility;
#else
vec3 iblDiffuse = getIBLDiffuse(normal) * baseColor.xyz;
const float giIBLWeight = 1.0;
#endif
vec3 iblSpecularMetal = getIBLRadianceGGX(normal, viewDirection, perceptualRoughness);
#if defined(GLOBAL_ILLUMINATION_DDGI) && defined(GI_DDGI_GLOSSY_RESIDUAL)
#if defined(GLOBAL_ILLUMINATION_DUGI) && defined(GI_DUGI_GLOSSY_RESIDUAL)
// Probe-derived glossy (matches mesh.frag / planet_renderpass.frag). Storage-agnostic: sample the probe field along
// the reflection vector as a broad prefiltered radiance (E(R)/pi) and lerp it into the prefiltered specular source by
// roughness (rough grass takes the probe local colour bleed, sharp keeps the environment reflection). giIBLWeight unchanged.
{
float ddgiGlossySky;
vec3 ddgiReflectionVector = normalize(reflect(-viewDirection, normal));
vec3 ddgiGlossyRadiance = ddgiSampleIrradiance(inWorldSpacePosition, ddgiReflectionVector, viewDirection, ddgiGlossySky) * OneOverPI; // broad reflection
#if defined(GI_DDGI_GLOSSY_RADIANCE)
float dugiGlossySky;
vec3 dugiReflectionVector = normalize(reflect(-viewDirection, normal));
vec3 dugiGlossyRadiance = dugiSampleIrradiance(inWorldSpacePosition, dugiReflectionVector, viewDirection, dugiGlossySky) * OneOverPI; // broad reflection
#if defined(GI_DUGI_GLOSSY_RADIANCE)
// Sharp prefiltered-radiance atlas for low roughness, fading to the broad source toward HI.
vec3 ddgiSharpGlossy = ddgiSampleGlossyRadiance(inWorldSpacePosition, normal, ddgiReflectionVector, viewDirection);
ddgiGlossyRadiance = mix(ddgiSharpGlossy, ddgiGlossyRadiance, smoothstep(GI_DDGI_GLOSSY_ROUGHNESS_LO, GI_DDGI_GLOSSY_ROUGHNESS_HI, perceptualRoughness));
vec3 dugiSharpGlossy = dugiSampleGlossyRadiance(inWorldSpacePosition, normal, dugiReflectionVector, viewDirection);
dugiGlossyRadiance = mix(dugiSharpGlossy, dugiGlossyRadiance, smoothstep(GI_DUGI_GLOSSY_ROUGHNESS_LO, GI_DUGI_GLOSSY_ROUGHNESS_HI, perceptualRoughness));
#endif
iblSpecularMetal = mix(iblSpecularMetal, ddgiGlossyRadiance, smoothstep(0.3, 0.8, perceptualRoughness));
iblSpecularMetal = mix(iblSpecularMetal, dugiGlossyRadiance, smoothstep(0.3, 0.8, perceptualRoughness));
}
#endif
vec3 iblSpecularDielectric = iblSpecularMetal;

View file

@ -196,15 +196,15 @@ const vec3 inModelScale = vec3(1.0);
#include "roughness.glsl"
#endif
#if defined(GLOBAL_ILLUMINATION_DDGI)
// DDGI probe field for ray-tracing-based global illumination. Only wired for the RT GI modes —
#if defined(GLOBAL_ILLUMINATION_DUGI)
// DUGI probe field for ray-tracing-based global illumination. Only wired for the RT GI modes —
// deliberately NOT for cascaded radiance hints or voxel cone tracing, since RSM-feeding / voxelizing planets would be
// overkill. Set 3 holds the probe data; the planet passes already use sets 0..2 (global, mesh-rendering-pass, planet
// textures, and set 3 may hold path-specific data: terrain-mesh SSBO in the mesh-shader path, empty placeholder in the
// vertex path). GI therefore lives at a fixed dedicated set 4 across all planet pipelines, mirroring mesh.frag's
// dedicated DDGI set.
#define DDGI_DESCRIPTOR_SET 4
#include "global_illumination_ddgi_sampling.glsl"
// dedicated DUGI set.
#define DUGI_DESCRIPTOR_SET 4
#include "global_illumination_dugi_sampling.glsl"
#endif
vec3 imageLightBasedLightDirection = vec3(0.0, 0.0, -1.0); // imageBasedSphericalHarmonicsMetaData.dominantLightDirection.xyz;
@ -581,24 +581,24 @@ void main(){
#include "lighting.glsl"
#undef LIGHTING_IMPLEMENTATION
#if defined(GLOBAL_ILLUMINATION_DDGI)
#if defined(GLOBAL_ILLUMINATION_DUGI)
// RT GI: the probe field provides the diffuse indirect (replacing the environment IBL diffuse); the IBL specular is
// kept but occluded by the probe sky-visibility (long-range "is the sky actually visible here", which the per-pixel AO
// misses) combined with the per-pixel specular occlusion. Diffuse-irradiance form (storage-agnostic, no dominant-light
// split) — appropriate for the mostly-diffuse planet terrain.
float ddgiSkyVisibility;
vec3 ddgiIrradiance = ddgiSampleIrradiance(inWorldSpacePosition, normal, viewDirection, ddgiSkyVisibility);
float dugiSkyVisibility;
vec3 dugiIrradiance = dugiSampleIrradiance(inWorldSpacePosition, normal, viewDirection, dugiSkyVisibility);
if(dot(baseColor.xyz, vec3(1.0)) > 1e-6){
colorOutput += ddgiIrradiance * baseColor.xyz * diffuseOcclusion * OneOverPI;
colorOutput += dugiIrradiance * baseColor.xyz * diffuseOcclusion * OneOverPI;
}
vec3 iblDiffuse = vec3(0.0);
float giIBLWeight = ddgiSkyVisibility;
float giIBLWeight = dugiSkyVisibility;
#else
vec3 iblDiffuse = getIBLDiffuse(normal) * baseColor.xyz;
const float giIBLWeight = 1.0;
#endif
vec3 iblSpecularMetal = getIBLRadianceGGX(normal, viewDirection, perceptualRoughness);
#if defined(GLOBAL_ILLUMINATION_DDGI) && defined(GI_DDGI_GLOSSY_RESIDUAL)
#if defined(GLOBAL_ILLUMINATION_DUGI) && defined(GI_DUGI_GLOSSY_RESIDUAL)
// Probe-derived glossy (matches mesh.frag; the planet pass previously had specular only from the environment IBL).
// Storage-agnostic: sample the probe field along the reflection vector as a broad prefiltered radiance (E(R)/pi ~ a rough
// reflected radiance) and lerp it into the prefiltered specular source by roughness — rough surfaces take the probe (it
@ -606,15 +606,15 @@ void main(){
// low-resolution probe atlas cannot resolve a sharp reflection; that is the job of the glossy radiance atlas). The split-sum
// BRDF term below and the giIBLWeight (sky-visibility) occlusion are unchanged, matching this pass's existing philosophy.
{
float ddgiGlossySky;
vec3 ddgiReflectionVector = normalize(reflect(-viewDirection, normal));
vec3 ddgiGlossyRadiance = ddgiSampleIrradiance(inWorldSpacePosition, ddgiReflectionVector, viewDirection, ddgiGlossySky) * OneOverPI; // broad reflection
#if defined(GI_DDGI_GLOSSY_RADIANCE)
float dugiGlossySky;
vec3 dugiReflectionVector = normalize(reflect(-viewDirection, normal));
vec3 dugiGlossyRadiance = dugiSampleIrradiance(inWorldSpacePosition, dugiReflectionVector, viewDirection, dugiGlossySky) * OneOverPI; // broad reflection
#if defined(GI_DUGI_GLOSSY_RADIANCE)
// Sharp prefiltered-radiance atlas for low roughness, fading to the broad source toward HI.
vec3 ddgiSharpGlossy = ddgiSampleGlossyRadiance(inWorldSpacePosition, normal, ddgiReflectionVector, viewDirection);
ddgiGlossyRadiance = mix(ddgiSharpGlossy, ddgiGlossyRadiance, smoothstep(GI_DDGI_GLOSSY_ROUGHNESS_LO, GI_DDGI_GLOSSY_ROUGHNESS_HI, perceptualRoughness));
vec3 dugiSharpGlossy = dugiSampleGlossyRadiance(inWorldSpacePosition, normal, dugiReflectionVector, viewDirection);
dugiGlossyRadiance = mix(dugiSharpGlossy, dugiGlossyRadiance, smoothstep(GI_DUGI_GLOSSY_ROUGHNESS_LO, GI_DUGI_GLOSSY_ROUGHNESS_HI, perceptualRoughness));
#endif
iblSpecularMetal = mix(iblSpecularMetal, ddgiGlossyRadiance, smoothstep(0.3, 0.8, perceptualRoughness));
iblSpecularMetal = mix(iblSpecularMetal, dugiGlossyRadiance, smoothstep(0.3, 0.8, perceptualRoughness));
}
#endif
vec3 iblSpecularDielectric = iblSpecularMetal;

View file

@ -248,25 +248,25 @@ mat4 planetInverseModelMatrix = inverse(planetModelMatrix);
#include "planet_caustics.glsl"
#endif
// DDGI probe field for ray-tracing-based global illumination, gated to RT GI modes — never
// DUGI probe field for ray-tracing-based global illumination, gated to RT GI modes — never
// CRH/VCT. Wired into the main water surface AND the underwater fullscreen pass (shore-foam ambient); WATER_CAUSTICS is
// excluded because that pass is purely additive refracted-sun light with no diffuse/ambient term for DDGI to feed. GI
// excluded because that pass is purely additive refracted-sun light with no diffuse/ambient term for DUGI to feed. GI
// lives at the fixed dedicated set 4 (the water pipelines use sets 0..3), mirroring planet_renderpass.frag / planet_grass.frag.
#if defined(GLOBAL_ILLUMINATION_DDGI) && !defined(WATER_CAUSTICS)
#define DDGI_DESCRIPTOR_SET 4
#include "global_illumination_ddgi_sampling.glsl"
#define WATER_DDGI 1
#if defined(GLOBAL_ILLUMINATION_DUGI) && !defined(WATER_CAUSTICS)
#define DUGI_DESCRIPTOR_SET 4
#include "global_illumination_dugi_sampling.glsl"
#define WATER_DUGI 1
#endif
// Diffuse ambient irradiance for the water surface at a given world position, in getIBLDiffuse()'s "ready to multiply by
// albedo" convention. Under the DDGI build variant it comes from the probe field (replacing the environment IBL diffuse);
// albedo" convention. Under the DUGI build variant it comes from the probe field (replacing the environment IBL diffuse);
// otherwise it is the environment IBL diffuse (which ignores the position). The specular reflection path stays IBL either
// way (water reflections are wanted). An explicit world position is taken because the underwater fullscreen pass has no
// per-fragment surface position (the file-scope inWorldSpacePosition is only valid on the tessellated surface) — the
// underwater shore-foam caller reconstructs the world position from the depth buffer instead. viewDirection is file-scope.
#if defined(WATER_DDGI)
#if defined(WATER_DUGI)
vec3 waterDiffuseAmbient(const in vec3 worldPosition, const in vec3 n, out float skyVisibility){
return ddgiSampleIrradiance(worldPosition, n, viewDirection, skyVisibility) * OneOverPI;
return dugiSampleIrradiance(worldPosition, n, viewDirection, skyVisibility) * OneOverPI;
}
#else
vec3 waterDiffuseAmbient(const in vec3 worldPosition, const in vec3 n, out float skyVisibility){
@ -879,21 +879,21 @@ vec4 doShade(float opaqueDepth, float surfaceDepth, bool underWater){
vec3 iblDiffuse = waterDiffuseAmbient(inWorldSpacePosition, normal) * baseColor.xyz;
vec3 iblSpecularMetal = getIBLRadianceGGX(normal, viewDirection, perceptualRoughness);
#if defined(WATER_DDGI) && defined(GI_DDGI_GLOSSY_RESIDUAL)
#if defined(WATER_DUGI) && defined(GI_DUGI_GLOSSY_RESIDUAL)
// Probe-derived glossy, roughness-gated. Water is normally near-mirror (low roughness), so smoothstep(0.3,0.8) keeps this ~inert and
// the sharp environment/SSR reflection wins (sharp water reflections are wanted — see waterDiffuseAmbient). It only kicks
// in for rough/foamy water, where a broad probe reflection (with local colour bleed) is appropriate. Storage-agnostic
// via ddgiSampleIrradiance (E(R)/pi ~ broad prefiltered radiance along the reflection vector).
// via dugiSampleIrradiance (E(R)/pi ~ broad prefiltered radiance along the reflection vector).
{
float ddgiGlossySky;
vec3 ddgiReflectionVector = normalize(reflect(-viewDirection, normal));
vec3 ddgiGlossyRadiance = ddgiSampleIrradiance(inWorldSpacePosition, ddgiReflectionVector, viewDirection, ddgiGlossySky) * OneOverPI; // broad reflection
#if defined(GI_DDGI_GLOSSY_RADIANCE)
float dugiGlossySky;
vec3 dugiReflectionVector = normalize(reflect(-viewDirection, normal));
vec3 dugiGlossyRadiance = dugiSampleIrradiance(inWorldSpacePosition, dugiReflectionVector, viewDirection, dugiGlossySky) * OneOverPI; // broad reflection
#if defined(GI_DUGI_GLOSSY_RADIANCE)
// Sharp prefiltered-radiance atlas for low roughness, fading to the broad source toward HI.
vec3 ddgiSharpGlossy = ddgiSampleGlossyRadiance(inWorldSpacePosition, normal, ddgiReflectionVector, viewDirection);
ddgiGlossyRadiance = mix(ddgiSharpGlossy, ddgiGlossyRadiance, smoothstep(GI_DDGI_GLOSSY_ROUGHNESS_LO, GI_DDGI_GLOSSY_ROUGHNESS_HI, perceptualRoughness));
vec3 dugiSharpGlossy = dugiSampleGlossyRadiance(inWorldSpacePosition, normal, dugiReflectionVector, viewDirection);
dugiGlossyRadiance = mix(dugiSharpGlossy, dugiGlossyRadiance, smoothstep(GI_DUGI_GLOSSY_ROUGHNESS_LO, GI_DUGI_GLOSSY_ROUGHNESS_HI, perceptualRoughness));
#endif
iblSpecularMetal = mix(iblSpecularMetal, ddgiGlossyRadiance, smoothstep(0.3, 0.8, perceptualRoughness));
iblSpecularMetal = mix(iblSpecularMetal, dugiGlossyRadiance, smoothstep(0.3, 0.8, perceptualRoughness));
}
#endif
vec3 iblSpecularDielectric = iblSpecularMetal;
@ -1117,8 +1117,8 @@ void main(){
if(waterRadius > 0.0){
float shoreDepth = max(0.0, waterRadius - groundRadius);
// The global workNormal/viewDirection are only set on the tessellated surface, not in this fullscreen pass, but
// applyShoreFoam's ambient lookup (waterDiffuseAmbient -> IBL or DDGI) reads them. Use the world-space surface
// up-normal at the shore point and the direction toward the camera so the DDGI/IBL diffuse stays well-defined.
// applyShoreFoam's ambient lookup (waterDiffuseAmbient -> IBL or DUGI) reads them. Use the world-space surface
// up-normal at the shore point and the direction toward the camera so the DUGI/IBL diffuse stays well-defined.
workNormal = normalize((planetModelMatrix * vec4(sphereNormal, 0.0)).xyz);
viewDirection = normalize(inverseViewMatrix[3].xyz - worldPos);
finalColor.xyz = applyShoreFoam(finalColor.xyz, planetPos, shoreDepth);