diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 3692046df..7c11eb0ac 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -14,6 +14,7 @@ #include "./Flax/Lighting.hlsl" #include "./Flax/ShadowsSampling.hlsl" #include "./Flax/ExponentialHeightFog.hlsl" +#include "./Flax/VolumetricFog.hlsl" @2// Forward Shading: Constants LightData DirectionalLight; LightData SkyLight; @@ -159,16 +160,11 @@ void PS_Forward( float fogSceneDistance = gBuffer.ViewPos.z; #endif float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0, fogSceneDistance); - if (ExponentialHeightFog.VolumetricFogMaxDistance > 0) { // Sample volumetric fog and mix it in float2 screenUV = materialInput.SvPosition.xy * ScreenSize.zw; - float3 viewVector = materialInput.WorldPosition - ViewPos; - float sceneDepth = length(viewVector); - float depthSlice = sceneDepth / ExponentialHeightFog.VolumetricFogMaxDistance; - float3 volumeUV = float3(screenUV, depthSlice); - float4 volumetricFog = VolumetricFogTexture.SampleLevel(SamplerLinearClamp, volumeUV, 0); + float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, materialInput.WorldPosition - ViewPos, ExponentialHeightFog.VolumetricFogMaxDistance, screenUV); fog = CombineVolumetricFog(fog, volumetricFog); } diff --git a/Content/Editor/MaterialTemplates/VolumeParticle.shader b/Content/Editor/MaterialTemplates/VolumeParticle.shader index eb94554c2..59d964101 100644 --- a/Content/Editor/MaterialTemplates/VolumeParticle.shader +++ b/Content/Editor/MaterialTemplates/VolumeParticle.shader @@ -8,6 +8,7 @@ #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" +#include "./Flax/VolumetricFog.hlsl" @7 // Primary constant buffer (with additional material parameters) @@ -21,6 +22,8 @@ float Dummy0; float VolumetricFogMaxDistance; int ParticleStride; int ParticleIndex; +float3 GridSliceParameters; +float Dummy1; @1META_CB_END // Particles attributes buffer @@ -202,19 +205,19 @@ Material GetMaterialPS(MaterialInput input) META_PS(true, FEATURE_LEVEL_SM5) void PS_VolumetricFog(Quad_GS2PS input, out float4 VBufferA : SV_Target0, out float4 VBufferB : SV_Target1) { + // Reproject grid position back to the screen and world space uint3 gridCoordinate = uint3(input.Vertex.Position.xy, input.LayerIndex); float3 cellOffset = 0.5f; float2 volumeUV = (gridCoordinate.xy + cellOffset.xy) / GridSize.xy; - float zSlice = gridCoordinate.z + cellOffset.z; - float sceneDepth = (zSlice / GridSize.z) * VolumetricFogMaxDistance / ViewFar; + float sceneDepth = GetDepthFromSlice(GridSliceParameters, gridCoordinate.z + cellOffset.z) / ViewFar; float deviceDepth = (ViewInfo.w / sceneDepth) + ViewInfo.z; float4 clipPos = float4(volumeUV * float2(2.0, -2.0) + float2(-1.0, 1.0), deviceDepth, 1.0); float4 wsPos = mul(clipPos, InverseViewProjectionMatrix); - float3 positionWS = wsPos.xyz / wsPos.w; + wsPos.xyz /= wsPos.w; // Get material parameters MaterialInput materialInput = (MaterialInput)0; - materialInput.WorldPosition = positionWS; + materialInput.WorldPosition = wsPos.xyz; materialInput.TexCoord = input.Vertex.TexCoord; materialInput.ParticleIndex = ParticleIndex; materialInput.TBN = float3x3(float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1)); diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 9c01cf2d0..45bc9e257 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 180 +#define MATERIAL_GRAPH_VERSION 181 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp index 60192202a..22d29c0be 100644 --- a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp @@ -25,6 +25,8 @@ PACK_STRUCT(struct VolumeParticleMaterialShaderData { float VolumetricFogMaxDistance; int32 ParticleStride; int32 ParticleIndex; + Float3 GridSliceParameters; + float Dummy1; }); DrawPass VolumeParticleMaterialShader::GetDrawModes() const @@ -86,6 +88,7 @@ void VolumeParticleMaterialShader::Bind(BindParameters& params) materialData->VolumetricFogMaxDistance = customData->VolumetricFogMaxDistance; materialData->ParticleStride = drawCall.Particle.Particles->Stride; materialData->ParticleIndex = customData->ParticleIndex; + materialData->GridSliceParameters = customData->GridSliceParameters; } // Bind constants diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index 898b884c2..949668771 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -630,6 +630,20 @@ void RenderTools::ComputeBoxModelDrawMatrix(const RenderView& view, const Orient resultIsViewInside = box.Contains(view.Position) == ContainmentType::Contains; } +float RenderTools::TemporalHalton(int32 index, int32 base) +{ + float result = 0.0f; + const float invBase = 1.0f / (float)base; + float fraction = invBase; + while (index > 0) + { + result += float(index % base) * fraction; + index /= base; + fraction *= invBase; + } + return result; +} + Float2 RenderTools::GetDepthBounds(const RenderView& view, const Float3& nearPoint, const Float3& farPoint) { // Point closest the view diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h index 301e3888f..2a2bc6357 100644 --- a/Source/Engine/Graphics/RenderTools.h +++ b/Source/Engine/Graphics/RenderTools.h @@ -148,6 +148,8 @@ public: static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside); static void ComputeBoxModelDrawMatrix(const RenderView& view, const OrientedBoundingBox& box, Matrix& resultWorld, bool& resultIsViewInside); + static float TemporalHalton(int32 index, int32 base); + // Calculates depth bounds to optimize drawing with depth buffer to cover only specific range of depth. Returns min and max depth (as Float2) to pass into GPUContext::SetDepthBounds. static Float2 GetDepthBounds(const RenderView& view, const Float3& nearPoint, const Float3& farPoint); static Float2 GetDepthBounds(const RenderView& view, const BoundingSphere& bounds); diff --git a/Source/Engine/Graphics/RenderView.cpp b/Source/Engine/Graphics/RenderView.cpp index dfe1964ad..0472ce346 100644 --- a/Source/Engine/Graphics/RenderView.cpp +++ b/Source/Engine/Graphics/RenderView.cpp @@ -5,9 +5,9 @@ #include "Engine/Level/Actors/Camera.h" #include "Engine/Core/Math/Double4x4.h" #include "Engine/Renderer/RenderList.h" -#include "Engine/Renderer/RendererPass.h" #include "RenderBuffers.h" #include "RenderTask.h" +#include "RenderTools.h" void RenderView::Prepare(RenderContext& renderContext) { @@ -29,8 +29,8 @@ void RenderView::Prepare(RenderContext& renderContext) // Calculate jitter const float jitterSpread = renderContext.List->Settings.AntiAliasing.TAA_JitterSpread; - const float jitterX = (RendererUtils::TemporalHalton(TaaFrameIndex + 1, 2) - 0.5f) * jitterSpread; - const float jitterY = (RendererUtils::TemporalHalton(TaaFrameIndex + 1, 3) - 0.5f) * jitterSpread; + const float jitterX = (RenderTools::TemporalHalton(TaaFrameIndex + 1, 2) - 0.5f) * jitterSpread; + const float jitterY = (RenderTools::TemporalHalton(TaaFrameIndex + 1, 3) - 0.5f) * jitterSpread; taaJitter = Float2(jitterX * 2.0f / width, jitterY * 2.0f / height); // Modify projection matrix diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 0ee719fa9..01fc77058 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -187,8 +187,8 @@ GPU_CB_STRUCT(Data { void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) { PROFILE_GPU_CPU("Exponential Height Fog"); - auto integratedLightScattering = renderContext.Buffers->VolumetricFog; - bool useVolumetricFog = integratedLightScattering != nullptr; + auto volumetricFogTexture = renderContext.Buffers->VolumetricFog; + bool useVolumetricFog = volumetricFogTexture != nullptr; // Setup shader inputs Data data; @@ -199,9 +199,10 @@ void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderCon context->UpdateCB(cb, &data); context->BindCB(0, cb); context->BindSR(0, renderContext.Buffers->DepthBuffer); - context->BindSR(1, integratedLightScattering ? integratedLightScattering->ViewVolume() : nullptr); + context->BindSR(1, volumetricFogTexture ? volumetricFogTexture->ViewVolume() : nullptr); // TODO: instead of rendering fullscreen triangle, draw quad transformed at the fog start distance (also it could use early depth discard) + // TODO: or use DepthBounds to limit the fog rendering to the distance range // Draw fog const int32 psIndex = (useVolumetricFog ? 1 : 0); diff --git a/Source/Engine/Renderer/RendererPass.h b/Source/Engine/Renderer/RendererPass.h index 32d3b86b9..fee05e302 100644 --- a/Source/Engine/Renderer/RendererPass.h +++ b/Source/Engine/Renderer/RendererPass.h @@ -13,25 +13,6 @@ #include "Engine/Profiler/Profiler.h" #include "Config.h" -class RendererUtils -{ -public: - - static float TemporalHalton(int32 index, int32 base) - { - float result = 0.0f; - const float invBase = 1.0f / base; - float fraction = invBase; - while (index > 0) - { - result += (index % base) * fraction; - index /= base; - fraction *= invBase; - } - return result; - } -}; - /// /// Base class for renderer components called render pass. /// Each render pass supports proper resources initialization and disposing. diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index fb4e8a82e..2b00470be 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -5,6 +5,7 @@ #include "GBufferPass.h" #include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/GPULimits.h" @@ -13,14 +14,12 @@ #include "Engine/Content/Assets/CubeTexture.h" #include "Engine/Content/Content.h" #include "Engine/Engine/Engine.h" +#include "Engine/Engine/Units.h" // Must match shader source int32 VolumetricFogGridInjectionGroupSize = 4; int32 VolumetricFogIntegrationGroupSize = 8; - -VolumetricFogPass::VolumetricFogPass() -{ -} +#define VOLUMETRIC_FOG_GRID_Z_LINEAR 1 String VolumetricFogPass::ToString() const { @@ -92,14 +91,52 @@ void VolumetricFogPass::Dispose() _shader = nullptr; } -float ComputeZSliceFromDepth(float sceneDepth, const VolumetricFogOptions& options, int32 gridSizeZ) +Float3 GetGridSliceParameters(float fogStart, float fogEnd, int32 gridSizeZ) { - return sceneDepth / options.Distance * (float)gridSizeZ; +#if VOLUMETRIC_FOG_GRID_Z_LINEAR + float sliceToDepth = fogEnd / (float)gridSizeZ; + return Float3(sliceToDepth, 1.0f / sliceToDepth, 0.0f); +#else + // Use logarithmic distribution for Z slices to have more resolution for close distances and less for far ones (less aliasing near camera) + const float distribution = 220.0f; // Manually adjusted to give a good distribution across the range + fogStart += UNITS_TO_METERS(10); // Bias start a bit for some more quality + float y = (fogEnd - fogStart * Math::Exp2((float)(gridSizeZ - 1) / distribution)) / (fogEnd - fogStart); + float x = (1.0f - y) / fogStart; + return Float3(x, y, distribution); +#endif } -bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) +float GetDepthFromSlice(float slice, const Float3& gridSliceParameters) +{ +#if VOLUMETRIC_FOG_GRID_Z_LINEAR + return slice * gridSliceParameters.X; +#else + return (Math::Exp2(slice / gridSliceParameters.Z) - gridSliceParameters.Y) / gridSliceParameters.X; +#endif +} + +float GetSliceFromDepth(float sceneDepth, const Float3& gridSliceParameters) +{ +#if VOLUMETRIC_FOG_GRID_Z_LINEAR + return sceneDepth * gridSliceParameters.Y; +#else + return Math::Log2(sceneDepth * gridSliceParameters.X + gridSliceParameters.Y) * gridSliceParameters.Z; +#endif +} + +struct alignas(Float4) RasterizeSphere +{ + Float3 Center; + float Radius; + Float3 ViewSpaceCenter; + uint16 VolumeZBoundsMin; + uint16 VolumeZBoundsMax; +}; + +bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context) { const auto fog = renderContext.List->Fog; + auto& options = _cache.Options; // Check if already prepared for this frame if (renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount) @@ -127,33 +164,29 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, } // Setup configuration - _cache.HistoryWeight = 0.9f; + _cache.FogJitter = true; + _cache.HistoryWeight = 0.92f; _cache.InverseSquaredLightDistanceBiasScale = 1.0f; - const auto quality = Graphics::VolumetricFogQuality; - switch (quality) + switch (Graphics::VolumetricFogQuality) { case Quality::Low: - _cache.GridPixelSize = 16; - _cache.GridSizeZ = 64; - _cache.FogJitter = false; + _cache.GridPixelSize = 24; + _cache.GridSizeZ = 50; _cache.MissedHistorySamplesCount = 1; break; case Quality::Medium: - _cache.GridPixelSize = 16; - _cache.GridSizeZ = 64; - _cache.FogJitter = true; - _cache.MissedHistorySamplesCount = 4; + _cache.GridPixelSize = 20; + _cache.GridSizeZ = 54; + _cache.MissedHistorySamplesCount = 2; break; case Quality::High: _cache.GridPixelSize = 16; - _cache.GridSizeZ = 128; - _cache.FogJitter = true; + _cache.GridSizeZ = 64; _cache.MissedHistorySamplesCount = 4; break; case Quality::Ultra: _cache.GridPixelSize = 8; _cache.GridSizeZ = 128; - _cache.FogJitter = true; _cache.MissedHistorySamplesCount = 8; break; } @@ -186,6 +219,7 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, _cache.Data.GridSizeIntZ = (uint32)_cache.GridSize.Z; _cache.Data.HistoryWeight = _cache.HistoryWeight; _cache.Data.FogParameters = options.FogParameters; + _cache.Data.GridSliceParameters = GetGridSliceParameters(renderContext.View.Near, options.Distance, _cache.GridSizeZ); _cache.Data.InverseSquaredLightDistanceBiasScale = _cache.InverseSquaredLightDistanceBiasScale; _cache.Data.PhaseG = options.ScatteringDistribution; _cache.Data.VolumetricFogMaxDistance = options.Distance; @@ -196,20 +230,24 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, // Fill frame jitter history const Float4 defaultOffset(0.5f, 0.5f, 0.5f, 0.0f); for (int32 i = 0; i < ARRAY_COUNT(_cache.Data.FrameJitterOffsets); i++) - { _cache.Data.FrameJitterOffsets[i] = defaultOffset; - } + _cache.SphereRasterizeRadiusBias = 0.0f; if (_cache.FogJitter) { for (int32 i = 0; i < _cache.MissedHistorySamplesCount; i++) { const uint64 frameNumber = renderContext.Task->LastUsedFrame - i; _cache.Data.FrameJitterOffsets[i] = Float4( - RendererUtils::TemporalHalton(frameNumber & 1023, 2), - RendererUtils::TemporalHalton(frameNumber & 1023, 3), - RendererUtils::TemporalHalton(frameNumber & 1023, 5), + RenderTools::TemporalHalton(frameNumber & 1023, 2), + RenderTools::TemporalHalton(frameNumber & 1023, 3), + RenderTools::TemporalHalton(frameNumber & 1023, 5), 0); } + + // Add bias to radius when using jittering to avoid pixelization on the circle borders (cell offset is randomized) + float worldUnitsPerDepthCell = options.Distance / _cache.GridSize.Z; + // TODO: include XY size too? + _cache.SphereRasterizeRadiusBias = worldUnitsPerDepthCell * 0.25f; } // Set constant buffer data @@ -235,7 +273,30 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, return false; } -GPUTextureView* VolumetricFogPass::GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) const +bool VolumetricFogPass::InitSphereRasterize(RasterizeSphere& sphere, RenderView& view, const Float3& center, float radius) +{ + ASSERT_LOW_LAYER(!center.IsNanOrInfinity() && !isnan(radius) && !isinf(radius)); + sphere.Center = center; + sphere.Radius = radius + _cache.SphereRasterizeRadiusBias; + + // Calculate sphere volume bounds in camera frustum depth range (min and max) + sphere.ViewSpaceCenter = Float3::Transform(center, view.View); + const float furthestSliceIndex = GetSliceFromDepth(sphere.ViewSpaceCenter.Z + sphere.Radius, _cache.Data.GridSliceParameters); + const float closestSliceIndex = GetSliceFromDepth(sphere.ViewSpaceCenter.Z - sphere.Radius, _cache.Data.GridSliceParameters); + sphere.VolumeZBoundsMin = (uint16)Math::Clamp(closestSliceIndex, 0.0f, _cache.GridSize.Z - 1.0f); + sphere.VolumeZBoundsMax = (uint16)Math::Clamp(furthestSliceIndex, 0.0f, _cache.GridSize.Z - 1.0f); + + // Cull + if ((view.Position - sphere.Center).LengthSquared() >= Math::Square(_cache.Options.Distance + sphere.Radius) || + sphere.VolumeZBoundsMin > sphere.VolumeZBoundsMax) + { + return true; + } + + return false; +} + +GPUTextureView* VolumetricFogPass::GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context) const { if (renderContext.Buffers->LocalShadowedLightScattering == nullptr) { @@ -251,28 +312,19 @@ GPUTextureView* VolumetricFogPass::GetLocalShadowedLightScattering(RenderContext } template -void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb2) +void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, T& light, PerLight& perLight, GPUConstantBuffer* cb2) { - const Float3 center = light.Position; - const float radius = light.Radius; - ASSERT(!center.IsNanOrInfinity() && !isnan(radius) && !isinf(radius)); - auto& cache = _cache; - - // Calculate light volume bounds in camera frustum depth range (min and max) - const Float3 viewSpaceLightBoundsOrigin = Float3::Transform(center, view.View); - const float furthestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z + radius, options, cache.GridSizeZ); - const float closestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z - radius, options, cache.GridSizeZ); - const int32 volumeZBoundsMin = (int32)Math::Clamp(closestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f); - const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f); - if (volumeZBoundsMin >= volumeZBoundsMax) + RasterizeSphere sphere; + if (InitSphereRasterize(sphere, view, light.Position, light.Radius)) return; + auto& cache = _cache; // Setup data perLight.SliceToDepth.X = cache.Data.GridSize.Z; perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance; - perLight.MinZ = volumeZBoundsMin; + perLight.MinZ = sphere.VolumeZBoundsMin; perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; - perLight.ViewSpaceBoundingSphere = Float4(viewSpaceLightBoundsOrigin, radius); + perLight.ViewSpaceBoundingSphere = Float4(sphere.ViewSpaceCenter, sphere.Radius); Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip); const bool withShadow = light.CastVolumetricShadow && light.HasShadow; light.SetShaderData(perLight.LocalLight, withShadow); @@ -288,7 +340,7 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte // Call rendering to the volume const int32 psIndex = withShadow ? 1 : 0; context->SetState(_psInjectLight.Get(psIndex)); - const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin; + const int32 instanceCount = sphere.VolumeZBoundsMax - sphere.VolumeZBoundsMin + 1; const int32 indexCount = _ibCircleRasterize->GetElementsCount(); context->BindVB(ToSpan(&_vbCircleRasterize, 1)); context->BindIB(_ibCircleRasterize); @@ -297,9 +349,8 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte void VolumetricFogPass::Render(RenderContext& renderContext) { - VolumetricFogOptions options; auto context = GPUDevice::Instance->GetMainContext(); - if (Init(renderContext, context, options)) + if (Init(renderContext, context)) return; auto& view = renderContext.View; auto& cache = _cache; @@ -409,6 +460,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) customData.Shader = _shader->GetShader(); customData.GridSize = cache.GridSize; customData.VolumetricFogMaxDistance = cache.Data.VolumetricFogMaxDistance; + customData.GridSliceParameters = _cache.Data.GridSliceParameters; bindParams.CustomData = &customData; bindParams.BindViewData(); bindParams.DrawCall = renderContext.List->VolumetricFogParticles.begin(); @@ -416,19 +468,8 @@ void VolumetricFogPass::Render(RenderContext& renderContext) for (auto& drawCall : renderContext.List->VolumetricFogParticles) { - const Float3 center = drawCall.Particle.VolumetricFog.Position; - const float radius = drawCall.Particle.VolumetricFog.Radius; - ASSERT(!center.IsNanOrInfinity() && !isnan(radius) && !isinf(radius)); - - // Calculate light volume bounds in camera frustum depth range (min and max) - const Float3 viewSpaceLightBoundsOrigin = Float3::Transform(center, view.View); - const float furthestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z + radius, options, cache.GridSizeZ); - const float closestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z - radius, options, cache.GridSizeZ); - const int32 volumeZBoundsMin = (int32)Math::Clamp(closestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f); - const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f); - - // Culling - if ((view.Position - center).LengthSquared() >= Math::Square(options.Distance + radius) || volumeZBoundsMin >= volumeZBoundsMax) + RasterizeSphere sphere; + if (InitSphereRasterize(sphere, view, drawCall.Particle.VolumetricFog.Position, drawCall.Particle.VolumetricFog.Radius)) continue; // Setup material shader data @@ -441,8 +482,8 @@ void VolumetricFogPass::Render(RenderContext& renderContext) auto cb2 = _shader->GetShader()->GetCB(2); perLight.SliceToDepth.X = cache.Data.GridSize.Z; perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance; - perLight.MinZ = volumeZBoundsMin; - perLight.ViewSpaceBoundingSphere = Float4(viewSpaceLightBoundsOrigin, radius); + perLight.MinZ = sphere.VolumeZBoundsMin; + perLight.ViewSpaceBoundingSphere = Float4(sphere.ViewSpaceCenter, sphere.Radius); Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip); // Upload data @@ -450,7 +491,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) context->BindCB(2, cb2); // Call rendering to the volume - const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin; + const int32 instanceCount = sphere.VolumeZBoundsMax - sphere.VolumeZBoundsMin + 1; const int32 indexCount = _ibCircleRasterize->GetElementsCount(); context->BindVB(ToSpan(&_vbCircleRasterize, 1)); context->BindIB(_ibCircleRasterize); @@ -467,18 +508,19 @@ void VolumetricFogPass::Render(RenderContext& renderContext) // Get lights to render Array> pointLights; Array> spotLights; + float distance = _cache.Options.Distance; for (int32 i = 0; i < renderContext.List->PointLights.Count(); i++) { const auto& light = renderContext.List->PointLights.Get()[i]; if (light.VolumetricScatteringIntensity > ZeroTolerance && - (view.Position - light.Position).LengthSquared() < Math::Square(options.Distance + light.Radius)) + (view.Position - light.Position).LengthSquared() < Math::Square(distance + light.Radius)) pointLights.Add(i); } for (int32 i = 0; i < renderContext.List->SpotLights.Count(); i++) { const auto& light = renderContext.List->SpotLights.Get()[i]; if (light.VolumetricScatteringIntensity > ZeroTolerance && - (view.Position - light.Position).LengthSquared() < Math::Square(options.Distance + light.Radius)) + (view.Position - light.Position).LengthSquared() < Math::Square(distance + light.Radius)) spotLights.Add(i); } @@ -488,7 +530,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) PROFILE_GPU_CPU_NAMED("Lights Injection"); // Allocate temporary buffer for light scattering injection - localShadowedLightScattering = GetLocalShadowedLightScattering(renderContext, context, options); + localShadowedLightScattering = GetLocalShadowedLightScattering(renderContext, context); // Prepare PerLight perLight; @@ -503,10 +545,14 @@ void VolumetricFogPass::Render(RenderContext& renderContext) // Render them to the volume context->BindSR(0, shadowMap); context->BindSR(1, shadowsBuffer); + auto* pointLightsIdxPtr = pointLights.Get(); + auto* pointLightsPtr = renderContext.List->PointLights.Get(); for (int32 i = 0; i < pointLights.Count(); i++) - RenderRadialLight(renderContext, context, view, options, renderContext.List->PointLights[pointLights[i]], perLight, cb2); + RenderRadialLight(renderContext, context, view, pointLightsPtr[pointLightsIdxPtr[i]], perLight, cb2); + auto* spotLightsIdxPtr = spotLights.Get(); + auto* spotLightsPtr = renderContext.List->SpotLights.Get(); for (int32 i = 0; i < spotLights.Count(); i++) - RenderRadialLight(renderContext, context, view, options, renderContext.List->SpotLights[spotLights[i]], perLight, cb2); + RenderRadialLight(renderContext, context, view, spotLightsPtr[spotLightsIdxPtr[i]], perLight, cb2); // Cleanup context->UnBindCB(2); @@ -557,20 +603,14 @@ void VolumetricFogPass::Render(RenderContext& renderContext) RenderTargetPool::Release(vBufferB); // Update the temporal history buffer - if (renderContext.Buffers->VolumetricFogHistory) - { - RenderTargetPool::Release(renderContext.Buffers->VolumetricFogHistory); - } + RenderTargetPool::Release(renderContext.Buffers->VolumetricFogHistory); renderContext.Buffers->VolumetricFogHistory = lightScattering; // Get buffer for the integrated light scattering (try to reuse the previous frame if it's valid) GPUTexture* integratedLightScattering = renderContext.Buffers->VolumetricFog; if (integratedLightScattering == nullptr || !Float3::NearEqual(integratedLightScattering->Size3(), cache.GridSize)) { - if (integratedLightScattering) - { - RenderTargetPool::Release(integratedLightScattering); - } + RenderTargetPool::Release(integratedLightScattering); integratedLightScattering = RenderTargetPool::Get(volumeDesc); RENDER_TARGET_POOL_SET_NAME(integratedLightScattering, "VolumetricFog.Integrated"); renderContext.Buffers->VolumetricFog = integratedLightScattering; @@ -583,10 +623,8 @@ void VolumetricFogPass::Render(RenderContext& renderContext) // Final Integration { PROFILE_GPU("Final Integration"); - context->BindUA(0, integratedLightScattering->ViewVolume()); context->BindSR(0, lightScattering->ViewVolume()); - context->Dispatch(_csFinalIntegration, groupCountX, groupCountY, 1); } diff --git a/Source/Engine/Renderer/VolumetricFogPass.h b/Source/Engine/Renderer/VolumetricFogPass.h index e795efee1..7205d165d 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.h +++ b/Source/Engine/Renderer/VolumetricFogPass.h @@ -5,6 +5,7 @@ #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/GPUPipelineStatePermutations.h" #include "RendererPass.h" +#include "DrawCall.h" #include "GI/DynamicDiffuseGlobalIllumination.h" struct VolumetricFogOptions; @@ -17,12 +18,12 @@ struct RenderPointLightData; class VolumetricFogPass : public RendererPass { public: - struct CustomData { GPUShader* Shader; Float3 GridSize; float VolumetricFogMaxDistance; + Float3 GridSliceParameters; int32 ParticleIndex; }; @@ -57,6 +58,8 @@ private: float InverseSquaredLightDistanceBiasScale; Float4 FogParameters; + Float3 GridSliceParameters; + float Dummy1; Matrix PrevWorldToClip; @@ -128,6 +131,13 @@ private: /// Float3 GridSize; + float SphereRasterizeRadiusBias; + + /// + /// Fog options(from renderer). + /// + VolumetricFogOptions Options; + /// /// The cached per-frame data for the constant buffer. /// @@ -137,13 +147,6 @@ private: FrameCache _cache; bool _isSupported; -public: - - /// - /// Init - /// - VolumetricFogPass(); - public: /// /// Renders the volumetric fog (generates integrated light scattering 3D texture). Does nothing if feature is disabled or not supported. @@ -152,12 +155,12 @@ public: void Render(RenderContext& renderContext); private: - - bool Init(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options); - GPUTextureView* GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) const; + bool Init(RenderContext& renderContext, GPUContext* context); + bool InitSphereRasterize(struct RasterizeSphere& sphere, RenderView& view, const Float3& center, float radius); + GPUTextureView* GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context) const; void InitCircleBuffer(); template - void RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb2); + void RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, T& light, PerLight& perLight, GPUConstantBuffer* cb2); #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { diff --git a/Source/Shaders/ExponentialHeightFog.hlsl b/Source/Shaders/ExponentialHeightFog.hlsl index b5af721d8..f6fb918f5 100644 --- a/Source/Shaders/ExponentialHeightFog.hlsl +++ b/Source/Shaders/ExponentialHeightFog.hlsl @@ -92,9 +92,4 @@ float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, fl return GetExponentialHeightFog(exponentialHeightFog, posWS, camWS, skipDistance, distance(posWS, camWS)); } -float4 CombineVolumetricFog(float4 fog, float4 volumetricFog) -{ - return float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a); -} - #endif diff --git a/Source/Shaders/Fog.shader b/Source/Shaders/Fog.shader index 5c4344688..20d2a8e65 100644 --- a/Source/Shaders/Fog.shader +++ b/Source/Shaders/Fog.shader @@ -5,6 +5,7 @@ #include "./Flax/Common.hlsl" #include "./Flax/GBufferCommon.hlsl" #include "./Flax/GBuffer.hlsl" +#include "./Flax/VolumetricFog.hlsl" #include "./Flax/ExponentialHeightFog.hlsl" // Disable Volumetric Fog if is not supported @@ -21,7 +22,7 @@ DECLARE_GBUFFERDATA_ACCESS(GBuffer) Texture2D Depth : register(t0); #if VOLUMETRIC_FOG -Texture3D IntegratedLightScattering : register(t1); +Texture3D VolumetricFogTexture : register(t1); #endif META_PS(true, FEATURE_LEVEL_ES2) @@ -34,23 +35,6 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0 GBufferData gBufferData = GetGBufferData(); float3 viewPos = GetViewPos(gBufferData, input.TexCoord, rawDepth); float3 worldPos = mul(float4(viewPos, 1), gBufferData.InvViewMatrix).xyz; - float3 viewVector = worldPos - GBuffer.ViewPos; - float sceneDepth = length(viewVector); - - // Calculate volumetric fog coordinates - float depthSlice = sceneDepth / ExponentialHeightFog.VolumetricFogMaxDistance; - float3 volumeUV = float3(input.TexCoord, depthSlice); - - // Debug code -#if VOLUMETRIC_FOG && 0 - volumeUV = worldPos / 1000; - if (!all(volumeUV >= 0 && volumeUV <= 1)) - return 0; - - return float4(IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0).rgb, 1); - //return float4(volumeUV, 1); - //return float4(worldPos / 100, 1); -#endif float skipDistance = 0; #if VOLUMETRIC_FOG @@ -62,7 +46,7 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0 #if VOLUMETRIC_FOG // Sample volumetric fog and mix it in - float4 volumetricFog = IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0); + float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, worldPos - GBuffer.ViewPos, ExponentialHeightFog.VolumetricFogMaxDistance, input.TexCoord); fog = CombineVolumetricFog(fog, volumetricFog); #endif diff --git a/Source/Shaders/Math.hlsl b/Source/Shaders/Math.hlsl index b590b60b4..c0a78ad54 100644 --- a/Source/Shaders/Math.hlsl +++ b/Source/Shaders/Math.hlsl @@ -256,4 +256,9 @@ float3 RotateAboutAxis(float4 normalizedRotationAxisAndAngle, float3 positionOnA return pointOnAxis + rotation - position; } +float Remap(float value, float fromMin, float fromMax, float toMin, float toMax) +{ + return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin; +} + #endif diff --git a/Source/Shaders/VolumetricFog.hlsl b/Source/Shaders/VolumetricFog.hlsl new file mode 100644 index 000000000..db5895b94 --- /dev/null +++ b/Source/Shaders/VolumetricFog.hlsl @@ -0,0 +1,40 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#ifndef __VOLUMETRIC_FOG__ +#define __VOLUMETRIC_FOG__ + +#define VOLUMETRIC_FOG_GRID_Z_LINEAR 1 + +float GetDepthFromSlice(float3 gridSliceParameters, float zSlice) +{ +#if VOLUMETRIC_FOG_GRID_Z_LINEAR + return zSlice * gridSliceParameters.x; +#else + return (exp2(zSlice / gridSliceParameters.z) - gridSliceParameters.y) / gridSliceParameters.x; +#endif +} + +float GetSliceFromDepth(float3 gridSliceParameters, float sceneDepth) +{ +#if VOLUMETRIC_FOG_GRID_Z_LINEAR + return sceneDepth * gridSliceParameters.y; +#else + return (log2(sceneDepth * gridSliceParameters.x + gridSliceParameters.y) * gridSliceParameters.z); +#endif +} + +float4 SampleVolumetricFog(Texture3D volumetricFogTexture, float3 viewVector, float maxDistance, float2 uv) +{ + float sceneDepth = length(viewVector); + float zSlice = sceneDepth / maxDistance; + // TODO: use GetSliceFromDepth instead to handle non-linear depth distributions + float3 volumeUV = float3(uv, zSlice); + return volumetricFogTexture.SampleLevel(SamplerLinearClamp, volumeUV, 0); +} + +float4 CombineVolumetricFog(float4 fog, float4 volumetricFog) +{ + return float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a); +} + +#endif diff --git a/Source/Shaders/VolumetricFog.shader b/Source/Shaders/VolumetricFog.shader index 80a0cf5d7..ae117a7d0 100644 --- a/Source/Shaders/VolumetricFog.shader +++ b/Source/Shaders/VolumetricFog.shader @@ -17,9 +17,11 @@ #define DEBUG_VOXELS 0 #include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" #include "./Flax/LightingCommon.hlsl" #include "./Flax/ShadowsSampling.hlsl" #include "./Flax/GBuffer.hlsl" +#include "./Flax/VolumetricFog.hlsl" #include "./Flax/GI/DDGI.hlsl" struct SkyLightData @@ -50,6 +52,8 @@ float VolumetricFogMaxDistance; float InverseSquaredLightDistanceBiasScale; float4 FogParameters; +float3 GridSliceParameters; +float Dummy1; float4x4 PrevWorldToClip; @@ -83,23 +87,18 @@ float GetPhase(float g, float cosTheta) return HenyeyGreensteinPhase(g, cosTheta); } -float GetSliceDepth(float zSlice) -{ - return (zSlice / GridSize.z) * VolumetricFogMaxDistance; -} - float3 GetCellPositionWS(uint3 gridCoordinate, float3 cellOffset, out float sceneDepth) { float2 volumeUV = (gridCoordinate.xy + cellOffset.xy) / GridSize.xy; - sceneDepth = GetSliceDepth(gridCoordinate.z + cellOffset.z) / GBuffer.ViewFar; + sceneDepth = GetDepthFromSlice(GridSliceParameters, gridCoordinate.z + cellOffset.z) / GBuffer.ViewFar; float deviceDepth = LinearZ2DeviceDepth(GBuffer, sceneDepth); return GetWorldPos(GBuffer, volumeUV, deviceDepth); } float3 GetCellPositionWS(uint3 gridCoordinate, float3 cellOffset) { - float temp; - return GetCellPositionWS(gridCoordinate, cellOffset, temp); + float sceneDepth; + return GetCellPositionWS(gridCoordinate, cellOffset, sceneDepth); } float3 GetVolumeUV(float3 worldPosition, float4x4 worldToClip) @@ -164,17 +163,16 @@ META_PERMUTATION_1(USE_SHADOW=1) float4 PS_InjectLight(Quad_GS2PS input) : SV_Target0 { uint3 gridCoordinate = uint3(input.Vertex.Position.xy, input.LayerIndex); - - // Prevent from shading locations outside the volume - if (!all(gridCoordinate < GridSizeInt)) + if (any(gridCoordinate >= GridSizeInt)) return 0; - + + // Supersample if history buffer is outside the view float3 historyUV = GetVolumeUV(GetCellPositionWS(gridCoordinate, 0.5f), PrevWorldToClip); float historyAlpha = HistoryWeight; FLATTEN if (any(historyUV < 0) || any(historyUV > 1)) historyAlpha = 0; - uint samplesCount = historyAlpha < 0.001f ? MissedHistorySamplesCount : 1; + uint samplesCount = historyAlpha < 0.01f ? MissedHistorySamplesCount : 1; float NoL = 0; bool isSpotLight = LocalLight.SpotAngles.x > -2.0f; @@ -182,8 +180,6 @@ float4 PS_InjectLight(Quad_GS2PS input) : SV_Target0 for (uint sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) { float3 cellOffset = FrameJitterOffsets[sampleIndex].xyz; - //float cellOffset = 0.5f; - float3 positionWS = GetCellPositionWS(gridCoordinate, cellOffset); float3 cameraVector = normalize(positionWS - GBuffer.ViewPos); float cellRadius = length(positionWS - GetCellPositionWS(gridCoordinate + uint3(1, 1, 1), cellOffset)); @@ -202,13 +198,9 @@ float4 PS_InjectLight(Quad_GS2PS input) : SV_Target0 if (attenuation > 0) { if (isSpotLight) - { shadow = SampleSpotLightShadow(LocalLight, ShadowsBuffer, ShadowMap, positionWS).SurfaceShadow; - } else - { shadow = SamplePointLightShadow(LocalLight, ShadowsBuffer, ShadowMap, positionWS).SurfaceShadow; - } } #endif @@ -226,12 +218,10 @@ RWTexture3D RWVBufferB : register(u1); META_CS(true, FEATURE_LEVEL_SM5) [numthreads(4, 4, 4)] -void CS_Initialize(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +void CS_Initialize(uint3 DispatchThreadId : SV_DispatchThreadID) { uint3 gridCoordinate = DispatchThreadId; - - float voxelOffset = 0.5f; - float3 positionWS = GetCellPositionWS(gridCoordinate, voxelOffset); + float3 positionWS = GetCellPositionWS(gridCoordinate, 0.5f); // Unpack the fog parameters (packing done in C++ ExponentialHeightFog::GetVolumetricFogOptions) float fogDensity = FogParameters.x; @@ -274,26 +264,25 @@ META_CS(true, FEATURE_LEVEL_SM5) META_PERMUTATION_1(USE_DDGI=0) META_PERMUTATION_1(USE_DDGI=1) [numthreads(4, 4, 4)] -void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +void CS_LightScattering(uint3 DispatchThreadId : SV_DispatchThreadID) { uint3 gridCoordinate = DispatchThreadId; - float3 lightScattering = 0; - uint samplesCount = 1; - + if (any(gridCoordinate >= GridSizeInt)) + return; + + // Supersample if history buffer is outside the view float3 historyUV = GetVolumeUV(GetCellPositionWS(gridCoordinate, 0.5f), PrevWorldToClip); float historyAlpha = HistoryWeight; FLATTEN if (any(historyUV < 0) || any(historyUV > 1)) historyAlpha = 0; - samplesCount = historyAlpha < 0.001f && all(gridCoordinate < GridSizeInt) ? MissedHistorySamplesCount : 1; - + uint samplesCount = historyAlpha < 0.01f ? MissedHistorySamplesCount : 1; + + float3 lightScattering = 0; for (uint sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) { float3 cellOffset = FrameJitterOffsets[sampleIndex].xyz; - //float3 cellOffset = 0.5f; - - float sceneDepth; - float3 positionWS = GetCellPositionWS(gridCoordinate, cellOffset, sceneDepth); + float3 positionWS = GetCellPositionWS(gridCoordinate, cellOffset); float3 cameraVector = positionWS - GBuffer.ViewPos; float cameraVectorLength = length(cameraVector); float3 cameraVectorNormalized = cameraVector / cameraVectorLength; @@ -306,8 +295,7 @@ void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_ #if USE_DDGI // Dynamic Diffuse Global Illumination - float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesData, ProbesDistance, ProbesIrradiance, positionWS, cameraVectorNormalized, 0.0f, cellOffset.x); - lightScattering += float4(irradiance, 1); + lightScattering += SampleDDGIIrradiance(DDGI, ProbesData, ProbesDistance, ProbesIrradiance, positionWS, cameraVectorNormalized, 0.0f, cellOffset.x); #else // Sky light if (SkyLight.VolumetricScatteringIntensity > 0) @@ -334,12 +322,9 @@ void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_ float4 historyScatteringAndExtinction = LightScatteringHistory.SampleLevel(SamplerLinearClamp, historyUV, 0); scatteringAndExtinction = lerp(scatteringAndExtinction, historyScatteringAndExtinction, historyAlpha); } - - if (all(gridCoordinate < GridSizeInt)) - { - scatteringAndExtinction = select(or(isnan(scatteringAndExtinction), isinf(scatteringAndExtinction)), 0, scatteringAndExtinction); - RWLightScattering[gridCoordinate] = max(scatteringAndExtinction, 0); - } + + scatteringAndExtinction = select(or(isnan(scatteringAndExtinction), isinf(scatteringAndExtinction)), 0, scatteringAndExtinction); + RWLightScattering[gridCoordinate] = max(scatteringAndExtinction, 0); } #elif defined(_CS_FinalIntegration) @@ -350,9 +335,11 @@ Texture3D LightScattering : register(t0); META_CS(true, FEATURE_LEVEL_SM5) [numthreads(8, 8, 1)] -void CS_FinalIntegration(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +void CS_FinalIntegration(uint3 DispatchThreadId : SV_DispatchThreadID) { uint3 gridCoordinate = DispatchThreadId; + if (any(gridCoordinate.xy >= GridSizeInt.xy)) + return; float4 acc = float4(0, 0, 0, 1); float3 prevPositionWS = GBuffer.ViewPos; @@ -363,11 +350,17 @@ void CS_FinalIntegration(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV float3 positionWS = GetCellPositionWS(coords, 0.5f); // Ref: "Physically Based and Unified Volumetric Rendering in Frostbite" - float transmittance = exp(-scatteringExtinction.w * length(positionWS - prevPositionWS)); + float stepDistance = length(positionWS - prevPositionWS); + float transmittance = exp(-scatteringExtinction.w * stepDistance); float3 scatteringIntegratedOverSlice = (scatteringExtinction.rgb - scatteringExtinction.rgb * transmittance) / max(scatteringExtinction.w, 0.00001f); + + // Apply distance fade + float distanceFade = Remap(layerIndex, GridSizeInt.z * 0.8f, GridSizeInt.z - 1, 1, 0); + scatteringIntegratedOverSlice *= distanceFade; + + // Accumulate acc.rgb += scatteringIntegratedOverSlice * acc.a; acc.a *= transmittance; - #if DEBUG_VOXELS RWIntegratedLightScattering[coords] = float4(scatteringExtinction.rgb, 1.0f); #elif DEBUG_VOXEL_WS_POS