From da4008575bc5ba583a01d1a34a5f3bbcb4f02f48 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 7 Apr 2022 17:16:06 +0200 Subject: [PATCH] Progress on surface atlas sampling --- Content/Shaders/GlobalSurfaceAtlas.flax | 4 +- .../Renderer/GlobalSurfaceAtlasPass.cpp | 107 ++++++++++++------ Source/Shaders/GlobalSurfaceAtlas.hlsl | 75 ++++++++++-- Source/Shaders/GlobalSurfaceAtlas.shader | 7 +- 4 files changed, 140 insertions(+), 53 deletions(-) diff --git a/Content/Shaders/GlobalSurfaceAtlas.flax b/Content/Shaders/GlobalSurfaceAtlas.flax index 24d65714f..8b8cbd705 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:6519ab7a3915ab48b643fa5b8b7a3076cfad0e356dae8158e5194499c87dec32 -size 2969 +oid sha256:13070c079b7a2e5530be1b9d9392b9cc36f41bc566bf86595fe0460a7be4f536 +size 3046 diff --git a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp index 7777608ef..f757a55b9 100644 --- a/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GlobalSurfaceAtlasPass.cpp @@ -17,7 +17,7 @@ #include "Engine/Utilities/RectPack.h" // This must match HLSL -#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE 5 // Amount of Vector4s per-object +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s 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) @@ -44,6 +44,11 @@ PACK_STRUCT(struct AtlasTileVertex struct GlobalSurfaceAtlasTile : RectPack { + Vector3 ViewDirection; + Vector3 ViewPosition; // TODO: use from ViewMatrix + Vector3 ViewBoundsSize; + Matrix ViewMatrix; + GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) : RectPack(x, y, width, height) { @@ -209,6 +214,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // TODO: configurable via graphics settings const int32 resolution = 4096; + const float resolutionInv = 1.0f / resolution; // TODO: configurable via postFx settings (maybe use Global SDF distance?) const float distance = 20000; @@ -350,6 +356,55 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co 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); + // TODO: try to optimize memory footprint (eg. merge scale into extents and use rotation+offset but reconstruct rotation from two axes with sign) + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object->Tiles[tileIndex]; + const int32 tileStart = 5 + tileIndex * 5; + if (!tile) + { + // Disable tile + objectData[tileStart + 4] = Vector4::Zero; + continue; + } + + // 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, tile->ViewPosition); + tile->ViewDirection = zAxis; + + // Create view matrix + tile->ViewMatrix.SetColumn1(Vector4(xAxis, -Vector3::Dot(xAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn2(Vector4(yAxis, -Vector3::Dot(yAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn3(Vector4(zAxis, -Vector3::Dot(zAxis, tile->ViewPosition))); + tile->ViewMatrix.SetColumn4(Vector4(0, 0, 0, 1)); + + // Calculate object bounds size in the view + OrientedBoundingBox viewBounds(object->Bounds); + viewBounds.Transform(tile->ViewMatrix); + Vector3 viewExtent; + Vector3::TransformNormal(viewBounds.Extents, viewBounds.Transformation, viewExtent); + tile->ViewBoundsSize = viewExtent.GetAbsolute() * 2.0f; + + // Per-tile data + const float tileWidth = (float)tile->Width - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + const float tileHeight = (float)tile->Height - GLOBAL_SURFACE_ATLAS_TILE_PADDING; + objectData[tileStart + 0] = Vector4(tile->X, tile->Y, tileWidth, tileHeight); + objectData[tileStart + 1] = Vector4(tile->ViewMatrix.M11, tile->ViewMatrix.M12, tile->ViewMatrix.M13, tile->ViewMatrix.M41); + objectData[tileStart + 2] = Vector4(tile->ViewMatrix.M21, tile->ViewMatrix.M22, tile->ViewMatrix.M23, tile->ViewMatrix.M42); + objectData[tileStart + 3] = Vector4(tile->ViewMatrix.M31, tile->ViewMatrix.M32, tile->ViewMatrix.M33, tile->ViewMatrix.M43); + objectData[tileStart + 4] = Vector4(tile->ViewBoundsSize, 1.0f); + } } } } @@ -418,7 +473,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Per-tile clear (with a single draw call) _vertexBuffer->Clear(); _vertexBuffer->Data.EnsureCapacity(_dirtyObjectsBuffer.Count() * 6 * sizeof(AtlasTileVertex)); - const Vector2 posToClipMul(2.0f / resolution, -2.0f / resolution); + const Vector2 posToClipMul(2.0f * resolutionInv, -2.0f * resolutionInv); const Vector2 posToClipAdd(-1.0f, 1.0f); for (const auto& e : _dirtyObjectsBuffer) { @@ -475,41 +530,14 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co 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.Position = tile->ViewPosition; + renderContextTiles.View.Direction = tile->ViewDirection; renderContextTiles.View.Near = -0.1f; // Small offset to prevent clipping with the closest triangles - renderContextTiles.View.Far = viewBoundsSize.Z + 0.2f; + renderContextTiles.View.Far = tile->ViewBoundsSize.Z + 0.2f; Matrix projectionMatrix; - Matrix::Ortho(viewBoundsSize.X, viewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); - renderContextTiles.View.SetUp(viewMatrix, projectionMatrix); + Matrix::Ortho(tile->ViewBoundsSize.X, tile->ViewBoundsSize.Y, renderContextTiles.View.Near, renderContextTiles.View.Far, projectionMatrix); + renderContextTiles.View.SetUp(tile->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); @@ -573,8 +601,15 @@ 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->BindSR(9, bindingData.Objects ? bindingData.Objects->View() : nullptr); + context->BindSR(8, bindingData.Objects ? bindingData.Objects->View() : nullptr); + context->BindSR(9, bindingData.Atlas[0]->View()); + { + GPUTexture* tex = bindingData.Atlas[1]; // Preview diffuse + //GPUTexture* tex = bindingData.Atlas[2]; // Preview normals + //GPUTexture* tex = bindingData.Atlas[3]; // Preview roughness/metalness/ao + //GPUTexture* tex = bindingData.Atlas[4]; // Preview direct light + context->BindSR(10, tex->View()); + } context->SetState(_psDebug); context->SetRenderTarget(output->View()); context->SetViewportAndScissors(outputSize.X, outputSize.Y); diff --git a/Source/Shaders/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GlobalSurfaceAtlas.hlsl index 8d0c07010..b0bcf3a6d 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GlobalSurfaceAtlas.hlsl @@ -4,12 +4,14 @@ #include "./Flax/Collisions.hlsl" // This must match C++ -#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE 5 // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_OBJECT_SIZE (5 + 6 * 5) // Amount of float4s per-object +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.3f // Cut-off value for tiles transitions blending during sampling struct GlobalSurfaceTile { - uint Index; - uint2 AtlasCoord; + float4 AtlasRect; + float4x4 WorldToLocal; + float3 ViewBoundsSize; bool Enabled; }; @@ -19,7 +21,6 @@ struct GlobalSurfaceObject float BoundsRadius; float4x4 WorldToLocal; float3 Extent; - GlobalSurfaceTile Tiles[6]; }; float4 LoadGlobalSurfaceAtlasObjectBounds(Buffer objects, uint objectIndex) @@ -37,7 +38,7 @@ GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint ob float4 vector1 = objects.Load(objectStart + 1); float4 vector2 = objects.Load(objectStart + 2); float4 vector3 = objects.Load(objectStart + 3); - float4 vector4 = objects.Load(objectStart + 4); + float4 vector4 = objects.Load(objectStart + 4); // w unused GlobalSurfaceObject object = (GlobalSurfaceObject)0; object.BoundsPosition = vector0.xyz; object.BoundsRadius = vector0.w; @@ -46,10 +47,30 @@ GlobalSurfaceObject LoadGlobalSurfaceAtlasObject(Buffer objects, uint ob 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; } +GlobalSurfaceTile LoadGlobalSurfaceAtlasTile(Buffer objects, uint objectIndex, uint tileIndex) +{ + // This must match C++ + const uint objectStart = objectIndex * GLOBAL_SURFACE_ATLAS_OBJECT_SIZE; + const uint tileStart = objectStart + 5 + tileIndex * 5; + float4 vector0 = objects.Load(tileStart + 0); + float4 vector1 = objects.Load(tileStart + 1); + float4 vector2 = objects.Load(tileStart + 2); + float4 vector3 = objects.Load(tileStart + 3); + float4 vector4 = objects.Load(tileStart + 4); + GlobalSurfaceTile tile = (GlobalSurfaceTile)0; + tile.AtlasRect = vector0.xyzw; + tile.WorldToLocal[0] = float4(vector1.xyz, 0.0f); + tile.WorldToLocal[1] = float4(vector2.xyz, 0.0f); + tile.WorldToLocal[2] = float4(vector3.xyz, 0.0f); + tile.WorldToLocal[3] = float4(vector1.w, vector2.w, vector3.w, 1.0f); + tile.ViewBoundsSize = vector4.xyz; + tile.Enabled = vector4.w > 0; + return tile; +} + // Global Surface Atlas data for a constant buffer struct GlobalSurfaceAtlasData { @@ -58,9 +79,10 @@ 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, Texture2D atlas, Buffer objects, float3 worldPosition, float3 worldNormal) +float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Buffer objects, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal) { float4 result = float4(0, 0, 0, 0); + float surfaceThreshold = 10.0f; // Additional threshold between object or tile size compared with input data (error due to SDF or LOD incorrect appearance) // TODO: add grid culling to object for faster lookup LOOP for (uint objectIndex = 0; objectIndex < data.ObjectsCount && result.a <= 0.0f; objectIndex++) @@ -71,16 +93,45 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, Texture2D atl 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)) + float3 localExtent = object.Extent + surfaceThreshold; + if (any(localPosition > localExtent) || any(localPosition < -localExtent)) continue; float3 localNormal = normalize(mul(worldNormal, (float3x3)object.WorldToLocal)); - // 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) + // Pick tiles to sample based on the directionality + // TODO: sample 1/2/3 tiles with weight based on sample normal + uint tileIndex = 2; + + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(objects, objectIndex, tileIndex); + + // Tile normal weight based on the sampling angle + float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); + float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal)); + normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD); + if (normalWeight <= 0.0f) + continue; + + // Get tile UV and depth at the world position + float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; + float tileDepth = tilePosition.z / tile.ViewBoundsSize.z; + float2 tileUV = (tilePosition.xy / tile.ViewBoundsSize.xy) + 0.5f; + tileUV.y = 1.0 - tileUV.y; + float2 atlasCoord = tileUV * tile.AtlasRect.zw + tile.AtlasRect.xy; + + // Tile depth weight based on sample position occlusion + // TODO: gather 4 depth samples to smooth weight (depth weight per-sample used late for bilinear weights) + float tileZ = depth.Load(int3(atlasCoord, 0)).x; + float depthThreshold = 2.0f * surfaceThreshold / tile.ViewBoundsSize.z; + float depthWeight = 1.0f - saturate((abs(tileDepth - tileZ) - depthThreshold) / (0.5f * depthThreshold)); + if (depthWeight <= 0.0f) + continue; + + // Sample atlas texture + // TODO: separate GatherRed/Blue/Green with bilinear weights + float4 color = atlas.Load(int3(atlasCoord, 0)); // TODO: implement Global Surface Atlas sampling - result = float4((float)(objectIndex + 1) / (float)data.ObjectsCount, 0, 0, 1); + result = float4(color.rgb, 1); } return result; } diff --git a/Source/Shaders/GlobalSurfaceAtlas.shader b/Source/Shaders/GlobalSurfaceAtlas.shader index 9003a52df..91f7bb0f0 100644 --- a/Source/Shaders/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GlobalSurfaceAtlas.shader @@ -37,8 +37,9 @@ 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(t8); +Texture2D GlobalSurfaceAtlasDepth : register(t9); +Texture2D GlobalSurfaceAtlasTex : register(t10); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) @@ -61,7 +62,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target //return float4(hit.HitNormal * 0.5f + 0.5f, 1); // Sample Global Surface Atlas at the hit location - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasTex, GlobalSurfaceAtlasObjects, hit.GetHitPosition(trace), -viewRay); + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay); return float4(surfaceColor.rgb, 1); }