Explorar el Código

[Terrain] Clipmap parameterizing (#8952)

* Parameterize clipmap configuration

Signed-off-by: jiaweig <[email protected]>
jiaweig hace 3 años
padre
commit
2ff326eb9a

+ 145 - 75
Gems/Terrain/Assets/Shaders/Terrain/ClipmapComputeHelpers.azsli

@@ -11,30 +11,47 @@
 #include <TerrainDetailHelpers.azsli>
 
 // Clipmap levels
-// --------|--------------|-------- level 0
-//   ------|--------------|------   level 1
-//     ----|--------------|----     level 2
-//       --|--------------|--       level 3
-//         |--------------|         level 4 (always covers the area defined by max render size)
-
-float GetClipmapScale(uint clipmapLevel)
+//         |<- clipmap size ->|
+// --------|------------------|-------- level 0
+//   ------|------------------|------   level 1
+//     ----|------------------|----     level 2
+//       --|------------------|--       level 3
+//                 ...                   ...
+//         |------------------|         level n = stack size - 1 (always covers the area defined by max render size)
+
+float GetMacroClipmapScaleInv(uint clipmapLevel)
 {
     return TerrainSrg::m_clipmapData.m_clipmapScaleInv[clipmapLevel].x;
 }
 
-float2 GetPreviousClipmapCenter(uint clipmapLevel)
+float GetDetailClipmapScaleInv(uint clipmapLevel)
+{
+    return TerrainSrg::m_clipmapData.m_clipmapScaleInv[clipmapLevel].y;
+}
+
+float2 GetPreviousMacroClipmapCenter(uint clipmapLevel)
+{
+    return TerrainSrg::m_clipmapData.m_macroClipmapCenters[clipmapLevel].xy;
+}
+
+float2 GetCurrentMacroClipmapCenter(uint clipmapLevel)
+{
+    return TerrainSrg::m_clipmapData.m_macroClipmapCenters[clipmapLevel].zw;
+}
+
+float2 GetPreviousDetailClipmapCenter(uint clipmapLevel)
 {
-    return TerrainSrg::m_clipmapData.m_clipmapCenters[clipmapLevel].xy;
+    return TerrainSrg::m_clipmapData.m_detailClipmapCenters[clipmapLevel].xy;
 }
 
-float2 GetCurrentClipmapCenter(uint clipmapLevel)
+float2 GetCurrentDetailClipmapCenter(uint clipmapLevel)
 {
-    return TerrainSrg::m_clipmapData.m_clipmapCenters[clipmapLevel].zw;
+    return TerrainSrg::m_clipmapData.m_detailClipmapCenters[clipmapLevel].zw;
 }
 
 // Get the world position at the pixel position in a clipmap.
 // Note: clipmap center is dynamic because we are using toroidal addressing. It's not always at the middle.
-float2 GetWorldPosition(float2 clipmapCenter, uint2 pixelPosition, uint clipmapLevel)
+float2 GetWorldPosition(float2 clipmapCenter, uint2 pixelPosition, uint clipmapLevel, float clipmapScaleInv, float maxRenderDistance)
 {
     float2 normalizedPixelPosition = (pixelPosition.xy + 0.5) / TerrainSrg::m_clipmapData.m_clipmapSize;
 
@@ -48,56 +65,44 @@ float2 GetWorldPosition(float2 clipmapCenter, uint2 pixelPosition, uint clipmapL
     distance.y -= step(0.5, distance.y);
     distance.y += step(distance.y, -0.5);
 
-    float clipmapScale = GetClipmapScale(clipmapLevel);
-    float2 maxRenderSize = TerrainSrg::m_clipmapData.m_maxRenderSize.xy;
-    float2 viewRelativePosition = distance * (maxRenderSize * clipmapScale);
+    float2 viewRelativePosition = distance * (maxRenderDistance * 2.0f * clipmapScaleInv);
     return TerrainSrg::m_clipmapData.m_currentViewPosition + viewRelativePosition;
 }
 
-int2 GetPixelPosition(float2 viewRelativePosition, uint clipmapLevel)
-{
-    float clipmapScale = GetClipmapScale(clipmapLevel);
-    float2 maxRenderSize = TerrainSrg::m_clipmapData.m_maxRenderSize.xy;
-    float2 normalizedDistance = viewRelativePosition / (maxRenderSize * clipmapScale);
-    float2 normalizedPixelPosition = GetCurrentClipmapCenter(clipmapLevel) + normalizedDistance;
-    // By clipmap mechanics, the normalized position can only fall into [-1.0, 2.0].
-    // We can use fraction after shifting 1.0 to get the actual position.
-    normalizedPixelPosition = frac(normalizedPixelPosition + float2(1.0, 1.0));
-
-    return int2(normalizedPixelPosition * TerrainSrg::m_clipmapData.m_clipmapSize);
-}
-
-float2 GetPreviousWorldPosition(uint2 pixelPosition, uint clipmapLevel)
+float2 GetCurrentWorldPositionFromMacroClipmaps(uint2 pixelPosition, uint clipmapLevel)
 {
-    float2 previousClipmapCenter = GetPreviousClipmapCenter(clipmapLevel);
-    return GetWorldPosition(previousClipmapCenter, pixelPosition, clipmapLevel);
+    float2 currentClipmapCenter = GetCurrentMacroClipmapCenter(clipmapLevel);
+    float clipmapScaleInv = GetMacroClipmapScaleInv(clipmapLevel);
+    float maxRenderDistance = TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance;
+    return GetWorldPosition(currentClipmapCenter, pixelPosition, clipmapLevel, clipmapScaleInv, maxRenderDistance);
 }
 
-float2 GetCurrentWorldPosition(uint2 pixelPosition, uint clipmapLevel)
+float2 GetCurrentWorldPositionFromDetailClipmaps(uint2 pixelPosition, uint clipmapLevel)
 {
-    float2 currentClipmapCenter = GetCurrentClipmapCenter(clipmapLevel);
-    return GetWorldPosition(currentClipmapCenter, pixelPosition, clipmapLevel);
+    float2 currentClipmapCenter = GetCurrentDetailClipmapCenter(clipmapLevel);
+    float clipmapScaleInv = GetDetailClipmapScaleInv(clipmapLevel);
+    float maxRenderDistance = TerrainSrg::m_clipmapData.m_detailClipmapMaxRenderDistance;
+    return GetWorldPosition(currentClipmapCenter, pixelPosition, clipmapLevel, clipmapScaleInv, maxRenderDistance);
 }
 
-float2 ddxPosition(uint clipmapLevel)
+float2 ddxPosition(float maxRenderDistance, float clipmapScaleInv)
 {
-    float clipmapScale = GetClipmapScale(clipmapLevel);
-    float2 dxdy = TerrainSrg::m_clipmapData.m_maxRenderSize / TerrainSrg::m_clipmapData.m_clipmapSize * clipmapScale;
-    return float2(dxdy.x, 0.0);
+    float dx = maxRenderDistance * 2.0 * clipmapScaleInv / TerrainSrg::m_clipmapData.m_clipmapSize;
+    return float2(dx, 0.0);
 }
 
-float2 ddyPosition(uint clipmapLevel)
+float2 ddyPosition(float maxRenderDistance, float clipmapScaleInv)
 {
-    float clipmapScale = GetClipmapScale(clipmapLevel);
-    float2 dxdy = TerrainSrg::m_clipmapData.m_maxRenderSize / TerrainSrg::m_clipmapData.m_clipmapSize * clipmapScale;
-    return float2(0.0, dxdy.y);
+    float dy = maxRenderDistance * 2.0 * clipmapScaleInv / TerrainSrg::m_clipmapData.m_clipmapSize;
+    return float2(0.0, dy);
 }
 
 struct ClipmapSample
 {
     float3 m_macroColor;
     float3 m_macroNormal;
-    bool   m_hasDetail;
+    bool   m_hasMacro;  // false if world position is out of the clipmap range. Needs increasing max render distance.
+    bool   m_hasDetail; // false if the position doesn't have detail or out of the clipmap range.
     DetailSurface m_detailSurface;
 };
 
@@ -107,52 +112,117 @@ float3 UnpackNormal(float2 packedNormal)
     return float3(packedNormal.xy, z);
 }
 
-uint3 WorldPositionToClipmapTexelIndex(float2 worldPosition, uint clipmapStackSize)
+// Calculate the most detailed clipmap level.
+uint CalculateClosestClipmapLevel(
+    float2 distanceFromViewPosition,
+    float maxRenderDistance,
+    float clipmapScaleBase,
+    uint clipmapStackSize
+)
 {
-    float2 currentViewPosition = TerrainSrg::m_clipmapData.m_currentViewPosition;
-    float2 halfMaxRenderSize = TerrainSrg::m_clipmapData.m_maxRenderSize / 2.0;
+    float2 maxRenderSize = float2(maxRenderDistance, maxRenderDistance);
     // The top clipmap's render distance
-    float2 halfMinRenderSize = halfMaxRenderSize / (float)(1u << (clipmapStackSize - 1u));
-
-    float2 distance = worldPosition - currentViewPosition;
-    float2 clampedDistance = clamp(halfMinRenderSize, abs(distance), halfMaxRenderSize);
-    float2 distanceRatio = halfMaxRenderSize / clampedDistance;
-    // Due to clamping, the max result of log2 is (clipmapStackSize - 1u), which falls into the first clipmap
-    uint clipmapLevel = clipmapStackSize - 1u - uint(floor(log2(min(distanceRatio.x, distanceRatio.y))));
-    uint2 pixelPosition = GetPixelPosition(distance, clipmapLevel);
-    
-    return uint3(pixelPosition, clipmapLevel);
+    float2 minRenderSize = maxRenderSize / pow(clipmapScaleBase, (float)(clipmapStackSize - 1u));
+
+    float2 clampedDistance = clamp(minRenderSize, abs(distanceFromViewPosition), maxRenderSize);
+    float2 distanceRatio = maxRenderSize / clampedDistance;
+    // Due to clamping, the max result of log is (clipmapStackSize - 1u), which falls into the first clipmap
+    uint clipmapLevel = clipmapStackSize - 1u - uint(floor(log(min(distanceRatio.x, distanceRatio.y))/log(clipmapScaleBase)));
+
+    return clipmapLevel;
+}
+
+uint2 CalculateClipmapUv(
+    float2 distanceFromViewPosition,
+    float maxRenderDistance,
+    float clipmapScaleInv,
+    float2 clipmapCenter
+)
+{
+    float2 normalizedDistance = distanceFromViewPosition / (maxRenderDistance * 2.0 * clipmapScaleInv);
+    float2 normalizedPixelPosition = clipmapCenter + normalizedDistance;
+    // By toroidal addressing, the normalized position can only fall into [-1.0, 2.0].
+    // We can use fraction after shifting 1.0 to get the actual position.
+    normalizedPixelPosition = frac(normalizedPixelPosition + float2(1.0, 1.0));
+
+    return uint2(normalizedPixelPosition * TerrainSrg::m_clipmapData.m_clipmapSize);
 }
 
 ClipmapSample SampleClipmap(float2 worldPosition)
 {
-    uint3 macroTexelIndex = WorldPositionToClipmapTexelIndex(worldPosition, MacroClipmapStackSize);
-    uint3 detailTexelIndex = WorldPositionToClipmapTexelIndex(worldPosition, DetailClipmapStackSize);
+    ClipmapSample data;
+
+    float2 distance = worldPosition - TerrainSrg::m_clipmapData.m_currentViewPosition;
+    float2 absDistance = abs(distance);
+    if (absDistance.x > TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance || absDistance.y > TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance)
+    {
+        data.m_hasMacro = false;
+        data.m_hasDetail = false;
+        return data;
+    }
+    data.m_hasMacro = true;
+
+    uint macroClipmapLevel = CalculateClosestClipmapLevel(
+        distance,
+        TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance,
+        TerrainSrg::m_clipmapData.m_macroClipmapScaleBase,
+        TerrainSrg::m_clipmapData.m_macroClipmapStackSize
+    );
+    uint2 macroClipmapUv = CalculateClipmapUv(
+        distance,
+        TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance,
+        GetMacroClipmapScaleInv(macroClipmapLevel),
+        GetCurrentMacroClipmapCenter(macroClipmapLevel)
+    );
+    uint3 macroTexelIndex = uint3(macroClipmapUv, macroClipmapLevel);
 
     float4 macroColor = TerrainSrg::m_macroColorClipmaps[macroTexelIndex];
     float2 macroNormalPacked = TerrainSrg::m_macroNormalClipmaps[macroTexelIndex];
-    float4 detailColor = TerrainSrg::m_detailColorClipmaps[detailTexelIndex];
-
-    ClipmapSample data;
 
     data.m_macroColor = macroColor.rgb;
     data.m_macroNormal = UnpackNormal(macroNormalPacked);
-    // alpha represents hasDetailSurface, 1.0 for true and 0.0 for false.
-    data.m_hasDetail = detailColor.a == 1.0;
 
-    if (data.m_hasDetail)
+    if (absDistance.x > TerrainSrg::m_clipmapData.m_detailClipmapMaxRenderDistance || absDistance.y > TerrainSrg::m_clipmapData.m_detailClipmapMaxRenderDistance)
+    {
+        data.m_hasDetail = false;
+    }
+    else
     {
-        float2 detailNormalPacked = TerrainSrg::m_detailNormalClipmaps[detailTexelIndex];
-        float detailHeight = TerrainSrg::m_detailHeightClipmaps[detailTexelIndex];
-        float4 detailMisc = TerrainSrg::m_detailMiscClipmaps[detailTexelIndex];
-
-        data.m_detailSurface.m_color = detailColor.rgb;
-        data.m_detailSurface.m_normal = UnpackNormal(detailNormalPacked);
-        data.m_detailSurface.m_roughness = detailMisc.x;
-        data.m_detailSurface.m_specularF0 = detailMisc.y;
-        data.m_detailSurface.m_metalness = detailMisc.z;
-        data.m_detailSurface.m_occlusion = detailMisc.w;
-        data.m_detailSurface.m_height = detailHeight;
+        uint detailClipmapLevel = CalculateClosestClipmapLevel(
+            distance,
+            TerrainSrg::m_clipmapData.m_detailClipmapMaxRenderDistance,
+            TerrainSrg::m_clipmapData.m_detailClipmapScaleBase,
+            TerrainSrg::m_clipmapData.m_detailClipmapStackSize
+        );
+        uint2 detailClipmapUv = CalculateClipmapUv(
+            distance,
+            TerrainSrg::m_clipmapData.m_detailClipmapMaxRenderDistance,
+            GetDetailClipmapScaleInv(detailClipmapLevel),
+            GetCurrentDetailClipmapCenter(detailClipmapLevel)
+        );
+        uint3 detailTexelIndex = uint3(detailClipmapUv, detailClipmapLevel);
+
+        float4 detailColor = TerrainSrg::m_detailColorClipmaps[detailTexelIndex];
+        // alpha represents hasDetailSurface, 1.0 for true and 0.0 for false.
+        data.m_hasDetail = detailColor.a == 1.0;
+
+        if (data.m_hasDetail)
+        {
+            float2 detailNormalPacked = TerrainSrg::m_detailNormalClipmaps[detailTexelIndex];
+            float detailHeight = TerrainSrg::m_detailHeightClipmaps[detailTexelIndex];
+            float detailRoughness = TerrainSrg::m_detailRoughnessClipmaps[detailTexelIndex];
+            float detailSpecularF0 = TerrainSrg::m_detailSpecularF0Clipmaps[detailTexelIndex];
+            float detailMetalness = TerrainSrg::m_detailMetalnessClipmaps[detailTexelIndex];
+            float detailOcclusion = TerrainSrg::m_detailOcclusionClipmaps[detailTexelIndex];
+
+            data.m_detailSurface.m_color = detailColor.rgb;
+            data.m_detailSurface.m_normal = UnpackNormal(detailNormalPacked);
+            data.m_detailSurface.m_roughness = detailRoughness;
+            data.m_detailSurface.m_specularF0 = detailSpecularF0;
+            data.m_detailSurface.m_metalness = detailMetalness;
+            data.m_detailSurface.m_occlusion = detailOcclusion;
+            data.m_detailSurface.m_height = detailHeight;
+        }
     }
 
     return data;

+ 47 - 22
Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailClipmapGenerationPass.azsl

@@ -17,9 +17,10 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
     RWTexture2DArray<float4> m_detailColorClipmaps;
     RWTexture2DArray<float2> m_detailNormalClipmaps;
     RWTexture2DArray<float> m_detailHeightClipmaps;
-    // Miscellany clipmap combining:
-    // roughness, specularF0, metalness, occlusion
-    RWTexture2DArray<float4> m_detailMiscClipmaps;
+    RWTexture2DArray<float> m_detailRoughnessClipmaps;
+    RWTexture2DArray<float> m_detailSpecularF0Clipmaps;
+    RWTexture2DArray<float> m_detailMetalnessClipmaps;
+    RWTexture2DArray<float> m_detailOcclusionClipmaps;
 }
 
 [numthreads(32,32,1)]
@@ -28,30 +29,48 @@ void MainCS(
 {
     uint2 pixelPosition = dispatchThreadID.xy;
 
-    for (uint clipmapLevel = 0; clipmapLevel < DetailClipmapStackSize; ++clipmapLevel)
+    for (uint clipmapLevel = 0; clipmapLevel < TerrainSrg::m_clipmapData.m_detailClipmapStackSize; ++clipmapLevel)
     {
-        uint3 texelAddress = uint3(dispatchThreadID.xy, clipmapLevel);
+        uint3 texelIndex = uint3(dispatchThreadID.xy, clipmapLevel);
 
-        float2 worldPosition = GetCurrentWorldPosition(pixelPosition, clipmapLevel);
+        float2 worldPosition = GetCurrentWorldPositionFromDetailClipmaps(pixelPosition, clipmapLevel);
 
         if (any(worldPosition < TerrainSrg::m_clipmapData.m_worldBoundsMin) || any(worldPosition > TerrainSrg::m_clipmapData.m_worldBoundsMax))
         {
             // alpha represents hasDetailSurface
-            PassSrg::m_detailColorClipmaps[texelAddress] = float4(TerrainMaterialSrg::m_baseColor.rgb, 0.0);
-            PassSrg::m_detailNormalClipmaps[texelAddress] = float2(0.0, 0.0);
-            PassSrg::m_detailHeightClipmaps[texelAddress] = 0.0;
-            PassSrg::m_detailMiscClipmaps[texelAddress] = float4(0.0, 0.0, 0.0, 0.0);
+            PassSrg::m_detailColorClipmaps[texelIndex] = float4(TerrainMaterialSrg::m_baseColor.rgb, 0.0);
+            PassSrg::m_detailNormalClipmaps[texelIndex] = float2(0.0, 0.0);
+            PassSrg::m_detailHeightClipmaps[texelIndex] = 0.0;
+            PassSrg::m_detailRoughnessClipmaps[texelIndex] = 0.0;
+            PassSrg::m_detailSpecularF0Clipmaps[texelIndex] = 0.0;
+            PassSrg::m_detailMetalnessClipmaps[texelIndex] = 0.0;
+            PassSrg::m_detailOcclusionClipmaps[texelIndex] = 0.0;
             continue;
         }
 
         DetailSurface detailSurface;
         float2 detailRegionCoord = worldPosition * TerrainSrg::m_detailMaterialIdScale;
         float2 detailUv = worldPosition * TerrainMaterialSrg::m_detailTextureMultiplier;
-        float2 detailUvDdx = ddxPosition(clipmapLevel) * TerrainMaterialSrg::m_detailTextureMultiplier;
-        float2 detailUvDdy = ddyPosition(clipmapLevel) * TerrainMaterialSrg::m_detailTextureMultiplier;
+        float clipmapScaleInv = GetDetailClipmapScaleInv(clipmapLevel);
+        float2 detailUvDdx = ddxPosition(TerrainSrg::m_clipmapData.m_detailClipmapMaxRenderDistance, clipmapScaleInv) * TerrainMaterialSrg::m_detailTextureMultiplier;
+        float2 detailUvDdy = ddyPosition(TerrainSrg::m_clipmapData.m_detailClipmapMaxRenderDistance, clipmapScaleInv) * TerrainMaterialSrg::m_detailTextureMultiplier;
 
-        uint3 texelIndex = WorldPositionToClipmapTexelIndex(worldPosition, MacroClipmapStackSize);
-        float3 macroColor = PassSrg::m_macroColorClipmaps[texelAddress];
+        float2 distance = worldPosition - TerrainSrg::m_clipmapData.m_currentViewPosition;
+        uint macroClipmapLevel = CalculateClosestClipmapLevel(
+            distance,
+            TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance,
+            TerrainSrg::m_clipmapData.m_macroClipmapScaleBase,
+            TerrainSrg::m_clipmapData.m_macroClipmapStackSize
+        );
+        uint2 macroClipmapUv = CalculateClipmapUv(
+            distance,
+            TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance,
+            GetMacroClipmapScaleInv(macroClipmapLevel),
+            GetCurrentMacroClipmapCenter(macroClipmapLevel)
+        );
+        uint3 macroTexelIndex = uint3(macroClipmapUv, macroClipmapLevel);
+
+        float3 macroColor = PassSrg::m_macroColorClipmaps[macroTexelIndex].rgb;
 
         bool hasDetailSurface = GetDetailSurface(detailSurface, detailRegionCoord, detailUv, detailUvDdx, detailUvDdy, macroColor);
 
@@ -60,18 +79,24 @@ void MainCS(
             float3 normal = normalize(detailSurface.m_normal);
 
             // alpha represents hasDetailSurface
-            PassSrg::m_detailColorClipmaps[texelAddress] = float4(detailSurface.m_color, 1.0);
-            PassSrg::m_detailNormalClipmaps[texelAddress] = float2(normal.xy);
-            PassSrg::m_detailHeightClipmaps[texelAddress] = detailSurface.m_height;
-            PassSrg::m_detailMiscClipmaps[texelAddress] = float4(detailSurface.m_roughness, detailSurface.m_specularF0, detailSurface.m_metalness, detailSurface.m_occlusion);
+            PassSrg::m_detailColorClipmaps[texelIndex] = float4(detailSurface.m_color, 1.0);
+            PassSrg::m_detailNormalClipmaps[texelIndex] = float2(normal.xy);
+            PassSrg::m_detailHeightClipmaps[texelIndex] = detailSurface.m_height;
+            PassSrg::m_detailRoughnessClipmaps[texelIndex] = detailSurface.m_roughness;
+            PassSrg::m_detailSpecularF0Clipmaps[texelIndex] = detailSurface.m_specularF0;
+            PassSrg::m_detailMetalnessClipmaps[texelIndex] = detailSurface.m_metalness;
+            PassSrg::m_detailOcclusionClipmaps[texelIndex] = detailSurface.m_occlusion;
         }
         else
         {
             // alpha represents hasDetailSurface
-            PassSrg::m_detailColorClipmaps[texelAddress] = float4(TerrainMaterialSrg::m_baseColor.rgb, 0.0);
-            PassSrg::m_detailNormalClipmaps[texelAddress] = float2(0.0, 0.0);
-            PassSrg::m_detailHeightClipmaps[texelAddress] = 0.0;
-            PassSrg::m_detailMiscClipmaps[texelAddress] = float4(0.0, 0.0, 0.0, 0.0);
+            PassSrg::m_detailColorClipmaps[texelIndex] = float4(TerrainMaterialSrg::m_baseColor.rgb, 0.0);
+            PassSrg::m_detailNormalClipmaps[texelIndex] = float2(0.0, 0.0);
+            PassSrg::m_detailHeightClipmaps[texelIndex] = 0.0;
+            PassSrg::m_detailRoughnessClipmaps[texelIndex] = 0.0;
+            PassSrg::m_detailSpecularF0Clipmaps[texelIndex] = 0.0;
+            PassSrg::m_detailMetalnessClipmaps[texelIndex] = 0.0;
+            PassSrg::m_detailOcclusionClipmaps[texelIndex] = 0.0;
         }
     }
 }

+ 8 - 8
Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroClipmapGenerationPass.azsl

@@ -22,21 +22,21 @@ void MainCS(
 {
     uint2 pixelPosition = dispatchThreadID.xy;
 
-    for (uint clipmapLevel = 0; clipmapLevel < MacroClipmapStackSize; ++clipmapLevel)
+    for (uint clipmapLevel = 0; clipmapLevel < TerrainSrg::m_clipmapData.m_macroClipmapStackSize; ++clipmapLevel)
     {
-        uint3 texelAddress = uint3(dispatchThreadID.xy, clipmapLevel);
+        uint3 texelIndex = uint3(dispatchThreadID.xy, clipmapLevel);
 
-        float2 worldPosition = GetCurrentWorldPosition(pixelPosition, clipmapLevel);
-        float2 positionDdx = ddxPosition(clipmapLevel);
-        float2 positionDdy = ddyPosition(clipmapLevel);
+        float2 worldPosition = GetCurrentWorldPositionFromMacroClipmaps(pixelPosition, clipmapLevel);
+        float clipmapScaleInv = GetMacroClipmapScaleInv(clipmapLevel);
+        float2 positionDdx = ddxPosition(TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance, clipmapScaleInv);
+        float2 positionDdy = ddyPosition(TerrainSrg::m_clipmapData.m_macroClipmapMaxRenderDistance, clipmapScaleInv);
 
         float3 macroColor;
         float3 macroNormal;
-        float normalFactor;
         SampleMacroTexture(worldPosition, positionDdx, positionDdy, macroColor, macroNormal);
 
-        PassSrg::m_macroColorClipmaps[texelAddress] = float4(macroColor, 1.0);
-        PassSrg::m_macroNormalClipmaps[texelAddress] = macroNormal.xy;
+        PassSrg::m_macroColorClipmaps[texelIndex] = float4(macroColor, 1.0);
+        PassSrg::m_macroNormalClipmaps[texelIndex] = macroNormal.xy;
     }
 }
 

+ 6 - 3
Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl

@@ -112,9 +112,13 @@ void GatherSurfaceDataFromClipmaps(
     )
 {
     ClipmapSample clipmapSample = SampleClipmap(position);
-    macroColor = clipmapSample.m_macroColor;
-    macroNormal = clipmapSample.m_macroNormal;
     hasDetailSurface = clipmapSample.m_hasDetail;
+
+    if (clipmapSample.m_hasMacro)
+    {
+        macroColor = clipmapSample.m_macroColor;
+        macroNormal = clipmapSample.m_macroNormal;
+    }
     if (hasDetailSurface)
     {
         detailSurface = clipmapSample.m_detailSurface;
@@ -145,7 +149,6 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
         GatherSurfaceDataFromMaterials(surface.position.xy, detailFactor, macroColor, macroNormal, hasDetailSurface, detailSurface);
     }
 
-
     const float macroRoughness = 1.0;
     const float macroSpecularF0 = 0.5;
     const float macroMetalness = 0.0;

+ 28 - 11
Gems/Terrain/Assets/Shaders/Terrain/TerrainSrg.azsli

@@ -10,8 +10,9 @@
 
 #include <Atom/Features/SrgSemantics.azsli>
 
-static const uint MacroClipmapStackSize = 3u;
-static const uint DetailClipmapStackSize = 5u;
+static const uint MacroClipmapStackSizeMax = 16u;
+static const uint DetailClipmapStackSizeMax = 16u;
+static const uint StackSizeMax = 16u; // = max(MacroClipmapStackSizeMax, DetailClipmapStackSizeMax)
 
 ShaderResourceGroupSemantic SRG_Terrain
 {
@@ -88,23 +89,38 @@ ShaderResourceGroup TerrainSrg : SRG_Terrain
         float2 m_worldBoundsMin;
         float2 m_worldBoundsMax;
 
-        // The max range that the clipmap is covering.
-        float2 m_maxRenderSize;
+        //! The max range that the clipmap is covering.
+        float m_macroClipmapMaxRenderDistance;
+        float m_detailClipmapMaxRenderDistance;
 
-        // The size of a single clipmap.
-        float2 m_clipmapSize;
+        //! The scale base between two adjacent clipmap layers.
+        //! For example, 3 means the (n+1)th clipmap covers 3^2 = 9 times
+        //! to what is covered by the nth clipmap.
+        float m_macroClipmapScaleBase;
+        float m_detailClipmapScaleBase;
+
+        //! Size of the clipmap stack.
+        uint m_macroClipmapStackSize;
+        uint m_detailClipmapStackSize;
+
+        //! The size of the clipmap image in each layer.
+        float m_clipmapSize;
+        
+        uint m_padding;
 
         // Clipmap centers in normalized UV coordinates [0, 1].
         // xy represent previous clipmap centers, zw represent current clipmap centers.
         // (Array elements will always be padded to 16, a float4 size. Storing both centers in float4 saves bandwidth.)
         // They are used for toroidal addressing and may move each frame based on the view point movement.
         // The move distance is scaled differently in each layer.
-        float4 m_clipmapCenters[DetailClipmapStackSize];
+        float4 m_macroClipmapCenters[MacroClipmapStackSizeMax];
+        float4 m_detailClipmapCenters[DetailClipmapStackSizeMax];
 
         // A list of reciprocal the clipmap scale [s],
         // where 1 pixel in the current layer of clipmap represents s meters. 
         // Fast lookup list to avoid redundant calculation in shaders.
-        float4 m_clipmapScaleInv[DetailClipmapStackSize];
+        // x: macro; y: detail
+        float4 m_clipmapScaleInv[StackSizeMax];
     };
 
     // Clipmap SRG
@@ -115,9 +131,10 @@ ShaderResourceGroup TerrainSrg : SRG_Terrain
     Texture2DArray<float4> m_detailColorClipmaps;
     Texture2DArray<float2> m_detailNormalClipmaps;
     Texture2DArray<float> m_detailHeightClipmaps;
-    // Miscellany clipmap combining:
-    // roughness, specularF0, metalness, occlusion
-    Texture2DArray<float4> m_detailMiscClipmaps;
+    Texture2DArray<float> m_detailRoughnessClipmaps;
+    Texture2DArray<float> m_detailSpecularF0Clipmaps;
+    Texture2DArray<float> m_detailMetalnessClipmaps;
+    Texture2DArray<float> m_detailOcclusionClipmaps;
 
     Texture2D<uint4> m_detailMaterialIdImage;
     StructuredBuffer<DetailMaterialData> m_detailMaterialData;

+ 12 - 3
Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainClipmapComputePass.cpp

@@ -102,7 +102,10 @@ namespace Terrain
             AZ::RHI::ShaderInputNameIndex(TerrainClipmapManager::ClipmapImageShaderInput[TerrainClipmapManager::ClipmapName::DetailColor]),
             AZ::RHI::ShaderInputNameIndex(TerrainClipmapManager::ClipmapImageShaderInput[TerrainClipmapManager::ClipmapName::DetailNormal]),
             AZ::RHI::ShaderInputNameIndex(TerrainClipmapManager::ClipmapImageShaderInput[TerrainClipmapManager::ClipmapName::DetailHeight]),
-            AZ::RHI::ShaderInputNameIndex(TerrainClipmapManager::ClipmapImageShaderInput[TerrainClipmapManager::ClipmapName::DetailMisc]),
+            AZ::RHI::ShaderInputNameIndex(TerrainClipmapManager::ClipmapImageShaderInput[TerrainClipmapManager::ClipmapName::DetailRoughness]),
+            AZ::RHI::ShaderInputNameIndex(TerrainClipmapManager::ClipmapImageShaderInput[TerrainClipmapManager::ClipmapName::DetailSpecularF0]),
+            AZ::RHI::ShaderInputNameIndex(TerrainClipmapManager::ClipmapImageShaderInput[TerrainClipmapManager::ClipmapName::DetailMetalness]),
+            AZ::RHI::ShaderInputNameIndex(TerrainClipmapManager::ClipmapImageShaderInput[TerrainClipmapManager::ClipmapName::DetailOcclusion])
         }
     {
     }
@@ -119,14 +122,20 @@ namespace Terrain
             clipmapManager.ImportClipmap(TerrainClipmapManager::ClipmapName::DetailColor, attachmentDatabase);
             clipmapManager.ImportClipmap(TerrainClipmapManager::ClipmapName::DetailNormal, attachmentDatabase);
             clipmapManager.ImportClipmap(TerrainClipmapManager::ClipmapName::DetailHeight, attachmentDatabase);
-            clipmapManager.ImportClipmap(TerrainClipmapManager::ClipmapName::DetailMisc, attachmentDatabase);
+            clipmapManager.ImportClipmap(TerrainClipmapManager::ClipmapName::DetailRoughness, attachmentDatabase);
+            clipmapManager.ImportClipmap(TerrainClipmapManager::ClipmapName::DetailSpecularF0, attachmentDatabase);
+            clipmapManager.ImportClipmap(TerrainClipmapManager::ClipmapName::DetailMetalness, attachmentDatabase);
+            clipmapManager.ImportClipmap(TerrainClipmapManager::ClipmapName::DetailOcclusion, attachmentDatabase);
 
             clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::MacroColor, AZ::RHI::ScopeAttachmentAccess::Read, frameGraph);
             clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::MacroNormal, AZ::RHI::ScopeAttachmentAccess::Read, frameGraph);
             clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::DetailColor, AZ::RHI::ScopeAttachmentAccess::ReadWrite, frameGraph);
             clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::DetailNormal, AZ::RHI::ScopeAttachmentAccess::ReadWrite, frameGraph);
             clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::DetailHeight, AZ::RHI::ScopeAttachmentAccess::ReadWrite, frameGraph);
