Add **Global Sign Distance Field** rendering (work in progress)

This commit is contained in:
Wojciech Figat
2022-03-18 16:18:00 +01:00
parent 10d09711d9
commit 8cca7f884b
18 changed files with 1109 additions and 5 deletions

BIN
Content/Shaders/GlobalSignDistanceField.flax (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -68,6 +68,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(TEXT("Shaders/MotionBlur"));
data.AddRootEngineAsset(TEXT("Shaders/BitonicSort"));
data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting"));
data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField"));
data.AddRootEngineAsset(TEXT("Shaders/Quad"));
data.AddRootEngineAsset(TEXT("Shaders/Reflections"));
data.AddRootEngineAsset(TEXT("Shaders/Shadows"));

View File

@@ -1407,6 +1407,7 @@ namespace FlaxEditor.Viewport
new ViewModeOptions(ViewMode.LODPreview, "LOD Preview"),
new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity"),
new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw"),
new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF"),
};
private void WidgetCamSpeedShowHide(Control cm)

View File

@@ -845,6 +845,8 @@ bool Foliage::Intersects(const Ray& ray, float& distance, Vector3& normal, int32
void Foliage::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Foliage rendering to Global SDF
if (Instances.IsEmpty())
return;
auto& view = renderContext.View;

View File

@@ -208,4 +208,8 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE(PlacementRandomRollAngle);
DESERIALIZE_BIT(PlacementAlignToNormal);
DESERIALIZE_BIT(PlacementRandomYaw);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
}

View File

@@ -702,6 +702,11 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32
/// </summary>
MotionVectors = 1 << 4,
/// <summary>
/// The Global Sign Distance Field (SDF) rendering pass.
/// </summary>
GlobalSDF = 1 << 5,
/// <summary>
/// The debug quad overdraw rendering (editor-only).
/// </summary>
@@ -712,13 +717,13 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32
/// The default set of draw passes for the scene objects.
/// </summary>
API_ENUM(Attributes="HideInEditor")
Default = Depth | GBuffer | Forward | Distortion | MotionVectors,
Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF,
/// <summary>
/// The all draw passes combined into a single mask.
/// </summary>
API_ENUM(Attributes="HideInEditor")
All = Depth | GBuffer | Forward | Distortion | MotionVectors,
All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF,
};
DECLARE_ENUM_OPERATORS(DrawPass);
@@ -847,6 +852,11 @@ API_ENUM() enum class ViewMode
/// Draw geometry overdraw to visualize performance of pixels rendering.
/// </summary>
QuadOverdraw = 23,
/// <summary>
/// Draw global Sign Distant Field (SDF) preview.
/// </summary>
GlobalSDF = 24,
};
/// <summary>

View File

@@ -689,6 +689,8 @@ void AnimatedModel::Update()
void AnimatedModel::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Animated Model rendering to Global SDF
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world);
const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass & (int32)renderContext.View.GetShadowsDrawPassMask(ShadowsMode));
@@ -806,6 +808,10 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
DESERIALIZE(RootMotionTarget);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
}
bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal)

View File

@@ -350,6 +350,8 @@ void SplineModel::Draw(RenderContext& renderContext)
if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None)
return;
auto model = Model.Get();
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Spline Model rendering to Global SDF
if (!Entries.IsValidFor(model))
Entries.Setup(model);
@@ -469,6 +471,10 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE(DrawModes);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
}
void SplineModel::OnTransformChanged()

View File

@@ -209,6 +209,8 @@ bool StaticModel::HasContentLoaded() const
void StaticModel::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Static Model rendering to Global SDF
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world);
const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass);
@@ -409,6 +411,9 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DrawModes = DrawPass::Depth;
}
}
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
{
const auto member = stream.FindMember("RenderPasses");

View File

@@ -494,6 +494,8 @@ bool ParticleEffect::HasContentLoaded() const
void ParticleEffect::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return;
_lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(GetPosition(), renderContext.View.Position));
Particles::DrawParticles(renderContext, this);
}
@@ -677,6 +679,10 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier*
{
ApplyModifiedParameters();
}
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
}
void ParticleEffect::EndPlay()

View File

