Refactor shadows rendering to use Shadow Map Atlas

This commit is contained in:
Wojtek Figat
2024-04-04 12:54:07 +02:00
parent 017def29d4
commit 61323f8526
31 changed files with 1115 additions and 1826 deletions

View File

@@ -6,12 +6,12 @@
#include "Engine/Graphics/Graphics.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Content/Assets/CubeTexture.h"
#include "Engine/Content/Content.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Graphics/GPUContext.h"
// Must match shader source
int32 VolumetricFogGridInjectionGroupSize = 4;
@@ -143,38 +143,30 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context,
switch (quality)
{
case Quality::Low:
{
_cache.GridPixelSize = 16;
_cache.GridSizeZ = 64;
_cache.FogJitter = false;
_cache.MissedHistorySamplesCount = 1;
break;
}
case Quality::Medium:
{
_cache.GridPixelSize = 16;
_cache.GridSizeZ = 64;
_cache.FogJitter = true;
_cache.MissedHistorySamplesCount = 4;
break;
}
case Quality::High:
{
_cache.GridPixelSize = 16;
_cache.GridSizeZ = 128;
_cache.FogJitter = true;
_cache.MissedHistorySamplesCount = 4;
break;
}
case Quality::Ultra:
{
_cache.GridPixelSize = 8;
_cache.GridSizeZ = 256;
_cache.FogJitter = true;
_cache.MissedHistorySamplesCount = 8;
break;
}
}
// Prepare
const int32 width = renderContext.Buffers->GetWidth();
@@ -202,7 +194,6 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context,
_cache.Data.VolumetricFogMaxDistance = options.Distance;
_cache.Data.MissedHistorySamplesCount = Math::Clamp(_cache.MissedHistorySamplesCount, 1, (int32)ARRAY_COUNT(_cache.Data.FrameJitterOffsets));
Matrix::Transpose(view.PrevViewProjection, _cache.Data.PrevWorldToClip);
_cache.Data.DirectionalLightShadow.NumCascades = 0;
_cache.Data.SkyLight.VolumetricScatteringIntensity = 0;
// Fill frame jitter history
@@ -262,83 +253,6 @@ GPUTextureView* VolumetricFogPass::GetLocalShadowedLightScattering(RenderContext
return renderContext.Buffers->LocalShadowedLightScattering->ViewVolume();
}
template<typename T>
void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, T& light, ShaderLightShadowData& shadow)
{
// Prepare
VolumetricFogOptions options;
if (Init(renderContext, context, options))
return;
auto& view = renderContext.View;
// Calculate light volume bounds in camera frustum depth range (min and max)
const Float3 center = light.Position;
const float radius = light.Radius;
Float3 viewSpaceLightBoundsOrigin = Float3::Transform(center, view.View);
float furthestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z + radius, options, _cache.GridSizeZ);
float closestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z - radius, options, _cache.GridSizeZ);
int32 volumeZBoundsMin = (int32)Math::Clamp(closestSliceIndexUnclamped, 0.0f, _cache.GridSize.Z - 1.0f);
int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, _cache.GridSize.Z - 1.0f);
// Cull light
if ((view.Position - center).LengthSquared() >= (options.Distance + radius) * (options.Distance + radius) || volumeZBoundsMin >= volumeZBoundsMax)
return;
PROFILE_GPU_CPU("Volumetric Fog Light");
// Allocate temporary buffer for light scattering injection
auto localShadowedLightScattering = GetLocalShadowedLightScattering(renderContext, context, options);
// Prepare
PerLight perLight;
auto cb0 = _shader->GetShader()->GetCB(0);
auto cb1 = _shader->GetShader()->GetCB(1);
// Bind the output
context->SetRenderTarget(localShadowedLightScattering);
context->SetViewportAndScissors(_cache.Data.GridSize.X, _cache.Data.GridSize.Y);
// Setup data
perLight.SliceToDepth.X = _cache.Data.GridSize.Z;
perLight.SliceToDepth.Y = _cache.Data.VolumetricFogMaxDistance;
perLight.MinZ = volumeZBoundsMin;
perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity;
perLight.ViewSpaceBoundingSphere = Float4(viewSpaceLightBoundsOrigin, radius);
Matrix::Transpose(view.Projection, perLight.ViewToVolumeClip);
light.SetShaderData(perLight.LocalLight, true);
perLight.LocalLightShadow = shadow;
// Upload data
context->UpdateCB(cb1, &perLight);
context->BindCB(0, cb0);
context->BindCB(1, cb1);
// Ensure to have valid buffers created
if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr)
InitCircleBuffer();
// Call rendering to the volume
const int32 psIndex = 1;
context->SetState(_psInjectLight.Get(psIndex));
const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin;
const int32 indexCount = _ibCircleRasterize->GetElementsCount();
ASSERT(instanceCount > 0);
context->BindVB(ToSpan(&_vbCircleRasterize, 1));
context->BindIB(_ibCircleRasterize);
context->DrawIndexedInstanced(indexCount, instanceCount, 0);
// Cleanup
context->UnBindCB(0);
context->UnBindCB(1);
auto viewport = renderContext.Task->GetViewport();
context->SetViewportAndScissors(viewport);
context->ResetRenderTarget();
context->FlushState();
// Mark as rendered
light.RenderedVolumetricFog = 1;
}
template<typename T>
void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb1)
{
@@ -353,106 +267,67 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte
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)
{
// TODO: use full scene shadows atlas and render point/spot lights with shadow into a fog volume
bool withShadow = false;
// Setup data
perLight.SliceToDepth.X = cache.Data.GridSize.Z;
perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance;
perLight.MinZ = volumeZBoundsMin;
perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity;
perLight.ViewSpaceBoundingSphere = Float4(viewSpaceLightBoundsOrigin, radius);
Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip);
light.SetShaderData(perLight.LocalLight, withShadow);
// Upload data
context->UpdateCB(cb1, &perLight);
context->BindCB(1, cb1);
// Ensure to have valid buffers created
if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr)
InitCircleBuffer();
// Call rendering to the volume
const int32 psIndex = withShadow ? 1 : 0;
context->SetState(_psInjectLight.Get(psIndex));
const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin;
const int32 indexCount = _ibCircleRasterize->GetElementsCount();
context->BindVB(ToSpan(&_vbCircleRasterize, 1));
context->BindIB(_ibCircleRasterize);
context->DrawIndexedInstanced(indexCount, instanceCount, 0);
}
}
void VolumetricFogPass::RenderLight(RenderContext& renderContext, GPUContext* context, RenderPointLightData& light, GPUTextureView* shadowMap, ShaderLightShadowData& shadow)
{
// Skip lights with no volumetric light influence or not casting volumetric shadow
if (light.VolumetricScatteringIntensity <= ZeroTolerance || !light.CastVolumetricShadow)
if (volumeZBoundsMin >= volumeZBoundsMax)
return;
ASSERT(shadowMap);
context->BindSR(5, shadowMap);
// Setup data
perLight.SliceToDepth.X = cache.Data.GridSize.Z;
perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance;
perLight.MinZ = volumeZBoundsMin;
perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity;
perLight.ViewSpaceBoundingSphere = Float4(viewSpaceLightBoundsOrigin, radius);
Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip);
const bool withShadow = light.CastVolumetricShadow && light.HasShadow;
light.SetShaderData(perLight.LocalLight, withShadow);
RenderRadialLight(renderContext, context, light, shadow);
// Upload data
context->UpdateCB(cb1, &perLight);
context->BindCB(1, cb1);
context->UnBindSR(5);
}
// Ensure to have valid buffers created
if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr)
InitCircleBuffer();
void VolumetricFogPass::RenderLight(RenderContext& renderContext, GPUContext* context, RenderSpotLightData& light, GPUTextureView* shadowMap, ShaderLightShadowData& shadow)
{
// Skip lights with no volumetric light influence or not casting volumetric shadow
if (light.VolumetricScatteringIntensity <= ZeroTolerance || !light.CastVolumetricShadow)
return;
ASSERT(shadowMap);
context->BindSR(6, shadowMap);
RenderRadialLight(renderContext, context, light, shadow);
context->UnBindSR(6);
// Call rendering to the volume
const int32 psIndex = withShadow ? 1 : 0;
context->SetState(_psInjectLight.Get(psIndex));
const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin;
const int32 indexCount = _ibCircleRasterize->GetElementsCount();
context->BindVB(ToSpan(&_vbCircleRasterize, 1));
context->BindIB(_ibCircleRasterize);
context->DrawIndexedInstanced(indexCount, instanceCount, 0);
}
void VolumetricFogPass::Render(RenderContext& renderContext)
{
// Prepare
VolumetricFogOptions options;
auto context = GPUDevice::Instance->GetMainContext();
if (Init(renderContext, context, options))
return;
auto& view = renderContext.View;
auto& cache = _cache;
PROFILE_GPU_CPU("Volumetric Fog");
// TODO: test exponential depth distribution (should give better quality near the camera)
// TODO: use tiled light culling and render unshadowed lights in single pass
// Try to get shadows atlas
GPUTexture* shadowMap;
GPUBufferView* shadowsBuffer;
ShadowsPass::GetShadowAtlas(renderContext.Buffers, shadowMap, shadowsBuffer);
// Init directional light data
GPUTextureView* dirLightShadowMap = nullptr;
Platform::MemoryClear(&_cache.Data.DirectionalLight, sizeof(_cache.Data.DirectionalLight));
if (renderContext.List->DirectionalLights.HasItems())
{
const int32 dirLightIndex = (int32)renderContext.List->DirectionalLights.Count() - 1;
const auto& dirLight = renderContext.List->DirectionalLights[dirLightIndex];
const float brightness = dirLight.VolumetricScatteringIntensity;
if (brightness > ZeroTolerance)
{
const auto shadowPass = ShadowsPass::Instance();
const bool useShadow = dirLight.CastVolumetricShadow && shadowPass->LastDirLightIndex == dirLightIndex;
const bool useShadow = shadowMap && dirLight.CastVolumetricShadow && dirLight.HasShadow;
dirLight.SetShaderData(_cache.Data.DirectionalLight, useShadow);
_cache.Data.DirectionalLight.Color *= brightness;
if (useShadow)
{
_cache.Data.DirectionalLightShadow = shadowPass->LastDirLight;
dirLightShadowMap = shadowPass->LastDirLightShadowMap;
}
else
{
_cache.Data.DirectionalLightShadow.NumCascades = 0;
}
}
}
@@ -475,6 +350,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
// Init sky light data
GPUTexture* skyLightImage = nullptr;
Platform::MemoryClear(&_cache.Data.SkyLight, sizeof(_cache.Data.SkyLight));
if (renderContext.List->SkyLights.HasItems() && !useDDGI)
{
const auto& skyLight = renderContext.List->SkyLights.Last();
@@ -510,13 +386,10 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
// Initialize fog volume properties
{
PROFILE_GPU("Initialize");
context->ResetRenderTarget();
context->BindUA(0, vBufferA->ViewVolume());
context->BindUA(1, vBufferB->ViewVolume());
context->Dispatch(_csInitialize, groupCountX, groupCountY, groupCountZ);
context->ResetUA();
}
@@ -557,7 +430,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f);
// Culling
if ((view.Position - center).LengthSquared() >= (options.Distance + radius) * (options.Distance + radius) || volumeZBoundsMin >= volumeZBoundsMax)
if ((view.Position - center).LengthSquared() >= Math::Square(options.Distance + radius) || volumeZBoundsMin >= volumeZBoundsMax)
continue;
// Setup material shader data
@@ -598,25 +471,17 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
Array<const RenderSpotLightData*, InlinedAllocation<64, RendererAllocation>> spotLights;
for (int32 i = 0; i < renderContext.List->PointLights.Count(); i++)
{
const auto& light = renderContext.List->PointLights[i];
if (light.VolumetricScatteringIntensity > ZeroTolerance && !light.RenderedVolumetricFog)
{
if ((view.Position - light.Position).LengthSquared() < (options.Distance + light.Radius) * (options.Distance + light.Radius))
{
pointLights.Add(&light);
}
}
const auto& light = renderContext.List->PointLights.Get()[i];
if (light.VolumetricScatteringIntensity > ZeroTolerance &&
(view.Position - light.Position).LengthSquared() < Math::Square(options.Distance + light.Radius))
pointLights.Add(&light);
}
for (int32 i = 0; i < renderContext.List->SpotLights.Count(); i++)
{
const auto& light = renderContext.List->SpotLights[i];
if (light.VolumetricScatteringIntensity > ZeroTolerance && !light.RenderedVolumetricFog)
{
if ((view.Position - light.Position).LengthSquared() < (options.Distance + light.Radius) * (options.Distance + light.Radius))
{
spotLights.Add(&light);
}
}
const auto& light = renderContext.List->SpotLights.Get()[i];
if (light.VolumetricScatteringIntensity > ZeroTolerance &&
(view.Position - light.Position).LengthSquared() < Math::Square(options.Distance + light.Radius))
spotLights.Add(&light);
}
// Skip if no lights to render
@@ -638,6 +503,8 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
context->SetViewportAndScissors((float)volumeDesc.Width, (float)volumeDesc.Height);
// Render them to the volume
context->BindSR(0, shadowMap);
context->BindSR(1, shadowsBuffer);
for (int32 i = 0; i < pointLights.Count(); i++)
RenderRadialLight(renderContext, context, view, options, *pointLights[i], perLight, cb1);
for (int32 i = 0; i < spotLights.Count(); i++)
@@ -666,19 +533,19 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
context->BindSR(1, vBufferB->ViewVolume());
context->BindSR(2, lightScatteringHistory ? lightScatteringHistory->ViewVolume() : nullptr);
context->BindSR(3, localShadowedLightScattering);
context->BindSR(4, dirLightShadowMap);
context->BindSR(4, shadowMap);
context->BindSR(5, shadowsBuffer);
int32 csIndex;
if (useDDGI)
{
context->BindSR(5, bindingDataDDGI.ProbesData);
context->BindSR(6, bindingDataDDGI.ProbesDistance);
context->BindSR(7, bindingDataDDGI.ProbesIrradiance);
context->BindSR(6, bindingDataDDGI.ProbesData);
context->BindSR(7, bindingDataDDGI.ProbesDistance);
context->BindSR(8, bindingDataDDGI.ProbesIrradiance);
csIndex = 1;
}
else
{
context->BindSR(5, skyLightImage);
context->BindSR(6, skyLightImage);
csIndex = 0;
}
context->Dispatch(_csLightScattering.Get(csIndex), groupCountX, groupCountY, groupCountZ);