From 016b96e9f09856dfbac1c1c3402d4a3ce862b000 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 4 Apr 2022 17:13:31 +0200 Subject: [PATCH] Add objects rasterization to Global Surface Atlas --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 367 +++++++++++++++++- .../Engine/Renderer/GlobalSurfaceAtlasPass.h | 14 +- Source/Shaders/GlobalSurfaceAtlas.hlsl | 74 +++- Source/Shaders/GlobalSurfaceAtlas.shader | 10 + 5 files changed, 457 insertions(+), 12 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 378f723e5..a78bf85ac 100644 --- a/Content/Shaders/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1b0c45d96dff29c3fceed3eede0b671110d99b4a5774f9e487760b4a0fd9563 -size 1878 +oid sha256:ac008a80c1862ce851e6dee12c32d45477f37b406033182449cd3f6f63e69e1f +size 2377 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index aac122295..fbdf85a02 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -3,6 +3,8 @@ #include "GlobalSurfaceAtlasPass.h" #include "GlobalSignDistanceFieldPass.h" #include "RenderList.h" +#include "Engine/Core/Math/Matrix3x3.h" +#include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Engine/Engine.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUDevice.h" @@ -10,6 +12,20 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Level/Actors/StaticModel.h" +#include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Utilities/RectPack.h" + +// This must match HLSL +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (1) +#define GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE (16 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE) +#define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles +#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 1 // Forces to redraw all object tiles every frame +#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) + +#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS +#include "Engine/Debug/DebugDraw.h" +#endif PACK_STRUCT(struct Data0 { @@ -19,20 +35,65 @@ PACK_STRUCT(struct Data0 float ViewFarPlane; Vector4 ViewFrustumWorldRays[4]; GlobalSignDistanceFieldPass::GlobalSDFData GlobalSDF; + GlobalSurfaceAtlasPass::GlobalSurfaceAtlasData GlobalSurfaceAtlas; }); +struct GlobalSurfaceAtlasTile : RectPack +{ + GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) + : RectPack(x, y, width, height) + { + } + + void OnInsert(class GlobalSurfaceAtlasCustomBuffer* buffer, Actor* actor, int32 tileIndex); + + void OnFree() + { + } +}; + +struct GlobalSurfaceAtlasObject +{ + uint64 LastFrameUsed; + GlobalSurfaceAtlasTile* Tiles[6] = {}; + OrientedBoundingBox Bounds; +}; + class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer { public: - GPUTexture* Dummy = nullptr; // TODO use some actual atlas textures + int32 Resolution = 0; + GPUTexture* AtlasDepth = nullptr; + GPUTexture* AtlasGBuffer0 = nullptr; + GPUTexture* AtlasGBuffer1 = nullptr; + GPUTexture* AtlasGBuffer2 = nullptr; + GPUTexture* AtlasDirectLight = nullptr; GlobalSurfaceAtlasPass::BindingData Result; + GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles + Dictionary Objects; + + FORCE_INLINE void Clear() + { + RenderTargetPool::Release(AtlasDepth); + RenderTargetPool::Release(AtlasGBuffer0); + RenderTargetPool::Release(AtlasGBuffer1); + RenderTargetPool::Release(AtlasGBuffer2); + RenderTargetPool::Release(AtlasDirectLight); + SAFE_DELETE(AtlasTiles); + Objects.Clear(); + } ~GlobalSurfaceAtlasCustomBuffer() { - RenderTargetPool::Release(Dummy); + Clear(); } }; +void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, Actor* actor, int32 tileIndex) +{ + buffer->Objects[actor].Tiles[tileIndex] = this; +} + String GlobalSurfaceAtlasPass::ToString() const { return TEXT("GlobalSurfaceAtlasPass"); @@ -48,6 +109,9 @@ bool GlobalSurfaceAtlasPass::Init() bool GlobalSurfaceAtlasPass::setupResources() { + if (!_supported) + return true; + // Load shader if (!_shader) { @@ -93,6 +157,7 @@ void GlobalSurfaceAtlasPass::Dispose() RendererPass::Dispose(); // Cleanup + SAFE_DELETE(_objectsBuffer); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; _shader = nullptr; @@ -114,22 +179,306 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result = surfaceAtlasData.Result; return false; } - + surfaceAtlasData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global Surface Atlas"); - return false; // TODO: configurable via graphics settings const int32 resolution = 4096; // TODO: configurable via postFx settings (maybe use Global SDF distance?) const float distance = 20000; - // TODO: Initialize buffers - surfaceAtlasData.LastFrameUsed = currentFrame; + // Initialize buffers + bool noCache = surfaceAtlasData.Resolution != resolution; + if (noCache) + { + surfaceAtlasData.Clear(); + surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); - // TODO: Rasterize world geometry into Global Surface Atlas + auto desc = GPUTextureDescription::New2D(resolution, resolution, PixelFormat::Unknown); + uint64 memUsage = 0; + // TODO: try using BC4/BC5/BC7 block compression for Surface Atlas (eg. for Tiles material properties) +#define INIT_ATLAS_TEXTURE(texture, format) desc.Format = format; surfaceAtlasData.texture = RenderTargetPool::Get(desc); if (!surfaceAtlasData.texture) return true; memUsage += surfaceAtlasData.texture->GetMemoryUsage() + INIT_ATLAS_TEXTURE(AtlasGBuffer0, GBUFFER0_FORMAT); + INIT_ATLAS_TEXTURE(AtlasGBuffer1, GBUFFER1_FORMAT); + INIT_ATLAS_TEXTURE(AtlasGBuffer2, GBUFFER2_FORMAT); + INIT_ATLAS_TEXTURE(AtlasDirectLight, LIGHT_BUFFER_FORMAT); + desc.Flags = GPUTextureFlags::DepthStencil | GPUTextureFlags::ShaderResource; + INIT_ATLAS_TEXTURE(AtlasDepth, PixelFormat::D16_UNorm); +#undef INIT_ATLAS_TEXTURE + surfaceAtlasData.Resolution = resolution; + LOG(Info, "Global Surface Atlas resolution: {0}, memory usage: {1} MB", resolution, memUsage / 1024 / 1024); + } + + // Add objects into the atlas + if (_objectsBuffer) + _objectsBuffer->Clear(); + else + _objectsBuffer = New(256 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")); + _dirtyObjectsBuffer.Clear(); + { + PROFILE_CPU_NAMED("Draw"); + const uint32 viewMask = renderContext.View.RenderLayersMask; + const Vector3 viewPosition = renderContext.View.Position; + const uint16 minTileResolution = 8; // Minimum size (in texels) of the tile in atlas + const uint16 maxTileResolution = 128; // Maximum size (in texels) of the tile in atlas + const uint16 tileResolutionAlignment = 8; // Alignment to snap (down) tiles resolution which allows to reuse atlas slots once object gets resizes/replaced by other object + const float minObjectRadius = 20.0f; // Skip too small objects + const float tileTexelsPerWorldUnit = 1.0f / 4.0f; // Scales the tiles resolution + const float distanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down + const float distanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down + const float distanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away + static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < minTileResolution, "Invalid tile size configuration."); + for (auto* scene : renderContext.List->Scenes) + { + // TODO: optimize for static objects (SceneRendering could have separate and optimized caching for static actors) + for (auto& e : scene->Actors) + { + if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition) < distance) + { + // TODO: move into actor-specific Draw() impl (eg. via GlobalSurfaceAtlas pass) + auto* staticModel = ScriptingObject::Cast(e.Actor); + if (staticModel && staticModel->Model && staticModel->Model->IsLoaded() && staticModel->Model->CanBeRendered()) + { + const bool staticLight = staticModel->HasStaticFlag(StaticFlags::Lightmap); + Matrix localToWorld; + staticModel->GetWorld(&localToWorld); + bool anyTile = false, dirty = false; + GlobalSurfaceAtlasObject* object = surfaceAtlasData.Objects.TryGet(e.Actor); + auto& lod = staticModel->Model->LODs.Last(); + BoundingBox localBounds = lod.GetBox(); + Vector3 boundsSize = localBounds.GetSize() * staticModel->GetScale(); + const float distanceScale = Math::Lerp(1.0f, distanceScaling, Math::InverseLerp(distanceScalingStart, distanceScalingEnd, CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition))); + const float tilesScale = tileTexelsPerWorldUnit * distanceScale; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + // Calculate optimal tile resolution for the object side + Vector3 boundsSizeTile = boundsSize; + boundsSizeTile.Raw[tileIndex / 2] = MAX_float; // Ignore depth size + boundsSizeTile.Absolute(); + uint16 tileResolution = (uint16)(boundsSizeTile.MinValue() * tilesScale); + if (tileResolution < minTileResolution) + { + // Skip too small surfaces + if (object && object->Tiles[tileIndex]) + { + object->Tiles[tileIndex]->Free(); + object->Tiles[tileIndex] = nullptr; + } + continue; + } + + // Clamp and snap to reduce atlas fragmentation + tileResolution = Math::Clamp(tileResolution, minTileResolution, maxTileResolution); + tileResolution = Math::AlignDown(tileResolution, tileResolutionAlignment); + + // Reuse current tile (refit only on a significant resolution change) + if (object && object->Tiles[tileIndex]) + { + const uint16 tileRefitResolutionStep = 32; + const uint16 currentSize = object->Tiles[tileIndex]->Width; + if (Math::Abs(tileResolution - currentSize) < tileRefitResolutionStep) + { + if (!staticLight) + { + // TODO: collect dirty tile to be rasterized once every X frames + } + anyTile = true; + continue; + } + object->Tiles[tileIndex]->Free(); + } + + // Insert tile into atlas + auto* tile = surfaceAtlasData.AtlasTiles->Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, e.Actor, tileIndex); + // TODO: try to perform atlas defragmentation if it's full (eg. max once per ~10s) + if (tile) + { + if (!object) + object = &surfaceAtlasData.Objects[e.Actor]; + object->Tiles[tileIndex] = tile; + anyTile = true; + dirty = true; + } + else if (object) + { + object->Tiles[tileIndex] = nullptr; + } + } + if (anyTile) + { + // Mark object as used + object->LastFrameUsed = currentFrame; + object->Bounds = OrientedBoundingBox(localBounds); + object->Bounds.Transform(localToWorld); + if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + _dirtyObjectsBuffer.Add(ToPair(e.Actor, object)); + // TODO: populate ObjectsBuffer with objects tiles data + + // Write to objects buffer (this must match unpacking logic in HLSL) + // TODO: cache data for static objects to optimize CPU perf (move ObjectsBuffer into surfaceAtlasData) + Vector4 objectData[GLOBAL_SURFACE_ATLAS_OBJECT_SIZE]; + objectData[0] = *(Vector4*)&e.Bounds; + _objectsBuffer->Write(objectData); + } + } + } + } + } + } + + // Remove unused objects + for (auto it = surfaceAtlasData.Objects.Begin(); it.IsNotEnd(); ++it) + { + if (it->Value.LastFrameUsed != currentFrame) + { + for (auto& tile : it->Value.Tiles) + { + if (tile) + tile->Free(); + } + surfaceAtlasData.Objects.Remove(it); + } + } + // TODO: perform atlas defragmentation after certain amount of tiles removal + + // Send objects data to the GPU + { + PROFILE_GPU_CPU("Update Objects"); + // TODO: cache objects data in surfaceAtlasData to reduce memory transfer + _objectsBuffer->Flush(context); + } + + // Rasterize world geometry material properties into Global Surface Atlas + if (_dirtyObjectsBuffer.Count() != 0) + { + PROFILE_GPU_CPU("Rasterize Tiles"); + + RenderContext renderContextTiles = renderContext; + renderContextTiles.List = RenderList::GetFromPool(); + renderContextTiles.View.Pass = DrawPass::GBuffer; + renderContextTiles.View.Mode = ViewMode::Default; + renderContextTiles.View.ModelLODBias += 100000; + renderContextTiles.View.ShadowModelLODBias += 100000; + renderContextTiles.View.IsSingleFrame = true; + renderContextTiles.View.Near = 0.0f; + renderContextTiles.View.Prepare(renderContextTiles); + + GPUTextureView* depthBuffer = surfaceAtlasData.AtlasDepth->View(); + GPUTextureView* targetBuffers[4] = + { + surfaceAtlasData.AtlasDirectLight->View(), + surfaceAtlasData.AtlasGBuffer0->View(), + surfaceAtlasData.AtlasGBuffer1->View(), + surfaceAtlasData.AtlasGBuffer2->View(), + }; + context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers))); + { + PROFILE_GPU_CPU("Clear"); + if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW) + { + /// Full-atlas hardware clear + context->ClearDepth(depthBuffer); + context->Clear(targetBuffers[0], Color::Transparent); + context->Clear(targetBuffers[1], Color::Transparent); + context->Clear(targetBuffers[2], Color::Transparent); + context->Clear(targetBuffers[3], Color(1, 0, 0, 0)); + } + else + { + // TODO: clear all dirt tiles in a single draw call (software) + } + } + renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].CanUseInstancing = false; + renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].CanUseInstancing = false; + for (const auto& e : _dirtyObjectsBuffer) + { + renderContextTiles.List->Clear(); + renderContextTiles.List->DrawCalls.Clear(); + renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].Indices.Clear(); + renderContextTiles.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].Indices.Clear(); + + // Fake projection matrix to disable Screen Size culling based on RenderTools::ComputeBoundsScreenRadiusSquared + renderContextTiles.View.Projection.Values[0][0] = 10000.0f; + + // Collect draw calls for the object + e.First->Draw(renderContextTiles); + + // Render all tiles into the atlas + GlobalSurfaceAtlasObject& object = *e.Second; +#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS + DebugDraw::DrawBox(object.Bounds, Color::Red.AlphaMultiplied(0.4f)); +#endif + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + + // Setup view to render object from the side + Vector3 xAxis, yAxis, zAxis = Vector3::Zero; + zAxis.Raw[tileIndex / 2] = tileIndex & 1 ? 1.0f : -1.0f; + yAxis = tileIndex == 2 || tileIndex == 3 ? Vector3::Right : Vector3::Up; + Vector3::Cross(yAxis, zAxis, xAxis); + Vector3 localSpaceOffset = -zAxis * object.Bounds.Extents; + Vector3::TransformNormal(xAxis, object.Bounds.Transformation, xAxis); + Vector3::TransformNormal(yAxis, object.Bounds.Transformation, yAxis); + Vector3::TransformNormal(zAxis, object.Bounds.Transformation, zAxis); + xAxis.NormalizeFast(); + yAxis.NormalizeFast(); + zAxis.NormalizeFast(); + Vector3::Transform(localSpaceOffset, object.Bounds.Transformation, renderContextTiles.View.Position); + renderContextTiles.View.Direction = zAxis; + + // Create view matrix + Matrix viewMatrix; + viewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, renderContextTiles.View.Position))); + viewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, renderContextTiles.View.Position))); + viewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, renderContextTiles.View.Position))); + viewMatrix.SetColumn4(Vector4(0, 0, 0, 1)); + + // Calculate object bounds size in the view + OrientedBoundingBox viewBounds(object.Bounds); + viewBounds.Transform(viewMatrix); + Vector3 viewExtent; + Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent); + Vector3 viewBoundsSize = viewExtent.GetAbsolute() * 2.0f; + + // Setup projection to capture object from the side + renderContextTiles.View.Near = -0.1f; // Small offset to prevent clipping with the closest triangles + renderContextTiles.View.Far = viewBoundsSize.Z + 0.2f; + Matrix projectionMatrix; + Matrix::Ortho(viewBoundsSize.X, viewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); + renderContextTiles.View.SetUp(viewMatrix, projectionMatrix); +#if GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_DRAW_OBJECTS + DebugDraw::DrawLine(renderContextTiles.View.Position, renderContextTiles.View.Position + renderContextTiles.View.Direction * 20.0f, Color::Orange); + DebugDraw::DrawWireSphere(BoundingSphere(renderContextTiles.View.Position, 10.0f), Color::Green); +#endif + + // Draw + context->SetViewportAndScissors(Viewport(tile->X, tile->Y, tileWidth, tileHeight)); + renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, DrawCallsListType::GBuffer); + renderContextTiles.List->ExecuteDrawCalls(renderContextTiles, DrawCallsListType::GBufferNoDecals); + } + } + context->ResetRenderTarget(); + RenderList::ReturnToPool(renderContextTiles.List); + } + + // TODO: update direct lighting atlas (for modified tiles and lights) + // TODO: update static lights only for dirty tiles (dynamic lights every X frames) + // TODO: use custom dynamic vertex buffer to decide which atlas tiles to shade with a light + + // TODO: indirect lighting apply to get infinite bounces for GI // Copy results - result.Dummy = surfaceAtlasData.Dummy; + result.Atlas[0] = surfaceAtlasData.AtlasDepth; + result.Atlas[1] = surfaceAtlasData.AtlasGBuffer0; + result.Atlas[2] = surfaceAtlasData.AtlasGBuffer1; + result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; + result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; + result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); surfaceAtlasData.Result = result; return false; } @@ -155,6 +504,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex for (int32 i = 0; i < 4; i++) data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); data.GlobalSDF = bindingDataSDF.GlobalSDF; + data.GlobalSurfaceAtlas = bindingData.GlobalSurfaceAtlas; context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } @@ -163,6 +513,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); } + context->BindSR(8, bindingData.Atlas[1]->View()); // TODO: pass Atlas[4]=AtlasDirectLight context->SetState(_psDebug); context->SetRenderTarget(output->View()); context->SetViewportAndScissors(outputSize.X, outputSize.Y); diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h index a759fd37b..45db66b2b 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -10,10 +10,18 @@ class FLAXENGINE_API GlobalSurfaceAtlasPass : public RendererPass { public: + // Constant buffer data for Global Surface Atlas access on a GPU. + PACK_STRUCT(struct GlobalSurfaceAtlasData + { + Vector3 Padding; + uint32 ObjectsCount; + }); + // Binding data for the GPU. struct BindingData { - GPUTexture* Dummy; // TODO: add textures + GPUTexture* Atlas[5]; + GlobalSurfaceAtlasData GlobalSurfaceAtlas; }; private: @@ -22,6 +30,10 @@ private: GPUPipelineState* _psDebug = nullptr; GPUConstantBuffer* _cb0 = nullptr; + // Rasterization cache + class DynamicTypedBuffer* _objectsBuffer = nullptr; + Array> _dirtyObjectsBuffer; + public: /// /// Renders the Global Surface Atlas. diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index bbad3d4e1..7ed0a239d 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -1,5 +1,77 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "./Flax/Common.hlsl" +#include "./Flax/Collisions.hlsl" -// TODO: implement Global Surface Atlas sampling +// This must match C++ +#define GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE (16 * 1) + +struct GlobalSurfaceTile +{ + uint Index; + uint2 AtlasCoord; + bool Enabled; +}; + +struct GlobalSurfaceObject +{ + float3 BoundsPosition; + float BoundsRadius; + float3x3 InvRotation; + float3 BoundsMin; + float3 BoundsMax; + GlobalSurfaceTile Tiles[6]; +}; + +float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectIndex) +{ + // This must match C++ + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE; + return objects.Load(objectStart); +} + +GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint objectIndex) +{ + // This must match C++ + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE; + float4 vector0 = objects.Load(objectStart + 0); + GlobalSurfaceObject object = (GlobalSurfaceObject)0; + object.BoundsPosition = vector0.xyz; + object.BoundsRadius = vector0.w; + // TODO: InvRotation + // TODO: BoundsMin + // TODO: BoundsMax + // TODO: Tiles + return object; +} + +// Global Surface Atlas data for a constant buffer +struct GlobalSurfaceAtlasData +{ + float3 Padding; + uint ObjectsCount; +}; + +// Samples the Global Surface Atlas and returns the lighting (with opacity) at the given world location (and direction). +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture3D atlas, Buffer objects, float3 worldPosition, float3 worldNormal) +{ + float4 result = float4(0, 0, 0, 0); + // TODO: add grid culling to object for faster lookup + LOOP + for (uint objectIndex = 0; objectIndex < data.ObjectsCount && result.a <= 0.0f; objectIndex++) + { + // Cull point vs sphere + float4 objectBounds = LoadGlobalSurfaceAtlasObjectBounds(objects, objectIndex); + if (distance(objectBounds.xyz, worldPosition) > objectBounds.w) + continue; + GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(objects, objectIndex); + + // TODO: project worldPosition and worldNormal into object-space + // TODO: select 1, 2 or 3 tiles from object that match normal vector + // TODO: sample tiles with weight based on sample normal (reject tile if projected UVs are outside 0-1 range) + + // TODO: implement Global Surface Atlas sampling + result = float4((objectIndex + 1) / data.ObjectsCount, 0, 0, 1); + } + return result; +} diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 4600982ce..4b0f00b16 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -12,17 +12,26 @@ float3 Padding00; float ViewFarPlane; float4 ViewFrustumWorldRays[4]; GlobalSDFData GlobalSDF; +GlobalSurfaceAtlasData GlobalSurfaceAtlas; META_CB_END #ifdef _PS_Debug Texture3D GlobalSDFTex[4] : register(t0); Texture3D GlobalSDFMip[4] : register(t4); +Texture2D GlobalSurfaceAtlasTex : register(t8); +//Buffer GlobalSurfaceAtlasObjects : register(t9); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) float4 PS_Debug(Quad_VS2PS input) : SV_Target { +#if 1 + // Preview Global Surface Atlas texture + float4 texSample = GlobalSurfaceAtlasTex.SampleLevel(SamplerLinearClamp, input.TexCoord, 0); + return float4(texSample.rgb, 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; @@ -34,6 +43,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); // TODO: debug draw Surface Cache + //float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlas, GlobalSurfaceAtlasObjects, hit.GetHitPosition(trace), -viewRay); // Debug draw SDF normals float3 color = hit.HitNormal * 0.5f + 0.5f;