@@ -0,0 +1,595 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#include "GlobalSignDistanceFieldPass.h"
#include "GBufferPass.h"
#include "RenderList.h"
#include "Engine/Core/Math/Int3.h"
#include "Engine/Core/Collections/HashSet.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Level/Scene/SceneRendering.h"
#include "Engine/Level/Actors/StaticModel.h"
// Some of those constants must match in shader
// TODO: try using R8 format for Global SDF
#define GLOBAL_SDF_FORMAT PixelFormat::R16_Float
#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28
#define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8
#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32
#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4
#define GLOBAL_SDF_MIP_GROUP_SIZE 4
#define GLOBAL_SDF_MIP_FLOODS 5
#define GLOBAL_SDF_DEBUG_CHUNKS 0
static_assert(GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT % 4 == 0, "Must be multiple of 4 due to data packing for GPU constant buffer.");
#if GLOBAL_SDF_DEBUG_CHUNKS
#include "Engine/Debug/DebugDraw.h"
#endif
PACK_STRUCT(struct ModelRasterizeData
{
Matrix WorldToVolume; // TODO: use 3x4 matrix
Matrix VolumeToWorld; // TODO: use 3x4 matrix
Vector3 VolumeToUVWMul;
float MipOffset;
Vector3 VolumeToUVWAdd;
float MaxDistance;
Vector3 VolumeLocalBoundsExtent;
float Padding0;
});
PACK_STRUCT(struct Data
{
Vector3 ViewWorldPos;
float ViewNearPlane;
Vector3 Padding00;
float ViewFarPlane;
Vector4 ViewFrustumWorldRays[4];
GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF;
});
PACK_STRUCT(struct ModelsRasterizeData
{
Int3 ChunkCoord;
float MaxDistance;
Vector3 CascadeCoordToPosMul;
int ModelsCount;
Vector3 CascadeCoordToPosAdd;
int32 CascadeResolution;
Vector2 Padding0;
int32 CascadeMipResolution;
int32 CascadeMipFactor;
uint32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT];
});
struct RasterizeModel
{
Matrix WorldToVolume;
Matrix VolumeToWorld;
Vector3 VolumeToUVWMul;
Vector3 VolumeToUVWAdd;
Vector3 VolumeLocalBoundsExtent;
float MipOffset;
const ModelBase::SDFData* SDF;
};
struct RasterizeChunk
{
int32 ModelsCount = 0;
int32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT];
};
constexpr int32 RasterizeChunkKeyHashResolution = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
struct RasterizeChunkKey
{
uint32 Hash;
int32 Layer;
Int3 Coord;
friend bool operator==(const RasterizeChunkKey& a, const RasterizeChunkKey& b)
{
return a.Hash == b.Hash && a.Coord == b.Coord && a.Layer == b.Layer;
}
};
uint32 GetHash(const RasterizeChunkKey& key)
{
return key.Hash;
}
class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer
{
public:
GPUTexture* Cascades[4] = {};
GPUTexture* CascadeMips[4] = {};
Vector3 Positions[4];
HashSet<RasterizeChunkKey> NonEmptyChunks[4];
~GlobalSignDistanceFieldCustomBuffer()
{
for (GPUTexture* cascade : Cascades)
RenderTargetPool::Release(cascade);
for (GPUTexture* mip : CascadeMips)
RenderTargetPool::Release(mip);
}
};
namespace
{
Dictionary<RasterizeChunkKey, RasterizeChunk> ChunksCache;
}
String GlobalSignDistanceFieldPass::ToString() const
{
return TEXT("GlobalSignDistanceFieldPass");
}
bool GlobalSignDistanceFieldPass::Init()
{
// Check platform support
auto device = GPUDevice::Instance;
if (device->GetFeatureLevel() < FeatureLevel::SM5 || !device->Limits.HasCompute || !device->Limits.HasTypedUAVLoad)
return false;
if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(device->GetFormatFeatures(GLOBAL_SDF_FORMAT).Support, FormatSupport::ShaderSample | FormatSupport::Texture3D))
return false;
// Create pipeline states
_psDebug = device->CreatePipelineState();
// Load shader
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/GlobalSignDistanceField"));
if (_shader == nullptr)
return false;
#if COMPILE_WITH_DEV_ENV
_shader.Get()->OnReloading.Bind<GlobalSignDistanceFieldPass, &GlobalSignDistanceFieldPass::OnShaderReloading>(this);
#endif
// Init buffer
_modelsBuffer = New<DynamicStructuredBuffer>(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer"));
return false;
}
bool GlobalSignDistanceFieldPass::setupResources()
{
// Check shader
if (!_shader || !_shader->IsLoaded())
return true;
const auto shader = _shader->GetShader();
_cb0 = shader->GetCB(0);
_cb1 = shader->GetCB(1);
_csRasterizeModel0 = shader->GetCS("CS_RasterizeModel", 0);
_csRasterizeModel1 = shader->GetCS("CS_RasterizeModel", 1);
_csClearChunk = shader->GetCS("CS_ClearChunk");
_csGenerateMip0 = shader->GetCS("CS_GenerateMip", 0);
_csGenerateMip1 = shader->GetCS("CS_GenerateMip", 1);
// Create pipeline state
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
if (!_psDebug->IsValid())
{
psDesc.PS = shader->GetPS("PS_Debug");
if (_psDebug->Init(psDesc))
return true;
}
return false;
}
#if USE_EDITOR
void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj)
{
_psDebug->ReleaseGPU();
_csRasterizeModel0 = nullptr;
_csRasterizeModel1 = nullptr;
_csClearChunk = nullptr;
_csGenerateMip0 = nullptr;
_csGenerateMip1 = nullptr;
_cb0 = nullptr;
_cb1 = nullptr;
invalidateResources();
}
#endif
void GlobalSignDistanceFieldPass::Dispose()
{
RendererPass::Dispose();
// Cleanup
Delete(_modelsBuffer);
_modelsTextures.Resize(0);
SAFE_DELETE_GPU_RESOURCE(_psDebug);
_shader = nullptr;
ChunksCache.Clear();
ChunksCache.SetCapacity(0);
}
bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result)
{
// Skip if not supported
if (setupResources())
return true;
if (renderContext.List->Scenes.Count() == 0)
return true;
auto& sdfData = *renderContext.Buffers->GetCustomBuffer<GlobalSignDistanceFieldCustomBuffer>(TEXT("GlobalSignDistanceField"));
// TODO: configurable via graphics settings
const int32 resolution = 256;
const int32 mipFactor = 4;
const int32 resolutionMip = Math::DivideAndRoundUp(resolution, mipFactor);
// TODO: configurable via postFx settings
const float distanceExtent = 2500.0f;
const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f };
// Skip if already done in the current frame
const auto currentFrame = Engine::FrameCount;
if (sdfData.LastFrameUsed != currentFrame)
{
PROFILE_GPU_CPU("Global SDF");
// Initialize buffers
sdfData.LastFrameUsed = currentFrame;
auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
bool updated = false;
for (GPUTexture*& cascade : sdfData.Cascades)
{
if (cascade && cascade->Width() != desc.Width)
{
RenderTargetPool::Release(cascade);
cascade = nullptr;
}
if (!cascade)
{
cascade = RenderTargetPool::Get(desc);
if (!cascade)
return true;
updated = true;
PROFILE_GPU_CPU("Init");
context->ClearUA(cascade, Vector4::One);
}
}
desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1);
for (GPUTexture*& cascadeMip : sdfData.CascadeMips)
{
if (cascadeMip && cascadeMip->Width() != desc.Width)
{
RenderTargetPool::Release(cascadeMip);
cascadeMip = nullptr;
}
if (!cascadeMip)
{
cascadeMip = RenderTargetPool::Get(desc);
if (!cascadeMip)
return true;
updated = true;
}
}
GPUTexture* tmpMip = nullptr;
if (updated)
LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0]->GetMemoryUsage() + sdfData.CascadeMips[0]->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024);
// Rasterize world geometry into Global SDF
renderContext.View.Pass = DrawPass::GlobalSDF;
uint32 viewMask = renderContext.View.RenderLayersMask;
const bool useCache = !updated && !renderContext.Task->IsCameraCut;
static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size.");
const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE);
auto& chunks = ChunksCache;
chunks.EnsureCapacity(rasterizeChunks * rasterizeChunks, false);
bool anyDraw = false;
const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 };
//const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 };
for (int32 cascade = 0; cascade < 4; cascade++)
{
// Reduce frequency of the updates
if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0)
continue;
const float distance = cascadesDistances[cascade];
const float maxDistance = distance * 2;
const float voxelSize = maxDistance / resolution;
const float snapping = voxelSize * mipFactor;
const Vector3 center = Vector3::Floor(renderContext.View.Position / snapping) * snapping;
// TODO: cascade scrolling on movement to reduce dirty chunks?
//const Vector3 center = Vector3::Zero;
sdfData.Positions[cascade] = center;
BoundingBox cascadeBounds(center - distance, center + distance);
// TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality)
const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade
const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
GPUTextureView* cascadeView = sdfData.Cascades[cascade]->ViewVolume();
GPUTextureView* cascadeMipView = sdfData.CascadeMips[cascade]->ViewVolume();
// Clear cascade before rasterization
{
PROFILE_CPU_NAMED("Clear");
chunks.Clear();
_modelsBuffer->Clear();
_modelsTextures.Clear();
}
int32 modelsBufferCount = 0;
// Draw all objects from all scenes into the cascade
for (auto* scene : renderContext.List->Scenes)
{
// TODO: optimize for moving camera (copy sdf)
// TODO: if chunk is made of static objects only then mark it as static and skip from rendering during the next frame (will need to track objects dirty state in the SceneRendering)
for (auto& e : scene->Actors)
{
if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds))
{
e.Actor->Draw(renderContext);
// TODO: move to be properly implemented per object type
auto staticModel = ScriptingObject::Cast<StaticModel>(e.Actor);
if (staticModel && staticModel->Model && staticModel->Model->IsLoaded() && staticModel->Model->CanBeRendered() && staticModel->DrawModes & DrawPass::GlobalSDF)
{
// okay so firstly we need SDF for this model
// TODO: implement SDF generation on model import
if (!staticModel->Model->SDF.Texture)
staticModel->Model->GenerateSDF();
ModelBase::SDFData& sdf = staticModel->Model->SDF;
if (sdf.Texture)
{
// Setup object data
BoundingBox objectBounds = staticModel->GetBox();
BoundingBox objectBoundsCascade;
const float objectMargin = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN;
Vector3::Clamp(objectBounds.Minimum - objectMargin, cascadeBounds.Minimum, cascadeBounds.Maximum, objectBoundsCascade.Minimum);
Vector3::Subtract(objectBoundsCascade.Minimum, cascadeBounds.Minimum, objectBoundsCascade.Minimum);
Vector3::Clamp(objectBounds.Maximum + objectMargin, cascadeBounds.Minimum, cascadeBounds.Maximum, objectBoundsCascade.Maximum);
Vector3::Subtract(objectBoundsCascade.Maximum, cascadeBounds.Minimum, objectBoundsCascade.Maximum);
Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize);
Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize);
Matrix localToWorld, worldToLocal, volumeToWorld;
staticModel->GetWorld(&localToWorld);
Matrix::Invert(localToWorld, worldToLocal);
BoundingBox localVolumeBounds(sdf.LocalBoundsMin, sdf.LocalBoundsMax);
Vector3 volumeLocalBoundsExtent = localVolumeBounds.GetSize() * 0.5f;
Matrix worldToVolume = worldToLocal * Matrix::Translation(-(localVolumeBounds.Minimum + volumeLocalBoundsExtent));
Matrix::Invert(worldToVolume, volumeToWorld);
// Pick the SDF mip for the cascade
int32 mipLevelIndex = 1;
float worldUnitsPerVoxel = sdf.WorldUnitsPerVoxel * localToWorld.GetScaleVector().MaxValue() * 2;
while (voxelSize > worldUnitsPerVoxel && mipLevelIndex < sdf.Texture->MipLevels())
{
mipLevelIndex++;
worldUnitsPerVoxel *= 2.0f;
}
mipLevelIndex--;
// Volume -> Local -> UVW
Vector3 volumeToUVWMul = sdf.LocalToUVWMul;
Vector3 volumeToUVWAdd = sdf.LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * sdf.LocalToUVWMul;
// Add model data for the GPU buffer
int32 modelIndex = modelsBufferCount++;
ModelRasterizeData modelData;
Matrix::Transpose(worldToVolume, modelData.WorldToVolume);
Matrix::Transpose(volumeToWorld, modelData.VolumeToWorld);
modelData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent;
modelData.VolumeToUVWMul = volumeToUVWMul;
modelData.VolumeToUVWAdd = volumeToUVWAdd;
modelData.MipOffset = (float)mipLevelIndex;
modelData.MaxDistance = sdf.MaxDistance;
_modelsBuffer->Write(modelData);
_modelsTextures.Add(sdf.Texture->ViewVolume());
// Inject object into the intersecting cascade chunks
RasterizeChunkKey key;
for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++)
{
for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++)
{
for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++)
{
key.Layer = 0;
key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X;
RasterizeChunk* chunk = &chunks[key];
// Move to the next layer if chunk has overflown
while (chunk->ModelsCount == GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT)
{
key.Layer++;
key.Hash += RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution;
chunk = &chunks[key];
}
chunk->Models[chunk->ModelsCount++] = modelIndex;
}
}
}
}
}
}
}
}
// Send models data to the GPU
{
PROFILE_GPU_CPU("Update Models");
_modelsBuffer->Flush(context);
}
// Perform batched chunks rasterization
if (!anyDraw)
{
anyDraw = true;
context->ResetSR();
tmpMip = RenderTargetPool::Get(desc);
if (!tmpMip)
return true;
}
ModelsRasterizeData data;
data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution;
data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f;
data.MaxDistance = maxDistance;
data.CascadeResolution = resolution;
data.CascadeMipResolution = resolutionMip;
data.CascadeMipFactor = mipFactor;
context->BindUA(0, cascadeView);
context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr);
if (_cb1)
context->BindCB(1, _cb1);
const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE;
auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade];
{
PROFILE_GPU_CPU("Clear Chunks");
for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it)
{
auto& key = it->Item;
if (chunks.ContainsKey(key))
continue;
// Clear empty chunk
nonEmptyChunks.Remove(it);
data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
if (_cb1)
context->UpdateCB(_cb1, &data);
context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
// TODO: don't stall with UAV barrier on D3D12 if UAVs don't change between dispatches
}
}
// TODO: rasterize models into global sdf relative to the cascade origin to prevent fp issues on large worlds
{
PROFILE_GPU_CPU("Rasterize Chunks");
for (auto& e : chunks)
{
// Rasterize non-empty chunk
auto& key = e.Key;
auto& chunk = e.Value;
for (int32 i = 0; i < chunk.ModelsCount; i++)
{
int32 model = chunk.Models[i];
data.Models[i] = model;
context->BindSR(i + 1, _modelsTextures[model]);
}
ASSERT_LOW_LAYER(chunk.ModelsCount != 0);
data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
data.ModelsCount = chunk.ModelsCount;
if (_cb1)
context->UpdateCB(_cb1, &data);
GPUShaderProgramCS* cs;
if (key.Layer == 0)
{
// First layer so can override existing chunk data
cs = _csRasterizeModel0;
nonEmptyChunks.Add(key);
}
else
{
// Another layer so need combine with existing chunk data
cs = _csRasterizeModel1;
}
context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
// TODO: don't stall with UAV barrier on D3D12 if UAVs don't change between dispatches - only for a sequence of _csRasterizeModel0 dispatches (maybe cache per-shader write/read flags for all UAVs?)
#if GLOBAL_SDF_DEBUG_CHUNKS
// Debug draw chunk bounds in world space with number of models in it
if (cascade + 1 == GLOBAL_SDF_DEBUG_CHUNKS)
{
Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize;
BoundingBox chunkBounds(chunkMin, chunkMin + chunkSize);
DebugDraw::DrawWireBox(chunkBounds, Color::Red, 0, false);
DebugDraw::DrawText(StringUtils::ToString(chunk.ModelsCount), chunkBounds.GetCenter() + Vector3(0, 50.0f * key.Layer, 0), Color::Red);
}
#endif
}
}
// Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res)
{
PROFILE_GPU_CPU("Generate Mip");
if (_cb1)
context->UpdateCB(_cb1, &data);
context->ResetUA();
context->BindSR(0, cascadeView);
context->BindUA(0, cascadeMipView);
const int32 mipDispatchGroups = Math::DivideAndRoundUp(resolutionMip, GLOBAL_SDF_MIP_GROUP_SIZE);
int32 floodFillIterations = chunks.Count() == 0 ? 1 : GLOBAL_SDF_MIP_FLOODS;
context->Dispatch(_csGenerateMip0, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
context->UnBindSR(0);
GPUTextureView* tmpMipView = tmpMip->ViewVolume();
for (int32 i = 1; i < floodFillIterations; i++)
{
context->ResetUA();
context->BindSR(0, cascadeMipView);
context->BindUA(0, tmpMipView);
context->Dispatch(_csGenerateMip1, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups);
Swap(tmpMipView, cascadeMipView);
}
if (floodFillIterations % 2 == 0)
Swap(tmpMipView, cascadeMipView);
}
}
RenderTargetPool::Release(tmpMip);
if (anyDraw)
{
context->UnBindCB(1);
context->ResetUA();
context->FlushState();
context->ResetSR();
context->FlushState();
}
}
// Copy results
static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count.");
static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.CascadeMips), "Invalid cascades count.");
Platform::MemoryCopy(result.Cascades, sdfData.Cascades, sizeof(result.Cascades));
Platform::MemoryCopy(result.CascadeMips, sdfData.CascadeMips, sizeof(result.CascadeMips));
for (int32 cascade = 0; cascade < 4; cascade++)
{
const float distance = cascadesDistances[cascade];
const float maxDistance = distance * 2;
const float voxelSize = maxDistance / resolution;
const Vector3 center = sdfData.Positions[cascade];
result.GlobalSDF.CascadePosDistance[cascade] = Vector4(center, distance);
result.GlobalSDF.CascadeVoxelSize.Raw[cascade] = voxelSize;
}
result.GlobalSDF.Resolution = (float)resolution;
return false;
}
void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output)
{
BindingData bindingData;
if (Render(renderContext, context, bindingData))
{
context->Draw(output, renderContext.Buffers->GBuffer0);
return;
}
PROFILE_GPU_CPU("Global SDF Debug");
const Vector2 outputSize(output->Size());
if (_cb0)
{
Data data;
data.ViewWorldPos = renderContext.View.Position;
data.ViewNearPlane = renderContext.View.Near;
data.ViewFarPlane = renderContext.View.Far;
for (int32 i = 0; i < 4; i++)
data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0);
data.GlobalSDF = bindingData.GlobalSDF;
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());
}
context->SetState(_psDebug);
context->SetRenderTarget(output->View());
context->SetViewportAndScissors(outputSize.X, outputSize.Y);
context->DrawFullscreenTriangle();
}

