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>
monroegm-disable-blank-issue-2
Ken Pruiksma 4 years ago committed by GitHub
parent 846e2736a5
commit e8b7bba0f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,6 +22,8 @@
#include <AzFramework/StringFunc/StringFunc.h>
#include <Atom/RHI.Reflect/Format.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
// for texture splitting
@ -352,7 +354,8 @@ namespace ImageProcessingAtom
// output conversion log
if (m_isSucceed && m_isFinished)
{
[[maybe_unused]] const uint32 sizeTotal = m_image->Get()->GetTextureMemory();
[[maybe_unused]] IImageObjectPtr imageObj = m_image->Get();
[[maybe_unused]] const uint32 sizeTotal = imageObj->GetTextureMemory();
if (m_input->m_isPreview)
{
AZ_TracePrintf("Image Processing", "Image (%d bytes) converted in %f seconds\n", sizeTotal, m_processTime);
@ -363,11 +366,10 @@ namespace ImageProcessingAtom
}
else
{
[[maybe_unused]] const PixelFormatInfo* formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(m_image->Get()->GetPixelFormat());
[[maybe_unused]] const PixelFormatInfo* formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(imageObj->GetPixelFormat());
[[maybe_unused]] const AZ::RHI::Format rhiFormat = Utils::PixelFormatToRHIFormat(imageObj->GetPixelFormat(), imageObj->HasImageFlags(EIF_SRGBRead));
AZ_TracePrintf("Image Processing", "Image [%dx%d] [%s] converted with preset [%s] [%s] and saved to [%s] (%d bytes) taking %f seconds\n",
m_image->Get()->GetWidth(0), m_image->Get()->GetHeight(0),
formatInfo->szName,
imageObj->GetWidth(0), imageObj->GetHeight(0), AZ::RHI::ToString(rhiFormat),
m_input->m_presetSetting.m_name.GetCStr(),
m_input->m_filePath.c_str(),
m_input->m_outputFolder.c_str(), sizeTotal, m_processTime);

@ -8,6 +8,7 @@
#pragma once
#include <Atom/Features/ColorManagement/TransformColor.azsli>
#include <Atom/Feature/BlendUtility.azsli>
enum DetailTextureFlags
{
@ -22,11 +23,11 @@ enum DetailTextureFlags
FlipNormalX = 0x00010000, //0b0000'0000'0000'0001'0000'0000'0000'0000
FlipNormalY = 0x00020000, //0b0000'0000'0000'0010'0000'0000'0000'0000
BlendModeMask = 0x000C0000, //0b0000'0000'0000'1100'0000'0000'0000'0000
BlendModeLerp = 0x00000000, //0b0000'0000'0000'0000'0000'0000'0000'0000
BlendModeLinearLight = 0x00040000, //0b0000'0000'0000'0100'0000'0000'0000'0000
BlendModeMultiply = 0x00080000, //0b0000'0000'0000'1000'0000'0000'0000'0000
BlendModeOverlay = 0x000C0000, //0b0000'0000'0000'1100'0000'0000'0000'0000
BlendModeMask = 0x001C0000, //0b0000'0000'0001'1100'0000'0000'0000'0000
BlendModeLerp = 0x00040000, //0b0000'0000'0000'0100'0000'0000'0000'0000
BlendModeLinearLight = 0x00080000, //0b0000'0000'0000'1000'0000'0000'0000'0000
BlendModeMultiply = 0x000C0000, //0b0000'0000'0000'1100'0000'0000'0000'0000
BlendModeOverlay = 0x00100000, //0b0000'0000'0001'0000'0000'0000'0000'0000
};
struct DetailSurface
@ -118,14 +119,37 @@ uint GetDetailHeightIndex(TerrainSrg::DetailMaterialData materialData)
// Detail material value getters
float3 GetDetailColor(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
float3 GetDetailColor(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy, float3 macroColor)
{
float3 color = materialData.m_baseColor;
if ((materialData.m_flags & DetailTextureFlags::UseTextureBaseColor) > 0)
{
color = TerrainSrg::m_textures[GetDetailColorIndex(materialData)].SampleGrad(TerrainMaterialSrg::m_sampler, uv, ddx, ddy).rgb;
color = TransformColor(color, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
color = sqrt(color); // Put in a more perceptual space for blending. This will be undone later.
}
return color * materialData.m_baseColorFactor;
uint blendMode = materialData.m_flags & DetailTextureFlags::BlendModeMask;
switch (blendMode)
{
case DetailTextureFlags::BlendModeLerp:
color = lerp(color, macroColor, materialData.m_baseColorFactor);
break;
case DetailTextureFlags::BlendModeLinearLight:
color = lerp(color, TextureBlend_LinearLight(macroColor, color), materialData.m_baseColorFactor);
break;
case DetailTextureFlags::BlendModeMultiply:
color = lerp(color, color * macroColor, materialData.m_baseColorFactor);
break;
case DetailTextureFlags::BlendModeOverlay:
color = lerp(color, TextureBlend_Overlay(color, macroColor), materialData.m_baseColorFactor);
break;
case 0:
color *= materialData.m_baseColorFactor;
break;
default:
color = float3(1.0, 0.0, 1.0); // Unknown blend mode.
}
return color;
}
float3 GetDetailNormal(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
@ -200,7 +224,7 @@ float GetDetailHeight(TerrainSrg::DetailMaterialData materialData, float2 uv, fl
return height;
}
void GetDetailSurfaceForMaterial(inout DetailSurface surface, uint materialId, float2 uv)
void GetDetailSurfaceForMaterial(inout DetailSurface surface, uint materialId, float2 uv, float3 macroColor)
{
TerrainSrg::DetailMaterialData detailMaterialData = TerrainSrg::m_detailMaterialData[materialId];
@ -224,7 +248,7 @@ void GetDetailSurfaceForMaterial(inout DetailSurface surface, uint materialId, f
float2 transformedUvDdx = transformedUvX - transformedUv;
float2 transformedUvDdy = transformedUvY - transformedUv;
surface.m_color = GetDetailColor(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_color = GetDetailColor(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy, macroColor);
surface.m_normal = GetDetailNormal(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_roughness = GetDetailRoughness(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_specularF0 = GetDetailSpecularF0(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
@ -270,15 +294,15 @@ void GetDebugDetailSurface(inout DetailSurface surface, uint material1, uint mat
}
//Blend a single detail material sample (with two possible material ids) onto a DetailSurface.
void BlendDetailMaterial(inout DetailSurface surface, uint material1, uint material2, float blend, float2 detailUv, float weight)
void BlendDetailMaterial(inout DetailSurface surface, uint material1, uint material2, float blend, float2 detailUv, float weight, float3 macroColor)
{
DetailSurface tempSurface;
GetDetailSurfaceForMaterial(tempSurface, material1, detailUv);
GetDetailSurfaceForMaterial(tempSurface, material1, detailUv, macroColor);
WeightDetailSurface(tempSurface, weight * (1.0 - blend));
AddDetailSurface(surface, tempSurface);
if (material2 != 0xFF)
{
GetDetailSurfaceForMaterial(tempSurface, material2, detailUv);
GetDetailSurfaceForMaterial(tempSurface, material2, detailUv, macroColor);
WeightDetailSurface(tempSurface, weight * blend);
AddDetailSurface(surface, tempSurface);
}
@ -291,14 +315,18 @@ 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 detailMaterialIdCoord, float2 detailUv)
bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdCoord, float2 detailUv, float3 macroColor)
{
float2 textureSize;
TerrainSrg::m_detailMaterialIdImage.GetDimensions(textureSize.x, textureSize.y);
int2 intTextureSize = textureSize;
// The detail material id texture wraps since the "center" point can be anywhere in the texture, so mod by textureSize
int2 detailMaterialIdTopLeft = ((int2(detailMaterialIdCoord) % textureSize) + textureSize) % textureSize;
int2 detailMaterialIdBottomRight = (detailMaterialIdTopLeft + 1) % textureSize;
macroColor = sqrt(macroColor); // Put macro color into an approximate perceptual space.
// The detail material id texture wraps since the "center" point can be anywhere in the texture, so mod by intTextureSize
int2 detailMaterialIdTopLeft = int2(floor(detailMaterialIdCoord)) % intTextureSize;
detailMaterialIdTopLeft = (detailMaterialIdTopLeft + intTextureSize) % intTextureSize; // Make a negative result from modulus positive.
int2 detailMaterialIdBottomRight = (detailMaterialIdTopLeft + 1) % intTextureSize;
// 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(detailMaterialIdTopLeft.x, detailMaterialIdBottomRight.y, 0));
@ -334,7 +362,7 @@ bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdCoord,
if (all(material1.x == material1.yzw) && all(material2.x == material2.yzw))
{
// Fast path for same material ids
GetDetailSurfaceForMaterial(surface, material1.x, detailUv);
GetDetailSurfaceForMaterial(surface, material1.x, detailUv, macroColor);
if (material2.x != 0xFF)
{
float4 material2Blends = 1.0 - blends;
@ -345,7 +373,7 @@ bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdCoord,
(gatherWeight.x * (1.0 - gatherWeight.y) * material2Blends.z) +
((1.0 - gatherWeight.x) * (1.0 - gatherWeight.y) * material2Blends.w);
WeightDetailSurface(surface, weight);
GetDetailSurfaceForMaterial(tempSurface, material2.x, detailUv);
GetDetailSurfaceForMaterial(tempSurface, material2.x, detailUv, macroColor);
WeightDetailSurface(tempSurface, 1.0 - weight);
AddDetailSurface(surface, tempSurface);
}
@ -356,22 +384,22 @@ bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdCoord,
// X
float weight = (1.0 - gatherWeight.x) * gatherWeight.y;
BlendDetailMaterial(surface, material1.x, material2.x, blends.x, detailUv, weight);
BlendDetailMaterial(surface, material1.x, material2.x, blends.x, detailUv, weight, macroColor);
// Y
weight = gatherWeight.x * gatherWeight.y;
BlendDetailMaterial(surface, material1.y, material2.y, blends.y, detailUv, weight);
BlendDetailMaterial(surface, material1.y, material2.y, blends.y, detailUv, weight, macroColor);
// Z
weight = gatherWeight.x * (1.0 - gatherWeight.y);
BlendDetailMaterial(surface, material1.z, material2.z, blends.z, detailUv, weight);
BlendDetailMaterial(surface, material1.z, material2.z, blends.z, detailUv, weight, macroColor);
// W
weight = (1.0 - gatherWeight.x) * (1.0 - gatherWeight.y);
BlendDetailMaterial(surface, material1.w, material2.w, blends.w, detailUv, weight);
BlendDetailMaterial(surface, material1.w, material2.w, blends.w, detailUv, weight, macroColor);
}
surface.m_normal = normalize(surface.m_normal);
surface.m_color = surface.m_color * surface.m_color; // Put back in linear space.
return true;
}

@ -149,7 +149,7 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
// Only sample detail textures if inside where detail materials should be drawn.
if (detailFactor < 1.0)
{
hasDetailSurface = GetDetailSurface(detailSurface, detailRegionCoord, detailUv);
hasDetailSurface = GetDetailSurface(detailSurface, detailRegionCoord, detailUv, macroColor);
}
const float macroRoughness = 1.0;

@ -135,31 +135,37 @@ namespace Terrain
void TerrainSurfaceMaterialsListComponent::Activate()
{
m_cachedAabb = AZ::Aabb::CreateNull();
// Start listening for data requests.
TerrainAreaMaterialRequestBus::Handler::BusConnect(GetEntityId());
// Start listening for shape changes.
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
// OnShapeChanged() will announce creation if the shape is valid
OnShapeChanged(LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged);
// Set all the materials as inactive and start loading.
auto checkLoadMaterial = [&](TerrainSurfaceMaterialMapping& material)
{
if (material.m_materialAsset.GetId().IsValid())
{
material.m_active = false;
material.m_materialAsset.QueueLoad();
AZ::Data::AssetBus::MultiHandler::BusConnect(material.m_materialAsset.GetId());
material.m_materialAsset.QueueLoad();
}
};
// Set all the materials as inactive and start loading.
checkLoadMaterial(m_configuration.m_defaultSurfaceMaterial);
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
checkLoadMaterial(surfaceMaterialMapping);
}
// Announce initial shape using OnShapeChanged
OnShapeChanged(LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged);
}
void TerrainSurfaceMaterialsListComponent::Deactivate()
{
// disconnect from busses
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
TerrainAreaMaterialRequestBus::Handler::BusDisconnect();
auto checkResetMaterial = [&](TerrainSurfaceMaterialMapping& material)
@ -168,8 +174,7 @@ namespace Terrain
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(material.m_materialAsset.GetId());
material.m_materialAsset.Release();
material.m_materialInstance.reset();
material.m_activeMaterialAssetId = AZ::Data::AssetId();
material.m_materialInstance.reset(); // Cause HandleMaterialStateChanges() to announce destroyed for active materials
}
};
@ -180,6 +185,16 @@ namespace Terrain
}
HandleMaterialStateChanges();
m_configuration.m_defaultSurfaceMaterial = {};
m_configuration.m_surfaceMaterials.clear();
if (m_cachedAabb.IsValid())
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingRegionDestroyed, GetEntityId(), m_cachedAabb);
m_cachedAabb = AZ::Aabb::CreateNull();
}
}
int TerrainSurfaceMaterialsListComponent::CountMaterialIdInstances(AZ::Data::AssetId id) const
@ -204,9 +219,6 @@ namespace Terrain
void TerrainSurfaceMaterialsListComponent::HandleMaterialStateChanges()
{
bool anyMaterialIsActive = false;
bool anyMaterialWasAlreadyActive = false;
{
// Handle default material first
auto& defaultMaterial = m_configuration.m_defaultSurfaceMaterial;
@ -214,12 +226,9 @@ namespace Terrain
const bool wasPreviouslyActive = defaultMaterial.m_active;
defaultMaterial.m_active = (defaultMaterial.m_materialInstance != nullptr);
anyMaterialWasAlreadyActive = wasPreviouslyActive;
anyMaterialIsActive = defaultMaterial.m_active;
if (!wasPreviouslyActive && !defaultMaterial.m_active)
{
// A material has been assigned but has not yet completed loading.
// A material has not been assigned or has not yet completed loading.
}
else if (!wasPreviouslyActive && defaultMaterial.m_active)
{
@ -227,6 +236,7 @@ namespace Terrain
&TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialCreated, GetEntityId(),
defaultMaterial.m_materialInstance);
defaultMaterial.m_previousChangeId = defaultMaterial.m_materialInstance->GetCurrentChangeId();
defaultMaterial.m_activeMaterialAssetId = defaultMaterial.m_materialInstance->GetAssetId();
}
else if (wasPreviouslyActive && !defaultMaterial.m_active)
{
@ -256,9 +266,6 @@ namespace Terrain
const bool wasPreviouslyActive = surfaceMaterialMapping.m_active;
surfaceMaterialMapping.m_active = surfaceMaterialMapping.m_materialInstance != nullptr;
anyMaterialWasAlreadyActive = anyMaterialWasAlreadyActive || wasPreviouslyActive;
anyMaterialIsActive = anyMaterialIsActive || surfaceMaterialMapping.m_active;
if (!wasPreviouslyActive && !surfaceMaterialMapping.m_active)
{
// A material has been assigned but has not yet completed loading.
@ -315,27 +322,6 @@ namespace Terrain
}
}
}
if (!anyMaterialWasAlreadyActive && anyMaterialIsActive)
{
// Cache the current shape bounds.
LmbrCentral::ShapeComponentRequestsBus::EventResult(
m_cachedAabb, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
// Start listening for data requests.
TerrainAreaMaterialRequestBus::Handler::BusConnect(GetEntityId());
// Start listening for shape changes.
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
}
else if (anyMaterialWasAlreadyActive && !anyMaterialIsActive)
{
// All materials have been deactivated, stop listening for requests and notifications.
m_cachedAabb = AZ::Aabb::CreateNull();
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
TerrainAreaMaterialRequestBus::Handler::BusDisconnect();
}
}
bool TerrainSurfaceMaterialsListComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
@ -359,16 +345,29 @@ namespace Terrain
}
void TerrainSurfaceMaterialsListComponent::OnShapeChanged([[maybe_unused]] ShapeComponentNotifications::ShapeChangeReasons reasons)
{
AZ::Aabb oldAabb = m_cachedAabb;
{
AZ::Aabb oldAabb = m_cachedAabb;
LmbrCentral::ShapeComponentRequestsBus::EventResult(
m_cachedAabb, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
LmbrCentral::ShapeComponentRequestsBus::EventResult(
m_cachedAabb, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingRegionChanged, GetEntityId(), oldAabb,
m_cachedAabb);
}
if (m_cachedAabb.IsValid() && !oldAabb.IsValid())
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingRegionCreated, GetEntityId(), m_cachedAabb);
}
else if (!m_cachedAabb.IsValid() && oldAabb.IsValid())
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingRegionDestroyed, GetEntityId(), oldAabb);
}
else if (m_cachedAabb.IsValid() && oldAabb.IsValid())
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingRegionChanged, GetEntityId(), oldAabb,
m_cachedAabb);
}
}
const AZ::Aabb& TerrainSurfaceMaterialsListComponent::GetTerrainSurfaceMaterialRegion() const
{
@ -380,34 +379,42 @@ namespace Terrain
return m_configuration.m_surfaceMaterials;
}
const TerrainSurfaceMaterialMapping& TerrainSurfaceMaterialsListComponent::GetDefaultMaterial() const
{
return m_configuration.m_defaultSurfaceMaterial;
}
void TerrainSurfaceMaterialsListComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
bool anyUpdated = false;
// Find the missing material instance with the correct id.
auto checkUpdateMaterialAsset = [](TerrainSurfaceMaterialMapping& mapping, const AZ::Data::Asset<AZ::Data::AssetData>& asset) -> bool
auto checkUpdateMaterialAsset = [&anyUpdated](TerrainSurfaceMaterialMapping& mapping, const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
if (mapping.m_materialAsset.GetId() == asset.GetId() &&
(!mapping.m_materialInstance || mapping.m_materialInstance->GetAssetId() != mapping.m_materialAsset.GetId()))
if (mapping.m_materialAsset.GetId() == asset.GetId())
{
mapping.m_materialInstance = AZ::RPI::Material::FindOrCreate(mapping.m_materialAsset);
if (!mapping.m_materialInstance || mapping.m_materialInstance->GetAssetId() != mapping.m_materialAsset.GetId())
{
mapping.m_materialInstance = AZ::RPI::Material::FindOrCreate(asset);
}
mapping.m_materialAsset.Release();
return true;
anyUpdated = true;
}
return false;
};
// First check the default material
if (!checkUpdateMaterialAsset(m_configuration.m_defaultSurfaceMaterial, asset))
checkUpdateMaterialAsset(m_configuration.m_defaultSurfaceMaterial, asset);
// Check all the surface material mappings. They may be referenced more than once.
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
// If the default materail wasn't updated, then check all the surface material mappings.
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (checkUpdateMaterialAsset(surfaceMaterialMapping, asset))
{
break;
}
}
checkUpdateMaterialAsset(surfaceMaterialMapping, asset);
}
if (anyUpdated)
{
HandleMaterialStateChanges();
}
HandleMaterialStateChanges();
}
void TerrainSurfaceMaterialsListComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)