-            clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::DetailMisc, AZ::RHI::ScopeAttachmentAccess::ReadWrite, frameGraph);
+            clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::DetailRoughness, AZ::RHI::ScopeAttachmentAccess::ReadWrite, frameGraph);
+            clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::DetailSpecularF0, AZ::RHI::ScopeAttachmentAccess::ReadWrite, frameGraph);
+            clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::DetailMetalness, AZ::RHI::ScopeAttachmentAccess::ReadWrite, frameGraph);
+            clipmapManager.UseClipmap(TerrainClipmapManager::ClipmapName::DetailOcclusion, AZ::RHI::ScopeAttachmentAccess::ReadWrite, frameGraph);
         }
 
         ComputePass::SetupFrameGraphDependencies(frameGraph);

+ 148 - 55
Gems/Terrain/Code/Source/TerrainRenderer/TerrainClipmapManager.cpp

@@ -18,6 +18,42 @@ namespace Terrain
         [[maybe_unused]] static const char* TerrainClipmapManagerName = "TerrainClipmapManager";
     }
 
+    //! Calculate how many layers of clipmap is needed.
+    //! Final result must be less or equal than the MacroClipmapStackSizeMax/DetailClipmapStackSizeMax.
+    uint32_t ClipmapConfiguration::CalculateMacroClipmapStackSize() const
+    {
+        float clipmapSize = aznumeric_cast<float>(m_clipmapSize);
+        float minRenderDistance = clipmapSize / m_macroClipmapMaxResolution / 2.0f; // Render distance is half of the image.
+
+        uint32_t stackSizeNeeded = 1u;
+        // Add more layers until it meets the max resolution.
+        for (float distance = m_macroClipmapMaxRenderDistance; distance > minRenderDistance; distance /= m_macroClipmapScaleBase)
+        {
+            ++stackSizeNeeded;
+        }
+
+        AZ_Assert(stackSizeNeeded <= MacroClipmapStackSizeMax, "Stack size needed is bigger than max. Consider increasing MacroClipmapStackSizeMax and the same name constant in TerrainSrg.azsli.");
+
+        return stackSizeNeeded;
+    }
+
+    uint32_t ClipmapConfiguration::CalculateDetailClipmapStackSize() const
+    {
+        float clipmapSize = aznumeric_cast<float>(m_clipmapSize);
+        float minRenderDistance = clipmapSize / m_detailClipmapMaxResolution / 2.0f; // Render distance is half of the image.
+
+        uint32_t stackSizeNeeded = 1u;
+        // Add more layers until it meets the max resolution.
+        for (float distance = m_detailClipmapMaxRenderDistance; distance > minRenderDistance; distance /= m_detailClipmapScaleBase)
+        {
+            ++stackSizeNeeded;
+        }
+
+        AZ_Assert(stackSizeNeeded <= MacroClipmapStackSizeMax, "Stack size needed is bigger than max. Consider increasing DetailClipmapStackSizeMax and the same name constant in TerrainSrg.azsli.");
+
+        return stackSizeNeeded;
+    }
+
     TerrainClipmapManager::TerrainClipmapManager()
         : m_terrainSrgClipmapImageIndex{
             AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::MacroColor]),
