Various DDGI improvements to the quality of the final effect

This commit is contained in:
Wojciech Figat
2022-06-06 15:04:58 +02:00
parent c147e3bff4
commit 5ee62be166
4 changed files with 100 additions and 70 deletions

BIN
Content/Shaders/GI/DDGI.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -116,44 +116,6 @@ void CalculateVolumeRandomRotation(Matrix3x3& matrix)
matrix.M33 = 1.0f - 2.0f * u3; matrix.M33 = 1.0f - 2.0f * u3;
} }
int32 AbsFloor(const float value)
{
return value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value);
}
int32 GetSignNotZero(const float value)
{
return value >= 0.0f ? 1 : -1;
}
Vector3 GetVolumeOrigin(DDGICustomBuffer& ddgiData)
{
return ddgiData.ProbesOrigin + Vector3(ddgiData.ProbeScrollOffsets) * ddgiData.ProbesSpacing;
}
void CalculateVolumeScrolling(DDGICustomBuffer& ddgiData, const Vector3& viewOrigin)
{
// Reset the volume origin and scroll offsets for each axis
for (int32 axis = 0; axis < 3; axis++)
{
if (ddgiData.ProbeScrollOffsets.Raw[axis] != 0 && (ddgiData.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0))
{
ddgiData.ProbesOrigin.Raw[axis] += (float)ddgiData.ProbeCounts.Raw[axis] * ddgiData.ProbesSpacing * (float)ddgiData.ProbeScrollDirections.Raw[axis];
ddgiData.ProbeScrollOffsets.Raw[axis] = 0;
}
}
// Calculate the count of grid cells between the view origin and the scroll anchor
const Vector3 translation = viewOrigin - GetVolumeOrigin(ddgiData);
for (int32 axis = 0; axis < 3; axis++)
{
const int32 scroll = AbsFloor(translation.Raw[axis] / ddgiData.ProbesSpacing);
ddgiData.ProbeScrollOffsets.Raw[axis] += scroll;
ddgiData.ProbeScrollClear[axis] = scroll != 0;
ddgiData.ProbeScrollDirections.Raw[axis] = GetSignNotZero(translation.Raw[axis]);
}
}
String DynamicDiffuseGlobalIlluminationPass::ToString() const String DynamicDiffuseGlobalIlluminationPass::ToString() const
{ {
return TEXT("DynamicDiffuseGlobalIlluminationPass"); return TEXT("DynamicDiffuseGlobalIlluminationPass");
@@ -294,6 +256,17 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
const Vector3 probesDistance = Vector3(probesCounts) * giResolution; const Vector3 probesDistance = Vector3(probesCounts) * giResolution;
const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality
// Calculate view origin
Vector3 viewOrigin = renderContext.View.Position;
Vector3 viewDirection = renderContext.View.Direction;
const float probesDistanceMax = probesDistance.MaxValue();
const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (probesDistanceMax * 2.0f), viewOrigin - probesDistance, viewOrigin + probesDistance);
const float viewOriginOffset = viewRayHit.Y * probesDistanceMax * 0.8f;
viewOrigin += viewDirection * viewOriginOffset;
const float viewOriginSnapping = giResolution;
viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping;
//viewOrigin = Vector3::Zero;
// Init buffers // Init buffers
const int32 probesCount = probesCounts.X * probesCounts.Y * probesCounts.Z; const int32 probesCount = probesCounts.X * probesCounts.Y * probesCounts.Z;
if (probesCount == 0 || indirectLightingIntensity <= ZeroTolerance) if (probesCount == 0 || indirectLightingIntensity <= ZeroTolerance)
@@ -308,6 +281,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
ddgiData.ProbeRaysCount = probeRaysCount; ddgiData.ProbeRaysCount = probeRaysCount;
ddgiData.ProbesSpacing = giResolution; ddgiData.ProbesSpacing = giResolution;
ddgiData.ProbeCounts = probesCounts; ddgiData.ProbeCounts = probesCounts;
ddgiData.ProbesOrigin = viewOrigin;
// Allocate probes textures // Allocate probes textures
uint64 memUsage = 0; uint64 memUsage = 0;
@@ -335,29 +309,36 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
context->ClearUA(ddgiData.ProbesDistance, Vector4::Zero); context->ClearUA(ddgiData.ProbesDistance, Vector4::Zero);
} }
// Compute random rotation matrix for probe rays orientation (randomized every frame)
Matrix3x3 raysRotationMatrix;
CalculateVolumeRandomRotation(raysRotationMatrix);
// Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement) // Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement)
Vector3 viewOrigin = renderContext.View.Position; {
Vector3 viewDirection = renderContext.View.Direction;
const float probesDistanceMax = probesDistance.MaxValue(); // Reset the volume origin and scroll offsets for each axis
const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (probesDistanceMax * 2.0f), viewOrigin - probesDistance, viewOrigin + probesDistance); for (int32 axis = 0; axis < 3; axis++)
const float viewOriginOffset = viewRayHit.Y * probesDistanceMax * 0.8f; {
viewOrigin += viewDirection * viewOriginOffset; if (ddgiData.ProbeScrollOffsets.Raw[axis] != 0 && (ddgiData.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0))
const float viewOriginSnapping = giResolution; {
viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping; ddgiData.ProbesOrigin.Raw[axis] += (float)ddgiData.ProbeCounts.Raw[axis] * ddgiData.ProbesSpacing * (float)ddgiData.ProbeScrollDirections.Raw[axis];
//viewOrigin = Vector3::Zero; ddgiData.ProbeScrollOffsets.Raw[axis] = 0;
CalculateVolumeScrolling(ddgiData, viewOrigin); }
}
// Calculate the count of grid cells between the view origin and the scroll anchor
const Vector3 volumeOrigin = ddgiData.ProbesOrigin + Vector3(ddgiData.ProbeScrollOffsets) * ddgiData.ProbesSpacing;
const Vector3 translation = viewOrigin - volumeOrigin;
for (int32 axis = 0; axis < 3; axis++)
{
const float value = translation.Raw[axis] / ddgiData.ProbesSpacing;
const int32 scroll = value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value);
ddgiData.ProbeScrollOffsets.Raw[axis] += scroll;
ddgiData.ProbeScrollClear[axis] = scroll != 0;
ddgiData.ProbeScrollDirections.Raw[axis] = translation.Raw[axis] >= 0.0f ? 1 : -1;
}
}
// Upload constants // Upload constants
{ {
ddgiData.Result.Constants.ProbesOrigin = ddgiData.ProbesOrigin; ddgiData.Result.Constants.ProbesOrigin = ddgiData.ProbesOrigin;
ddgiData.Result.Constants.ProbesSpacing = ddgiData.ProbesSpacing; ddgiData.Result.Constants.ProbesSpacing = ddgiData.ProbesSpacing;
Quaternion& raysRotation = *(Quaternion*)&ddgiData.Result.Constants.RaysRotation;
Quaternion::RotationMatrix(raysRotationMatrix, raysRotation);
raysRotation.Conjugate();
ddgiData.Result.Constants.ProbesCounts[0] = probesCounts.X; ddgiData.Result.Constants.ProbesCounts[0] = probesCounts.X;
ddgiData.Result.Constants.ProbesCounts[1] = probesCounts.Y; ddgiData.Result.Constants.ProbesCounts[1] = probesCounts.Y;
ddgiData.Result.Constants.ProbesCounts[2] = probesCounts.Z; ddgiData.Result.Constants.ProbesCounts[2] = probesCounts.Z;
@@ -375,6 +356,13 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->View(); ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->View();
ddgiData.Result.ProbesIrradiance = ddgiData.ProbesIrradiance->View(); ddgiData.Result.ProbesIrradiance = ddgiData.ProbesIrradiance->View();
// Compute random rotation matrix for probe rays orientation (randomized every frame)
Matrix3x3 raysRotationMatrix;
CalculateVolumeRandomRotation(raysRotationMatrix);
Quaternion& raysRotation = *(Quaternion*)&ddgiData.Result.Constants.RaysRotation;
Quaternion::RotationMatrix(raysRotationMatrix, raysRotation);
raysRotation.Conjugate();
Data0 data; Data0 data;
data.DDGI = ddgiData.Result.Constants; data.DDGI = ddgiData.Result.Constants;
data.GlobalSDF = bindingDataSDF.Constants; data.GlobalSDF = bindingDataSDF.Constants;

View File

@@ -119,7 +119,7 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID)
probeState.xyz = float3(0, 0, 0); probeState.xyz = float3(0, 0, 0);
} }
} }
RWProbesState[probeDataCoords] = probeState; RWProbesState[probeDataCoords] = probeState;
} }
@@ -236,7 +236,8 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd
uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount; uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount;
if (probeCoords[planeIndex] == coord) if (probeCoords[planeIndex] == coord)
{ {
// Skip scrolled probes // Clear and skip scrolled probes
RWOutput[outputCoords] = float4(0, 0, 0, 0);
skip = true; skip = true;
} }
} }
@@ -262,11 +263,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd
} }
GroupMemoryBarrierWithGroupSync(); GroupMemoryBarrierWithGroupSync();
if (skip) if (skip)
{
// Clear probe
RWOutput[outputCoords] = float4(0, 0, 0, 0);
return; return;
}
// Calculate octahedral projection for probe (unwraps spherical projection into a square) // Calculate octahedral projection for probe (unwraps spherical projection into a square)
float2 octahedralCoords = GetOctahedralCoords(DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); float2 octahedralCoords = GetOctahedralCoords(DispatchThreadId.xy, DDGI_PROBE_RESOLUTION);

