Add **Global Sign Distance Field** rendering (work in progress)
This commit is contained in:
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
Normal file
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -68,6 +68,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
|||||||
data.AddRootEngineAsset(TEXT("Shaders/MotionBlur"));
|
data.AddRootEngineAsset(TEXT("Shaders/MotionBlur"));
|
||||||
data.AddRootEngineAsset(TEXT("Shaders/BitonicSort"));
|
data.AddRootEngineAsset(TEXT("Shaders/BitonicSort"));
|
||||||
data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting"));
|
data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting"));
|
||||||
|
data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField"));
|
||||||
data.AddRootEngineAsset(TEXT("Shaders/Quad"));
|
data.AddRootEngineAsset(TEXT("Shaders/Quad"));
|
||||||
data.AddRootEngineAsset(TEXT("Shaders/Reflections"));
|
data.AddRootEngineAsset(TEXT("Shaders/Reflections"));
|
||||||
data.AddRootEngineAsset(TEXT("Shaders/Shadows"));
|
data.AddRootEngineAsset(TEXT("Shaders/Shadows"));
|
||||||
|
|||||||
@@ -1407,6 +1407,7 @@ namespace FlaxEditor.Viewport
|
|||||||
new ViewModeOptions(ViewMode.LODPreview, "LOD Preview"),
|
new ViewModeOptions(ViewMode.LODPreview, "LOD Preview"),
|
||||||
new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity"),
|
new ViewModeOptions(ViewMode.MaterialComplexity, "Material Complexity"),
|
||||||
new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw"),
|
new ViewModeOptions(ViewMode.QuadOverdraw, "Quad Overdraw"),
|
||||||
|
new ViewModeOptions(ViewMode.GlobalSDF, "Global SDF"),
|
||||||
};
|
};
|
||||||
|
|
||||||
private void WidgetCamSpeedShowHide(Control cm)
|
private void WidgetCamSpeedShowHide(Control cm)
|
||||||
|
|||||||
@@ -845,6 +845,8 @@ bool Foliage::Intersects(const Ray& ray, float& distance, Vector3& normal, int32
|
|||||||
|
|
||||||
void Foliage::Draw(RenderContext& renderContext)
|
void Foliage::Draw(RenderContext& renderContext)
|
||||||
{
|
{
|
||||||
|
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
||||||
|
return; // TODO: Foliage rendering to Global SDF
|
||||||
if (Instances.IsEmpty())
|
if (Instances.IsEmpty())
|
||||||
return;
|
return;
|
||||||
auto& view = renderContext.View;
|
auto& view = renderContext.View;
|
||||||
|
|||||||
@@ -208,4 +208,8 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
|
|||||||
DESERIALIZE(PlacementRandomRollAngle);
|
DESERIALIZE(PlacementRandomRollAngle);
|
||||||
DESERIALIZE_BIT(PlacementAlignToNormal);
|
DESERIALIZE_BIT(PlacementAlignToNormal);
|
||||||
DESERIALIZE_BIT(PlacementRandomYaw);
|
DESERIALIZE_BIT(PlacementRandomYaw);
|
||||||
|
|
||||||
|
// [Deprecated on 07.02.2022, expires on 07.02.2024]
|
||||||
|
if (modifier->EngineBuild <= 6330)
|
||||||
|
DrawModes |= DrawPass::GlobalSDF;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -702,6 +702,11 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
MotionVectors = 1 << 4,
|
MotionVectors = 1 << 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Global Sign Distance Field (SDF) rendering pass.
|
||||||
|
/// </summary>
|
||||||
|
GlobalSDF = 1 << 5,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The debug quad overdraw rendering (editor-only).
|
/// The debug quad overdraw rendering (editor-only).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -712,13 +717,13 @@ API_ENUM(Attributes="Flags") enum class DrawPass : int32
|
|||||||
/// The default set of draw passes for the scene objects.
|
/// The default set of draw passes for the scene objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_ENUM(Attributes="HideInEditor")
|
API_ENUM(Attributes="HideInEditor")
|
||||||
Default = Depth | GBuffer | Forward | Distortion | MotionVectors,
|
Default = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The all draw passes combined into a single mask.
|
/// The all draw passes combined into a single mask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_ENUM(Attributes="HideInEditor")
|
API_ENUM(Attributes="HideInEditor")
|
||||||
All = Depth | GBuffer | Forward | Distortion | MotionVectors,
|
All = Depth | GBuffer | Forward | Distortion | MotionVectors | GlobalSDF,
|
||||||
};
|
};
|
||||||
|
|
||||||
DECLARE_ENUM_OPERATORS(DrawPass);
|
DECLARE_ENUM_OPERATORS(DrawPass);
|
||||||
@@ -847,6 +852,11 @@ API_ENUM() enum class ViewMode
|
|||||||
/// Draw geometry overdraw to visualize performance of pixels rendering.
|
/// Draw geometry overdraw to visualize performance of pixels rendering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
QuadOverdraw = 23,
|
QuadOverdraw = 23,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw global Sign Distant Field (SDF) preview.
|
||||||
|
/// </summary>
|
||||||
|
GlobalSDF = 24,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -689,6 +689,8 @@ void AnimatedModel::Update()
|
|||||||
|
|
||||||
void AnimatedModel::Draw(RenderContext& renderContext)
|
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);
|
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world);
|
||||||
|
|
||||||
const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass & (int32)renderContext.View.GetShadowsDrawPassMask(ShadowsMode));
|
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);
|
DESERIALIZE(RootMotionTarget);
|
||||||
|
|
||||||
Entries.DeserializeIfExists(stream, "Buffer", modifier);
|
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)
|
bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal)
|
||||||
|
|||||||
@@ -350,6 +350,8 @@ void SplineModel::Draw(RenderContext& renderContext)
|
|||||||
if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None)
|
if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None)
|
||||||
return;
|
return;
|
||||||
auto model = Model.Get();
|
auto model = Model.Get();
|
||||||
|
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
||||||
|
return; // TODO: Spline Model rendering to Global SDF
|
||||||
if (!Entries.IsValidFor(model))
|
if (!Entries.IsValidFor(model))
|
||||||
Entries.Setup(model);
|
Entries.Setup(model);
|
||||||
|
|
||||||
@@ -469,6 +471,10 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
|
|||||||
DESERIALIZE(DrawModes);
|
DESERIALIZE(DrawModes);
|
||||||
|
|
||||||
Entries.DeserializeIfExists(stream, "Buffer", modifier);
|
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()
|
void SplineModel::OnTransformChanged()
|
||||||
|
|||||||
@@ -209,6 +209,8 @@ bool StaticModel::HasContentLoaded() const
|
|||||||
|
|
||||||
void StaticModel::Draw(RenderContext& renderContext)
|
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);
|
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world);
|
||||||
|
|
||||||
const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass);
|
const DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass);
|
||||||
@@ -409,6 +411,9 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
|
|||||||
DrawModes = DrawPass::Depth;
|
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");
|
const auto member = stream.FindMember("RenderPasses");
|
||||||
|
|||||||
@@ -494,6 +494,8 @@ bool ParticleEffect::HasContentLoaded() const
|
|||||||
|
|
||||||
void ParticleEffect::Draw(RenderContext& renderContext)
|
void ParticleEffect::Draw(RenderContext& renderContext)
|
||||||
{
|
{
|
||||||
|
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
||||||
|
return;
|
||||||
_lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(GetPosition(), renderContext.View.Position));
|
_lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(GetPosition(), renderContext.View.Position));
|
||||||
Particles::DrawParticles(renderContext, this);
|
Particles::DrawParticles(renderContext, this);
|
||||||
}
|
}
|
||||||
@@ -677,6 +679,10 @@ void ParticleEffect::Deserialize(DeserializeStream& stream, ISerializeModifier*
|
|||||||
{
|
{
|
||||||
ApplyModifiedParameters();
|
ApplyModifiedParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Deprecated on 07.02.2022, expires on 07.02.2024]
|
||||||
|
if (modifier->EngineBuild <= 6330)
|
||||||
|
DrawModes |= DrawPass::GlobalSDF;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticleEffect::EndPlay()
|
void ParticleEffect::EndPlay()
|
||||||
|
|||||||
595
Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp
Normal file
595
Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp
Normal 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();
|
||||||
|
}
|
||||||
75
Source/Engine/Renderer/GlobalSignDistanceFieldPass.h
Normal file
75
Source/Engine/Renderer/GlobalSignDistanceFieldPass.h
Normal 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;
|
||||||
|
};
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "VolumetricFogPass.h"
|
#include "VolumetricFogPass.h"
|
||||||
#include "HistogramPass.h"
|
#include "HistogramPass.h"
|
||||||
#include "AtmospherePreCompute.h"
|
#include "AtmospherePreCompute.h"
|
||||||
|
#include "GlobalSignDistanceFieldPass.h"
|
||||||
#include "Utils/MultiScaler.h"
|
#include "Utils/MultiScaler.h"
|
||||||
#include "Utils/BitonicSort.h"
|
#include "Utils/BitonicSort.h"
|
||||||
#include "AntiAliasing/FXAA.h"
|
#include "AntiAliasing/FXAA.h"
|
||||||
@@ -45,7 +46,6 @@ Array<RendererPassBase*> PassList(64);
|
|||||||
class RendererService : public EngineService
|
class RendererService : public EngineService
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
RendererService()
|
RendererService()
|
||||||
: EngineService(TEXT("Renderer"), 20)
|
: EngineService(TEXT("Renderer"), 20)
|
||||||
{
|
{
|
||||||
@@ -81,6 +81,7 @@ bool RendererService::Init()
|
|||||||
PassList.Add(TAA::Instance());
|
PassList.Add(TAA::Instance());
|
||||||
PassList.Add(SMAA::Instance());
|
PassList.Add(SMAA::Instance());
|
||||||
PassList.Add(HistogramPass::Instance());
|
PassList.Add(HistogramPass::Instance());
|
||||||
|
PassList.Add(GlobalSignDistanceFieldPass::Instance());
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
PassList.Add(QuadOverdrawPass::Instance());
|
PassList.Add(QuadOverdrawPass::Instance());
|
||||||
#endif
|
#endif
|
||||||
@@ -340,8 +341,14 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext)
|
|||||||
// Fill GBuffer
|
// Fill GBuffer
|
||||||
GBufferPass::Instance()->Fill(renderContext, lightBuffer->View());
|
GBufferPass::Instance()->Fill(renderContext, lightBuffer->View());
|
||||||
|
|
||||||
// Check if debug emissive light
|
// Debug drawing
|
||||||
if (renderContext.View.Mode == ViewMode::Emissive || renderContext.View.Mode == ViewMode::LightmapUVsDensity)
|
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->ResetRenderTarget();
|
||||||
context->SetRenderTarget(task->GetOutputView());
|
context->SetRenderTarget(task->GetOutputView());
|
||||||
|
|||||||
@@ -505,6 +505,8 @@ void Terrain::Draw(RenderContext& renderContext)
|
|||||||
DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass);
|
DrawPass drawModes = (DrawPass)(DrawModes & renderContext.View.Pass);
|
||||||
if (drawModes == DrawPass::None)
|
if (drawModes == DrawPass::None)
|
||||||
return;
|
return;
|
||||||
|
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
||||||
|
return; // TODO: Terrain rendering to Global SDF
|
||||||
|
|
||||||
PROFILE_CPU();
|
PROFILE_CPU();
|
||||||
|
|
||||||
@@ -727,6 +729,10 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Deprecated on 07.02.2022, expires on 07.02.2024]
|
||||||
|
if (modifier->EngineBuild <= 6330)
|
||||||
|
DrawModes |= DrawPass::GlobalSDF;
|
||||||
}
|
}
|
||||||
|
|
||||||
RigidBody* Terrain::GetAttachedRigidBody() const
|
RigidBody* Terrain::GetAttachedRigidBody() const
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ bool SpriteRender::HasContentLoaded() const
|
|||||||
|
|
||||||
void SpriteRender::Draw(RenderContext& renderContext)
|
void SpriteRender::Draw(RenderContext& renderContext)
|
||||||
{
|
{
|
||||||
|
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
||||||
|
return;
|
||||||
if (!Material || !Material->IsLoaded() || !_quadModel || !_quadModel->IsLoaded())
|
if (!Material || !Material->IsLoaded() || !_quadModel || !_quadModel->IsLoaded())
|
||||||
return;
|
return;
|
||||||
auto model = _quadModel.As<Model>();
|
auto model = _quadModel.As<Model>();
|
||||||
|
|||||||
@@ -340,6 +340,8 @@ bool TextRender::HasContentLoaded() const
|
|||||||
|
|
||||||
void TextRender::Draw(RenderContext& renderContext)
|
void TextRender::Draw(RenderContext& renderContext)
|
||||||
{
|
{
|
||||||
|
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
||||||
|
return; // TODO: Text rendering to Global SDF
|
||||||
if (_isDirty)
|
if (_isDirty)
|
||||||
{
|
{
|
||||||
UpdateLayout();
|
UpdateLayout();
|
||||||
@@ -478,6 +480,10 @@ void TextRender::Deserialize(DeserializeStream& stream, ISerializeModifier* modi
|
|||||||
DESERIALIZE_MEMBER(Scale, _layoutOptions.Scale);
|
DESERIALIZE_MEMBER(Scale, _layoutOptions.Scale);
|
||||||
DESERIALIZE_MEMBER(GapScale, _layoutOptions.BaseLinesGapScale);
|
DESERIALIZE_MEMBER(GapScale, _layoutOptions.BaseLinesGapScale);
|
||||||
|
|
||||||
|
// [Deprecated on 07.02.2022, expires on 07.02.2024]
|
||||||
|
if (modifier->EngineBuild <= 6330)
|
||||||
|
DrawModes |= DrawPass::GlobalSDF;
|
||||||
|
|
||||||
_isDirty = true;
|
_isDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
153
Source/Shaders/GlobalSignDistanceField.hlsl
Normal file
153
Source/Shaders/GlobalSignDistanceField.hlsl
Normal 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;
|
||||||
|
}
|
||||||
216
Source/Shaders/GlobalSignDistanceField.shader
Normal file
216
Source/Shaders/GlobalSignDistanceField.shader
Normal 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
|
||||||
Reference in New Issue
Block a user