@@ -25,7 +61,10 @@ namespace Terrain
             AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::DetailColor]),
             AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::DetailNormal]),
             AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::DetailHeight]),
-            AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::DetailMisc])
+            AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::DetailRoughness]),
+            AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::DetailSpecularF0]),
+            AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::DetailMetalness]),
+            AZ::RHI::ShaderInputNameIndex(ClipmapImageShaderInput[ClipmapName::DetailOcclusion])
         }
     {
     }
@@ -40,10 +79,13 @@ namespace Terrain
             return;
         }
 
-        m_isInitialized = UpdateSrgIndices(terrainSrg);
+        m_macroClipmapStackSize = m_config.CalculateMacroClipmapStackSize();
+        m_detailClipmapStackSize = m_config.CalculateDetailClipmapStackSize();
 
         InitializeClipmapData();
         InitializeClipmapImages();
+
+        m_isInitialized = UpdateSrgIndices(terrainSrg);
     }
     
     bool TerrainClipmapManager::UpdateSrgIndices(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
@@ -92,20 +134,9 @@ namespace Terrain
 
     void TerrainClipmapManager::InitializeClipmapData()
     {
-        float clipmapScaleInv = 1.0f;
-        for (int32_t clipmapIndex = DetailClipmapStackSize - 1; clipmapIndex >= 0; --clipmapIndex)
-        {
-            AZ::Vector4(0.5f).StoreToFloat4(m_clipmapData.m_clipmapCenters[clipmapIndex].data());
-            AZ::Vector4(clipmapScaleInv).StoreToFloat4(m_clipmapData.m_clipmapScaleInv[clipmapIndex].data());
-            clipmapScaleInv /= 2.0f;
-        }
-
         AZ::Vector2::CreateZero().StoreToFloat2(m_clipmapData.m_previousViewPosition.data());
         AZ::Vector2::CreateZero().StoreToFloat2(m_clipmapData.m_currentViewPosition.data());
 
-        m_clipmapData.m_clipmapSize[0] = ClipmapSizeWidth;
-        m_clipmapData.m_clipmapSize[1] = ClipmapSizeHeight;
-
         AZ::Aabb worldBounds = AZ::Aabb::CreateNull();
         AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
             worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
@@ -115,9 +146,31 @@ namespace Terrain
         m_clipmapData.m_worldBoundsMax[0] = worldBounds.GetMax().GetX();
         m_clipmapData.m_worldBoundsMax[1] = worldBounds.GetMax().GetY();
 
-        // Use world size for now.
-        const AZ::Vector3 worldSize = worldBounds.GetMax() - worldBounds.GetMin();
-        AZ::Vector2(worldSize.GetX(), worldSize.GetY()).StoreToFloat2(m_clipmapData.m_maxRenderSize.data());
+        m_clipmapData.m_macroClipmapMaxRenderDistance = m_config.m_macroClipmapMaxRenderDistance;
+        m_clipmapData.m_detailClipmapMaxRenderDistance = m_config.m_detailClipmapMaxRenderDistance;
+
+        m_clipmapData.m_macroClipmapScaleBase = m_config.m_macroClipmapScaleBase;
+        m_clipmapData.m_detailClipmapScaleBase = m_config.m_detailClipmapScaleBase;
+
+        m_clipmapData.m_macroClipmapStackSize = m_macroClipmapStackSize;
+        m_clipmapData.m_detailClipmapStackSize = m_detailClipmapStackSize;
+
+        m_clipmapData.m_clipmapSize = aznumeric_cast<float>(m_config.m_clipmapSize);
+
+        float clipmapScaleInv = 1.0f;
+        for (int32_t clipmapIndex = m_macroClipmapStackSize - 1; clipmapIndex >= 0; --clipmapIndex)
+        {
+            AZ::Vector4(0.5f).StoreToFloat4(m_clipmapData.m_macroClipmapCenters[clipmapIndex].data());
+            m_clipmapData.m_clipmapScaleInv[clipmapIndex][0] = clipmapScaleInv;
+            clipmapScaleInv /= m_config.m_macroClipmapScaleBase;
+        }
+        clipmapScaleInv = 1.0f;
+        for (int32_t clipmapIndex = m_detailClipmapStackSize - 1; clipmapIndex >= 0; --clipmapIndex)
+        {
+            AZ::Vector4(0.5f).StoreToFloat4(m_clipmapData.m_detailClipmapCenters[clipmapIndex].data());
+            m_clipmapData.m_clipmapScaleInv[clipmapIndex][1] = clipmapScaleInv;
+            clipmapScaleInv /= m_config.m_detailClipmapScaleBase;
+        }
     }
 
     void TerrainClipmapManager::InitializeClipmapImages()
@@ -126,34 +179,30 @@ namespace Terrain
 
         AZ::RHI::ImageDescriptor imageDesc;
         imageDesc.m_bindFlags = AZ::RHI::ImageBindFlags::ShaderReadWrite;
-        imageDesc.m_size = AZ::RHI::Size(ClipmapSizeWidth, ClipmapSizeHeight, 1);
+        imageDesc.m_size = AZ::RHI::Size(m_config.m_clipmapSize, m_config.m_clipmapSize, 1);
 
         // TODO: Test and find the most suitable precision for color map.
         imageDesc.m_format = AZ::RHI::Format::R8G8B8A8_UNORM;
-        imageDesc.m_arraySize = MacroClipmapStackSize;
+        imageDesc.m_arraySize = aznumeric_cast<uint16_t>(m_macroClipmapStackSize);
 
         AZ::Name macroColorClipmapName = AZ::Name("MacroColorClipmaps");
         m_clipmaps[ClipmapName::MacroColor] =
             AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, macroColorClipmapName, nullptr, nullptr);
 
