Add Global SDF quality setting and support for variable cascades count and resolution

This commit is contained in:
Wojciech Figat
2022-06-10 14:30:40 +02:00
parent 3b27ae5fa9
commit f685c67275
13 changed files with 128 additions and 77 deletions

View File

@@ -64,6 +64,14 @@ namespace FlaxEditor.Windows
set => Graphics.VolumetricFogQuality = value;
}
[DefaultValue(Quality.High)]
[EditorOrder(1280), EditorDisplay("Quality"), Tooltip("The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.")]
public Quality GlobalSDFQuality
{
get => Graphics.GlobalSDFQuality;
set => Graphics.GlobalSDFQuality = value;
}
[DefaultValue(Quality.Medium)]
[EditorOrder(1300), EditorDisplay("Quality", "Shadows Quality"), Tooltip("The shadows quality.")]
public Quality ShadowsQuality

View File

@@ -13,4 +13,5 @@ void GraphicsSettings::Apply()
Graphics::ShadowsQuality = ShadowsQuality;
Graphics::ShadowMapsQuality = ShadowMapsQuality;
Graphics::AllowCSMBlending = AllowCSMBlending;
Graphics::GlobalSDFQuality = GlobalSDFQuality;
}

View File

@@ -68,6 +68,12 @@ public:
API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")")
bool EnableGlobalSDF = false;
/// <summary>
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.
/// </summary>
API_FIELD(Attributes="EditorOrder(2005), DefaultValue(Quality.High), EditorDisplay(\"Quality\")")
Quality GlobalSDFQuality = Quality::High;
#if USE_EDITOR
/// <summary>
/// If checked, the 'Generate SDF' option will be checked on model import options by default. Use it if your project uses Global SDF (eg. for Global Illumination or particles).

View File

@@ -14,6 +14,7 @@ Quality Graphics::VolumetricFogQuality = Quality::High;
Quality Graphics::ShadowsQuality = Quality::Medium;
Quality Graphics::ShadowMapsQuality = Quality::Medium;
bool Graphics::AllowCSMBlending = false;
Quality Graphics::GlobalSDFQuality = Quality::High;
#if GRAPHICS_API_NULL
extern GPUDevice* CreateGPUDeviceNull();

View File

@@ -53,6 +53,11 @@ public:
/// </summary>
API_FIELD() static bool AllowCSMBlending;
/// <summary>
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.
/// </summary>
API_FIELD() static Quality GlobalSDFQuality;
public:
/// <summary>

View File

@@ -488,8 +488,7 @@ void MaterialParameter::Bind(BindMeta& meta) const
GlobalSignDistanceFieldPass::BindingData bindingData;
if (GlobalSignDistanceFieldPass::Instance()->Get(meta.Buffers, bindingData))
Platform::MemoryClear(&bindingData, sizeof(bindingData));
for (int32 i = 0; i < 4; i++)
meta.Context->BindSR(_registerIndex + i, bindingData.Cascades[i] ? bindingData.Cascades[i]->ViewVolume() : nullptr);
bindingData.BindCascades(meta.Context, _registerIndex);
*((GlobalSignDistanceFieldPass::ConstantsData*)(meta.Constants.Get() + _offset)) = bindingData.Constants;
break;
}

View File

@@ -456,10 +456,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
{
PROFILE_GPU_CPU("Probes Classification");
uint32 threadGroups = Math::DivideAndRoundUp(probesCountCascade, DDGI_PROBE_CLASSIFY_GROUP_SIZE);
for (int32 i = 0; i < 4; i++)
{
context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume());
}
bindingDataSDF.BindCascades(context, 0);
context->BindUA(0, ddgiData.Result.ProbesState);
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
{
@@ -501,11 +498,8 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
// Global SDF with Global Surface Atlas software raytracing (thread X - per probe ray, thread Y - per probe)
ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0);
for (int32 i = 0; i < 4; i++)
{
context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume());
context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume());
}
bindingDataSDF.BindCascades(context, 0);
bindingDataSDF.BindCascadeMips(context, 4);
context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr);
context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr);
context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View());