View File

@@ -0,0 +1,75 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#pragma once
#include "RendererPass.h"
/// <summary>
/// Global Sign Distance Field (SDF) rendering pass. Composites scene geometry into series of 3D volume textures that cover the world around the camera for global distance field sampling.
/// </summary>
class GlobalSignDistanceFieldPass : public RendererPass<GlobalSignDistanceFieldPass>
{
public:
// Constant buffer data for Global SDF access on a GPU.
PACK_STRUCT(struct GlobalSDFData
{
Vector4 CascadePosDistance[4];
Vector4 CascadeVoxelSize;
Vector3 Padding;
float Resolution;
});
// Binding data for the GPU.
struct BindingData
{
GPUTexture* Cascades[4];
GPUTexture* CascadeMips[4];
GlobalSDFData GlobalSDF;
};
private:
AssetReference<Shader> _shader;
GPUPipelineState* _psDebug = nullptr;
GPUShaderProgramCS* _csRasterizeModel0 = nullptr;
GPUShaderProgramCS* _csRasterizeModel1 = nullptr;
GPUShaderProgramCS* _csClearChunk = nullptr;
GPUShaderProgramCS* _csGenerateMip0 = nullptr;
GPUShaderProgramCS* _csGenerateMip1 = nullptr;
GPUConstantBuffer* _cb0 = nullptr;
GPUConstantBuffer* _cb1 = nullptr;
class DynamicStructuredBuffer* _modelsBuffer = nullptr;
Array<GPUTextureView*> _modelsTextures;
public:
/// <summary>
/// Renders the Global SDF.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="context">The GPU context.</param>
/// <param name="result">The result Global SDF data for binding to the shaders.</param>
/// <returns>True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false.</returns>
bool Render(RenderContext& renderContext, GPUContext* context, BindingData& result);
/// <summary>
/// Renders the debug view.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="context">The GPU context.</param>
/// <param name="output">The output buffer.</param>
void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output);
private:
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj);
#endif
public:
// [RendererPass]
String ToString() const override;
bool Init() override;
void Dispose() override;
protected:
// [RendererPass]
bool setupResources() override;
};

