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/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"));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()
|
||||
|
||||
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 "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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
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