Parcourir la source

Fix Light Culling for Ortographic camera (#18516)

* Fix Light Culling for Ortographic camera

Signed-off-by: Mateusz Wasilewski <[email protected]>

* Update tile bounding box calculation

Signed-off-by: Mateusz Wasilewski <[email protected]>

---------

Signed-off-by: Mateusz Wasilewski <[email protected]>
Mateusz Wasilewski il y a 9 mois
Parent
commit
ffb61a1e1b

+ 48 - 32
Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCulling.azsl

@@ -7,6 +7,7 @@
  */
 
 #include <scenesrg.srgi>
+#include <viewsrg.srgi>
 
 // Perform light culling on a compute shader
 
@@ -19,8 +20,6 @@ ShaderResourceGroup PassSrg : SRG_PerPass
 {
     struct LightCullingConstants
     {
-        float4x4        m_worldToView;
-        float4          m_screenUVToRay;
         float2          m_gridPixel;
         float2          m_gridHalfPixel;
         uint            m_gridWidth;
@@ -65,13 +64,13 @@ bool IsVectorPointingTowardsEye(const float3 dir)
 
 float3 WorldToView_Point(float3 p)
 {
-    float3 result = mul(PassSrg::m_constantData.m_worldToView, float4(p, 1.0)).xyz;    
+    float3 result = mul(ViewSrg::m_viewMatrix, float4(p, 1.0)).xyz;    
     return result;
 }
 
 float3 WorldToView_Vector(float3 v)
 {
-    float3 result = mul((float3x3)PassSrg::m_constantData.m_worldToView, v);
+    float3 result = mul((float3x3)ViewSrg::m_viewMatrix, v);
     return result;
 }
 
@@ -108,32 +107,6 @@ bool TestSphereVsCone(float3 spherePos, float sphereRadius, float3 origin, float
     return angleOk && backOk && frontOk;
 }
 
-
-float2 ScreenUvToRay(float2 uv)
-{
-    return uv * PassSrg::m_constantData.m_screenUVToRay.xy + PassSrg::m_constantData.m_screenUVToRay.zw;
-}
-
-// Returns screen rays
-// xy contains the top left corner 
-// zw contains the bottom right corner
-// These rays are constructed assuming distance to them along z is 1.0
-// This lets us simply multiply the numbers by an actual depth value to get a position in the tile in view space
-
-// we also return: tileCenterUv which is 2D screenCoordinates of the center of the tile
-float4 ComputeScreenRays(uint2 tileId, out float2 tileCenterUv)
-{
-    float2 tile_uv = float2(tileId) * PassSrg::m_constantData.m_gridPixel;
-
-    tileCenterUv = tile_uv + PassSrg::m_constantData.m_gridHalfPixel;
-
-    float4 tileRect;
-    tileRect.xy = ScreenUvToRay(tile_uv);
-    tileRect.zw = ScreenUvToRay(tile_uv + PassSrg::m_constantData.m_gridPixel);
-
-    return tileRect;
-}
-
 uint NextPowerTwo(uint x)
 {
     // https://wickedengine.net/2018/01/05/next-power-of-two-in-hlsl/
@@ -527,6 +500,50 @@ uint WriteCullingDataToMainMemory(uint lightCount, uint groupIndex, uint3 groupI
     return lightCount;
 }
 
+// Converts depth in view space to depth buffer depth 
+// It is used when depth is in view space and we want to use it as depth for projection matrix
+float ViewSpaceToDepthBuffer(float depth)
+{
+    float a = ViewSrg::m_projectionMatrix[2][3];
+    float b = ViewSrg::m_projectionMatrix[2][2];
+
+    return a / depth - b;
+}
+
+float3 GetViewSpacePosition(float2 tileUv, float depth)
+{
+    // Map UV from [0, 1] to NDC [-1, 1]
+    tileUv = tileUv * 2.0f - 1.0f;
+    float4 clipPos = float4(tileUv.x, -tileUv.y, depth, 1.0f);
+    float4 viewPos = mul(ViewSrg::m_projectionMatrixInverse, clipPos);
+
+    return viewPos.xyz / viewPos.w;
+}
+
+// This function builds an AABB in view space that encompasses the tile
+// The AABB is built by taking the four corners of the tile and the center of the tile, and transforming them to view space
+void BuildAabb(TileLightData tileLightData, uint2 tileId, out float3 aabbCenter, out float3 aabbExtents, out float2 tileCenterUv)
+{
+    float2 tileUvMin = float2(tileId) * PassSrg::m_constantData.m_gridPixel;
+    float2 tileUvMax = tileUvMin + PassSrg::m_constantData.m_gridPixel;
+
+    tileCenterUv = tileUvMin + PassSrg::m_constantData.m_gridHalfPixel;
+
+    const float depthMin = ViewSpaceToDepthBuffer(-tileLightData.zNear);;
+    const float depthMax = ViewSpaceToDepthBuffer(-tileLightData.zFar);
+
+    float3 pt0 = GetViewSpacePosition(tileUvMin, depthMin);
+    float3 pt1 = GetViewSpacePosition(tileUvMin, depthMax);
+    float3 pt2 = GetViewSpacePosition(tileUvMax, depthMin);
+    float3 pt3 = GetViewSpacePosition(tileUvMax, depthMax);
+
+    float3 aabbMin = min(min(pt0, pt1), min(pt2, pt3));
+    float3 aabbMax = max(max(pt0, pt1), max(pt2, pt3));
+
+    aabbCenter = (aabbMin + aabbMax) * 0.5f;
+    aabbExtents = (aabbMax - aabbMin);
+}
+
 // This shader is invoke one thread-group per on-screen tile
 // e.g. if the screen resolution is 1920x1080, with 16x16 tiles, there will be 120x68 tiles (and 120x68 thread groups)
 // Each thread-group is dedicated to culling all lights against that screen-tile.
@@ -566,9 +583,8 @@ void MainCS(
     TileLightData tileLightData = ReadTileLightData(groupID);
   
     float2 tileCenterUv;
-    float4 tileRect = ComputeScreenRays(groupID.xy, tileCenterUv);   
     float3 aabb_center, aabb_extents;
-    BuildAabb(tileRect, tileLightData, aabb_center, aabb_extents);                
+    BuildAabb(tileLightData, groupID.xy, aabb_center, aabb_extents, tileCenterUv);
     GroupMemoryBarrierWithGroupSync();
     
     CullDecals(groupIndex, tileLightData, aabb_center, aabb_extents, tileCenterUv); 

+ 6 - 68
Gems/Atom/Feature/Common/Code/Source/CoreLights/LightCullingPass.cpp

@@ -43,67 +43,6 @@ namespace AZ
 {
     namespace Render
     {
-        enum PlaneType
-        {
-            PlaneLeft,
-            PlaneRight,
-            PlaneBottom,
-            PlaneTop,
-            PlaneNear,
-            PlaneFar,
-
-            PlanesNum
-        };
-
-        static void ViewProjectionMatrixToPlanes(const AZ::Matrix4x4& viewToClip, AZStd::array<AZ::Plane, PlanesNum>& planes)
-        {
-            AZ::Vector4 l = viewToClip.GetRow(3) + viewToClip.GetRow(0);
-            AZ::Vector4 r = viewToClip.GetRow(3) - viewToClip.GetRow(0);
-            AZ::Vector4 b = viewToClip.GetRow(3) + viewToClip.GetRow(1);
-            AZ::Vector4 t = viewToClip.GetRow(3) - viewToClip.GetRow(1);
-            AZ::Vector4 f = viewToClip.GetRow(3) - viewToClip.GetRow(2);
-            AZ::Vector4 n = viewToClip.GetRow(2);
-
-            l /= sqrtf(l.Dot3(l.GetAsVector3()));
-            r /= sqrtf(r.Dot3(r.GetAsVector3()));
-            b /= sqrtf(b.Dot3(b.GetAsVector3()));
-            t /= sqrtf(t.Dot3(t.GetAsVector3()));
-            n /= AZStd::max(sqrtf(n.Dot3(n.GetAsVector3())), FLT_MIN);
-            f /= AZStd::max(sqrtf(f.Dot3(f.GetAsVector3())), FLT_MIN);
-
-            bool reversedDepthBuffer = abs(n.GetW()) > abs(f.GetW());
-            if (reversedDepthBuffer)
-            {
-                AZStd::swap(n, f);
-            }
-
-            planes[PlaneLeft].Set(l);
-            planes[PlaneRight].Set(r);
-            planes[PlaneBottom].Set(b);
-            planes[PlaneTop].Set(t);
-            planes[PlaneNear].Set(n);
-            planes[PlaneFar].Set(f);
-        }
-
-        // given a 0 to 1 screen uv position, these constants can be used to construct a view-space ray to that location
-        static AZStd::array<float, 4> GenerateScreenUVToRayConstants(const AZ::Matrix4x4& viewToClip)
-        {
-            AZStd::array<Plane, PlanesNum> planes;
-            ViewProjectionMatrixToPlanes(viewToClip, planes);
-
-            // What we are doing here is converting from a plane normal to (eventually in the shader) a ray with a z length of 1.0
-            // Once we have that we simply multiply by the tile depth to get a view space position
-
-            // The reciprocal here is from our desire to get a ray with a length of 1.0
-            float leftX, rightX, bottomY, topY;
-            leftX = planes[PlaneLeft].GetNormal().GetZ() / planes[PlaneLeft].GetNormal().GetX();
-            rightX = planes[PlaneRight].GetNormal().GetZ() / planes[PlaneRight].GetNormal().GetX();
-            bottomY = planes[PlaneBottom].GetNormal().GetZ() / planes[PlaneBottom].GetNormal().GetY();
-            topY = planes[PlaneTop].GetNormal().GetZ() / planes[PlaneTop].GetNormal().GetY();
-
-            return AZStd::array<float, 4>{rightX - leftX, bottomY - topY, leftX, topY};
-        }
-
         RPI::Ptr<LightCullingPass> LightCullingPass::Create(const RPI::PassDescriptor& descriptor)
         {
             RPI::Ptr<LightCullingPass> pass = aznew LightCullingPass(descriptor);
@@ -139,6 +78,10 @@ namespace AZ
             SetConstantdataToSRG();
 
             BindPassSrg(context, m_shaderResourceGroup);
+            if (RPI::ViewPtr view = GetView())
+            {
+                BindSrg(view->GetRHIShaderResourceGroup());
+            }
 
             m_shaderResourceGroup->Compile();
         }
@@ -208,17 +151,12 @@ namespace AZ
         {
             struct LightCullingConstants
             {
-                AZStd::array<float, 16> m_worldToView;
-                AZStd::array<float, 4> m_screenUVToRay;
                 AZStd::array<float, 2> m_gridPixel;
                 AZStd::array<float, 2> m_gridHalfPixel;
-                uint32_t             m_gridWidth;
-                uint32_t             m_padding[3];
+                uint32_t m_gridWidth;
+                uint32_t m_padding[3];
             } cullingConstants{};
 
-            RPI::ViewPtr view = m_pipeline->GetFirstView(GetPipelineViewTag());
-            view->GetWorldToViewMatrix().StoreToRowMajorFloat16(cullingConstants.m_worldToView.data());
-            cullingConstants.m_screenUVToRay = GenerateScreenUVToRayConstants(view->GetViewToClipMatrix());
             cullingConstants.m_gridPixel = ComputeGridPixelSize();
             cullingConstants.m_gridHalfPixel[0] = cullingConstants.m_gridPixel[0] * 0.5f;
             cullingConstants.m_gridHalfPixel[1] = cullingConstants.m_gridPixel[1] * 0.5f;