View File

@@ -21,6 +21,7 @@
#include "VolumetricFogPass.h"
#include "HistogramPass.h"
#include "AtmospherePreCompute.h"
#include "GlobalSignDistanceFieldPass.h"
#include "Utils/MultiScaler.h"
#include "Utils/BitonicSort.h"
#include "AntiAliasing/FXAA.h"
@@ -45,7 +46,6 @@ Array<RendererPassBase*> PassList(64);
class RendererService : public EngineService
{
public:
RendererService()
: EngineService(TEXT("Renderer"), 20)
{
@@ -81,6 +81,7 @@ bool RendererService::Init()
PassList.Add(TAA::Instance());
PassList.Add(SMAA::Instance());
PassList.Add(HistogramPass::Instance());
PassList.Add(GlobalSignDistanceFieldPass::Instance());
#if USE_EDITOR
PassList.Add(QuadOverdrawPass::Instance());
#endif
@@ -340,8 +341,14 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext)
// Fill GBuffer
GBufferPass::Instance()->Fill(renderContext, lightBuffer->View());
// Check if debug emissive light
if (renderContext.View.Mode == ViewMode::Emissive || renderContext.View.Mode == ViewMode::LightmapUVsDensity)
// Debug drawing
if (renderContext.View.Mode == ViewMode::GlobalSDF)
{
GlobalSignDistanceFieldPass::Instance()->RenderDebug(renderContext, context, lightBuffer);
}
if (renderContext.View.Mode == ViewMode::Emissive ||
renderContext.View.Mode == ViewMode::LightmapUVsDensity ||
renderContext.View.Mode == ViewMode::GlobalSDF)
{
context->ResetRenderTarget();
context->SetRenderTarget(task->GetOutputView());

View File

@@ -505,6 +505,8 @@ void Terrain::Draw(RenderContext& renderContext)
DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass);
if (drawModes == DrawPass::None)
return;
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Terrain rendering to Global SDF
PROFILE_CPU();
@@ -727,6 +729,10 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
}
#endif
}
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
}
RigidBody* Terrain::GetAttachedRigidBody() const