View File

@@ -7,6 +7,7 @@
#define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling #define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling
#define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4 #define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4
#define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile #define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile
#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED 1 // Enables using tile normal threshold to prevent sampling pixels behind the view point (but might cause back artifacts)
#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.05f // Cut-off value for tiles transitions blending during sampling #define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.05f // Cut-off value for tiles transitions blending during sampling
#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) #define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes)
@@ -108,12 +109,14 @@ float3 SampleGlobalSurfaceAtlasTex(Texture2D atlas, float2 atlasUV, float4 bilin
float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold) float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold)
{ {
#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED
// Tile normal weight based on the sampling angle // Tile normal weight based on the sampling angle
float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal));
float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal)); float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal));
normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD); normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD);
if (normalWeight <= 0.0f) if (normalWeight <= 0.0f)
return 0; return 0;
#endif
// Get tile UV and depth at the world position // Get tile UV and depth at the world position
float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz;
@@ -141,7 +144,10 @@ float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSur
if (tileZ[i] >= 1.0f) if (tileZ[i] >= 1.0f)
depthVisibility[i] = 0.0f; depthVisibility[i] = 0.0f;
} }
float sampleWeight = normalWeight * dot(depthVisibility, bilinearWeights); float sampleWeight = dot(depthVisibility, bilinearWeights);
#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED
sampleWeight *= normalWeight;
#endif
if (sampleWeight <= 0.0f) if (sampleWeight <= 0.0f)
return 0; return 0;
bilinearWeights = depthVisibility * bilinearWeights; bilinearWeights = depthVisibility * bilinearWeights;
@@ -209,26 +215,65 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu
worldToLocal[2] *= invScale.z; worldToLocal[2] *= invScale.z;
// Sample tiles based on the directionality // Sample tiles based on the directionality
#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED
float3 localNormal = normalize(mul(worldNormal, worldToLocal)); float3 localNormal = normalize(mul(worldNormal, worldToLocal));
float3 localNormalSq = localNormal * localNormal; float3 localNormalSq = localNormal * localNormal;
uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; uint tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 0 : 1];
if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0)
{
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
}
tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3];
if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0)
{
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
}
tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5];
if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0)
{
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
}
#else
uint tileOffset = object.TileOffsets[0];
if (tileOffset != 0)
{ {
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
} }
tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3]; tileOffset = object.TileOffsets[1];
if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) if (tileOffset != 0)
{ {
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
} }
tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5]; tileOffset = object.TileOffsets[2];
if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) if (tileOffset != 0)
{ {
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
} }
tileOffset = object.TileOffsets[3];
if (tileOffset != 0)
{
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
}
tileOffset = object.TileOffsets[4];
if (tileOffset != 0)
{
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
}
tileOffset = object.TileOffsets[5];
if (tileOffset != 0)
{
GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset);
result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold);
}
#endif
} }
// Normalize result // Normalize result