-        imageDesc.m_arraySize = DetailClipmapStackSize;
+        imageDesc.m_arraySize = aznumeric_cast<uint16_t>(m_detailClipmapStackSize);
 
         AZ::Name detailColorClipmapName = AZ::Name("DetailColorClipmaps");
         m_clipmaps[ClipmapName::DetailColor] =
             AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, detailColorClipmapName, nullptr, nullptr);
 
-        AZ::Name detailMiscClipmapName = AZ::Name("DetailMiscClipmaps");
-        m_clipmaps[ClipmapName::DetailMisc] =
-            AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, detailMiscClipmapName, nullptr, nullptr);
-
         imageDesc.m_format = AZ::RHI::Format::R16G16_SNORM;
-        imageDesc.m_arraySize = MacroClipmapStackSize;
+        imageDesc.m_arraySize = aznumeric_cast<uint16_t>(m_macroClipmapStackSize);
 
         AZ::Name macroNormalClipmapName = AZ::Name("MacroNormalClipmaps");
         m_clipmaps[ClipmapName::MacroNormal] =
             AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, macroNormalClipmapName, nullptr, nullptr);
 
-        imageDesc.m_arraySize = DetailClipmapStackSize;
+        imageDesc.m_arraySize = aznumeric_cast<uint16_t>(m_detailClipmapStackSize);
 
         AZ::Name detailNormalClipmapName = AZ::Name("DetailNormalClipmaps");
         m_clipmaps[ClipmapName::DetailNormal] =
