// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "GlobalSignDistanceFieldPass.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 // 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 // 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 // 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 #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 DecodeMul; Vector3 VolumeLocalBoundsExtent; float DecodeAdd; }); 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 { bool Dynamic = false; 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; 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; } }; uint32 GetHash(const RasterizeChunkKey& key) { return key.Hash; } 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: CascadeData Cascades[4]; HashSet ObjectTypes; HashSet SDFTextures; GlobalSignDistanceFieldPass::BindingData Result; void OnSDFTextureDeleted(ScriptingObject* object) { auto* texture = (GPUTexture*)object; if (SDFTextures.Remove(texture)) { texture->Deleted.Unbind(this); texture->ResidentMipsChanged.Unbind(this); } } void OnSDFTextureResidentMipsChanged(GPUTexture* texture) { // Stop tracking texture streaming once it gets fully loaded if (texture->ResidentMipLevels() == texture->MipLevels()) { OnSDFTextureDeleted(texture); // Clear static chunks cache for (auto& cascade : Cascades) cascade.StaticChunks.Clear(); } } FORCE_INLINE void OnSceneRenderingDirty(const BoundingBox& objectBounds) { 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(); } }; namespace { Dictionary ChunksCache; } String GlobalSignDistanceFieldPass::ToString() const { return TEXT("GlobalSignDistanceFieldPass"); } bool GlobalSignDistanceFieldPass::Init() { // Check platform support const auto device = GPUDevice::Instance; _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad && FORMAT_FEATURES_ARE_SUPPORTED(device->GetFormatFeatures(GLOBAL_SDF_FORMAT).Support, FormatSupport::ShaderSample | FormatSupport::Texture3D); return false; } bool GlobalSignDistanceFieldPass::setupResources() { if (!_supported) return true; // Load shader if (!_shader) { _shader = Content::LoadAsyncInternal(TEXT("Shaders/GlobalSignDistanceField")); if (_shader == nullptr) return true; #if COMPILE_WITH_DEV_ENV _shader.Get()->OnReloading.Bind(this); #endif } if (!_shader->IsLoaded()) return true; const auto device = GPUDevice::Instance; const auto shader = _shader->GetShader(); // 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"); _csGenerateMip0 = shader->GetCS("CS_GenerateMip", 0); _csGenerateMip1 = shader->GetCS("CS_GenerateMip", 1); // Init buffer if (!_modelsBuffer) _modelsBuffer = New(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer")); // Create pipeline state GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; if (!_psDebug) { _psDebug = device->CreatePipelineState(); psDesc.PS = shader->GetPS("PS_Debug"); if (_psDebug->Init(psDesc)) return true; } return false; } #if COMPILE_WITH_DEV_ENV void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj) { SAFE_DELETE_GPU_RESOURCE(_psDebug); _csRasterizeModel0 = nullptr; _csRasterizeModel1 = nullptr; _csClearChunk = nullptr; _csGenerateMip0 = nullptr; _csGenerateMip1 = nullptr; _cb0 = nullptr; _cb1 = nullptr; invalidateResources(); } #endif void GlobalSignDistanceFieldPass::Dispose() { RendererPass::Dispose(); // Cleanup SAFE_DELETE(_modelsBuffer); _modelsTextures.Resize(0); SAFE_DELETE_GPU_RESOURCE(_psDebug); _shader = nullptr; ChunksCache.Clear(); ChunksCache.SetCapacity(0); } bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) { auto* sdfData = buffers ? buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")) : nullptr; if (sdfData && sdfData->LastFrameUsed + 1 >= Engine::FrameCount) // Allow to use SDF from the previous frame (eg. particles in Editor using the Editor viewport in Game viewport - Game render task runs first) { result = sdfData->Result; return false; } return true; } bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContext* context, BindingData& result) { // Skip if not supported if (checkIfSkipPass()) return true; if (renderContext.List->Scenes.Count() == 0) return true; auto& sdfData = *renderContext.Buffers->GetCustomBuffer(TEXT("GlobalSignDistanceField")); // Skip if already done in the current frame const auto currentFrame = Engine::FrameCount; if (sdfData.LastFrameUsed == currentFrame) { result = sdfData.Result; return false; } sdfData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global SDF"); // TODO: configurable via graphics settings const int32 resolution = 256; 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 }; // Initialize buffers auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); bool updated = false; for (auto& cascade : sdfData.Cascades) { GPUTexture*& texture = cascade.Texture; if (texture && texture->Width() != desc.Width) { RenderTargetPool::Release(texture); texture = nullptr; } if (!texture) { texture = RenderTargetPool::Get(desc); if (!texture) return true; updated = true; } } desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); for (auto& cascade : sdfData.Cascades) { GPUTexture*& texture = cascade.Mip; if (texture && texture->Width() != desc.Width) { RenderTargetPool::Release(texture); texture = nullptr; } if (!texture) { texture = RenderTargetPool::Get(desc); if (!texture) return true; updated = true; } } GPUTexture* tmpMip = nullptr; if (updated) { 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; 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++) for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) { // Reduce frequency of the updates if (useCache && (Engine::FrameCount % cascadeFrequencies[cascadeIndex]) != 0) continue; auto& cascade = sdfData.Cascades[cascadeIndex]; const float distance = cascadesDistances[cascadeIndex]; const float maxDistance = distance * 2; const float voxelSize = maxDistance / resolution; 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; 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 GPUTextureView* cascadeView = cascade.Texture->ViewVolume(); GPUTextureView* cascadeMipView = cascade.Mip->ViewVolume(); // Clear cascade before rasterization { PROFILE_CPU_NAMED("Clear"); chunks.Clear(); _modelsBuffer->Clear(); _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 (SceneRendering* scene : renderContext.List->Scenes) { for (const auto& e : scene->Actors) { if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBounds, e.Bounds)) { e.Actor->Draw(renderContext); } } } } // 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 = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; context->BindUA(0, cascadeView); context->BindCB(1, _cb1); const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; bool anyChunkDispatch = false; { PROFILE_GPU_CPU("Clear Chunks"); for (auto it = cascade.NonEmptyChunks.Begin(); it.IsNotEnd(); ++it) { auto& key = it->Item; if (chunks.ContainsKey(key)) continue; // Clear empty chunk cascade.NonEmptyChunks.Remove(it); data.ChunkCoord = key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; 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"); // Update static chunks for (auto it = chunks.Begin(); it.IsNotEnd(); ++it) { 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); } } // Send models data to the GPU if (chunks.Count() != 0) { PROFILE_GPU_CPU("Update Models"); _modelsBuffer->Flush(context); } context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr); // 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++) { int32 model = chunk.Models[i]; data.Models[i] = model; context->BindSR(i + 1, _modelsTextures[model]); } ASSERT_LOW_LAYER(chunk.ModelsCount != 0); data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; data.ModelsCount = chunk.ModelsCount; 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 (cascadeIndex + 1 == GLOBAL_SDF_DEBUG_CHUNKS) { 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(count), chunkBounds.GetCenter(), Color::Red); } #endif } // Rasterize non-empty chunk (additive layers so so need combine with 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++) { int32 model = chunk.Models[i]; data.Models[i] = model; context->BindSR(i + 1, _modelsTextures[model]); } ASSERT_LOW_LAYER(chunk.ModelsCount != 0); data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; data.ModelsCount = chunk.ModelsCount; 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"); 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.Cascades), "Invalid cascades count."); static_assert(ARRAY_COUNT(sdfData.Cascades) == 4, "Invalid cascades count."); for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) { auto& cascade = sdfData.Cascades[cascadeIndex]; const float distance = cascadesDistances[cascadeIndex]; const float maxDistance = distance * 2; const float voxelSize = maxDistance / resolution; 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; 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()); { 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(); } void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds) { if (!sdf.Texture || sdf.Texture->ResidentMipLevels() == 0) return; // Setup object data 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); const float chunkSize = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize); Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize); Matrix worldToLocal, volumeToWorld; 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.DecodeMul = 2.0f * sdf.MaxDistance; modelData.DecodeAdd = -sdf.MaxDistance; _modelsBuffer->Write(modelData); _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++) { 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]; chunk->Dynamic |= dynamic; // Move to the next layer if chunk has overflown while (chunk->ModelsCount == GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT) { key.NextLayer(); chunk = &chunks[key]; } chunk->Models[chunk->ModelsCount++] = modelIndex; } } } // Track streaming for SDF textures used in static chunks to invalidate cache if (!dynamic && sdf.Texture->ResidentMipLevels() != sdf.Texture->MipLevels() && !_sdfData->SDFTextures.Contains(sdf.Texture)) { sdf.Texture->Deleted.Bind(_sdfData); sdf.Texture->ResidentMipsChanged.Bind(_sdfData); _sdfData->SDFTextures.Add(sdf.Texture); } }