diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 1b733c177..24d65714f 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:6be2f094757f54ce8080ceb7e53834cd4b0082b8b0ce83184ac3163518311715 -size 3000 +oid sha256:6519ab7a3915ab48b643fa5b8b7a3076cfad0e356dae8158e5194499c87dec32 +size 2969 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 3a99cc01b..7777608ef 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -17,8 +17,7 @@ #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_OBJECT_SIZE 5 // Amount of Vector4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles #define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_TILES_REDRAW 0 // 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) @@ -74,10 +73,16 @@ public: GPUTexture* AtlasGBuffer1 = nullptr; GPUTexture* AtlasGBuffer2 = nullptr; GPUTexture* AtlasDirectLight = nullptr; + DynamicTypedBuffer ObjectsBuffer; GlobalSurfaceAtlasPass::BindingData Result; GlobalSurfaceAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles Dictionary Objects; + GlobalSurfaceAtlasCustomBuffer() + : ObjectsBuffer(256 * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE, PixelFormat::R32G32B32A32_Float, false, TEXT("GlobalSurfaceAtlas.ObjectsBuffer")) + { + } + FORCE_INLINE void Clear() { RenderTargetPool::Release(AtlasDepth); @@ -86,6 +91,7 @@ public: RenderTargetPool::Release(AtlasGBuffer2); RenderTargetPool::Release(AtlasDirectLight); SAFE_DELETE(AtlasTiles); + ObjectsBuffer.Clear(); Objects.Clear(); } @@ -176,7 +182,6 @@ void GlobalSurfaceAtlasPass::Dispose() // Cleanup SAFE_DELETE(_vertexBuffer); - SAFE_DELETE(_objectsBuffer); SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; @@ -232,10 +237,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co _vertexBuffer = New(0u, (uint32)sizeof(AtlasTileVertex), TEXT("GlobalSurfaceAtlas.VertexBuffer")); // 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")); + surfaceAtlasData.ObjectsBuffer.Clear(); _dirtyObjectsBuffer.Clear(); { PROFILE_CPU_NAMED("Draw"); @@ -245,7 +247,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co 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 / 20.0f; // Scales the tiles resolution + const float tileTexelsPerWorldUnit = 1.0f / 10.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 @@ -337,13 +339,17 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co object->LastFrameDirty = currentFrame; _dirtyObjectsBuffer.Add(ToPair(e.Actor, object)); } - // TODO: populate ObjectsBuffer with objects tiles data // Write to objects buffer (this must match unpacking logic in HLSL) + Matrix worldToLocalBounds; + Matrix::Invert(object->Bounds.Transformation, worldToLocalBounds); // TODO: cache data for static objects to optimize CPU perf (move ObjectsBuffer into surfaceAtlasData) - Vector4 objectData[GLOBAL_SURFACE_ATLAS_OBJECT_SIZE]; + auto* objectData = surfaceAtlasData.ObjectsBuffer.WriteReserve(GLOBAL_SURFACE_ATLAS_OBJECT_SIZE); objectData[0] = *(Vector4*)&e.Bounds; - _objectsBuffer->Write(objectData); + objectData[1] = Vector4(worldToLocalBounds.M11, worldToLocalBounds.M12, worldToLocalBounds.M13, worldToLocalBounds.M41); + objectData[2] = Vector4(worldToLocalBounds.M21, worldToLocalBounds.M22, worldToLocalBounds.M23, worldToLocalBounds.M42); + objectData[3] = Vector4(worldToLocalBounds.M31, worldToLocalBounds.M32, worldToLocalBounds.M33, worldToLocalBounds.M43); + objectData[4] = Vector4(object->Bounds.Extents, 0.0f); } } } @@ -369,8 +375,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Send objects data to the GPU { PROFILE_GPU_CPU("Update Objects"); - // TODO: cache objects data in surfaceAtlasData to reduce memory transfer - _objectsBuffer->Flush(context); + surfaceAtlasData.ObjectsBuffer.Flush(context); } // Rasterize world geometry material properties into Global Surface Atlas @@ -532,6 +537,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.Atlas[2] = surfaceAtlasData.AtlasGBuffer1; result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; + result.Objects = surfaceAtlasData.ObjectsBuffer.GetBuffer(); result.GlobalSurfaceAtlas.ObjectsCount = surfaceAtlasData.Objects.Count(); surfaceAtlasData.Result = result; return false; @@ -568,6 +574,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); } context->BindSR(8, bindingData.Atlas[1]->View()); // TODO: pass Atlas[4]=AtlasDirectLight + context->BindSR(9, bindingData.Objects ? bindingData.Objects->View() : nullptr); 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 4966913d9..b39862614 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.h @@ -21,6 +21,7 @@ public: struct BindingData { GPUTexture* Atlas[5]; + GPUBuffer* Objects; GlobalSurfaceAtlasData GlobalSurfaceAtlas; }; @@ -33,7 +34,6 @@ private: // Rasterization cache class DynamicVertexBuffer* _vertexBuffer = nullptr; - class DynamicTypedBuffer* _objectsBuffer = nullptr; Array> _dirtyObjectsBuffer; public: diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 7ed0a239d..8d0c07010 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -4,7 +4,7 @@ #include "./Flax/Collisions.hlsl" // This must match C++ -#define GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE (16 * 1) +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE 5 // Amount of float4s per-object struct GlobalSurfaceTile { @@ -17,30 +17,35 @@ struct GlobalSurfaceObject { float3 BoundsPosition; float BoundsRadius; - float3x3 InvRotation; - float3 BoundsMin; - float3 BoundsMax; + float4x4 WorldToLocal; + float3 Extent; GlobalSurfaceTile Tiles[6]; }; float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectIndex) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE; + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; return objects.Load(objectStart); } GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint objectIndex) { // This must match C++ - const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_STRIDE; + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; float4 vector0 = objects.Load(objectStart + 0); + float4 vector1 = objects.Load(objectStart + 1); + float4 vector2 = objects.Load(objectStart + 2); + float4 vector3 = objects.Load(objectStart + 3); + float4 vector4 = objects.Load(objectStart + 4); GlobalSurfaceObject object = (GlobalSurfaceObject)0; object.BoundsPosition = vector0.xyz; object.BoundsRadius = vector0.w; - // TODO: InvRotation - // TODO: BoundsMin - // TODO: BoundsMax + object.WorldToLocal[0] = float4(vector1.xyz, 0.0f); + object.WorldToLocal[1] = float4(vector2.xyz, 0.0f); + object.WorldToLocal[2] = float4(vector3.xyz, 0.0f); + object.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); + object.Extent = vector4.xyz; // TODO: Tiles return object; } @@ -53,7 +58,7 @@ struct GlobalSurfaceAtlasData }; // 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 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture2D atlas, Buffer objects, float3 worldPosition, float3 worldNormal) { float4 result = float4(0, 0, 0, 0); // TODO: add grid culling to object for faster lookup @@ -65,13 +70,17 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture3D objectBounds.w) continue; GlobalSurfaceObject object = LoadGlobalSurfaceAtlasObject(objects, objectIndex); + float3 localPosition = mul(float4(worldPosition, 1), object.WorldToLocal).xyz; + object.Extent += 10.0f; // TODO: why SDF is so enlarged compared to actual bounds? + if (any(localPosition > object.Extent) || any(localPosition < -object.Extent)) + continue; + float3 localNormal = normalize(mul(worldNormal, (float3x3)object.WorldToLocal)); - // 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); + result = float4((float)(objectIndex + 1) / (float)data.ObjectsCount, 0, 0, 1); } return result; } diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 1f8101f6c..9003a52df 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -38,16 +38,15 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl Texture3D GlobalSDFTex[4] : register(t0); Texture3D GlobalSDFMip[4] : register(t4); Texture2D GlobalSurfaceAtlasTex : register(t8); -//Buffer GlobalSurfaceAtlasObjects : register(t9); +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 +#if 0 // Preview Global Surface Atlas texture - float4 texSample = GlobalSurfaceAtlasTex.SampleLevel(SamplerLinearClamp, input.TexCoord, 0); - return float4(texSample.rgb, 1); + return float4(GlobalSurfaceAtlasTex.SampleLevel(SamplerLinearClamp, input.TexCoord, 0).rgb, 1); #endif // Shot a ray from camera into the Global SDF @@ -59,13 +58,11 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); if (!hit.IsHit()) return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); + //return float4(hit.HitNormal * 0.5f + 0.5f, 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; - return float4(color, 1); + // Sample Global Surface Atlas at the hit location + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasTex, GlobalSurfaceAtlasObjects, hit.GetHitPosition(trace), -viewRay); + return float4(surfaceColor.rgb, 1); } #endif