@@ -164,6 +213,61 @@ namespace Terrain
         AZ::Name detailHeightClipmapName = AZ::Name("DetailHeightClipmaps");
         m_clipmaps[ClipmapName::DetailHeight] =
             AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, detailHeightClipmapName, nullptr, nullptr);
+
+        imageDesc.m_format = AZ::RHI::Format::R8_UNORM;
+
+        AZ::Name detailRoughnessClipmapName = AZ::Name("DetailRoughnessClipmaps");
+        m_clipmaps[ClipmapName::DetailRoughness] =
+            AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, detailRoughnessClipmapName, nullptr, nullptr);
+
+        AZ::Name detailSpecularF0ClipmapName = AZ::Name("DetailSpecularF0Clipmaps");
+        m_clipmaps[ClipmapName::DetailSpecularF0] =
+            AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, detailSpecularF0ClipmapName, nullptr, nullptr);
+
+        AZ::Name detailMetalnessClipmapName = AZ::Name("DetailMetalnessClipmaps");
+        m_clipmaps[ClipmapName::DetailMetalness] =
+            AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, detailMetalnessClipmapName, nullptr, nullptr);
+
+        imageDesc.m_format = AZ::RHI::Format::R16_FLOAT;
+
+        AZ::Name detailOcclusionClipmapName = AZ::Name("DetailOcclusionClipmaps");
+        m_clipmaps[ClipmapName::DetailOcclusion] =
+            AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, detailOcclusionClipmapName, nullptr, nullptr);
+    }
+
+    void TerrainClipmapManager::UpdateClipmapDataHelper(const AZ::Vector2& scaledTranslation, AZStd::array<float, 4>& dataToUpdate)
+    {
+        dataToUpdate[0] = dataToUpdate[2];
+        dataToUpdate[1] = dataToUpdate[3];
+
+        // If normalized translation on a certain level of clipmap goes out of the current clipmap representation,
+        // a full update will be triggered and the center will be reset to the center.
+        if (AZStd::abs(scaledTranslation.GetX()) >= 1.0f || AZStd::abs(scaledTranslation.GetY()) >= 1.0f)
+        {
+            dataToUpdate[2] = 0.5f;
+            dataToUpdate[3] = 0.5f;
+        }
+        else
+        {
+            float centerX = dataToUpdate[2] + scaledTranslation.GetX();
+            float centerY = dataToUpdate[3] + scaledTranslation.GetY();
+
+            AZ_Assert(
+                (centerX > -1.0f) && (centerX < 2.0f) && (centerY > -1.0f) && (centerY < 2.0f),
+                "The new translated clipmap center should be within this range. Otherwise it should fall into the other if branch "
+                "and reset the center to (0.5, 0.5).");
+
+            // Equivalent to modulation.
+            // It wraps around to use toroidal addressing.
+            centerX -= centerX > 1.0f ? 1.0f : 0.0f;
+            centerX += centerX < 0.0f ? 1.0f : 0.0f;
+
+            centerY -= centerY > 1.0f ? 1.0f : 0.0f;
+            centerY += centerY < 0.0f ? 1.0f : 0.0f;
+
+            dataToUpdate[2] = centerX;
+            dataToUpdate[3] = centerY;
+        }
     }
 
     void TerrainClipmapManager::UpdateClipmapData(const AZ::Vector3& cameraPosition)