View File

@@ -753,11 +753,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View());
context->BindSR(3, surfaceAtlasData.AtlasDepth->View());
context->BindSR(4, _objectsBuffer->GetBuffer()->View());
for (int32 i = 0; i < 4; i++)
{
context->BindSR(i + 5, bindingDataSDF.Cascades[i]->ViewVolume());
context->BindSR(i + 9, bindingDataSDF.CascadeMips[i]->ViewVolume());
}
bindingDataSDF.BindCascades(context, 5);
bindingDataSDF.BindCascadeMips(context, 9);
context->BindCB(0, _cb0);
Data0 data;
data.ViewWorldPos = renderContext.View.Position;
@@ -921,11 +918,8 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
context->UpdateCB(_cb0, &data);
context->BindCB(0, _cb0);
}
for (int32 i = 0; i < 4; i++)
{
context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume());
context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume());
}
bindingDataSDF.BindCascades(context, 0);
bindingDataSDF.BindCascadeMips(context, 4);
context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr);
context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr);
context->BindSR(10, bindingData.AtlasDepth->View());
@@ -954,11 +948,8 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
context->ResetRenderTarget();
// Rebind resources
for (int32 i = 0; i < 4; i++)
{
context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume());
context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume());
}
bindingDataSDF.BindCascades(context, 0);
bindingDataSDF.BindCascadeMips(context, 4);
context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr);
context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr);
context->BindSR(10, bindingData.AtlasDepth->View());

View File

