Files
o3de/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp
T
Ken Pruiksma e8b7bba0f2 Adding support for blending the terain detail material with the macro material. (#7557)
* Adding support for blending the terain detail material with the macro material. Also includes several bug fixes
- The detail materail manager wasn't querying for default terrain region materials when it initializes, so it was missing some creation events that it would later get destruction events for and explode.
- Change the way the default material weights against other surface weights to avoid some discontinuities
- Fixed an issue where a default material id wouldn't initialize as invalid.

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

* Several improvements and bug fixes
- Adding explicit creation / destruction events for material regions.
- Material regions will no longer try to "hide" when they don't have any materials
- Default detail materials will now only be used in areas where there are no other material assignments
- Fixed a bug where materials might not show up on surfaces when they were assigned to multiple regions or surface tags.

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

* Simplified loop that assigns materials based on surface tags. Slightly simplified shader code regarding detail material id coordinates.

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

* Fixes from PR review

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

* Making sure region changed doesn't get announced in the case where the old and new regions are both invalid.

Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>
2022-02-17 11:06:24 -06:00

975 lines
44 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <TerrainRenderer/TerrainDetailMaterialManager.h>
#include <TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h>
#include <AzCore/Console/Console.h>
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Shader/ShaderSystemInterface.h>
#include <Atom/Utils/MaterialUtils.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
namespace Terrain
{
namespace
{
[[maybe_unused]] static const char* TerrainDetailMaterialManagerName = "TerrainDetailMaterialManager";
static const char* TerrainDetailChars = "TerrainDetail";
}
namespace DetailMaterialInputs
{
static const char* const BaseColorColor("baseColor.color");
static const char* const BaseColorMap("baseColor.textureMap");
static const char* const BaseColorUseTexture("baseColor.useTexture");
static const char* const BaseColorFactor("baseColor.factor");
static const char* const BaseColorBlendMode("baseColor.textureBlendMode");
static const char* const MetallicMap("metallic.textureMap");
static const char* const MetallicUseTexture("metallic.useTexture");
static const char* const MetallicFactor("metallic.factor");
static const char* const RoughnessMap("roughness.textureMap");
static const char* const RoughnessUseTexture("roughness.useTexture");
static const char* const RoughnessFactor("roughness.factor");
static const char* const RoughnessLowerBound("roughness.lowerBound");
static const char* const RoughnessUpperBound("roughness.upperBound");
static const char* const SpecularF0Map("specularF0.textureMap");
static const char* const SpecularF0UseTexture("specularF0.useTexture");
static const char* const SpecularF0Factor("specularF0.factor");
static const char* const NormalMap("normal.textureMap");
static const char* const NormalUseTexture("normal.useTexture");
static const char* const NormalFactor("normal.factor");
static const char* const NormalFlipX("normal.flipX");
static const char* const NormalFlipY("normal.flipY");
static const char* const DiffuseOcclusionMap("occlusion.diffuseTextureMap");
static const char* const DiffuseOcclusionUseTexture("occlusion.diffuseUseTexture");
static const char* const DiffuseOcclusionFactor("occlusion.diffuseFactor");
static const char* const HeightMap("parallax.textureMap");
static const char* const HeightUseTexture("parallax.useTexture");
static const char* const HeightFactor("parallax.factor");
static const char* const HeightOffset("parallax.offset");
static const char* const HeightBlendFactor("parallax.blendFactor");
static const char* const UvCenter("uv.center");
static const char* const UvScale("uv.scale");
static const char* const UvTileU("uv.tileU");
static const char* const UvTileV("uv.tileV");
static const char* const UvOffsetU("uv.offsetU");
static const char* const UvOffsetV("uv.offsetV");
static const char* const UvRotateDegrees("uv.rotateDegrees");
}
namespace TerrainSrgInputs
{
static const char* const DetailMaterialIdImage("m_detailMaterialIdImage");
static const char* const DetailMaterialData("m_detailMaterialData");
static const char* const DetailMaterialScale("m_detailMaterialIdScale");
}
AZ_CVAR(bool,
r_terrainDebugDetailMaterials,
false,
[](const bool& value)
{
AZ::RPI::ShaderSystemInterface::Get()->SetGlobalShaderOption(AZ::Name{ "o_debugDetailMaterialIds" }, AZ::RPI::ShaderOptionValue{ value });
},
AZ::ConsoleFunctorFlags::Null,
"Turns on debugging for detail material ids for terrain."
);
AZ_CVAR(bool,
r_terrainDebugDetailImageUpdates,
false,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Turns on debugging for detail material update regions for terrain."
);
void TerrainDetailMaterialManager::Initialize(
const AZStd::shared_ptr<AZ::Render::BindlessImageArrayHandler>& bindlessImageHandler,
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
{
AZ_Error(TerrainDetailMaterialManagerName, bindlessImageHandler, "bindlessImageHandler must not be null.");
AZ_Error(TerrainDetailMaterialManagerName, terrainSrg, "terrainSrg must not be null.");
AZ_Error(TerrainDetailMaterialManagerName, !m_isInitialized, "Already initialized.");
if (!bindlessImageHandler || !terrainSrg || m_isInitialized)
{
return;
}
InitializePassthroughDetailMaterial();
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))
{
m_bindlessImageHandler = bindlessImageHandler;
// Find any detail material areas that have already been created.
TerrainAreaMaterialRequestBus::EnumerateHandlers(
[&](TerrainAreaMaterialRequests* handler)
{
const AZ::Aabb& bounds = handler->GetTerrainSurfaceMaterialRegion();
const AZStd::vector<TerrainSurfaceMaterialMapping> materialMappings = handler->GetSurfaceMaterialMappings();
AZ::EntityId entityId = *(Terrain::TerrainAreaMaterialRequestBus::GetCurrentBusId());
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
materialRegion.m_region = bounds;
if (handler->GetDefaultMaterial().m_materialInstance)
{
OnTerrainDefaultSurfaceMaterialCreated(entityId, handler->GetDefaultMaterial().m_materialInstance);
}
for (const auto& materialMapping : materialMappings)
{
if (materialMapping.m_materialInstance)
{
OnTerrainSurfaceMaterialMappingCreated(entityId, materialMapping.m_surfaceTag, materialMapping.m_materialInstance);
}
}
return true;
}
);
TerrainAreaMaterialNotificationBus::Handler::BusConnect();
AZ::Aabb worldBounds = AZ::Aabb::CreateNull();
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
OnTerrainDataChanged(worldBounds, TerrainDataChangedMask::SurfaceData);
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
m_isInitialized = true;
}
}
bool TerrainDetailMaterialManager::UpdateSrgIndices(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
{
const AZ::RHI::ShaderResourceGroupLayout* terrainSrgLayout = terrainSrg->GetLayout();
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_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;
desc.m_bufferName = "Detail Material Data";
desc.m_bufferSrgName = TerrainSrgInputs::DetailMaterialData;
desc.m_elementSize = sizeof(DetailMaterialShaderData);
desc.m_srgLayout = terrainSrgLayout;
m_detailMaterialDataBuffer = AZ::Render::GpuBufferHandler(desc);
bool IndicesValid =
m_detailMaterialIdPropertyIndex.IsValid() &&
m_detailScalePropertyIndex.IsValid();
m_detailImageNeedsUpdate = true;
m_detailMaterialBufferNeedsUpdate = true;
return IndicesValid && m_detailMaterialDataBuffer.IsValid();
}
void TerrainDetailMaterialManager::RemoveAllImages()
{
for (const DetailMaterialData& materialData: m_detailMaterials.GetDataVector())
{
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(materialData.m_detailMaterialBufferIndex);
auto checkRemoveImage = [&](uint16_t index)
{
if (index != 0xFFFF)
{
m_bindlessImageHandler->RemoveBindlessImage(index);
}
};
checkRemoveImage(shaderData.m_colorImageIndex);
checkRemoveImage(shaderData.m_normalImageIndex);
checkRemoveImage(shaderData.m_roughnessImageIndex);
checkRemoveImage(shaderData.m_metalnessImageIndex);
checkRemoveImage(shaderData.m_specularF0ImageIndex);
checkRemoveImage(shaderData.m_occlusionImageIndex);
checkRemoveImage(shaderData.m_heightImageIndex);
}
}
bool TerrainDetailMaterialManager::IsInitialized() const
{
return m_isInitialized;
}
void TerrainDetailMaterialManager::Reset()
{
RemoveAllImages();
m_detailTextureImage = {};
m_detailMaterials.Clear();
m_detailMaterialRegions.Clear();
m_detailMaterialShaderData.Clear();
m_detailMaterialDataBuffer.Release();
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
m_detailMaterialBufferNeedsUpdate = false;
m_detailImageNeedsUpdate = false;
TerrainAreaMaterialNotificationBus::Handler::BusDisconnect();
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect();
m_isInitialized = false;
}
void TerrainDetailMaterialManager::Update(const AZ::Vector3& cameraPosition, AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
{
if (m_detailMaterialBufferNeedsUpdate)
{
m_detailMaterialBufferNeedsUpdate = false;
m_detailMaterialDataBuffer.UpdateBuffer(m_detailMaterialShaderData.GetRawData(), aznumeric_cast<uint32_t>(m_detailMaterialShaderData.GetSize()));
}
CheckUpdateDetailTexture(cameraPosition);
if (m_detailImageNeedsUpdate)
{
terrainSrg->SetConstant(m_detailScalePropertyIndex, 1.0f / DetailTextureScale);
terrainSrg->SetImage(m_detailMaterialIdPropertyIndex, m_detailTextureImage);
m_detailMaterialDataBuffer.UpdateSrg(terrainSrg.get());
m_detailImageNeedsUpdate = false;
}
}
void TerrainDetailMaterialManager::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
{
if ((dataChangedMask & TerrainDataChangedMask::SurfaceData) != 0)
{
m_dirtyDetailRegion.AddAabb(dirtyRegion);
}
}
bool TerrainDetailMaterialManager::ForSurfaceTag(DetailMaterialListRegion& materialRegion,
SurfaceData::SurfaceTag surfaceTag, DefaultMaterialSurfaceCallback callback)
{
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
{
if (surface.m_surfaceTag == surfaceTag)
{
callback(surface);
return true;
}
}
return false;
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialCreated(AZ::EntityId entityId, MaterialInstance material)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialCreated() called for region that doesn't exist.");
return;
}
AZ_Error("TerrainDetailMaterialManager", materialRegion->m_defaultDetailMaterialId == InvalidDetailMaterialId,
"Default detail material created but was already set for this region.");
materialRegion->m_defaultDetailMaterialId = CreateOrUpdateDetailMaterial(material);
m_detailMaterials.GetData(materialRegion->m_defaultDetailMaterialId).m_refCount++;
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialDestroyed(AZ::EntityId entityId)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialDestroyed() called for region that doesn't exist.");
return;
}
if (materialRegion->m_defaultDetailMaterialId == InvalidDetailMaterialId)
{
AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialDestroyed() called for a region without a default material");
return;
}
CheckDetailMaterialForDeletion(materialRegion->m_defaultDetailMaterialId);
materialRegion->m_defaultDetailMaterialId = InvalidDetailMaterialId;
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, MaterialInstance newMaterial)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialChanged() called for region that doesn't exist.");
return;
}
// Update existing entry or create a new material entry
uint16_t materialId = CreateOrUpdateDetailMaterial(newMaterial);
if (materialRegion->m_defaultDetailMaterialId != materialId)
{
m_detailMaterials.GetData(materialId).m_refCount++;
CheckDetailMaterialForDeletion(materialRegion->m_defaultDetailMaterialId);
materialRegion->m_defaultDetailMaterialId = materialId;
}
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingCreated() called for region that doesn't exist.");
return;
}
// Validate that the surface tag is new
ForSurfaceTag(*materialRegion, surfaceTag, [](DetailMaterialSurface&)
{
AZ_Error(TerrainDetailMaterialManagerName, false, "Already have a surface material mapping for this surface tag.");
});
uint16_t detailMaterialId = CreateOrUpdateDetailMaterial(material);
materialRegion->m_materialsForSurfaces.push_back({ surfaceTag, detailMaterialId });
m_detailMaterials.GetData(detailMaterialId).m_refCount++;
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingDestroyed() called for region that doesn't exist.");
return;
}
[[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, surfaceTag,
[&](DetailMaterialSurface& surface)
{
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
if (surface.m_surfaceTag != materialRegion->m_materialsForSurfaces.back().m_surfaceTag)
{
AZStd::swap(surface, materialRegion->m_materialsForSurfaces.back());
}
materialRegion->m_materialsForSurfaces.pop_back();
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
});
AZ_Error(TerrainDetailMaterialManagerName, found, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed().");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingMaterialChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingMaterialChanged() called for region that doesn't exist.");
return;
}
// Update existing entry or create a new material entry
uint16_t materialId = CreateOrUpdateDetailMaterial(material);
[[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, surfaceTag,
[&](DetailMaterialSurface& surface)
{
if (surface.m_detailMaterialId != materialId)
{
// Updated material was a different asset than the old material, decrement ref count and
// delete if no other surface tags are using it.
m_detailMaterials.GetData(materialId).m_refCount++;
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
surface.m_detailMaterialId = materialId;
}
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
});
AZ_Assert(found, "OnTerrainSurfaceMaterialMappingMaterialChanged() called for tag that doesn't exist.");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingTagChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag oldTag, SurfaceData::SurfaceTag newTag)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingTagChanged() called for region that doesn't exist.");
return;
}
[[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, oldTag,
[&](DetailMaterialSurface& surface)
{
surface.m_surfaceTag = newTag;
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
});
AZ_Assert(found, "OnTerrainSurfaceMaterialMappingTagChanged() called for tag that doesn't exist.");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingRegionCreated(AZ::EntityId entityId, const AZ::Aabb& region)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
materialRegion.m_region = region;
if (materialRegion.HasMaterials())
{
m_dirtyDetailRegion.AddAabb(region);
}
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingRegionDestroyed(AZ::EntityId entityId, const AZ::Aabb& oldRegion)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingRegionDestroyed() called for region that doesn't exist.");
return;
}
if (materialRegion->HasMaterials())
{
m_dirtyDetailRegion.AddAabb(oldRegion);
}
m_detailMaterialRegions.RemoveData(materialRegion);
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingRegionChanged() called for region that doesn't exist.");
return;
}
materialRegion->m_region = newRegion;
if (materialRegion->HasMaterials())
{
m_dirtyDetailRegion.AddAabb(oldRegion);
m_dirtyDetailRegion.AddAabb(newRegion);
}
}
void TerrainDetailMaterialManager::CheckDetailMaterialForDeletion(uint16_t detailMaterialId)
{
auto& detailMaterialData = m_detailMaterials.GetData(detailMaterialId);
if (--detailMaterialData.m_refCount == 0)
{
uint16_t bufferIndex = detailMaterialData.m_detailMaterialBufferIndex;
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(bufferIndex);
for (uint16_t imageIndex :
{
shaderData.m_colorImageIndex,
shaderData.m_normalImageIndex,
shaderData.m_roughnessImageIndex,
shaderData.m_metalnessImageIndex,
shaderData.m_specularF0ImageIndex,
shaderData.m_occlusionImageIndex,
shaderData.m_heightImageIndex
})
{
if (imageIndex != InvalidImageIndex)
{
m_bindlessImageHandler->RemoveBindlessImage(imageIndex);
}
}
m_detailMaterialShaderData.Release(bufferIndex);
m_detailMaterials.RemoveIndex(detailMaterialId);
m_detailMaterialBufferNeedsUpdate = true;
}
}
uint16_t TerrainDetailMaterialManager::CreateOrUpdateDetailMaterial(MaterialInstance material)
{
static constexpr uint16_t InvalidDetailMaterial = 0xFFFF;
uint16_t detailMaterialId = InvalidDetailMaterial;
for (const auto& detailMaterialData : m_detailMaterials.GetDataVector())
{
if (detailMaterialData.m_assetId == material->GetAssetId())
{
detailMaterialId = m_detailMaterials.GetIndexForData(&detailMaterialData);
UpdateDetailMaterialData(detailMaterialId, material);
break;
}
}
AZ_Assert(m_detailMaterialShaderData.GetSize() < 0xFF, "Only 255 detail materials supported.");
if (detailMaterialId == InvalidDetailMaterial && m_detailMaterialShaderData.GetSize() < 0xFF)
{
detailMaterialId = m_detailMaterials.GetFreeSlotIndex();
auto& detailMaterialData = m_detailMaterials.GetData(detailMaterialId);
detailMaterialData.m_detailMaterialBufferIndex = aznumeric_cast<uint16_t>(m_detailMaterialShaderData.Reserve());
UpdateDetailMaterialData(detailMaterialId, material);
}
return detailMaterialId;
}
void TerrainDetailMaterialManager::UpdateDetailMaterialData(uint16_t detailMaterialIndex, MaterialInstance material)
{
DetailMaterialData& materialData = m_detailMaterials.GetData(detailMaterialIndex);
if (materialData.m_materialChangeId == material->GetCurrentChangeId())
{
return; // material hasn't changed, nothing to do
}
materialData.m_materialChangeId = material->GetCurrentChangeId();
materialData.m_assetId = material->GetAssetId();
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(materialData.m_detailMaterialBufferIndex);
shaderData = DetailMaterialShaderData();
DetailTextureFlags& flags = shaderData.m_flags;
flags = DetailTextureFlags::None;
auto getIndex = [&](const char* const indexName) -> AZ::RPI::MaterialPropertyIndex
{
const AZ::RPI::MaterialPropertyIndex index = material->FindPropertyIndex(AZ::Name(indexName));
AZ_Warning(TerrainDetailMaterialManagerName, index.IsValid(), "Failed to find shader input constant %s.", indexName);
return index;
};
auto applyProperty = [&](const char* const indexName, auto& ref) -> void
{
const auto index = getIndex(indexName);
if (index.IsValid())
{
// GetValue<T>() expects the actaul type, not a reference type, so the reference needs to be removed.
using TypeRefRemoved = AZStd::remove_cvref_t<decltype(ref)>;
ref = material->GetPropertyValue(index).GetValue<TypeRefRemoved>();
}
};
auto applyImage = [&](const char* const indexName, AZ::Data::Instance<AZ::RPI::Image>& ref, const char* const usingFlagName, DetailTextureFlags flagToSet, uint16_t& imageIndex) -> void
{
// Determine if an image exists and if its using flag allows it to be used.
const auto index = getIndex(indexName);
const auto useTextureIndex = getIndex(usingFlagName);
bool useTextureValue = true;
if (useTextureIndex.IsValid())
{
useTextureValue = material->GetPropertyValue(useTextureIndex).GetValue<bool>();
}
if (index.IsValid() && useTextureValue)
{
ref = material->GetPropertyValue(index).GetValue<AZ::Data::Instance<AZ::RPI::Image>>();
}
useTextureValue = useTextureValue && ref;
flags = DetailTextureFlags(useTextureValue ? (flags | flagToSet) : (flags & ~flagToSet));
// Update queues to add/remove textures depending on if the image is used
if (ref)
{
if (imageIndex == InvalidImageIndex)
{
imageIndex = m_bindlessImageHandler->AppendBindlessImage(ref->GetImageView());
}
else
{
m_bindlessImageHandler->UpdateBindlessImage(imageIndex, ref->GetImageView());
}
}
else if (imageIndex != InvalidImageIndex)
{
m_bindlessImageHandler->RemoveBindlessImage(imageIndex);
imageIndex = InvalidImageIndex;
}
};
auto applyFlag = [&](const char* const indexName, DetailTextureFlags flagToSet) -> void
{
const auto index = getIndex(indexName);
if (index.IsValid())
{
bool flagValue = material->GetPropertyValue(index).GetValue<bool>();
flags = DetailTextureFlags(flagValue ? flags | flagToSet : flags);
}
};
auto getEnumName = [&](const char* const indexName) -> const AZStd::string_view
{
const auto index = getIndex(indexName);
if (index.IsValid())
{
uint32_t enumIndex = material->GetPropertyValue(index).GetValue<uint32_t>();
const AZ::Name& enumName = material->GetMaterialPropertiesLayout()->GetPropertyDescriptor(index)->GetEnumName(enumIndex);
return enumName.GetStringView();
}
return "";
};
using namespace DetailMaterialInputs;
applyImage(BaseColorMap, materialData.m_colorImage, BaseColorUseTexture, DetailTextureFlags::UseTextureBaseColor, shaderData.m_colorImageIndex);
applyProperty(BaseColorFactor, shaderData.m_baseColorFactor);
const auto index = getIndex(BaseColorColor);
if (index.IsValid())
{
AZ::Color baseColor = material->GetPropertyValue(index).GetValue<AZ::Color>();
shaderData.m_baseColorRed = baseColor.GetR();
shaderData.m_baseColorGreen = baseColor.GetG();
shaderData.m_baseColorBlue = baseColor.GetB();
}
const AZStd::string_view& blendModeString = getEnumName(BaseColorBlendMode);
if (blendModeString == "Multiply")
{
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeMultiply);
}
else if (blendModeString == "LinearLight")
{
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeLinearLight);
}
else if (blendModeString == "Lerp")
{
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeLerp);
}
else if (blendModeString == "Overlay")
{
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeOverlay);
}
applyImage(MetallicMap, materialData.m_metalnessImage, MetallicUseTexture, DetailTextureFlags::UseTextureMetallic, shaderData.m_metalnessImageIndex);
applyProperty(MetallicFactor, shaderData.m_metalFactor);
applyImage(RoughnessMap, materialData.m_roughnessImage, RoughnessUseTexture, DetailTextureFlags::UseTextureRoughness, shaderData.m_roughnessImageIndex);
if ((flags & DetailTextureFlags::UseTextureRoughness) > 0)
{
float lowerBound = 0.0;
float upperBound = 1.0;
applyProperty(RoughnessLowerBound, lowerBound);
applyProperty(RoughnessUpperBound, upperBound);
shaderData.m_roughnessBias = lowerBound;
shaderData.m_roughnessScale = upperBound - lowerBound;
}
else
{
shaderData.m_roughnessBias = 0.0;
applyProperty(RoughnessFactor, shaderData.m_roughnessScale);
}
applyImage(SpecularF0Map, materialData.m_specularF0Image, SpecularF0UseTexture, DetailTextureFlags::UseTextureSpecularF0, shaderData.m_specularF0ImageIndex);
applyProperty(SpecularF0Factor, shaderData.m_specularF0Factor);
applyImage(NormalMap, materialData.m_normalImage, NormalUseTexture, DetailTextureFlags::UseTextureNormal, shaderData.m_normalImageIndex);
applyProperty(NormalFactor, shaderData.m_normalFactor);
applyFlag(NormalFlipX, DetailTextureFlags::FlipNormalX);
applyFlag(NormalFlipY, DetailTextureFlags::FlipNormalY);
applyImage(DiffuseOcclusionMap, materialData.m_occlusionImage, DiffuseOcclusionUseTexture, DetailTextureFlags::UseTextureOcclusion, shaderData.m_occlusionImageIndex);
applyProperty(DiffuseOcclusionFactor, shaderData.m_occlusionFactor);
applyImage(HeightMap, materialData.m_heightImage, HeightUseTexture, DetailTextureFlags::UseTextureHeight, shaderData.m_heightImageIndex);
applyProperty(HeightFactor, shaderData.m_heightFactor);
applyProperty(HeightOffset, shaderData.m_heightOffset);
applyProperty(HeightBlendFactor, shaderData.m_heightBlendFactor);
AZ::Render::UvTransformDescriptor transformDescriptor;
applyProperty(UvCenter, transformDescriptor.m_center);
applyProperty(UvScale, transformDescriptor.m_scale);
applyProperty(UvTileU, transformDescriptor.m_scaleX);
applyProperty(UvTileV, transformDescriptor.m_scaleY);
applyProperty(UvOffsetU, transformDescriptor.m_translateX);
applyProperty(UvOffsetV, transformDescriptor.m_translateY);
applyProperty(UvRotateDegrees, transformDescriptor.m_rotateDegrees);
AZStd::array<AZ::Render::TransformType, 3> order =
{
AZ::Render::TransformType::Rotate,
AZ::Render::TransformType::Translate,
AZ::Render::TransformType::Scale,
};
AZ::Matrix3x3 uvTransformMatrix = AZ::Render::CreateUvTransformMatrix(transformDescriptor, order);
uvTransformMatrix.GetRow(0).StoreToFloat3(&shaderData.m_uvTransform[0]);
uvTransformMatrix.GetRow(1).StoreToFloat3(&shaderData.m_uvTransform[4]);
uvTransformMatrix.GetRow(2).StoreToFloat3(&shaderData.m_uvTransform[8]);
// Store a hash of the matrix in element in an unused portion for quick comparisons in the shader
size_t hash64 = 0;
for (float value : shaderData.m_uvTransform)
{
AZStd::hash_combine(hash64, value);
}
uint32_t hash32 = uint32_t((hash64 ^ (hash64 >> 32)) & 0xFFFFFFFF);
shaderData.m_uvTransform[3] = *reinterpret_cast<float*>(&hash32);
m_detailMaterialBufferNeedsUpdate = true;
}
void TerrainDetailMaterialManager::CheckUpdateDetailTexture(const AZ::Vector3& cameraPosition)
{
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
const AZ::Data::Instance<AZ::RPI::AttachmentImagePool> imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D(
AZ::RHI::ImageBindFlags::ShaderRead, DetailTextureSize, DetailTextureSize, AZ::RHI::Format::R8G8B8A8_UINT
);
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.");
ClipmapBounds::ClipmapBoundsRegionList updateRegions = m_detailMaterialIdBounds.TransformRegion(m_detailMaterialIdBounds.GetWorldBounds());
for (const auto& region : updateRegions)
{
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
}
}
else
{
// Update the edge regions
for (const auto& region : edgeUpdatedRegions)
{
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
}
if (m_dirtyDetailRegion.IsValid())
{
m_dirtyDetailRegion = m_dirtyDetailRegion.GetClamped(untouchedRegion);
if (m_dirtyDetailRegion.IsValid())
{
ClipmapBounds::ClipmapBoundsRegionList updateRegions = m_detailMaterialIdBounds.TransformRegion(m_dirtyDetailRegion);
for (const auto& region : updateRegions)
{
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
}
}
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
}
}
}
void TerrainDetailMaterialManager::UpdateDetailTexture(const AZ::Aabb& worldUpdateAabb, const Aabb2i& textureUpdateAabb)
{
if (!m_detailTextureImage)
{
return;
}
struct DetailMaterialPixel
{
uint8_t m_material1{ 255 };
uint8_t m_material2{ 255 };
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;
AZStd::vector<DetailMaterialPixel> pixels(width * height);
auto perPositionCallback = [this, &pixels, &width](
size_t xIndex, 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.
DetailMaterialPixel& pixel = pixels.at(yIndex * width + xIndex);
uint32_t foundMaterials = 0;
float firstWeight = 0.0f;
float secondWeight = 0.0f;
AZ::Vector2 position(surfacePoint.m_position.GetX(), surfacePoint.m_position.GetY());
const DetailMaterialListRegion* region = FindRegionForPosition(position);
if (region == nullptr)
{
pixel.m_material1 = m_passthroughMaterialId;
return;
}
for (const auto& surfaceTagWeight : surfacePoint.m_surfaceTags)
{
if (surfaceTagWeight.m_weight > 0.0f)
{
AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType;
uint16_t materialId = GetDetailMaterialForSurfaceType(*region, surfaceType);
if (materialId < 255)
{
if (foundMaterials == 0)
{
// Found first material. Save its weight to calculate blend later.
++foundMaterials;
pixel.m_material1 = aznumeric_cast<uint8_t>(materialId);
firstWeight = surfaceTagWeight.m_weight;
}
else if (materialId == pixel.m_material1)
{
// Same material as the first material, just add the weights.
firstWeight += surfaceTagWeight.m_weight;
}
else if (foundMaterials == 1)
{
// Found second material. Save its weight to calculate blend later.
++foundMaterials;
secondWeight += surfaceTagWeight.m_weight;
pixel.m_material2 = aznumeric_cast<uint8_t>(materialId);
}
else if (materialId == pixel.m_material2)
{
// Same material as the second material, just add the weights.
secondWeight += surfaceTagWeight.m_weight;
}
else
{
break;
}
}
}
}
if (foundMaterials == 0)
{
// No materials found, so use the default material.
uint8_t defaultMaterial = region->m_defaultDetailMaterialId == InvalidDetailMaterialId ? m_passthroughMaterialId :
aznumeric_cast<uint8_t>(m_detailMaterials.GetData(region->m_defaultDetailMaterialId).m_detailMaterialBufferIndex);
pixel.m_material1 = defaultMaterial;
}
else if (foundMaterials == 2)
{
float totalWeight = firstWeight + secondWeight;
float blendWeight = 1.0f - (firstWeight / totalWeight);
pixel.m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
}
};
AZ::Vector2 stepSize(DetailTextureScale);
AZ::Aabb offsetWorldAabb = worldUpdateAabb.GetTranslated(AZ::Vector3(DetailTextureScale * 0.5f)); // offset by half a pixel
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromRegion,
offsetWorldAabb, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
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::GetDetailMaterialForSurfaceType(const DetailMaterialListRegion& materialRegion, AZ::Crc32 surfaceType) const
{
for (const auto& materialSurface : materialRegion.m_materialsForSurfaces)
{
if (materialSurface.m_surfaceTag == surfaceType)
{
return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex;
}
}
return InvalidDetailMaterialId;
}
auto TerrainDetailMaterialManager::FindRegionForPosition(const AZ::Vector2& position) const -> const DetailMaterialListRegion*
{
for (const auto& materialRegion : m_detailMaterialRegions.GetDataVector())
{
if (materialRegion.m_region.Contains(AZ::Vector3(position.GetX(), position.GetY(), 0.0f)))
{
return &materialRegion;
}
}
return nullptr;
}
void TerrainDetailMaterialManager::InitializePassthroughDetailMaterial()
{
m_passthroughMaterialId = aznumeric_cast<uint8_t>(m_detailMaterialShaderData.Reserve());
DetailMaterialShaderData& materialShaderData = m_detailMaterialShaderData.GetElement(m_passthroughMaterialId);
// Material defaults to white (1.0, 1.0, 1.0), set the blend mode to multiply so it passes through to the macro material.
materialShaderData.m_flags = DetailTextureFlags::BlendModeMultiply;
}
auto TerrainDetailMaterialManager::FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container)
-> DetailMaterialListRegion*
{
for (DetailMaterialListRegion& data : container.GetDataVector())
{
if (data.m_entityId == entityId)
{
return &data;
}
}
return nullptr;
}
auto TerrainDetailMaterialManager::FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container)
-> DetailMaterialListRegion&
{
DetailMaterialListRegion* dataPtr = FindByEntityId(entityId, container);
if (dataPtr != nullptr)
{
return *dataPtr;
}
const uint16_t slotId = container.GetFreeSlotIndex();
AZ_Assert(slotId != AZ::Render::IndexedDataVector<TerrainDetailMaterialManager>::NoFreeSlot, "Ran out of indices");
DetailMaterialListRegion& data = container.GetData(slotId);
data.m_entityId = entityId;
return data;
}
void TerrainDetailMaterialManager::RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container)
{
for (DetailMaterialListRegion& data : container.GetDataVector())
{
if (data.m_entityId == entityId)
{
container.RemoveData(&data);
return;
}
}
AZ_Assert(false, "Entity Id not found in container.")
}
}