@@ -176,49 +280,38 @@ namespace Terrain
         m_clipmapData.m_currentViewPosition[0] = cameraPosition.GetX();
         m_clipmapData.m_currentViewPosition[1] = cameraPosition.GetY();
 
-        const AZ::Vector2 maxRenderSize = AZ::Vector2(m_clipmapData.m_maxRenderSize[0], m_clipmapData.m_maxRenderSize[1]);
         const AZ::Vector2 viewTranslation = AZ::Vector2(
             m_clipmapData.m_currentViewPosition[0] - m_clipmapData.m_previousViewPosition[0],
             m_clipmapData.m_currentViewPosition[1] - m_clipmapData.m_previousViewPosition[1]);
-        const AZ::Vector2 normalizedViewTranslation = viewTranslation / maxRenderSize;
 
-        float clipmapScale = 1.0f;
-        for (int32_t clipmapIndex = DetailClipmapStackSize - 1; clipmapIndex >= 0; --clipmapIndex)
+        // macro clipmap data:
         {
-            m_clipmapData.m_clipmapCenters[clipmapIndex][0] = m_clipmapData.m_clipmapCenters[clipmapIndex][2];
-            m_clipmapData.m_clipmapCenters[clipmapIndex][1] = m_clipmapData.m_clipmapCenters[clipmapIndex][3];
+            const AZ::Vector2 normalizedViewTranslation = viewTranslation / m_config.m_macroClipmapMaxRenderDistance;
 
-            const AZ::Vector2 scaledTranslation = normalizedViewTranslation * clipmapScale;
-            // If normalized translation on a certain level of clipmap goes out of the current clipmap representation,
-            // a full update will be triggered and the center will be reset to the center.
-            if (AZStd::abs(scaledTranslation.GetX()) >= 1.0f || AZStd::abs(scaledTranslation.GetY()) >= 1.0f)
+            float clipmapScale = 1.0f;
+            for (int32_t clipmapIndex = m_macroClipmapStackSize - 1; clipmapIndex >= 0; --clipmapIndex)
             {
-                m_clipmapData.m_clipmapCenters[clipmapIndex][2] = 0.5f;
-                m_clipmapData.m_clipmapCenters[clipmapIndex][3] = 0.5f;
+                const AZ::Vector2 scaledTranslation = normalizedViewTranslation * clipmapScale;
+
+                UpdateClipmapDataHelper(scaledTranslation, m_clipmapData.m_macroClipmapCenters[clipmapIndex]);
+
+                clipmapScale *= m_config.m_macroClipmapScaleBase;
             }
-            else
-            {
-                float centerX = m_clipmapData.m_clipmapCenters[clipmapIndex][2] + scaledTranslation.GetX();
-                float centerY = m_clipmapData.m_clipmapCenters[clipmapIndex][3] + scaledTranslation.GetY();
+        }
 
-                AZ_Assert(
-                    (centerX > -1.0f) && (centerX < 2.0f) && (centerY > -1.0f) && (centerY < 2.0f),
-                    "The new translated clipmap center should be within this range. Otherwise it should fall into the other if branch and "
-                    "reset the center to (0.5, 0.5).");
+        // detail clipmap data:
+        {
+            const AZ::Vector2 normalizedViewTranslation = viewTranslation / m_config.m_detailClipmapMaxRenderDistance;
 
-                // Equivalent to modulation.
-                // It wraps around to use toroidal addressing.
-                centerX -= centerX > 1.0f ? 1.0f : 0.0f;
-                centerX += centerX < 0.0f ? 1.0f : 0.0f;
+            float clipmapScale = 1.0f;
+            for (int32_t clipmapIndex = m_detailClipmapStackSize - 1; clipmapIndex >= 0; --clipmapIndex)
+            {
+                const AZ::Vector2 scaledTranslation = normalizedViewTranslation * clipmapScale;
 
-                centerY -= centerY > 1.0f ? 1.0f : 0.0f;
-                centerY += centerY < 0.0f ? 1.0f : 0.0f;
+                UpdateClipmapDataHelper(scaledTranslation, m_clipmapData.m_detailClipmapCenters[clipmapIndex]);
 
-                m_clipmapData.m_clipmapCenters[clipmapIndex][2] = centerX;
-                m_clipmapData.m_clipmapCenters[clipmapIndex][3] = centerY;
+                clipmapScale *= m_config.m_detailClipmapScaleBase;
             }
-
-            clipmapScale *= 2.0f;
         }
     }
 