@@ -7,6 +7,7 @@
#include "Engine/Engine/Engine.h"
#include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Graphics.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/RenderTargetPool.h"
@@ -173,7 +174,8 @@ struct CascadeData
class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener
{
public:
CascadeData Cascades[4];
int32 Resolution = 0;
Array<CascadeData, FixedAllocation<4>> Cascades;
HashSet<ScriptingTypeHandle> ObjectTypes;
HashSet<GPUTexture*> SDFTextures;
GlobalSignDistanceFieldPass::BindingData Result;
@@ -349,6 +351,18 @@ void GlobalSignDistanceFieldPass::Dispose()
ChunksCache.SetCapacity(0);
}
void GlobalSignDistanceFieldPass::BindingData::BindCascades(GPUContext* context, int32 srvSlot)
{
for (int32 i = 0; i < 4; i++)
context->BindSR(srvSlot + i, Cascades[i] ? Cascades[i]->ViewVolume() : nullptr);
}
void GlobalSignDistanceFieldPass::BindingData::BindCascadeMips(GPUContext* context, int32 srvSlot)
{
for (int32 i = 0; i < 4; i++)
context->BindSR(srvSlot + i, CascadeMips[i] ? CascadeMips[i]->ViewVolume() : nullptr);
}
bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result)
{
auto* sdfData = buffers ? buffers->FindCustomBuffer<GlobalSignDistanceFieldCustomBuffer>(TEXT("GlobalSignDistanceField")) : nullptr;
@@ -379,49 +393,71 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
sdfData.LastFrameUsed = currentFrame;
PROFILE_GPU_CPU("Global SDF");
// TODO: configurable via graphics settings
const int32 resolution = 256;
// Setup options
int32 resolution, cascadesCount;
switch (Graphics::GlobalSDFQuality)
{
case Quality::Low:
resolution = 128;
cascadesCount = 2;
break;
case Quality::Medium:
resolution = 128;
cascadesCount = 3;
break;
case Quality::High:
resolution = 192;
cascadesCount = 4;
break;
case Quality::Ultra:
default:
resolution = 256;
cascadesCount = 4;
break;
}
const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR);
// TODO: configurable via postFx settings
const int32 cascadesCount = 4; // in range 1-4
const float distance = true ? 20000.0f : 16000.0f; // TODO: switch based if using GI, then use GI range
const float cascadesDistanceScales[] = { 1.0f, 2.0f, 4.0f, 8.0f };
const float cascadesDistanceScales[] = { 1.0f, 2.5f, 5.0f, 10.0f };
const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1];
// Initialize buffers
auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
bool updated = false;
for (auto& cascade : sdfData.Cascades)
if (sdfData.Cascades.Count() != cascadesCount || sdfData.Resolution != resolution)
{
GPUTexture*& texture = cascade.Texture;
if (texture && texture->Width() != desc.Width)
sdfData.Cascades.Resize(cascadesCount);
sdfData.Resolution = resolution;
updated = true;
auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
for (auto& cascade : sdfData.Cascades)
{
RenderTargetPool::Release(texture);
texture = nullptr;
}
if (!texture)
{
texture = RenderTargetPool::Get(desc);
GPUTexture*& texture = cascade.Texture;
if (texture && texture->Width() != desc.Width)
{
RenderTargetPool::Release(texture);
texture = nullptr;
}
if (!texture)
return true;
updated = true;
{
texture = RenderTargetPool::Get(desc);
if (!texture)
return true;
}
}
}
desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
for (auto& cascade : sdfData.Cascades)
{
GPUTexture*& texture = cascade.Mip;
if (texture && texture->Width() != desc.Width)
desc.Width = desc.Height = desc.Depth = resolutionMip;
for (auto& cascade : sdfData.Cascades)
{
RenderTargetPool::Release(texture);
texture = nullptr;
}
if (!texture)
{
texture = RenderTargetPool::Get(desc);
GPUTexture*& texture = cascade.Mip;
if (texture && texture->Width() != desc.Width)
{
RenderTargetPool::Release(texture);
texture = nullptr;
}
if (!texture)
return true;
updated = true;
{
texture = RenderTargetPool::Get(desc);
if (!texture)
return true;
}
}
}
GPUTexture* tmpMip = nullptr;
@@ -451,7 +487,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
bool anyDraw = false;
const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 };
//const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 };
for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++)
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
{
// Reduce frequency of the updates
if (useCache && (Engine::FrameCount % cascadeFrequencies[cascadeIndex]) != 0)
@@ -513,12 +549,13 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
{
anyDraw = true;
context->ResetSR();
auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
tmpMip = RenderTargetPool::Get(desc);
if (!tmpMip)
return true;
}
ModelsRasterizeData data;
data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution;
data.CascadeCoordToPosMul = cascadeBounds.GetSize() / (float)resolution;
data.CascadeCoordToPosAdd = cascadeBounds.Minimum + cascadeVoxelSize * 0.5f;
data.MaxDistance = cascadeMaxDistance;
data.CascadeResolution = resolution;
@@ -725,8 +762,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
// Copy results
static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count.");
static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count.");
static_assert(ARRAY_COUNT(sdfData.Cascades) == 4, "Invalid cascades count.");
for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++)
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
{
auto& cascade = sdfData.Cascades[cascadeIndex];
const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex];
@@ -738,7 +774,15 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
result.Cascades[cascadeIndex] = cascade.Texture;
result.CascadeMips[cascadeIndex] = cascade.Mip;
}
for (int32 cascadeIndex = cascadesCount; cascadeIndex < 4; cascadeIndex++)
{
result.Constants.CascadePosDistance[cascadeIndex] = result.Constants.CascadePosDistance[cascadesCount - 1];
result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = result.Constants.CascadeVoxelSize.Raw[cascadesCount - 1];
result.Cascades[cascadeIndex] = nullptr;
result.CascadeMips[cascadeIndex] = nullptr;
}
result.Constants.Resolution = (float)resolution;
result.Constants.CascadesCount = cascadesCount;
sdfData.Result = result;
return false;
}
@@ -765,11 +809,8 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC
context->UpdateCB(_cb0, &data);
context->BindCB(0, _cb0);
}
for (int32 i = 0; i < 4; i++)
{
context->BindSR(i, bindingData.Cascades[i]->ViewVolume());
context->BindSR(i + 4, bindingData.CascadeMips[i]->ViewVolume());
}
bindingData.BindCascades(context, 0);
bindingData.BindCascadeMips(context, 4);
context->SetState(_psDebug);
context->SetRenderTarget(output->View());
context->SetViewportAndScissors(outputSize.X, outputSize.Y);

