TerrainDetailMaterialManager to use ClipmapBounds for its detail mateiral id texture. (#7182)

* Switching TerrainDetailMaterialManager to use ClipmapBounds for its detail mateiral id texture. This results in a lot of code removal and some simplifications for the shader.

Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>

* Changing some names to be clearer

Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>

* Reducing the update multiple back to 1 since this texture is updated purely on the CPU

Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>

* Updates from PR review

Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>
This commit is contained in:
Ken Pruiksma
2022-01-27 15:33:23 -06:00
committed by GitHub
parent cc1e86ccde
commit 7946886126
6 changed files with 116 additions and 303 deletions
@@ -274,25 +274,20 @@ instance, if detailMaterialIdUv falls perfectly in-between all 4 samples, then e
Each sample can have two different detail materials defined with a blend value to determine their relative contribution.
The detailUv is used for sampling the textures of each detail material.
*/
bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdUv, float2 detailUv)
bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdCoord, float2 detailUv)
{
float2 textureSize;
TerrainSrg::m_detailMaterialIdImage.GetDimensions(textureSize.x, textureSize.y);
float2 detailMaterialIdCoord = detailMaterialIdUv * textureSize; // uv -> pixel coordinate
// detailMaterialIdCoord could be negative, so add textureSize to ensure it is positive
detailMaterialIdCoord += textureSize;
// The detail material id texture wraps since the "center" point can be anywhere in the texture, so mod by texturesize
int2 detailMaterailIdTopLeft = int2(detailMaterialIdCoord) % textureSize;
int2 detailMaterailIdBottomRight = (int2(detailMaterialIdCoord) + 1) % textureSize;
int2 detailMaterialIdTopLeft = ((int2(detailMaterialIdCoord) % textureSize) + textureSize) % textureSize;
int2 detailMaterialIdBottomRight = (detailMaterialIdTopLeft + 1) % textureSize;
// Using Load() to gather the nearest 4 samples (Gather4() isn't used because of precision issues with uvs).
uint4 s1 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterailIdTopLeft.x, detailMaterailIdBottomRight.y, 0));
uint4 s2 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterailIdBottomRight, 0));
uint4 s3 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterailIdBottomRight.x, detailMaterailIdTopLeft.y, 0));
uint4 s4 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterailIdTopLeft, 0));
uint4 s1 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterialIdTopLeft.x, detailMaterialIdBottomRight.y, 0));
uint4 s2 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterialIdBottomRight, 0));
uint4 s3 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterialIdBottomRight.x, detailMaterialIdTopLeft.y, 0));
uint4 s4 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterialIdTopLeft, 0));
uint4 material1 = uint4(s1.x, s2.x, s3.x, s4.x);
uint4 material2 = uint4(s1.y, s2.y, s3.y, s4.y);
@@ -143,16 +143,13 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
// ------- Base Color -------
DetailSurface detailSurface = GetDefaultDetailSurface();
float2 detailRegionMin = TerrainSrg::m_detailAabb.xy;
float2 detailRegionMax = TerrainSrg::m_detailAabb.zw;
float2 detailRegionUv = (surface.position.xy - detailRegionMin) / (detailRegionMax - detailRegionMin);
float2 detailRegionCoord = surface.position.xy * TerrainSrg::m_detailMaterialIdScale;
bool hasDetailSurface = false;
// Check to make sure we're inside the detail texture's bounds and within where detail textures should be drawn.
if (detailFactor < 1.0 && all(detailRegionUv > TerrainSrg::m_detailHalfPixelUv) && all(detailRegionUv < 1.0 - TerrainSrg::m_detailHalfPixelUv))
// Only sample detail textures if inside where detail materials should be drawn.
if (detailFactor < 1.0)
{
detailRegionUv += TerrainSrg::m_detailMaterialIdImageCenter - 0.5;
hasDetailSurface = GetDetailSurface(detailSurface, detailRegionUv, detailUv);
hasDetailSurface = GetDetailSurface(detailSurface, detailRegionCoord, detailUv);
}
const float macroRoughness = 1.0;
@@ -81,9 +81,7 @@ ShaderResourceGroup TerrainSrg : SRG_Terrain
MacroMaterialGrid m_macroMaterialGrid;
Texture2D m_textures[]; // bindless array of all textures for detail and macro materials
float2 m_detailMaterialIdImageCenter;
float m_detailHalfPixelUv;
float4 m_detailAabb;
float m_detailMaterialIdScale;
}
@@ -89,6 +89,7 @@ namespace Terrain
{
public:
ClipmapBounds() = default;
explicit ClipmapBounds(const ClipmapBoundsDescriptor& desc);
~ClipmapBounds() = default;
@@ -62,9 +62,7 @@ namespace Terrain
{
static const char* const DetailMaterialIdImage("m_detailMaterialIdImage");
static const char* const DetailMaterialData("m_detailMaterialData");
static const char* const DetailMaterialIdImageCenter("m_detailMaterialIdImageCenter");
static const char* const DetailHalfPixelUv("m_detailHalfPixelUv");
static const char* const DetailAabb("m_detailAabb");
static const char* const DetailMaterialScale("m_detailMaterialIdScale");
}
AZ_CVAR(bool,
@@ -98,6 +96,14 @@ namespace Terrain
{
return;
}
ClipmapBoundsDescriptor desc;
desc.m_clipmapUpdateMultiple = 1;
desc.m_clipToWorldScale = DetailTextureScale;
desc.m_size = DetailTextureSize;
// Initialize world space to a value that won't match the initial camera position.
desc.m_worldSpaceCenter = AZ::Vector2(AZStd::numeric_limits<float>::max(), 0.0f);
m_detailMaterialIdBounds = ClipmapBounds(desc);
if (UpdateSrgIndices(terrainSrg))
{
@@ -144,14 +150,8 @@ namespace Terrain
m_detailMaterialIdPropertyIndex = terrainSrgLayout->FindShaderInputImageIndex(AZ::Name(TerrainSrgInputs::DetailMaterialIdImage));
AZ_Error(TerrainDetailMaterialManagerName, m_detailMaterialIdPropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailMaterialIdImage);
m_detailCenterPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailMaterialIdImageCenter));
AZ_Error(TerrainDetailMaterialManagerName, m_detailCenterPropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailMaterialIdImageCenter);
m_detailHalfPixelUvPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailHalfPixelUv));
AZ_Error(TerrainDetailMaterialManagerName, m_detailHalfPixelUvPropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailHalfPixelUv);
m_detailAabbPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailAabb));
AZ_Error(TerrainDetailMaterialManagerName, m_detailAabbPropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailAabb);
m_detailScalePropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailMaterialScale));
AZ_Error(TerrainDetailMaterialManagerName, m_detailScalePropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailMaterialScale);
// Set up the gpu buffer for detail material data
AZ::Render::GpuBufferHandler::Descriptor desc;
@@ -163,9 +163,7 @@ namespace Terrain
bool IndicesValid =
m_detailMaterialIdPropertyIndex.IsValid() &&
m_detailCenterPropertyIndex.IsValid() &&
m_detailHalfPixelUvPropertyIndex.IsValid() &&
m_detailAabbPropertyIndex.IsValid();
m_detailScalePropertyIndex.IsValid();
m_detailImageNeedsUpdate = true;
m_detailMaterialBufferNeedsUpdate = true;
@@ -214,9 +212,6 @@ namespace Terrain
m_detailMaterialDataBuffer.Release();
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
m_previousCameraPosition = AZ::Vector3(AZStd::numeric_limits<float>::max(), 0.0, 0.0);
m_detailTextureBounds = {};
m_detailTextureCenter = {};
m_detailMaterialBufferNeedsUpdate = false;
m_detailImageNeedsUpdate = false;
@@ -234,54 +229,19 @@ namespace Terrain
m_detailMaterialBufferNeedsUpdate = false;
m_detailMaterialDataBuffer.UpdateBuffer(m_detailMaterialShaderData.GetRawData(), aznumeric_cast<uint32_t>(m_detailMaterialShaderData.GetSize()));
}
CheckUpdateDetailTexture(cameraPosition);
if (m_dirtyDetailRegion.IsValid() || !cameraPosition.IsClose(m_previousCameraPosition) || m_detailImageNeedsUpdate)
if (m_detailImageNeedsUpdate)
{
if (r_terrainDebugDetailImageUpdates)
{
AZ_Printf("TerrainDetailMaterialManager", "Previous Camera: (%f, %f, %f) New Cameara: (%f, %f, %f)",
m_previousCameraPosition.GetX(), m_previousCameraPosition.GetY(), m_previousCameraPosition.GetZ(),
cameraPosition.GetX(), cameraPosition.GetY(), cameraPosition.GetZ());
}
int32_t newDetailTexturePosX = aznumeric_cast<int32_t>(AZStd::roundf(cameraPosition.GetX() / DetailTextureScale));
int32_t newDetailTexturePosY = aznumeric_cast<int32_t>(AZStd::roundf(cameraPosition.GetY() / DetailTextureScale));
Aabb2i newBounds;
newBounds.m_min.m_x = newDetailTexturePosX - DetailTextureSizeHalf;
newBounds.m_min.m_y = newDetailTexturePosY - DetailTextureSizeHalf;
newBounds.m_max.m_x = newDetailTexturePosX + DetailTextureSizeHalf;
newBounds.m_max.m_y = newDetailTexturePosY + DetailTextureSizeHalf;
// Use modulo to find the center point in texture space. Care must be taken so negative values are
// handled appropriately (ie, we want -1 % 1024 to equal 1023, not -1)
Vector2i newCenter;
newCenter.m_x = (DetailTextureSize + (newDetailTexturePosX % DetailTextureSize)) % DetailTextureSize;
newCenter.m_y = (DetailTextureSize + (newDetailTexturePosY % DetailTextureSize)) % DetailTextureSize;
CheckUpdateDetailTexture(newBounds, newCenter);
m_detailTextureBounds = newBounds;
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
m_previousCameraPosition = cameraPosition;
AZ::Vector4 detailAabb = AZ::Vector4(
m_detailTextureBounds.m_min.m_x * DetailTextureScale,
m_detailTextureBounds.m_min.m_y * DetailTextureScale,
m_detailTextureBounds.m_max.m_x * DetailTextureScale,
m_detailTextureBounds.m_max.m_y * DetailTextureScale
);
AZ::Vector2 detailUvOffset = AZ::Vector2(float(newCenter.m_x) / DetailTextureSize, float(newCenter.m_y) / DetailTextureSize);
terrainSrg->SetConstant(m_detailAabbPropertyIndex, detailAabb);
terrainSrg->SetConstant(m_detailHalfPixelUvPropertyIndex, 0.5f / DetailTextureSize);
terrainSrg->SetConstant(m_detailCenterPropertyIndex, detailUvOffset);
terrainSrg->SetConstant(m_detailScalePropertyIndex, 1.0f / DetailTextureScale);
terrainSrg->SetImage(m_detailMaterialIdPropertyIndex, m_detailTextureImage);
m_detailMaterialDataBuffer.UpdateSrg(terrainSrg.get());
m_detailImageNeedsUpdate = false;
}
m_detailImageNeedsUpdate = false;
}
void TerrainDetailMaterialManager::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
@@ -591,15 +551,13 @@ namespace Terrain
m_detailMaterialBufferNeedsUpdate = true;
}
void TerrainDetailMaterialManager::CheckUpdateDetailTexture(const Aabb2i& newBounds, const Vector2i& newCenter)
void TerrainDetailMaterialManager::CheckUpdateDetailTexture(const AZ::Vector3& cameraPosition)
{
if (r_terrainDebugDetailImageUpdates)
{
AZ_Printf("TerrainDetailMaterialManager", "Old Bounds: m(%i, %i)M(%i, %i) New Bounds: m(%i, %i)M(%i, %i)",
m_detailTextureBounds.m_min.m_x, m_detailTextureBounds.m_min.m_y, m_detailTextureBounds.m_max.m_x, m_detailTextureBounds.m_max.m_y,
newBounds.m_min.m_x, newBounds.m_min.m_y, newBounds.m_max.m_x, newBounds.m_max.m_y
);
}
AZ::Aabb untouchedRegion = AZ::Aabb::CreateNull();
ClipmapBounds::ClipmapBoundsRegionList edgeUpdatedRegions =
m_detailMaterialIdBounds.UpdateCenter(AZ::Vector2(cameraPosition.GetX(), cameraPosition.GetY()), &untouchedRegion);
if (!m_detailTextureImage)
{
// If the m_detailTextureImage doesn't exist, create it and populate the entire texture
@@ -611,111 +569,36 @@ namespace Terrain
const AZ::Name TerrainDetailName = AZ::Name(TerrainDetailChars);
m_detailTextureImage = AZ::RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, TerrainDetailName, nullptr, nullptr);
AZ_Error(TerrainDetailMaterialManagerName, m_detailTextureImage, "Failed to initialize the detail texture image.");
UpdateDetailTexture(newBounds, newBounds, newCenter);
ClipmapBounds::ClipmapBoundsRegionList updateRegions = m_detailMaterialIdBounds.TransformRegion(m_detailMaterialIdBounds.GetWorldBounds());
for (auto& region : updateRegions)
{
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
}
}
else
{
// If the new bounds of the detail texture are different than the old bounds, then the edges of the texture need to be updated.
int32_t offsetX = m_detailTextureBounds.m_min.m_x - newBounds.m_min.m_x;
// Horizontal edge update
if (newBounds.m_min.m_x != m_detailTextureBounds.m_min.m_x)
// Update the edge regions
for (auto& region : edgeUpdatedRegions)
{
Aabb2i updateBounds;
if (newBounds.m_min.m_x < m_detailTextureBounds.m_min.m_x)
{
updateBounds.m_min.m_x = newBounds.m_min.m_x;
updateBounds.m_max.m_x = m_detailTextureBounds.m_min.m_x;
}
else
{
updateBounds.m_min.m_x = m_detailTextureBounds.m_max.m_x;
updateBounds.m_max.m_x = newBounds.m_max.m_x;
}
updateBounds.m_min.m_y = newBounds.m_min.m_y;
updateBounds.m_max.m_y = newBounds.m_max.m_y;
if (r_terrainDebugDetailImageUpdates)
{
AZ_Printf("TerrainDetailMaterialManager", "Updating horizontal edge: m(%i, %i)M(%i, %i)",
updateBounds.m_min.m_x, updateBounds.m_min.m_y, updateBounds.m_max.m_x, updateBounds.m_max.m_y);
}
UpdateDetailTexture(updateBounds, newBounds, newCenter);
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
}
// Vertical edge update
if (newBounds.m_min.m_y != m_detailTextureBounds.m_min.m_y)
{
Aabb2i updateBounds;
// Don't update areas that have already been updated in the horizontal update.
updateBounds.m_min.m_x = newBounds.m_min.m_x + AZ::GetMax(0, offsetX);
updateBounds.m_max.m_x = newBounds.m_max.m_x + AZ::GetMin(0, offsetX);
if (newBounds.m_min.m_y < m_detailTextureBounds.m_min.m_y)
{
updateBounds.m_min.m_y = newBounds.m_min.m_y;
updateBounds.m_max.m_y = m_detailTextureBounds.m_min.m_y;
}
else
{
updateBounds.m_min.m_y = m_detailTextureBounds.m_max.m_y;
updateBounds.m_max.m_y = newBounds.m_max.m_y;
}
if (r_terrainDebugDetailImageUpdates)
{
AZ_Printf("TerrainDetailMaterialManager", "Updating vertical edge: m(%i, %i)M(%i, %i)",
updateBounds.m_min.m_x, updateBounds.m_min.m_y, updateBounds.m_max.m_x, updateBounds.m_max.m_y);
}
UpdateDetailTexture(updateBounds, newBounds, newCenter);
}
m_dirtyDetailRegion = m_dirtyDetailRegion.GetClamped(untouchedRegion);
if (m_dirtyDetailRegion.IsValid())
{
if (r_terrainDebugDetailImageUpdates)
ClipmapBounds::ClipmapBoundsRegionList updateRegions = m_detailMaterialIdBounds.TransformRegion(m_dirtyDetailRegion);
for (auto& region : updateRegions)
{
AZ_Printf("TerrainDetailMaterialManager", "m_dirtyDetailRegion: m(%f, %f)M(%f, %f)",
m_dirtyDetailRegion.GetMin().GetX(), m_dirtyDetailRegion.GetMin().GetY(), m_dirtyDetailRegion.GetMax().GetX(), m_dirtyDetailRegion.GetMax().GetY());
}
// If any regions are marked as dirty, then they should be updated.
AZ::Vector3 currentMin = AZ::Vector3(newBounds.m_min.m_x * DetailTextureScale, newBounds.m_min.m_y * DetailTextureScale, -0.5f);
AZ::Vector3 currentMax = AZ::Vector3(newBounds.m_max.m_x * DetailTextureScale, newBounds.m_max.m_y * DetailTextureScale, 0.5f);
AZ::Aabb detailTextureCoverage = AZ::Aabb::CreateFromMinMax(currentMin, currentMax);
AZ::Vector3 previousMin = AZ::Vector3(m_detailTextureBounds.m_min.m_x * DetailTextureScale, m_detailTextureBounds.m_min.m_y * DetailTextureScale, -0.5f);
AZ::Vector3 previousMax = AZ::Vector3(m_detailTextureBounds.m_max.m_x * DetailTextureScale, m_detailTextureBounds.m_max.m_y * DetailTextureScale, 0.5f);
AZ::Aabb previousCoverage = AZ::Aabb::CreateFromMinMax(previousMin, previousMax);
// Area of texture not already updated by camera movement above.
AZ::Aabb clampedCoverage = previousCoverage.GetClamped(detailTextureCoverage);
// Clamp the dirty region to the area of the detail texture that is visible and not already updated.
clampedCoverage.Clamp(m_dirtyDetailRegion);
if (clampedCoverage.IsValid())
{
Aabb2i updateBounds;
updateBounds.m_min.m_x = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMin().GetX() / DetailTextureScale));
updateBounds.m_min.m_y = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMin().GetY() / DetailTextureScale));
updateBounds.m_max.m_x = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMax().GetX() / DetailTextureScale));
updateBounds.m_max.m_y = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMax().GetY() / DetailTextureScale));
if (updateBounds.m_min.m_x < updateBounds.m_max.m_x && updateBounds.m_min.m_y < updateBounds.m_max.m_y)
{
if (r_terrainDebugDetailImageUpdates)
{
AZ_Printf("TerrainDetailMaterialManager", "Updating dirty region: m(%i, %i)M(%i, %i)",
updateBounds.m_min.m_x, updateBounds.m_min.m_y, updateBounds.m_max.m_x, updateBounds.m_max.m_y);
}
UpdateDetailTexture(updateBounds, newBounds, newCenter);
}
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
}
}
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
}
}
void TerrainDetailMaterialManager::UpdateDetailTexture(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel)
void TerrainDetailMaterialManager::UpdateDetailTexture(const AZ::Aabb& worldUpdateAabb, const Aabb2i& textureUpdateAabb)
{
if (!m_detailTextureImage)
{
@@ -729,129 +612,77 @@ namespace Terrain
uint8_t m_blend{ 0 }; // 0 = full weight on material1, 255 = full weight on material2
uint8_t m_padding{ 0 };
};
const int32_t width = textureUpdateAabb.m_max.m_x - textureUpdateAabb.m_min.m_x;
const int32_t height = textureUpdateAabb.m_max.m_y - textureUpdateAabb.m_min.m_y;
// Because the center of the detail texture may be offset, each update area may actually need to be split into
// up to 4 separate update areas in each sector of the quadrant.
AZStd::array<Aabb2i, 4> textureSpaceAreas;
AZStd::array<Aabb2i, 4> scaledWorldSpaceAreas;
uint8_t updateAreaCount = CalculateUpdateRegions(updateArea, textureBounds, centerPixel, textureSpaceAreas, scaledWorldSpaceAreas);
AZStd::vector<DetailMaterialPixel> pixels(width * height);
uint32_t index = 0;
if (updateAreaCount > 0)
auto perPositionCallback = [this, &pixels, &index](
[[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
const AzFramework::SurfaceData::SurfacePoint& surfacePoint,
[[maybe_unused]] bool terrainExists)
{
m_detailImageNeedsUpdate = true;
}
// Pull the data for each area updated and use it to construct an update for the detail material id texture.
for (uint8_t i = 0; i < updateAreaCount; ++i)
{
const Aabb2i& quadrantTextureArea = textureSpaceAreas[i];
const Aabb2i& quadrantWorldArea = scaledWorldSpaceAreas[i];
AZStd::vector<DetailMaterialPixel> pixels;
pixels.resize((quadrantWorldArea.m_max.m_x - quadrantWorldArea.m_min.m_x) * (quadrantWorldArea.m_max.m_y - quadrantWorldArea.m_min.m_y));
uint32_t index = 0;
auto perPositionCallback = [this, &pixels, &index](
[[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
const AzFramework::SurfaceData::SurfacePoint& surfacePoint,
[[maybe_unused]] bool terrainExists)
// Store the top two surface weights in the texture with m_blend storing the relative weight.
bool isFirstMaterial = true;
float firstWeight = 0.0f;
AZ::Vector2 position(surfacePoint.m_position.GetX(), surfacePoint.m_position.GetY());
for (const auto& surfaceTagWeight : surfacePoint.m_surfaceTags)
{
// Store the top two surface weights in the texture with m_blend storing the relative weight.
bool isFirstMaterial = true;
float firstWeight = 0.0f;
AZ::Vector2 position(surfacePoint.m_position.GetX(), surfacePoint.m_position.GetY());
for (const auto& surfaceTagWeight : surfacePoint.m_surfaceTags)
if (surfaceTagWeight.m_weight > 0.0f)
{
if (surfaceTagWeight.m_weight > 0.0f)
AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType;
uint16_t materialId = GetDetailMaterialForSurfaceTypeAndPosition(surfaceType, position);
if (materialId != m_detailMaterials.NoFreeSlot && materialId < 255)
{
AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType;
uint16_t materialId = GetDetailMaterialForSurfaceTypeAndPosition(surfaceType, position);
if (materialId != m_detailMaterials.NoFreeSlot && materialId < 255)
if (isFirstMaterial)
{
if (isFirstMaterial)
{
pixels.at(index).m_material1 = aznumeric_cast<uint8_t>(materialId);
firstWeight = surfaceTagWeight.m_weight;
// m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct.
isFirstMaterial = false;
}
else
{
pixels.at(index).m_material2 = aznumeric_cast<uint8_t>(materialId);
float totalWeight = firstWeight + surfaceTagWeight.m_weight;
float blendWeight = 1.0f - (firstWeight / totalWeight);
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
break;
}
pixels.at(index).m_material1 = aznumeric_cast<uint8_t>(materialId);
firstWeight = surfaceTagWeight.m_weight;
// m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct.
isFirstMaterial = false;
}
else
{
pixels.at(index).m_material2 = aznumeric_cast<uint8_t>(materialId);
float totalWeight = firstWeight + surfaceTagWeight.m_weight;
float blendWeight = 1.0f - (firstWeight / totalWeight);
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
break;
}
}
else
{
break; // since the list is ordered, no other materials are in the list with positive weights.
}
}
++index;
};
AZ::Vector3 worldMin(quadrantWorldArea.m_min.m_x * DetailTextureScale, quadrantWorldArea.m_min.m_y * DetailTextureScale, 0.0f);
AZ::Vector3 worldMax(quadrantWorldArea.m_max.m_x * DetailTextureScale, quadrantWorldArea.m_max.m_y * DetailTextureScale, 0.0f);
AZ::Vector2 stepSize(DetailTextureScale);
AZ::Aabb region;
region.Set(worldMin, worldMax);
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromRegion,
region, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
const int32_t left = quadrantTextureArea.m_min.m_x;
const int32_t top = quadrantTextureArea.m_min.m_y;
const int32_t width = quadrantTextureArea.m_max.m_x - quadrantTextureArea.m_min.m_x;
const int32_t height = quadrantTextureArea.m_max.m_y - quadrantTextureArea.m_min.m_y;
AZ::RHI::ImageUpdateRequest imageUpdateRequest;
imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = aznumeric_cast<uint32_t>(left);
imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast<uint32_t>(top);
imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = width * sizeof(DetailMaterialPixel);
imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(DetailMaterialPixel);
imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = height;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = width;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = height;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1;
imageUpdateRequest.m_sourceData = pixels.data();
imageUpdateRequest.m_image = m_detailTextureImage->GetRHIImage();
m_detailTextureImage->UpdateImageContents(imageUpdateRequest);
}
}
uint8_t TerrainDetailMaterialManager::CalculateUpdateRegions(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel,
AZStd::array<Aabb2i, 4>& textureSpaceAreas, AZStd::array<Aabb2i, 4>& scaledWorldSpaceAreas)
{
Vector2i centerOffset = { centerPixel.m_x - DetailTextureSizeHalf, centerPixel.m_y - DetailTextureSizeHalf };
int32_t quadrantXOffset = centerPixel.m_x < DetailTextureSizeHalf ? DetailTextureSize : -DetailTextureSize;
int32_t quadrantYOffset = centerPixel.m_y < DetailTextureSizeHalf ? DetailTextureSize : -DetailTextureSize;
uint8_t numQuadrants = 0;
// For each of the 4 quadrants:
auto calculateQuadrant = [&](Vector2i quadrantOffset)
{
Aabb2i offsetUpdateArea = updateArea + centerOffset + quadrantOffset;
Aabb2i updateSectionBounds = textureBounds.GetClamped(offsetUpdateArea);
if (updateSectionBounds.IsValid())
{
textureSpaceAreas[numQuadrants] = updateSectionBounds - textureBounds.m_min;
scaledWorldSpaceAreas[numQuadrants] = updateSectionBounds - centerOffset - quadrantOffset;
++numQuadrants;
else
{
break; // since the list is ordered, no other materials are in the list with positive weights.
}
}
++index;
};
AZ::Vector2 stepSize(DetailTextureScale);
AZ::Aabb offsetWorldAabb = worldUpdateAabb.GetTranslated(AZ::Vector3(DetailTextureScale * 0.5f)); // offset by half a pixel
calculateQuadrant({ 0, 0 });
calculateQuadrant({ quadrantXOffset, 0 });
calculateQuadrant({ 0, quadrantYOffset });
calculateQuadrant({ quadrantXOffset, quadrantYOffset });
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromRegion,
offsetWorldAabb, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
return numQuadrants;
const int32_t left = textureUpdateAabb.m_min.m_x;
const int32_t top = textureUpdateAabb.m_min.m_y;
AZ::RHI::ImageUpdateRequest imageUpdateRequest;
imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = aznumeric_cast<uint32_t>(left);
imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast<uint32_t>(top);
imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = width * sizeof(DetailMaterialPixel);
imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(DetailMaterialPixel);
imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = height;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = width;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = height;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1;
imageUpdateRequest.m_sourceData = pixels.data();
imageUpdateRequest.m_image = m_detailTextureImage->GetRHIImage();
m_detailTextureImage->UpdateImageContents(imageUpdateRequest);
}
uint16_t TerrainDetailMaterialManager::GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position)
@@ -16,6 +16,7 @@
#include <TerrainRenderer/Aabb2i.h>
#include <TerrainRenderer/BindlessImageArrayHandler.h>
#include <TerrainRenderer/ClipmapBounds.h>
#include <TerrainRenderer/TerrainAreaMaterialRequestBus.h>
#include <TerrainRenderer/Vector2i.h>
@@ -178,22 +179,16 @@ namespace Terrain
//! Updates a specific detail material with settings from a material instance
void UpdateDetailMaterialData(uint16_t detailMaterialIndex, MaterialInstance material);
//! Checks to see if the detail material id texture needs to update based on new center and bounds. Any
//! Checks to see if the detail material id texture needs to update based on the camera position. Any
//! required updates are then executed.
void CheckUpdateDetailTexture(const Aabb2i& newBounds, const Vector2i& newCenter);
void CheckUpdateDetailTexture(const AZ::Vector3& cameraPosition);
//! Updates the detail texture in a given area
void UpdateDetailTexture(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel);
void UpdateDetailTexture(const AZ::Aabb& worldUpdateAabb, const Aabb2i& textureUpdateAabb);
//! Finds the detail material Id for a surface type and position
uint16_t GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position);
//! Calculates which regions of the detail material id texture need to be updated based on the update area. Since
//! the "center" of the detail material id texture can move, a single update region in contiguous world space may
//! map to up to 4 different areas on teh detail material id texture.
uint8_t CalculateUpdateRegions(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel,
AZStd::array<Aabb2i, 4>& textureSpaceAreas, AZStd::array<Aabb2i, 4>& scaledWorldSpaceAreas);
DetailMaterialListRegion* FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
DetailMaterialListRegion& FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
void RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
@@ -208,15 +203,11 @@ namespace Terrain
AZ::Render::GpuBufferHandler m_detailMaterialDataBuffer;
AZ::Aabb m_dirtyDetailRegion{ AZ::Aabb::CreateNull() };
AZ::Vector3 m_previousCameraPosition = AZ::Vector3(AZStd::numeric_limits<float>::max(), 0.0, 0.0);
Aabb2i m_detailTextureBounds;
Vector2i m_detailTextureCenter;
ClipmapBounds m_detailMaterialIdBounds;
AZ::RHI::ShaderInputImageIndex m_detailMaterialIdPropertyIndex;
AZ::RHI::ShaderInputBufferIndex m_detailMaterialDataIndex;
AZ::RHI::ShaderInputConstantIndex m_detailCenterPropertyIndex;
AZ::RHI::ShaderInputConstantIndex m_detailAabbPropertyIndex;
AZ::RHI::ShaderInputConstantIndex m_detailHalfPixelUvPropertyIndex;
AZ::RHI::ShaderInputConstantIndex m_detailScalePropertyIndex;
bool m_isInitialized{ false };
bool m_detailMaterialBufferNeedsUpdate{ false };