+ 71 - 18
Gems/Terrain/Code/Source/TerrainRenderer/TerrainClipmapManager.h

@@ -17,6 +17,44 @@
 
 namespace Terrain
 {
+    struct ClipmapConfiguration
+    {
+        AZ_CLASS_ALLOCATOR(ClipmapConfiguration, AZ::SystemAllocator, 0);
+        AZ_RTTI(ClipmapConfiguration, "{5CC8A81E-B850-46BA-A577-E21530D9ED04}");
+
+        ClipmapConfiguration() = default;
+        virtual ~ClipmapConfiguration() = default;
+
+        //! Max clipmap number that can have. Used to initialize fixed arrays.
+        static constexpr uint32_t MacroClipmapStackSizeMax = 16;
+        static constexpr uint32_t DetailClipmapStackSizeMax = 16;
+
+        //! The size of the clipmap image in each layer.
+        uint32_t m_clipmapSize = 1024u;
+
+        //! Max render distance that the lowest resolution clipmap can cover.
+        //! Distance in: meters.
+        float m_macroClipmapMaxRenderDistance = 2048.0f;
+        float m_detailClipmapMaxRenderDistance = 512.0f;
+
+        //! Max resolution of the clipmap stack.
+        //! The actual max resolution may be bigger due to rounding.
+        //! Resolution in: texels per meter.
+        float m_macroClipmapMaxResolution = 2.0f;
+        float m_detailClipmapMaxResolution = 256.0f;
+
+        //! The scale base between two adjacent clipmap layers.
+        //! For example, 3 means the (n+1)th clipmap covers 3^2 = 9 times
+        //! to what is covered by the nth clipmap.
+        float m_macroClipmapScaleBase = 2.0f;
+        float m_detailClipmapScaleBase = 3.0f;
+
+        //! Calculate how many layers of clipmap is needed.
+        //! Final result must be less or equal than the MacroClipmapStackSizeMax/DetailClipmapStackSizeMax.
+        uint32_t CalculateMacroClipmapStackSize() const;
+        uint32_t CalculateDetailClipmapStackSize() const;
+    };
+
     class TerrainClipmapManager
     {
         friend class TerrainClipmapGenerationPass;
@@ -34,9 +72,10 @@ namespace Terrain
             DetailColor,
             DetailNormal,
             DetailHeight,
-            // Miscellany clipmap combining:
-            // roughness, specularF0, metalness, occlusion
-            DetailMisc,
+            DetailRoughness,
+            DetailSpecularF0,
+            DetailMetalness,
+            DetailOcclusion,
 
             Count
         };
@@ -49,7 +88,10 @@ namespace Terrain
             "m_detailColorClipmaps",
             "m_detailNormalClipmaps",
             "m_detailHeightClipmaps",
-            "m_detailMiscClipmaps"
+            "m_detailRoughnessClipmaps",
+            "m_detailSpecularF0Clipmaps",
+            "m_detailMetalnessClipmaps",
+            "m_detailOcclusionClipmaps",
         };
 
         void Initialize(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg);