View File

@@ -15,7 +15,8 @@ public:
{
Vector4 CascadePosDistance[4];
Vector4 CascadeVoxelSize;
Vector3 Padding;
Vector2 Padding;
uint32 CascadesCount;
float Resolution;
});
@@ -25,6 +26,9 @@ public:
GPUTexture* Cascades[4];
GPUTexture* CascadeMips[4];
ConstantsData Constants;
void BindCascades(GPUContext* context, int32 srvSlot);
void BindCascadeMips(GPUContext* context, int32 srvSlot);
};
private:

View File

@@ -181,7 +181,7 @@ void CS_TraceRays(uint3 DispatchThreadId : SV_DispatchThreadID)
{
// Sample Global Surface Atlas to get the lighting at the hit location
float3 hitPosition = hit.GetHitPosition(trace);
float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit);
float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(GlobalSDF, hit);
float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hitPosition, -probeRayDirection, surfaceThreshold);
radiance = float4(surfaceColor.rgb, hit.HitTime);

View File

@@ -293,7 +293,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target
if (hit.IsHit())
{
// Sample Global Surface Atlas at the hit location
float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit);
float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(GlobalSDF, hit);
color = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold).rgb;
//color = hit.HitNormal * 0.5f + 0.5f;
}

View File

@@ -13,7 +13,8 @@ struct GlobalSDFData
{
float4 CascadePosDistance[4];
float4 CascadeVoxelSize;
float3 Padding;
float2 Padding;
uint CascadesCount;
float Resolution;
};
@@ -65,7 +66,7 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D<float> tex[4], float3
if (distance <= 0.0f)
return GLOBAL_SDF_WORLD_SIZE;
UNROLL
for (uint cascade = 0; cascade < 4; cascade++)
for (uint cascade = 0; cascade < data.CascadesCount; cascade++)
{
float4 cascadePosDistance = data.CascadePosDistance[cascade];
float cascadeMaxDistance = cascadePosDistance.w * 2;
@@ -89,7 +90,7 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D<float> tex[4]
if (data.CascadePosDistance[3].w <= 0.0f)
return gradient;
UNROLL
for (uint cascade = 0; cascade < 4; cascade++)
for (uint cascade = 0; cascade < data.CascadesCount; cascade++)
{
float4 cascadePosDistance = data.CascadePosDistance[cascade];
float cascadeMaxDistance = cascadePosDistance.w * 2;
@@ -124,7 +125,7 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D<float> tex[4]
float traceMaxDistance = min(trace.MaxDistance, data.CascadePosDistance[3].w * 2);
float3 traceEndPosition = trace.WorldPosition + trace.WorldDirection * traceMaxDistance;
UNROLL
for (uint cascade = 0; cascade < 4 && hit.HitTime < 0.0f; cascade++)
for (uint cascade = 0; cascade < data.CascadesCount && hit.HitTime < 0.0f; cascade++)
{
float4 cascadePosDistance = data.CascadePosDistance[cascade];
float cascadeMaxDistance = cascadePosDistance.w * 2;
@@ -206,8 +207,8 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D<float> tex[4]
}
// Calculates the surface threshold for Global Surface Atlas sampling which matches the Global SDF trace to reduce artifacts
float GetGlobalSurfaceAtlasThreshold(GlobalSDFHit hit)
float GetGlobalSurfaceAtlasThreshold(const GlobalSDFData data, const GlobalSDFHit hit)
{
// Scale the threshold based on the hit cascade (less precision)
return hit.HitCascade * 20.0f + 25.0f;
// Scale the threshold based on the hit cascade (less precision)
return data.CascadeVoxelSize[hit.HitCascade] * 1.1f;
}