View File

@@ -106,6 +106,8 @@ bool SpriteRender::HasContentLoaded() const
void SpriteRender::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return;
if (!Material || !Material->IsLoaded() || !_quadModel || !_quadModel->IsLoaded())
return;
auto model = _quadModel.As<Model>();

View File

@@ -340,6 +340,8 @@ bool TextRender::HasContentLoaded() const
void TextRender::Draw(RenderContext& renderContext)
{
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Text rendering to Global SDF
if (_isDirty)
{
UpdateLayout();
@@ -478,6 +480,10 @@ void TextRender::Deserialize(DeserializeStream& stream, ISerializeModifier* modi
DESERIALIZE_MEMBER(Scale, _layoutOptions.Scale);
DESERIALIZE_MEMBER(GapScale, _layoutOptions.BaseLinesGapScale);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
DrawModes |= DrawPass::GlobalSDF;
_isDirty = true;
}

View File

@@ -0,0 +1,153 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#include "./Flax/Common.hlsl"
#include "./Flax/Collisions.hlsl"
#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32
#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4
#define GLOBAL_SDF_MIP_FLOODS 5
// Global SDF data for a constant buffer
struct GlobalSDFData
{
float4 CascadePosDistance[4];
float4 CascadeVoxelSize;
float3 Padding;
float Resolution;
};
// Global SDF ray trace settings.
struct GlobalSDFTrace
{
float3 WorldPosition;
float MinDistance;
float3 WorldDirection;
float MaxDistance;
float StepScale;
void Init(float3 worldPosition, float3 worldDirection, float minDistance, float maxDistance, float stepScale = 1.0f)
{
WorldPosition = worldPosition;
WorldDirection = worldDirection;
MinDistance = minDistance;
MaxDistance = maxDistance;
StepScale = stepScale;
}
};
// Global SDF ray trace hit information.
struct GlobalSDFHit
{
float HitTime;
uint HitCascade;
uint StepsCount;
bool IsHit()
{
return HitTime >= 0.0f;
}
float3 GetHitPosition(const GlobalSDFTrace trace)
{
return trace.WorldPosition + trace.WorldDirection * HitTime;
}
};
// Samples the Global SDF and returns the distance to the closest surface (in world units) at the given world location.
float SampleGlobalSDF(const GlobalSDFData data, Texture3D<float> tex[4], float3 worldPosition, uint minCascade = 0)
{
float distance = data.CascadePosDistance[3].w * 2.0f;
for (uint cascade = minCascade; cascade < 4; cascade++)
{
float4 cascadePosDistance = data.CascadePosDistance[cascade];
float cascadeMaxDistance = cascadePosDistance.w * 2;
float3 posInCascade = worldPosition - cascadePosDistance.xyz;
float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f;
if (any(cascadeUV < 0) || any(cascadeUV > 1))
continue;
// TODO: sample mip first
float cascadeDistance = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0);
if (cascadeDistance < 1.0f)
{
distance = cascadeDistance * cascadeMaxDistance;
break;
}
}
return distance;
}
// Ray traces the Global SDF.
GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D<float> tex[4], Texture3D<float> mips[4], const GlobalSDFTrace trace, uint minCascade = 0)
{
GlobalSDFHit hit;
hit.HitTime = -1.0f;
hit.HitCascade = 0;
hit.StepsCount = 0;
float chunkSizeDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / data.Resolution; // Size of the chunk in SDF distance (0-1)
float chunkMarginDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN / data.Resolution; // Size of the chunk margin in SDF distance (0-1)
float nextIntersectionStart = 0.0f;
float traceMaxDistance = min(trace.MaxDistance, data.CascadePosDistance[3].w * 2);
float3 traceEndPosition = trace.WorldPosition + trace.WorldDirection * traceMaxDistance;
UNROLL
for (uint cascade = minCascade; cascade < 4 && hit.HitTime < 0.0f; cascade++)
{
float4 cascadePosDistance = data.CascadePosDistance[cascade];
float cascadeMaxDistance = cascadePosDistance.w * 2;
float voxelSize = data.CascadeVoxelSize[cascade];
float voxelExtent = voxelSize * 0.5f;
float cascadeMinStep = voxelSize;
// Hit the cascade bounds to find the intersection points
float2 intersections = LineHitBox(trace.WorldPosition, traceEndPosition, cascadePosDistance.xyz - cascadePosDistance.www, cascadePosDistance.xyz + cascadePosDistance.www);
intersections.xy *= traceMaxDistance;
intersections.x = max(intersections.x, nextIntersectionStart);
if (intersections.x >= intersections.y)
break;
// Skip the current cascade tracing on the next cascade
nextIntersectionStart = intersections.y;
// Walk over the cascade SDF
uint step = 0;
float stepTime = intersections.x;
LOOP
for (; step < 250 && stepTime < intersections.y; step++)
{
float3 stepPosition = trace.WorldPosition + trace.WorldDirection * stepTime;
// Sample SDF
float3 posInCascade = stepPosition - cascadePosDistance.xyz;
float3 cascadeUV = posInCascade / cascadeMaxDistance + 0.5f;
float stepDistance = mips[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0);
if (stepDistance < chunkSizeDistance)
{
float stepDistanceTex = tex[cascade].SampleLevel(SamplerLinearClamp, cascadeUV, 0);
if (stepDistanceTex < chunkMarginDistance * 2)
{
stepDistance = stepDistanceTex;
}
}
else
{
// Assume no SDF nearby so perform a jump
stepDistance = chunkSizeDistance;
}
stepDistance *= cascadeMaxDistance;
// Detect surface hit
float minSurfaceThickness = voxelExtent * saturate(stepTime / (voxelExtent * 2.0f));
if (stepDistance < minSurfaceThickness)
{
// Surface hit
hit.HitTime = max(stepTime + stepDistance - minSurfaceThickness, 0.0f);
hit.HitCascade = cascade;
break;
}
// Move forward
stepTime += max(stepDistance * trace.StepScale, cascadeMinStep);
}
hit.StepsCount += step;
}
return hit;
}

