From 5ee62be16631685872a79c11b1105eb19ff43417 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 6 Jun 2022 15:04:58 +0200 Subject: [PATCH] Various DDGI improvements to the quality of the final effect --- Content/Shaders/GI/DDGI.flax | 4 +- .../GI/DynamicDiffuseGlobalIllumination.cpp | 98 ++++++++----------- Source/Shaders/GI/DDGI.shader | 9 +- Source/Shaders/GI/GlobalSurfaceAtlas.hlsl | 59 +++++++++-- 4 files changed, 100 insertions(+), 70 deletions(-) diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index d68e89a7b..e240f1dd1 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:589d77f14b69b0af963b848ee5a4342c0986304f64296a29d21f45d7e0e6ea88 -size 18536 +oid sha256:6472e0a74ad1b42770b0ae80b05fd1c7ff44de32bf4294e8e4b9f97be40535c5 +size 18517 diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 8521cd1c0..d8adfacd0 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -116,44 +116,6 @@ void CalculateVolumeRandomRotation(Matrix3x3& matrix) 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 { return TEXT("DynamicDiffuseGlobalIlluminationPass"); @@ -294,6 +256,17 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, 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 + // 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 const int32 probesCount = probesCounts.X * probesCounts.Y * probesCounts.Z; if (probesCount == 0 || indirectLightingIntensity <= ZeroTolerance) @@ -308,6 +281,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, ddgiData.ProbeRaysCount = probeRaysCount; ddgiData.ProbesSpacing = giResolution; ddgiData.ProbeCounts = probesCounts; + ddgiData.ProbesOrigin = viewOrigin; // Allocate probes textures uint64 memUsage = 0; @@ -335,29 +309,36 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, 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) - 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; - CalculateVolumeScrolling(ddgiData, 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 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 { ddgiData.Result.Constants.ProbesOrigin = ddgiData.ProbesOrigin; 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[1] = probesCounts.Y; ddgiData.Result.Constants.ProbesCounts[2] = probesCounts.Z; @@ -375,6 +356,13 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->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; data.DDGI = ddgiData.Result.Constants; data.GlobalSDF = bindingDataSDF.Constants; diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index c6a680291..e13ae04fe 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -119,7 +119,7 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) probeState.xyz = float3(0, 0, 0); } } - + 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; if (probeCoords[planeIndex] == coord) { - // Skip scrolled probes + // Clear and skip scrolled probes + RWOutput[outputCoords] = float4(0, 0, 0, 0); skip = true; } } @@ -262,11 +263,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd } GroupMemoryBarrierWithGroupSync(); if (skip) - { - // Clear probe - RWOutput[outputCoords] = float4(0, 0, 0, 0); return; - } // Calculate octahedral projection for probe (unwraps spherical projection into a square) float2 octahedralCoords = GetOctahedralCoords(DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl index 6d1d0adbb..92f727db2 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl @@ -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_GROUP_SIZE 4 #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_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) { +#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED // Tile normal weight based on the sampling angle float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); 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); if (normalWeight <= 0.0f) return 0; +#endif // Get tile UV and depth at the world position float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; @@ -141,7 +144,10 @@ float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSur if (tileZ[i] >= 1.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) return 0; bilinearWeights = depthVisibility * bilinearWeights; @@ -209,26 +215,65 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu worldToLocal[2] *= invScale.z; // Sample tiles based on the directionality +#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED float3 localNormal = normalize(mul(worldNormal, worldToLocal)); float3 localNormalSq = localNormal * localNormal; - uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; - if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + 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) + { + 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); 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) + tileOffset = object.TileOffsets[1]; + if (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) + tileOffset = object.TileOffsets[2]; + if (tileOffset != 0) { GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); 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