diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 132202dac..a32512b1b 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -215,7 +215,7 @@ void StaticModel::Draw(RenderContext& renderContext) return; if (renderContext.View.Pass == DrawPass::GlobalSDF) { - GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(Model->SDF, _world, _box); + GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _world, _box); return; } GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, _world); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 60bcde40b..e287ba6f0 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -17,13 +17,15 @@ // 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_MODEL_MAX_COUNT 28 // The maximum amount of models to rasterize at once as a batch into Global SDF. #define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 -#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 -#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 +#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 // Global SDF chunk size in voxels. +#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 // The margin in voxels around objects for culling. Reduces artifacts but reduces performance. +#define GLOBAL_SDF_RASTERIZE_MIP_FACTOR 4 // Global SDF mip resolution downscale factor. #define GLOBAL_SDF_MIP_GROUP_SIZE 4 -#define GLOBAL_SDF_MIP_FLOODS 5 +#define GLOBAL_SDF_MIP_FLOODS 5 // Amount of flood fill passes for mip. #define GLOBAL_SDF_DEBUG_CHUNKS 0 +#define GLOBAL_SDF_ACTOR_IS_STATIC(actor) ((actor->GetStaticFlags() & (StaticFlags::Lightmap | StaticFlags::Transform)) == (int32)(StaticFlags::Lightmap | StaticFlags::Transform)) 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 @@ -79,6 +81,7 @@ struct RasterizeModel struct RasterizeChunk { + bool Dynamic = false; int32 ModelsCount = 0; int32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; }; @@ -91,6 +94,12 @@ struct RasterizeChunkKey int32 Layer; Int3 Coord; + FORCE_INLINE void NextLayer() + { + Layer++; + Hash += RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution; + } + friend bool operator==(const RasterizeChunkKey& a, const RasterizeChunkKey& b) { return a.Hash == b.Hash && a.Coord == b.Coord && a.Layer == b.Layer; @@ -102,21 +111,97 @@ uint32 GetHash(const RasterizeChunkKey& key) return key.Hash; } -class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer +struct CascadeData +{ + GPUTexture* Texture = nullptr; + GPUTexture* Mip = nullptr; + Vector3 Position; + float VoxelSize; + BoundingBox Bounds; + HashSet NonEmptyChunks; + HashSet StaticChunks; + + FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) + { + if (StaticChunks.IsEmpty() || !Bounds.Intersects(objectBounds)) + return; + + BoundingBox objectBoundsCascade; + const float objectMargin = VoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN; + Vector3::Clamp(objectBounds.Minimum - objectMargin, Bounds.Minimum, Bounds.Maximum, objectBoundsCascade.Minimum); + Vector3::Subtract(objectBoundsCascade.Minimum, Bounds.Minimum, objectBoundsCascade.Minimum); + Vector3::Clamp(objectBounds.Maximum + objectMargin, Bounds.Minimum, Bounds.Maximum, objectBoundsCascade.Maximum); + Vector3::Subtract(objectBoundsCascade.Maximum, Bounds.Minimum, objectBoundsCascade.Maximum); + const float chunkSize = VoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + const Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); + const Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); + + // Invalidate static chunks intersecting with dirty bounds + RasterizeChunkKey key; + key.Layer = 0; + 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.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; + StaticChunks.Remove(key); + } + } + } + } + + ~CascadeData() + { + RenderTargetPool::Release(Texture); + RenderTargetPool::Release(Mip); + } +}; + +class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener { public: - GPUTexture* Cascades[4] = {}; - GPUTexture* CascadeMips[4] = {}; - Vector3 Positions[4]; - HashSet NonEmptyChunks[4]; + CascadeData Cascades[4]; + HashSet ObjectTypes; GlobalSignDistanceFieldPass::BindingData Result; - ~GlobalSignDistanceFieldCustomBuffer() + FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) { - for (GPUTexture* cascade : Cascades) - RenderTargetPool::Release(cascade); - for (GPUTexture* mip : CascadeMips) - RenderTargetPool::Release(mip); + for (auto& cascade : Cascades) + cascade.OnSceneRenderingDirty(objectBounds); + } + + // [ISceneRenderingListener] + void OnSceneRenderingAddActor(Actor* a) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingUpdateActor(Actor* a, const BoundingSphere& prevBounds) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(BoundingBox::FromSphere(prevBounds)); + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingRemoveActor(Actor* a) override + { + if (GLOBAL_SDF_ACTOR_IS_STATIC(a) && ObjectTypes.Contains(a->GetTypeHandle())) + { + OnSceneRenderingDirty(a->GetBox()); + } + } + + void OnSceneRenderingClear(SceneRendering* scene) override + { + for (auto& cascade : Cascades) + cascade.StaticChunks.Clear(); } }; @@ -163,6 +248,8 @@ bool GlobalSignDistanceFieldPass::setupResources() // Check shader _cb0 = shader->GetCB(0); _cb1 = shader->GetCB(1); + if (!_cb0 || !_cb1) + return true; _csRasterizeModel0 = shader->GetCS("CS_RasterizeModel", 0); _csRasterizeModel1 = shader->GetCS("CS_RasterizeModel", 1); _csClearChunk = shader->GetCS("CS_ClearChunk"); @@ -248,8 +335,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // TODO: configurable via graphics settings const int32 resolution = 256; - const int32 mipFactor = 4; - const int32 resolutionMip = Math::DivideAndRoundUp(resolution, mipFactor); + const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); // TODO: configurable via postFx settings const float distanceExtent = 2000.0f; const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; @@ -257,47 +343,59 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // Initialize buffers auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); bool updated = false; - for (GPUTexture*& cascade : sdfData.Cascades) + for (auto& cascade : sdfData.Cascades) { - if (cascade && cascade->Width() != desc.Width) + GPUTexture*& texture = cascade.Texture; + if (texture && texture->Width() != desc.Width) { - RenderTargetPool::Release(cascade); - cascade = nullptr; + RenderTargetPool::Release(texture); + texture = nullptr; } - if (!cascade) + if (!texture) { - cascade = RenderTargetPool::Get(desc); - if (!cascade) + texture = RenderTargetPool::Get(desc); + if (!texture) 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) + for (auto& cascade : sdfData.Cascades) { - if (cascadeMip && cascadeMip->Width() != desc.Width) + GPUTexture*& texture = cascade.Mip; + if (texture && texture->Width() != desc.Width) { - RenderTargetPool::Release(cascadeMip); - cascadeMip = nullptr; + RenderTargetPool::Release(texture); + texture = nullptr; } - if (!cascadeMip) + if (!texture) { - cascadeMip = RenderTargetPool::Get(desc); - if (!cascadeMip) + texture = RenderTargetPool::Get(desc); + if (!texture) 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); + { + PROFILE_GPU_CPU("Init"); + for (auto& cascade : sdfData.Cascades) + { + cascade.NonEmptyChunks.Clear(); + cascade.StaticChunks.Clear(); + context->ClearUA(cascade.Texture, Vector4::One); + context->ClearUA(cascade.Mip, Vector4::One); + } + LOG(Info, "Global SDF memory usage: {0} MB", (sdfData.Cascades[0].Texture->GetMemoryUsage() + sdfData.Cascades[0].Mip->GetMemoryUsage()) * ARRAY_COUNT(sdfData.Cascades) / 1024 / 1024); + } + for (SceneRendering* scene : renderContext.List->Scenes) + sdfData.ListenSceneRendering(scene); // Rasterize world geometry into Global SDF renderContext.View.Pass = DrawPass::GlobalSDF; uint32 viewMask = renderContext.View.RenderLayersMask; - const bool useCache = !updated && !renderContext.Task->IsCameraCut; + const bool useCache = !updated; 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; @@ -306,24 +404,24 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; for (int32 cascade = 0; cascade < 4; cascade++) + for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) { // Reduce frequency of the updates - if (useCache && (Engine::FrameCount % cascadeFrequencies[cascade]) != 0) + if (useCache && (Engine::FrameCount % cascadeFrequencies[cascadeIndex]) != 0) continue; - const float distance = cascadesDistances[cascade]; + auto& cascade = sdfData.Cascades[cascadeIndex]; + const float distance = cascadesDistances[cascadeIndex]; 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 float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); + const Vector3 center = Vector3::Floor(renderContext.View.Position / chunkSize) * chunkSize; //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(); + GPUTextureView* cascadeView = cascade.Texture->ViewVolume(); + GPUTextureView* cascadeMipView = cascade.Mip->ViewVolume(); // Clear cascade before rasterization { @@ -333,17 +431,26 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex _modelsTextures.Clear(); } + // Check if cascade center has been moved + if (!(useCache && Vector3::NearEqual(cascade.Position, center, voxelSize))) + { + // TODO: optimize for moving camera (copy sdf for cached chunks) + cascade.StaticChunks.Clear(); + } + cascade.Position = center; + cascade.VoxelSize = voxelSize; + cascade.Bounds = cascadeBounds; + // Draw all objects from all scenes into the cascade _modelsBufferCount = 0; _voxelSize = voxelSize; _cascadeBounds = cascadeBounds; + _sdfData = &sdfData; { PROFILE_CPU_NAMED("Draw"); - for (auto* scene : renderContext.List->Scenes) + for (SceneRendering* 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) + for (const auto& e : scene->Actors) { if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) { @@ -374,38 +481,62 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex data.MaxDistance = maxDistance; data.CascadeResolution = resolution; data.CascadeMipResolution = resolutionMip; - data.CascadeMipFactor = mipFactor; + data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; context->BindUA(0, cascadeView); context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); - if (_cb1) - context->BindCB(1, _cb1); + context->BindCB(1, _cb1); const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; - auto& nonEmptyChunks = sdfData.NonEmptyChunks[cascade]; + bool anyChunkDispatch = false; { PROFILE_GPU_CPU("Clear Chunks"); - for (auto it = nonEmptyChunks.Begin(); it.IsNotEnd(); ++it) + for (auto it = cascade.NonEmptyChunks.Begin(); it.IsNotEnd(); ++it) { auto& key = it->Item; if (chunks.ContainsKey(key)) continue; // Clear empty chunk - nonEmptyChunks.Remove(it); + cascade.NonEmptyChunks.Remove(it); data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - if (_cb1) - context->UpdateCB(_cb1, &data); + context->UpdateCB(_cb1, &data); context->Dispatch(_csClearChunk, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + anyChunkDispatch = true; // TODO: don't stall with UAV barrier on D3D12/Vulkan 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) + + // Update static chunks + for (auto it = chunks.Begin(); it.IsNotEnd(); ++it) { - // Rasterize non-empty chunk (first layer so can override existing chunk data) - auto& key = e.Key; - if (key.Layer != 0) + auto& e = *it; + if (e.Key.Layer != 0) + continue; + if (e.Value.Dynamic) + { + // Remove static chunk with dynamic objects + cascade.StaticChunks.Remove(e.Key); + } + else if (cascade.StaticChunks.Contains(e.Key)) + { + // Skip updating static chunk + auto key = e.Key; + while (chunks.Remove(key)) + key.NextLayer(); + } + else + { + // Add to cache (render now but skip next frame) + cascade.StaticChunks.Add(e.Key); + } + } + + // Rasterize non-empty chunk (first layer so can override existing chunk data) + for (const auto& e : chunks) + { + if (e.Key.Layer != 0) continue; auto& chunk = e.Value; for (int32 i = 0; i < chunk.ModelsCount; i++) @@ -415,30 +546,38 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex context->BindSR(i + 1, _modelsTextures[model]); } ASSERT_LOW_LAYER(chunk.ModelsCount != 0); - data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; data.ModelsCount = chunk.ModelsCount; - if (_cb1) - context->UpdateCB(_cb1, &data); - nonEmptyChunks.Add(key); + context->UpdateCB(_cb1, &data); + cascade.NonEmptyChunks.Add(e.Key); context->Dispatch(_csRasterizeModel0, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + anyChunkDispatch = true; // TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between 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) + if (cascadeIndex + 1 == GLOBAL_SDF_DEBUG_CHUNKS) { - Vector3 chunkMin = cascadeBounds.Minimum + Vector3(key.Coord) * chunkSize; + int32 count = chunk.ModelsCount; + RasterizeChunkKey tmp = e.Key; + tmp.NextLayer(); + while (chunks.ContainsKey(tmp)) + { + count += chunks[tmp].ModelsCount; + tmp.NextLayer(); + } + Vector3 chunkMin = cascadeBounds.Minimum + Vector3(e.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); + DebugDraw::DrawText(StringUtils::ToString(count), chunkBounds.GetCenter(), Color::Red); } #endif } - for (auto& e : chunks) + + // Rasterize non-empty chunk (additive layers so so need combine with existing chunk data) + for (const auto& e : chunks) { - // Rasterize non-empty chunk (additive layers so so need combine with existing chunk data) - auto& key = e.Key; - if (key.Layer == 0) + if (e.Key.Layer == 0) continue; auto& chunk = e.Value; for (int32 i = 0; i < chunk.ModelsCount; i++) @@ -448,19 +587,19 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex context->BindSR(i + 1, _modelsTextures[model]); } ASSERT_LOW_LAYER(chunk.ModelsCount != 0); - data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; data.ModelsCount = chunk.ModelsCount; - if (_cb1) - context->UpdateCB(_cb1, &data); + context->UpdateCB(_cb1, &data); context->Dispatch(_csRasterizeModel1, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups); + anyChunkDispatch = true; } } // Generate mip out of cascade (empty chunks have distance value 1 which is incorrect so mip will be used as a fallback - lower res) + if (updated || anyChunkDispatch) { PROFILE_GPU_CPU("Generate Mip"); - if (_cb1) - context->UpdateCB(_cb1, &data); + context->UpdateCB(_cb1, &data); context->ResetUA(); context->BindSR(0, cascadeView); context->BindUA(0, cascadeMipView); @@ -494,17 +633,19 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // Copy results static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); - static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.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++) + static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); + static_assert(ARRAY_COUNT(sdfData.Cascades) == 4, "Invalid cascades count."); + for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) { - const float distance = cascadesDistances[cascade]; + auto& cascade = sdfData.Cascades[cascadeIndex]; + const float distance = cascadesDistances[cascadeIndex]; 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; + const Vector3 center = cascade.Position; + result.GlobalSDF.CascadePosDistance[cascadeIndex] = Vector4(center, distance); + result.GlobalSDF.CascadeVoxelSize.Raw[cascadeIndex] = voxelSize; + result.Cascades[cascadeIndex] = cascade.Texture; + result.CascadeMips[cascadeIndex] = cascade.Mip; } result.GlobalSDF.Resolution = (float)resolution; sdfData.Result = result; @@ -522,7 +663,6 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC PROFILE_GPU_CPU("Global SDF Debug"); const Vector2 outputSize(output->Size()); - if (_cb0) { Data data; data.ViewWorldPos = renderContext.View.Position; @@ -545,7 +685,7 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC context->DrawFullscreenTriangle(); } -void GlobalSignDistanceFieldPass::RasterizeModelSDF(const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds) +void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds) { if (!sdf.Texture) return; @@ -596,8 +736,10 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(const ModelBase::SDFData& sd _modelsTextures.Add(sdf.Texture->ViewVolume()); // Inject object into the intersecting cascade chunks + _sdfData->ObjectTypes.Add(actor->GetTypeHandle()); RasterizeChunkKey key; auto& chunks = ChunksCache; + const bool dynamic = !GLOBAL_SDF_ACTOR_IS_STATIC(actor); 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++) @@ -607,12 +749,12 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(const ModelBase::SDFData& sd key.Layer = 0; key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X; RasterizeChunk* chunk = &chunks[key]; + chunk->Dynamic |= dynamic; // 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; + key.NextLayer(); chunk = &chunks[key]; } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 1e48ee25b..6243d00cb 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -45,6 +45,7 @@ private: int32 _modelsBufferCount; float _voxelSize; BoundingBox _cascadeBounds; + class GlobalSignDistanceFieldCustomBuffer* _sdfData; public: /// @@ -73,7 +74,7 @@ public: void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output); // Rasterize Model SDF into the Global SDF. Call it from actor Draw() method during DrawPass::GlobalSDF. - void RasterizeModelSDF(const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds); + void RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds); private: #if COMPILE_WITH_DEV_ENV