View File

@@ -0,0 +1,216 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#include "./Flax/Common.hlsl"
#include "./Flax/Math.hlsl"
#include "./Flax/GlobalSignDistanceField.hlsl"
#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28
#define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8
#define GLOBAL_SDF_MIP_GROUP_SIZE 4
struct ModelRasterizeData
{
float4x4 WorldToVolume; // TODO: use 3x4 matrix
float4x4 VolumeToWorld; // TODO: use 3x4 matrix
float3 VolumeToUVWMul;
float MipOffset;
float3 VolumeToUVWAdd;
float MaxDistance;
float3 VolumeLocalBoundsExtent;
float Padding0;
};
META_CB_BEGIN(0, Data)
float3 ViewWorldPos;
float ViewNearPlane;
float3 Padding00;
float ViewFarPlane;
float4 ViewFrustumWorldRays[4];
GlobalSDFData GlobalSDF;
META_CB_END
META_CB_BEGIN(1, ModelsRasterizeData)
int3 ChunkCoord;
float MaxDistance;
float3 CascadeCoordToPosMul;
int ModelsCount;
float3 CascadeCoordToPosAdd;
int CascadeResolution;
float2 Padding0;
int CascadeMipResolution;
int CascadeMipFactor;
uint4 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT / 4];
META_CB_END
float CombineDistanceToSDF(float sdf, float distanceToSDF)
{
// Simple sum (aprox)
//return sdf + distanceToSDF;
// Negative distinace inside the SDF
if (sdf <= 0 && distanceToSDF <= 0) return sdf;
// Worst-case scenario with triangle edge (C^2 = A^2 + B^2)
return sqrt(Square(max(sdf, 0)) + Square(distanceToSDF));
}
#if defined(_CS_RasterizeModel)
RWTexture3D<float> GlobalSDFTex : register(u0);
StructuredBuffer<ModelRasterizeData> ModelsBuffer : register(t0);
Texture3D<float> ModelSDFTex[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT] : register(t1);
float DistanceToModelSDF(float minDistance, ModelRasterizeData modelData, Texture3D<float> modelSDFTex, float3 worldPos)
{
// Compute SDF volume UVs and distance in world-space to the volume bounds
float3 volumePos = mul(float4(worldPos, 1), modelData.WorldToVolume).xyz;
float3 volumeUV = volumePos * modelData.VolumeToUVWMul + modelData.VolumeToUVWAdd;
float3 volumePosClamped = clamp(volumePos, -modelData.VolumeLocalBoundsExtent, modelData.VolumeLocalBoundsExtent);
float3 worldPosClamped = mul(float4(volumePosClamped, 1), modelData.VolumeToWorld).xyz;
float distanceToVolume = distance(worldPos, worldPosClamped);
// Skip sampling SDF if there is already a better result
BRANCH if (minDistance <= distanceToVolume) return distanceToVolume;
// Sample SDF
float volumeDistance = modelSDFTex.SampleLevel(SamplerLinearClamp, volumeUV, modelData.MipOffset).x * modelData.MaxDistance;
// Combine distance to the volume with distance to the surface inside the model
float result = CombineDistanceToSDF(volumeDistance, distanceToVolume);
if (distanceToVolume > 0)
{
// Prevent negative distance outside the model
result = max(distanceToVolume, result);
}
return result;
}
// Compute shader for rasterizing model SDF into Global SDF
META_CS(true, FEATURE_LEVEL_SM5)
META_PERMUTATION_1(READ_SDF=0)
META_PERMUTATION_1(READ_SDF=1)
[numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)]
void CS_RasterizeModel(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID)
{
uint3 voxelCoord = ChunkCoord + DispatchThreadId;
float3 voxelWorldPos = voxelCoord * CascadeCoordToPosMul + CascadeCoordToPosAdd;
float minDistance = MaxDistance;
#if READ_SDF
minDistance *= GlobalSDFTex[voxelCoord];
#endif
for (int i = 0; i < ModelsCount; i++)
{
ModelRasterizeData modelData = ModelsBuffer[Models[i / 4][i % 4]];
float modelDistance = DistanceToModelSDF(minDistance, modelData, ModelSDFTex[i], voxelWorldPos);
minDistance = min(minDistance, modelDistance);
}
GlobalSDFTex[voxelCoord] = saturate(minDistance / MaxDistance);
}
#endif
#if defined(_CS_ClearChunk)
RWTexture3D<float> GlobalSDFTex : register(u0);
// Compute shader for clearing Global SDF chunk
META_CS(true, FEATURE_LEVEL_SM5)
[numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)]
void CS_ClearChunk(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID)
{
uint3 voxelCoord = ChunkCoord + DispatchThreadId;
GlobalSDFTex[voxelCoord] = 1.0f;
}
#endif
#if defined(_CS_GenerateMip)
RWTexture3D<float> GlobalSDFMip : register(u0);
Texture3D<float> GlobalSDFTex : register(t0);
float SampleSDF(uint3 voxelCoordMip, int3 offset)
{
#if SAMPLE_MIP
// Sampling Global SDF Mip
float resolution = CascadeMipResolution;
#else
// Sampling Global SDF Tex
voxelCoordMip *= CascadeMipFactor;
float resolution = CascadeResolution;
#endif
// Sample SDF
voxelCoordMip = (uint3)clamp((int3)voxelCoordMip + offset, 0, resolution - 1);
float result = GlobalSDFTex[voxelCoordMip].r;
// Extend by distance to the sampled texel location
float distanceInWorldUnits = length(offset) * (MaxDistance / resolution);
float distanceToVoxel = distanceInWorldUnits / MaxDistance;
result = CombineDistanceToSDF(result, distanceToVoxel);
return result;
}
// Compute shader for generating mip for Global SDF (uses flood fill algorithm)
META_CS(true, FEATURE_LEVEL_SM5)
META_PERMUTATION_1(SAMPLE_MIP=0)
META_PERMUTATION_1(SAMPLE_MIP=1)
[numthreads(GLOBAL_SDF_MIP_GROUP_SIZE, GLOBAL_SDF_MIP_GROUP_SIZE, GLOBAL_SDF_MIP_GROUP_SIZE)]
void CS_GenerateMip(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID)
{
uint3 voxelCoordMip = DispatchThreadId;
float minDistance = SampleSDF(voxelCoordMip, int3(0, 0, 0));
// Find the distance to the closest surface by sampling the nearby area (flood fill)
minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(1, 0, 0)));
minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 1, 0)));
minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 0, 1)));
minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(-1, 0, 0)));
minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, -1, 0)));
minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 0, -1)));
GlobalSDFMip[voxelCoordMip] = minDistance;
}
#endif
#ifdef _PS_Debug
Texture3D<float> GlobalSDFTex[4] : register(t0);
Texture3D<float> GlobalSDFMip[4] : register(t4);
// Pixel shader for Global SDF debug drawing
META_PS(true, FEATURE_LEVEL_SM5)
float4 PS_Debug(Quad_VS2PS input) : SV_Target
{
#if 0
// Preview Global SDF slice
float zSlice = 0.6f;
float mip = 0;
uint cascade = 0;
float distance01 = GlobalSDFTex[cascade].SampleLevel(SamplerLinearClamp, float3(input.TexCoord, zSlice), mip).x;
//float distance01 = GlobalSDFMip[cascade].SampleLevel(SamplerLinearClamp, float3(input.TexCoord, zSlice), mip).x;
float distance = distance01 * GlobalSDF.CascadePosDistance[cascade].w;
if (abs(distance) < 1)
return float4(1, 0, 0, 1);
if (distance01 < 0)
return float4(0, 0, 1 - distance01, 1);
return float4(0, 1 - distance01, 0, 1);
#endif
// Shot a ray from camera into the Global SDF
GlobalSDFTrace trace;
float3 viewRay = lerp(lerp(ViewFrustumWorldRays[3], ViewFrustumWorldRays[0], input.TexCoord.x), lerp(ViewFrustumWorldRays[2], ViewFrustumWorldRays[1], input.TexCoord.x), 1 - input.TexCoord.y).xyz;
viewRay = normalize(viewRay - ViewWorldPos);
trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane);
GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace);
// Debug draw
float3 color = saturate(hit.StepsCount / 80.0f).xxx;
if (!hit.IsHit())
color.rg *= 0.4f;
return float4(color, 1);
}
#endif