@@ -67,18 +109,10 @@ namespace Terrain
         AZ::Data::Instance<AZ::RPI::AttachmentImage> GetClipmapImage(ClipmapName clipmapName) const;
     private:
         void UpdateClipmapData(const AZ::Vector3& cameraPosition);
+        void UpdateClipmapDataHelper(const AZ::Vector2& scaledTranslation, AZStd::array<float, 4>& dataToUpdate);
         void InitializeClipmapData();
         void InitializeClipmapImages();
 
-        static constexpr uint32_t MacroClipmapStackSize = 3;
-        static constexpr uint32_t DetailClipmapStackSize = 5;
-        static constexpr uint32_t ClipmapSizeWidth = 1024;
-        static constexpr uint32_t ClipmapSizeHeight = 1024;
-
-        static_assert(DetailClipmapStackSize >= MacroClipmapStackSize,
-            "Macro clipmaps use lower resolutions. Array construction will use"
-            "max(DetailClipmapStackSize, MacroClipmapStackSize).");
-
         struct ClipmapData
         {
             //! The 2D xy-plane view position where the main camera is.
@@ -90,21 +124,36 @@ namespace Terrain
             AZStd::array<float, 2> m_worldBoundsMax;
 
             //! The max range that the clipmap is covering.
-            AZStd::array<float, 2> m_maxRenderSize;
+            float m_macroClipmapMaxRenderDistance;
+            float m_detailClipmapMaxRenderDistance;
 
-            //! The size of a single clipmap.
-            AZStd::array<float, 2> m_clipmapSize;
+            //! The scale base between two adjacent clipmap layers.
+            //! For example, 3 means the (n+1)th clipmap covers 3^2 = 9 times
+            //! to what is covered by the nth clipmap.
+            float m_macroClipmapScaleBase;
+            float m_detailClipmapScaleBase;
+
+            //! Size of the clipmap stack.
+            uint32_t m_macroClipmapStackSize;
+            uint32_t m_detailClipmapStackSize;
+
+            //! The size of the clipmap image in each layer.
+            float m_clipmapSize;
+
+            uint32_t m_padding;
 
             //! Clipmap centers in normalized UV coordinates [0, 1].
             // 0,1: previous clipmap centers; 2,3: current clipmap centers.
             // They are used for toroidal addressing and may move each frame based on the view point movement.
             // The move distance is scaled differently in each layer.
-            AZStd::array<AZStd::array<float, 4>, DetailClipmapStackSize> m_clipmapCenters;
+            AZStd::array<AZStd::array<float, 4>, ClipmapConfiguration::MacroClipmapStackSizeMax> m_macroClipmapCenters;
+            AZStd::array<AZStd::array<float, 4>, ClipmapConfiguration::DetailClipmapStackSizeMax> m_detailClipmapCenters;
 
             //! A list of reciprocal the clipmap scale [s],
             //! where 1 pixel in the current layer of clipmap represents s meters.
             //! Fast lookup list to avoid redundant calculation in shaders.
-            AZStd::array<AZStd::array<float, 4>, DetailClipmapStackSize> m_clipmapScaleInv;
+            //! x: macro; y: detail
+            AZStd::array<AZStd::array<float, 4>, AZStd::max(ClipmapConfiguration::MacroClipmapStackSizeMax, ClipmapConfiguration::DetailClipmapStackSizeMax)> m_clipmapScaleInv;
         };
 
         ClipmapData m_clipmapData;
@@ -114,6 +163,10 @@ namespace Terrain
 
         AZ::Data::Instance<AZ::RPI::AttachmentImage> m_clipmaps[ClipmapName::Count];
 
+        uint32_t m_macroClipmapStackSize;
+        uint32_t m_detailClipmapStackSize;
+
+        ClipmapConfiguration m_config;
         bool m_isInitialized = false;
     };
 }