diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index e6aed28897..1b13d3bb98 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -155,9 +155,11 @@ namespace Terrain const AZ::Transform transform = AZ::Transform::CreateTranslation(worldBounds.GetCenter()); - AZ::Vector2 queryResolution = AZ::Vector2(1.0f); + AZ::Vector2 queryResolution2D = AZ::Vector2(1.0f); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + queryResolution2D, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. + float queryResolution = queryResolution2D.GetX(); // Sectors need to be rebuilt if the world bounds change in the x/y, or the sample spacing changes. m_areaData.m_rebuildSectors = m_areaData.m_rebuildSectors || @@ -165,16 +167,11 @@ namespace Terrain m_areaData.m_terrainBounds.GetMin().GetY() != worldBounds.GetMin().GetY() || m_areaData.m_terrainBounds.GetMax().GetX() != worldBounds.GetMax().GetX() || m_areaData.m_terrainBounds.GetMax().GetY() != worldBounds.GetMax().GetY() || - m_areaData.m_sampleSpacing != queryResolution.GetX(); + m_areaData.m_sampleSpacing != queryResolution; m_areaData.m_transform = transform; m_areaData.m_terrainBounds = worldBounds; - m_areaData.m_heightmapImageWidth = aznumeric_cast(worldBounds.GetXExtent() / queryResolution.GetX()); - m_areaData.m_heightmapImageHeight = aznumeric_cast(worldBounds.GetYExtent() / queryResolution.GetY()); - m_areaData.m_updateWidth = aznumeric_cast(m_dirtyRegion.GetXExtent() / queryResolution.GetX()); - m_areaData.m_updateHeight = aznumeric_cast(m_dirtyRegion.GetYExtent() / queryResolution.GetY()); - // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. - m_areaData.m_sampleSpacing = queryResolution.GetX(); + m_areaData.m_sampleSpacing = queryResolution; m_areaData.m_heightmapUpdated = true; } @@ -261,31 +258,42 @@ namespace Terrain void TerrainFeatureProcessor::UpdateTerrainData() { static const AZ::Name TerrainHeightmapName = AZ::Name(TerrainHeightmapChars); - - uint32_t width = m_areaData.m_updateWidth; - uint32_t height = m_areaData.m_updateHeight; - const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds; + const float queryResolution = m_areaData.m_sampleSpacing; + const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds; - const AZ::RHI::Size worldSize = AZ::RHI::Size(m_areaData.m_heightmapImageWidth, m_areaData.m_heightmapImageHeight, 1); + int32_t heightmapImageXStart = aznumeric_cast(AZStd::ceilf(worldBounds.GetMin().GetX() / queryResolution)); + int32_t heightmapImageXEnd = aznumeric_cast(AZStd::floorf(worldBounds.GetMax().GetX() / queryResolution)) + 1; + int32_t heightmapImageYStart = aznumeric_cast(AZStd::ceilf(worldBounds.GetMin().GetY() / queryResolution)); + int32_t heightmapImageYEnd = aznumeric_cast(AZStd::floorf(worldBounds.GetMax().GetY() / queryResolution)) + 1; + uint32_t heightmapImageWidth = heightmapImageXEnd - heightmapImageXStart; + uint32_t heightmapImageHeight = heightmapImageYEnd - heightmapImageYStart; - if (!m_areaData.m_heightmapImage || m_areaData.m_heightmapImage->GetDescriptor().m_size != worldSize) - { - // World size changed, so the whole world needs updating. - width = worldSize.m_width; - height = worldSize.m_height; - m_dirtyRegion = worldBounds; + const AZ::RHI::Size heightmapSize = AZ::RHI::Size(heightmapImageWidth, heightmapImageHeight, 1); + if (!m_areaData.m_heightmapImage || m_areaData.m_heightmapImage->GetDescriptor().m_size != heightmapSize) + { const AZ::Data::Instance imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool(); AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D( - AZ::RHI::ImageBindFlags::ShaderRead, width, height, AZ::RHI::Format::R16_UNORM + AZ::RHI::ImageBindFlags::ShaderRead, heightmapSize.m_width, heightmapSize.m_height, AZ::RHI::Format::R16_UNORM ); + m_areaData.m_heightmapImage = AZ::RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, TerrainHeightmapName, nullptr, nullptr); AZ_Error(TerrainFPName, m_areaData.m_heightmapImage, "Failed to initialize the heightmap image!"); + + // World size changed, so the whole height map needs updating. + m_dirtyRegion = worldBounds; } + + int32_t xStart = aznumeric_cast(AZStd::ceilf(m_dirtyRegion.GetMin().GetX() / queryResolution)); + int32_t xEnd = aznumeric_cast(AZStd::floorf(m_dirtyRegion.GetMax().GetX() / queryResolution)) + 1; + int32_t yStart = aznumeric_cast(AZStd::ceilf(m_dirtyRegion.GetMin().GetY() / queryResolution)); + int32_t yEnd = aznumeric_cast(AZStd::floorf(m_dirtyRegion.GetMax().GetY() / queryResolution)) + 1; + uint32_t updateWidth = xEnd - xStart; + uint32_t updateHeight = yEnd - yStart; AZStd::vector pixels; - pixels.reserve(width * height); + pixels.reserve(updateWidth * updateHeight); { // Block other threads from accessing the surface data bus while we are in GetHeightFromFloats (which may call into the SurfaceData bus). @@ -297,18 +305,17 @@ namespace Terrain auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - for (uint32_t y = 0; y < height; y++) + for (int32_t y = yStart; y < yEnd; y++) { - for (uint32_t x = 0; x < width; x++) + for (int32_t x = xStart; x < xEnd; x++) { bool terrainExists = true; float terrainHeight = 0.0f; + float xPos = x * queryResolution; + float yPos = y * queryResolution; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( terrainHeight, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, - (x * queryResolution) + m_dirtyRegion.GetMin().GetX(), - (y * queryResolution) + m_dirtyRegion.GetMin().GetY(), - AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, - &terrainExists); + xPos, yPos, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); const float clampedHeight = AZ::GetClamp((terrainHeight - worldBounds.GetMin().GetZ()) / worldBounds.GetExtents().GetZ(), 0.0f, 1.0f); const float expandedHeight = AZStd::roundf(clampedHeight * AZStd::numeric_limits::max()); @@ -321,16 +328,18 @@ namespace Terrain if (m_areaData.m_heightmapImage) { - const float left = (m_dirtyRegion.GetMin().GetX() - worldBounds.GetMin().GetX()) / queryResolution; - const float top = (m_dirtyRegion.GetMin().GetY() - worldBounds.GetMin().GetY()) / queryResolution; + constexpr uint32_t BytesPerPixel = sizeof(uint16_t); + const float left = xStart - (worldBounds.GetMin().GetX() / queryResolution); + const float top = yStart - (worldBounds.GetMin().GetY() / queryResolution); + 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(uint16_t); - imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(uint16_t); - 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_bytesPerRow = updateWidth * BytesPerPixel; + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = updateWidth * updateHeight * BytesPerPixel; + imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = updateHeight; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = updateWidth; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = updateHeight; imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1; imageUpdateRequest.m_sourceData = pixels.data(); imageUpdateRequest.m_image = m_areaData.m_heightmapImage->GetRHIImage(); @@ -492,6 +501,12 @@ namespace Terrain m_areaData.m_heightmapUpdated = false; m_areaData.m_macroMaterialsUpdated = false; + AZStd::array uvStep = + { + 1.0f / aznumeric_cast(m_areaData.m_terrainBounds.GetXExtent() / m_areaData.m_sampleSpacing), + 1.0f / aznumeric_cast(m_areaData.m_terrainBounds.GetYExtent() / m_areaData.m_sampleSpacing), + }; + for (SectorData& sectorData : m_sectorData) { ShaderTerrainData terrainDataForSrg; @@ -509,11 +524,7 @@ namespace Terrain ((yPatch + GridMeters) - terrainBounds.GetMin().GetY()) / terrainBounds.GetYExtent() }; - terrainDataForSrg.m_uvStep = - { - 1.0f / m_areaData.m_heightmapImageWidth, - 1.0f / m_areaData.m_heightmapImageHeight, - }; + terrainDataForSrg.m_uvStep = uvStep; AZ::Transform transform = m_areaData.m_transform; transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ()); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index f82fd8ecb0..91e3ce9a5c 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -169,10 +169,6 @@ namespace Terrain AZ::Transform m_transform{ AZ::Transform::CreateIdentity() }; AZ::Aabb m_terrainBounds{ AZ::Aabb::CreateNull() }; AZ::Data::Instance m_heightmapImage; - uint32_t m_heightmapImageWidth{ 0 }; - uint32_t m_heightmapImageHeight{ 0 }; - uint32_t m_updateWidth{ 0 }; - uint32_t m_updateHeight{ 0 }; float m_sampleSpacing{ 0.0f }; bool m_heightmapUpdated{ true }; bool m_macroMaterialsUpdated{ true };