diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailHelpers.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailHelpers.azsli index b18f2885db..b566b727ff 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailHelpers.azsli +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailHelpers.azsli @@ -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); diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl index 741f45d775..c9c3e0ab00 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -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; diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainSrg.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainSrg.azsli index 3fcf8d75ca..bbf060a1ec 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainSrg.azsli +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainSrg.azsli @@ -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; } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/ClipmapBounds.h b/Gems/Terrain/Code/Source/TerrainRenderer/ClipmapBounds.h index 1aac4d8c9e..7c11f4b3a5 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/ClipmapBounds.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/ClipmapBounds.h @@ -89,6 +89,7 @@ namespace Terrain { public: + ClipmapBounds() = default; explicit ClipmapBounds(const ClipmapBoundsDescriptor& desc); ~ClipmapBounds() = default; diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp index 1f43b476a1..96b363d8c6 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp @@ -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::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::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(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(AZStd::roundf(cameraPosition.GetX() / DetailTextureScale)); - int32_t newDetailTexturePosY = aznumeric_cast(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(AZStd::roundf(clampedCoverage.GetMin().GetX() / DetailTextureScale)); - updateBounds.m_min.m_y = aznumeric_cast(AZStd::roundf(clampedCoverage.GetMin().GetY() / DetailTextureScale)); - updateBounds.m_max.m_x = aznumeric_cast(AZStd::roundf(clampedCoverage.GetMax().GetX() / DetailTextureScale)); - updateBounds.m_max.m_y = aznumeric_cast(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 textureSpaceAreas; - AZStd::array scaledWorldSpaceAreas; - uint8_t updateAreaCount = CalculateUpdateRegions(updateArea, textureBounds, centerPixel, textureSpaceAreas, scaledWorldSpaceAreas); + AZStd::vector 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 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(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(materialId); - float totalWeight = firstWeight + surfaceTagWeight.m_weight; - float blendWeight = 1.0f - (firstWeight / totalWeight); - pixels.at(index).m_blend = aznumeric_cast(AZStd::round(blendWeight * 255.0f)); - break; - } + pixels.at(index).m_material1 = aznumeric_cast(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(materialId); + float totalWeight = firstWeight + surfaceTagWeight.m_weight; + float blendWeight = 1.0f - (firstWeight / totalWeight); + pixels.at(index).m_blend = aznumeric_cast(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(left); - imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast(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& textureSpaceAreas, AZStd::array& 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(left); + imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast(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) diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h index 98f2c26dd8..667b1b2e85 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -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& textureSpaceAreas, AZStd::array& scaledWorldSpaceAreas); - DetailMaterialListRegion* FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); DetailMaterialListRegion& FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); void RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& 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::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 };