@ -101,6 +101,7 @@ namespace Terrain
// TerrainAreaMaterialRequestBus
const AZ::Aabb& GetTerrainSurfaceMaterialRegion() const override;
const AZStd::vector<TerrainSurfaceMaterialMapping>& GetSurfaceMaterialMappings() const override;
const TerrainSurfaceMaterialMapping& GetDefaultMaterial() const override;
//////////////////////////////////////////////////////////////////////////
// AZ::Data::AssetBus::Handler
@ -109,6 +110,6 @@ namespace Terrain
TerrainSurfaceMaterialsListConfig m_configuration;
AZ::Aabb m_cachedAabb;
AZ::Aabb m_cachedAabb{ AZ::Aabb::CreateNull() };
};
} // namespace Terrain

@ -29,8 +29,11 @@ namespace Terrain
//! Get the Aabb for the region where a TerrainSurfaceMaterialMapping exists
virtual const AZ::Aabb& GetTerrainSurfaceMaterialRegion() const = 0;
//! Get the Material asset assigned to a particular surface tag.
//! Get the Materials assigned to various surface tags.
virtual const AZStd::vector<struct TerrainSurfaceMaterialMapping>& GetSurfaceMaterialMappings() const = 0;
//! Get the default material
virtual const TerrainSurfaceMaterialMapping& GetDefaultMaterial() const = 0;
};
using TerrainAreaMaterialRequestBus = AZ::EBus<TerrainAreaMaterialRequests>;
@ -95,6 +98,20 @@ namespace Terrain
[[maybe_unused]] AZ::Data::Instance<AZ::RPI::Material> material)
{
}
//! A set of surface material mappings has been created
virtual void OnTerrainSurfaceMaterialMappingRegionCreated(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] const AZ::Aabb& region)
{
}
//! A set of surface material mappings has been destroyed
virtual void OnTerrainSurfaceMaterialMappingRegionDestroyed(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] const AZ::Aabb& oldRegion)
{
}
//! The bounds of this set of surface material mappings has changed
virtual void OnTerrainSurfaceMaterialMappingRegionChanged(

@ -131,6 +131,11 @@ namespace Terrain
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)
@ -214,7 +219,6 @@ namespace Terrain
void TerrainDetailMaterialManager::Reset()
{
RemoveAllImages();
m_bindlessImageHandler.reset();
m_detailTextureImage = {};
m_detailMaterials.Clear();
@ -279,13 +283,18 @@ namespace Terrain
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialCreated(AZ::EntityId entityId, MaterialInstance material)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
AZ_Error("TerrainDetailMaterialManager", materialRegion.m_defaultDetailMaterialId == InvalidDetailMaterailId,
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).refCount++;
m_dirtyDetailRegion.AddAabb(materialRegion.m_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)
@ -296,9 +305,15 @@ namespace Terrain
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 = InvalidDetailMaterailId;
materialRegion->m_defaultDetailMaterialId = InvalidDetailMaterialId;
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, MaterialInstance newMaterial)
@ -314,44 +329,54 @@ namespace Terrain
uint16_t materialId = CreateOrUpdateDetailMaterial(newMaterial);
if (materialRegion->m_defaultDetailMaterialId != materialId)
{
++m_detailMaterials.GetData(materialId).refCount;
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 = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
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&)
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).refCount++;
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
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 = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
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,
[[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, surfaceTag,
[&](DetailMaterialSurface& surface)
{
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
if (surface.m_surfaceTag != materialRegion.m_materialsForSurfaces.back().m_surfaceTag)
if (surface.m_surfaceTag != materialRegion->m_materialsForSurfaces.back().m_surfaceTag)
{
AZStd::swap(surface, materialRegion.m_materialsForSurfaces.back());
AZStd::swap(surface, materialRegion->m_materialsForSurfaces.back());
}
materialRegion.m_materialsForSurfaces.pop_back();
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
return;
materialRegion->m_materialsForSurfaces.pop_back();
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
});
AZ_Error(TerrainDetailMaterialManagerName, found, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed().");
@ -377,7 +402,7 @@ namespace Terrain
{
// 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).refCount;
m_detailMaterials.GetData(materialId).m_refCount++;
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
surface.m_detailMaterialId = materialId;
}
@ -405,19 +430,55 @@ namespace Terrain
});
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 = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
materialRegion.m_region = newRegion;
m_dirtyDetailRegion.AddAabb(oldRegion);
m_dirtyDetailRegion.AddAabb(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.refCount == 0)
if (--detailMaterialData.m_refCount == 0)
{
uint16_t bufferIndex = detailMaterialData.m_detailMaterialBufferIndex;
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(bufferIndex);
@ -451,7 +512,7 @@ namespace Terrain
static constexpr uint16_t InvalidDetailMaterial = 0xFFFF;
uint16_t detailMaterialId = InvalidDetailMaterial;
for (auto& detailMaterialData : m_detailMaterials.GetDataVector())
for (const auto& detailMaterialData : m_detailMaterials.GetDataVector())
{
if (detailMaterialData.m_assetId == material->GetAssetId())
{
@ -476,8 +537,6 @@ namespace Terrain
void TerrainDetailMaterialManager::UpdateDetailMaterialData(uint16_t detailMaterialIndex, MaterialInstance material)
{
DetailMaterialData& materialData = m_detailMaterials.GetData(detailMaterialIndex);
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(materialData.m_detailMaterialBufferIndex);
if (materialData.m_materialChangeId == material->GetCurrentChangeId())
{
return; // material hasn't changed, nothing to do
@ -485,8 +544,12 @@ namespace Terrain
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
{
@ -512,6 +575,7 @@ namespace Terrain
const auto index = getIndex(indexName);
const auto useTextureIndex = getIndex(usingFlagName);
bool useTextureValue = true;
if (useTextureIndex.IsValid())
{
useTextureValue = material->GetPropertyValue(useTextureIndex).GetValue<bool>();
@ -684,7 +748,7 @@ namespace Terrain
AZ_Error(TerrainDetailMaterialManagerName, m_detailTextureImage, "Failed to initialize the detail texture image.");
ClipmapBounds::ClipmapBoundsRegionList updateRegions = m_detailMaterialIdBounds.TransformRegion(m_detailMaterialIdBounds.GetWorldBounds());
for (auto& region : updateRegions)
for (const auto& region : updateRegions)
{
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
}
@ -692,22 +756,24 @@ namespace Terrain
else
{
// Update the edge regions
for (auto& region : edgeUpdatedRegions)
for (const auto& region : edgeUpdatedRegions)
{
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
}
m_dirtyDetailRegion = m_dirtyDetailRegion.GetClamped(untouchedRegion);
if (m_dirtyDetailRegion.IsValid())
{
ClipmapBounds::ClipmapBoundsRegionList updateRegions = m_detailMaterialIdBounds.TransformRegion(m_dirtyDetailRegion);
for (auto& region : updateRegions)
m_dirtyDetailRegion = m_dirtyDetailRegion.GetClamped(untouchedRegion);
if (m_dirtyDetailRegion.IsValid())
{
UpdateDetailTexture(region.m_worldAabb, region.m_localAabb);
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();
}
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
}
}
@ -730,23 +796,24 @@ namespace Terrain
const int32_t height = textureUpdateAabb.m_max.m_y - textureUpdateAabb.m_min.m_y;
AZStd::vector<DetailMaterialPixel> pixels(width * height);
uint32_t index = 0;
auto perPositionCallback = [this, &pixels, &index](
[[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
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.
bool isFirstMaterial = true;
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)
{
pixels.at(index).m_material1 = m_passthroughMaterialId;
++index;
pixel.m_material1 = m_passthroughMaterialId;
return;
}
@ -758,61 +825,51 @@ namespace Terrain
uint16_t materialId = GetDetailMaterialForSurfaceType(*region, surfaceType);
if (materialId < 255)
{
if (isFirstMaterial)
if (foundMaterials == 0)
{
// First material is valid. Save its weight to calculate blend later
pixels.at(index).m_material1 = aznumeric_cast<uint8_t>(materialId);
// Found first material. Save its weight to calculate blend later.
++foundMaterials;
pixel.m_material1 = aznumeric_cast<uint8_t>(materialId);
firstWeight = surfaceTagWeight.m_weight;
isFirstMaterial = false;
static constexpr float MaxValueBeforeRounding = 254.5f / 255.0f;
if (firstWeight >= MaxValueBeforeRounding)
{
break;
}
}
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
{
// Second material is valid, weight is relative based on first material's weight.
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;
}
}
continue; // search for second material
}
else
{
// No more valid materials in list since surfaceTagWeight is ordered.
uint8_t defaultMaterial = region->m_defaultDetailMaterialId == InvalidDetailMaterailId ? m_passthroughMaterialId :
aznumeric_cast<uint8_t>(m_detailMaterials.GetData(region->m_defaultDetailMaterialId).m_detailMaterialBufferIndex);
if (isFirstMaterial)
{
// Only one material and it's the default material.
pixels.at(index).m_material1 = defaultMaterial;
}
else
{
// Second material is default, weight is exactly what the first material requested
pixels.at(index).m_material2 = defaultMaterial;
float blendWeight = 1.0f - AZStd::clamp<float>(firstWeight, 0.0f, 1.0f);
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
}
}
if (pixels.at(index).m_material1 == pixels.at(index).m_material2)
{
// If the materials are the same, then make the blend 100% on the first id so the shader
// doesn't blend identical materials
pixels.at(index).m_blend = 0;
}
break;
}
++index;
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);
@ -848,7 +905,7 @@ namespace Terrain
return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex;
}
}
return InvalidDetailMaterailId;
return InvalidDetailMaterialId;
}
auto TerrainDetailMaterialManager::FindRegionForPosition(const AZ::Vector2& position) const -> const DetailMaterialListRegion*

@ -58,6 +58,7 @@ namespace Terrain
enum DetailTextureFlags : uint32_t
{
None = 0b0000'0000'0000'0000'0000'0000'0000'0000,
UseTextureBaseColor = 0b0000'0000'0000'0000'0000'0000'0000'0001,
UseTextureNormal = 0b0000'0000'0000'0000'0000'0000'0000'0010,
UseTextureMetallic = 0b0000'0000'0000'0000'0000'0000'0000'0100,
@ -69,11 +70,11 @@ namespace Terrain
FlipNormalX = 0b0000'0000'0000'0001'0000'0000'0000'0000,
FlipNormalY = 0b0000'0000'0000'0010'0000'0000'0000'0000,
BlendModeMask = 0b0000'0000'0000'1100'0000'0000'0000'0000,
BlendModeLerp = 0b0000'0000'0000'0000'0000'0000'0000'0000,
BlendModeLinearLight = 0b0000'0000'0000'0100'0000'0000'0000'0000,
BlendModeMultiply = 0b0000'0000'0000'1000'0000'0000'0000'0000,
BlendModeOverlay = 0b0000'0000'0000'1100'0000'0000'0000'0000,
BlendModeMask = 0b0000'0000'0001'1100'0000'0000'0000'0000,
BlendModeLerp = 0b0000'0000'0000'0100'0000'0000'0000'0000,
BlendModeLinearLight = 0b0000'0000'0000'1000'0000'0000'0000'0000,
BlendModeMultiply = 0b0000'0000'0000'1100'0000'0000'0000'0000,
BlendModeOverlay = 0b0000'0000'0001'0000'0000'0000'0000'0000,
};
struct DetailMaterialShaderData
@ -94,11 +95,11 @@ namespace Terrain
float m_baseColorFactor{ 1.0f };
float m_normalFactor{ 1.0f };
float m_metalFactor{ 1.0f };
float m_metalFactor{ 0.0f };
float m_roughnessScale{ 1.0f };
float m_roughnessBias{ 0.0f };
float m_specularF0Factor{ 1.0f };
float m_specularF0Factor{ 0.5f };
float m_occlusionFactor{ 1.0f };
float m_heightFactor{ 1.0f };
float m_heightOffset{ 0.0f };
@ -119,9 +120,9 @@ namespace Terrain
uint16_t m_heightImageIndex{ InvalidImageIndex };
// 16 byte aligned
uint16_t m_padding1;
uint32_t m_padding2;
uint32_t m_padding3;
uint16_t m_padding1{ 0 };
uint32_t m_padding2{ 0 };
uint32_t m_padding3{ 0 };
};
static_assert(sizeof(DetailMaterialShaderData) % 16 == 0, "DetailMaterialShaderData must be 16 byte aligned.");
@ -129,7 +130,7 @@ namespace Terrain
{
AZ::Data::AssetId m_assetId;
AZ::RPI::Material::ChangeId m_materialChangeId{AZ::RPI::Material::DEFAULT_CHANGE_ID};
uint32_t refCount = 0;
uint32_t m_refCount = 0;
uint16_t m_detailMaterialBufferIndex{ 0xFFFF };
AZ::Data::Instance<AZ::RPI::Image> m_colorImage;
@ -152,11 +153,16 @@ namespace Terrain
AZ::EntityId m_entityId;
AZ::Aabb m_region{AZ::Aabb::CreateNull()};
AZStd::vector<DetailMaterialSurface> m_materialsForSurfaces;
uint16_t m_defaultDetailMaterialId;
};
uint16_t m_defaultDetailMaterialId{ 0xFFFF };
bool HasMaterials()
{
return m_defaultDetailMaterialId != InvalidDetailMaterialId || !m_materialsForSurfaces.empty();
}
};
using DetailMaterialContainer = AZ::Render::IndexedDataVector<DetailMaterialData>;
static constexpr auto InvalidDetailMaterailId = DetailMaterialContainer::NoFreeSlot;
static constexpr auto InvalidDetailMaterialId = DetailMaterialContainer::NoFreeSlot;
// System-level parameters
static constexpr int32_t DetailTextureSize{ 1024 };
@ -175,6 +181,8 @@ namespace Terrain
void OnTerrainSurfaceMaterialMappingMaterialChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingTagChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag oldSurfaceTag, SurfaceData::SurfaceTag newSurfaceTag) override;
void OnTerrainSurfaceMaterialMappingRegionCreated(AZ::EntityId entityId, const AZ::Aabb& region) override;
void OnTerrainSurfaceMaterialMappingRegionDestroyed(AZ::EntityId entityId, const AZ::Aabb& oldRegion) override;
void OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override;
//! Removes all images from all detail materials from the bindless image array

Loading…
Cancel
Save