Detail material Id texture created from surface weights. (#4984)

* Added some structs for detail materials

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

* Added some template functions for looking up materials. Added lookups for all the relevant detail material fields in StandardPBR.

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

* Added some structs for detail materials

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

* Added some template functions for looking up materials. Added lookups for all the relevant detail material fields in StandardPBR.

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

* Added support for generating a detail material texture with IDs populated from surface weights.

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

* Updated TerrainAreaMaterailRequestBus to have separate calls for region vs materials instead of the awkward out parameter
Update MaterialPropertyDescriptor so that you can retrieve enum names by ID
Several bug fixes / updates to the terrain feature processor dealing with detail materials.

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

* Updating detail material texture based on offsets. Not quite working yet but close. Added visualization for detail material in shader (currently on, will be turned off before final commit)

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

* Small bugfixes
* Fix compile error in non-unity builds
* Fixed backwards x/y loops causing the wrong pixels to update
* Fixed selection of surface type with multiple surface weights

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Adding seam to detail texture debug display. Offseting edges by a half-pixel to avoid bleed. Disabling debugging detail textures by default.

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

* Missing file from last commit for detail material change.

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

* Cleanups

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

* bug fix

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

* Bug fix in the terrain fp for TerrainAreaMaterialRequestBus returning incomplete materials on GetSurfaceMaterialMappings

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

* Some PR updates. Exposing detail material id debugging through a cvar.

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

* Various updates from review.

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

* PR updates dealing with debug texture boundary line.

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

* Hiding some fields from the terrain material

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

* Fixing type in generic lambda for linux / android

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

Co-authored-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
monroegm-disable-blank-issue-2
Ken Pruiksma 4 years ago committed by GitHub
parent 0e4e84eb73
commit 97920feaf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -106,6 +106,9 @@ namespace AZ
static constexpr uint32_t InvalidEnumValue = std::numeric_limits<uint32_t>::max();
uint32_t GetEnumValue(const AZ::Name& enumName) const;
//! Returns the name of the enum from its index. An empty name is returned for an invalid id.
const AZ::Name& GetEnumName(uint32_t enumValue) const;
//! Returns the unique name ID of this property
const Name& GetName() const;

@ -203,6 +203,16 @@ namespace AZ
return InvalidEnumValue;
}
const AZ::Name& MaterialPropertyDescriptor::GetEnumName(uint32_t enumValue) const
{
if (enumValue < m_enumNames.size())
{
return m_enumNames.at(enumValue);
}
static AZ::Name EmptyName = AZ::Name();
return EmptyName;
}
} // namespace RPI
} // namespace AZ

@ -92,13 +92,58 @@
{
"id": "heightmapImage",
"displayName": "Heightmap Image",
"description": "Heightmap of the terrain, controlled by the runtime.",
"description": "Heightmap of the terrain. Controlled by the runtime.",
"visibility": "Hidden",
"type": "Image",
"connection": {
"type": "ShaderInput",
"id": "m_heightmapImage"
}
},
{
"id": "detailMaterialIdImage",
"displayName": "Detail Material Id Image",
"description": "Texture containing detail material Ids and weights. Controlled by the runtime.",
"visibility": "Hidden",
"type": "Image",
"connection": {
"type": "ShaderInput",
"id": "m_detailMaterialIdImage"
}
},
{
"id": "detailMaterialIdCenter",
"displayName": "Detail Material Id Image Center",
"description": "The center position of the detail material Id image. Controlled by the runtime.",
"visibility": "Hidden",
"type": "Vector2",
"connection": {
"type": "ShaderInput",
"id": "m_detailMaterialIdImageCenter"
}
},
{
"id": "detailAabb",
"displayName": "Detail material bounds in 2d",
"description": "The 2d world space bounds of the detail id material. Controlled by the runtime.",
"visibility": "Hidden",
"type": "Vector4",
"connection": {
"type": "ShaderInput",
"id": "m_detailAabb"
}
},
{
"id": "detailHalfPixelUv",
"displayName": "Detail texture half pixel uv size",
"description": "Uv size of a half pixel in the detail material id texture. Controlled by the runtime.",
"visibility": "Hidden",
"type": "float",
"connection": {
"type": "ShaderInput",
"id": "m_detailHalfPixelUv"
}
},
{
"id": "detailTextureMultiplier",
"displayName": "Detail Texture UV Multiplier",

@ -93,10 +93,15 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial
{
Texture2D m_heightmapImage;
Texture2D<uint4> m_detailMaterialIdImage;
float2 m_detailMaterialIdImageCenter;
float m_detailTextureMultiplier;
float m_detailFadeDistance;
float m_detailFadeLength;
float4 m_detailAabb;
float m_detailHalfPixelUv;
Sampler HeightmapSampler
{
MinFilter = Linear;
@ -117,6 +122,15 @@ ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial
MaxAnisotropy = 16;
};
Sampler m_detailSampler
{
AddressU = Wrap;
AddressV = Wrap;
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
};
// Base Color
float3 m_baseColor;
float m_baseColorFactor;

@ -17,6 +17,7 @@
#include <Atom/Features/PBR/Lighting/StandardLighting.azsli>
#include <Atom/Features/Shadow/DirectionalLightShadow.azsli>
#include <Atom/Features/PBR/Decals.azsli>
#include <Atom/Features/ColorManagement/TransformColor.azsli>
struct VSOutput
{
@ -29,6 +30,8 @@ struct VSOutput
float2 m_uv : UV1;
};
option bool o_debugDetailMaterialIds = false;
VSOutput TerrainPBR_MainPassVS(VertexInput IN)
{
VSOutput OUT;
@ -121,6 +124,48 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
float3 detailColor = GetBaseColorInput(TerrainMaterialSrg::m_baseColorMap, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
float3 blendedColor = BlendBaseColor(lerp(detailColor, TerrainMaterialSrg::m_baseColor.rgb, detailFactor), macroColor, TerrainMaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
// ------- Debug detail materials using random colors -------
// This assigns a random color to each material, turns off any kind of distance fading, and draws a black line at the texture edges.
if (o_debugDetailMaterialIds)
{
float2 detailRegionMin = TerrainMaterialSrg::m_detailAabb.xy;
float2 detailRegionMax = TerrainMaterialSrg::m_detailAabb.zw;
float2 detailRegionUv = (surface.position.xy - detailRegionMin) / (detailRegionMax - detailRegionMin);
if (all(detailRegionUv > TerrainMaterialSrg::m_detailHalfPixelUv) && all(detailRegionUv < 1.0 - TerrainMaterialSrg::m_detailHalfPixelUv))
{
detailRegionUv += TerrainMaterialSrg::m_detailMaterialIdImageCenter - (0.5);
uint material1 = TerrainMaterialSrg::m_detailMaterialIdImage.GatherRed(TerrainMaterialSrg::m_detailSampler, detailRegionUv, 0).r;
uint material2 = TerrainMaterialSrg::m_detailMaterialIdImage.GatherGreen(TerrainMaterialSrg::m_detailSampler, detailRegionUv, 0).r;
float blend = float(TerrainMaterialSrg::m_detailMaterialIdImage.GatherBlue(TerrainMaterialSrg::m_detailSampler, detailRegionUv, 0).r) / 0xFF;
float3 material1Color = float3(0.1, 0.1, 0.1);
float3 material2Color = float3(0.1, 0.1, 0.1);
// Get a reasonably random hue for the material id
if (material1 != 255)
{
float hue1 = (material1 * 25043 % 256) / 256.0;
material1Color = HsvToRgb(float3(hue1, 1.0, 1.0));
}
if (material2 != 255)
{
float hue2 = (material2 * 25043 % 256) / 256.0;
material2Color = HsvToRgb(float3(hue2, 1.0, 1.0));
}
blendedColor = lerp(material1Color, material2Color, blend);
float seamBlend = 0.0;
const float halfLineWidth = 1.0 / 2048.0;
if (any(abs(detailRegionUv) % 1.0 < halfLineWidth) || any(abs(detailRegionUv) % 1.0 > 1.0 - halfLineWidth))
{
seamBlend = 1.0;
}
blendedColor = lerp(blendedColor, float3(0.0, 0.0, 0.0), seamBlend); // draw texture seams
blendedColor = pow(blendedColor , 2.2);
}
}
// ------- Specular -------
float specularF0Factor = GetSpecularInput(TerrainMaterialSrg::m_specularF0Map, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
specularF0Factor = lerp(specularF0Factor, 0.5, detailFactor);

@ -123,20 +123,23 @@ namespace Terrain
{
surfaceMaterialMapping.m_active = false;
surfaceMaterialMapping.m_materialAsset.QueueLoad();
AZ::Data::AssetBus::Handler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId());
AZ::Data::AssetBus::MultiHandler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId());
}
}
// Announce initial shape using OnShapeChanged
OnShapeChanged(LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged);
}
void TerrainSurfaceMaterialsListComponent::Deactivate()
{
TerrainAreaMaterialRequestBus::Handler::BusDisconnect();
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid())
{
AZ::Data::AssetBus::Handler::BusDisconnect(surfaceMaterialMapping.m_materialAsset.GetId());
surfaceMaterialMapping.m_materialAsset.Release();
surfaceMaterialMapping.m_materialInstance.reset();
surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId();
@ -202,7 +205,7 @@ namespace Terrain
// Don't disconnect from the AssetBus if this material is mapped more than once.
if (CountMaterialIDInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1)
{
AZ::Data::AssetBus::Handler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId);
AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId);
}
surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId();
@ -273,12 +276,14 @@ namespace Terrain
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingRegionChanged, GetEntityId(), oldAabb,
m_cachedAabb);
}
const AZStd::vector<TerrainSurfaceMaterialMapping>& TerrainSurfaceMaterialsListComponent::GetSurfaceMaterialMappings(
AZ::Aabb& region) const
const AZ::Aabb& TerrainSurfaceMaterialsListComponent::GetTerrainSurfaceMaterialRegion() const
{
region = m_cachedAabb;
return m_cachedAabb;
}
const AZStd::vector<TerrainSurfaceMaterialMapping>& TerrainSurfaceMaterialsListComponent::GetSurfaceMaterialMappings() const
{
return m_configuration.m_surfaceMaterials;
}

@ -53,7 +53,7 @@ namespace Terrain
class TerrainSurfaceMaterialsListComponent
: public AZ::Component
, private TerrainAreaMaterialRequestBus::Handler
, private AZ::Data::AssetBus::Handler
, private AZ::Data::AssetBus::MultiHandler
, private LmbrCentral::ShapeComponentNotificationsBus::Handler
{
public:
@ -86,7 +86,8 @@ namespace Terrain
//////////////////////////////////////////////////////////////////////////
// TerrainAreaMaterialRequestBus
const AZStd::vector<TerrainSurfaceMaterialMapping>& GetSurfaceMaterialMappings(AZ::Aabb& region) const override;
const AZ::Aabb& GetTerrainSurfaceMaterialRegion() const override;
const AZStd::vector<TerrainSurfaceMaterialMapping>& GetSurfaceMaterialMappings() const override;
//////////////////////////////////////////////////////////////////////////
// AZ::Data::AssetBus::Handler

@ -9,13 +9,9 @@
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <SurfaceData/SurfaceDataTypes.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h>
namespace Terrain
{
//! This bus provides retrieval of information from Terrain Surfaces.
@ -30,8 +26,11 @@ namespace Terrain
virtual ~TerrainAreaMaterialRequests() = default;
//! 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.
virtual const AZStd::vector<struct TerrainSurfaceMaterialMapping>& GetSurfaceMaterialMappings(AZ::Aabb& region) const = 0;
virtual const AZStd::vector<struct TerrainSurfaceMaterialMapping>& GetSurfaceMaterialMappings() const = 0;
};
using TerrainAreaMaterialRequestBus = AZ::EBus<TerrainAreaMaterialRequests>;

@ -7,11 +7,13 @@
*/
#include <TerrainRenderer/TerrainFeatureProcessor.h>
#include <TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Math/Frustum.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/math.h>
#include <AzCore/Math/Frustum.h>
#include <Atom/Utils/Utils.h>
@ -45,12 +47,49 @@ namespace Terrain
{
[[maybe_unused]] const char* TerrainFPName = "TerrainFeatureProcessor";
const char* TerrainHeightmapChars = "TerrainHeightmap";
const char* TerrainDetailChars = "TerrainDetail";
}
namespace MaterialInputs
{
// Terrain material
static const char* const HeightmapImage("settings.heightmapImage");
static const char* const DetailMaterialIdImage("settings.detailMaterialIdImage");
static const char* const DetailCenter("settings.detailMaterialIdCenter");
static const char* const DetailAabb("settings.detailAabb");
static const char* const DetailHalfPixelUv("settings.detailHalfPixelUv");
}
namespace DetailMaterialInputs
{
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 RoughnessUpperBound("roughness.lowerBound");
static const char* const RoughnessLowerBound("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");
}
namespace ShaderInputs
@ -62,6 +101,17 @@ namespace Terrain
static const char* const MacroColorMap("m_macroColorMap");
static const char* const MacroNormalMap("m_macroNormalMap");
}
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."
);
void TerrainFeatureProcessor::Reflect(AZ::ReflectContext* context)
@ -78,6 +128,12 @@ namespace Terrain
{
Initialize();
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
m_handleGlobalShaderOptionUpdate = AZ::RPI::ShaderSystemInterface::GlobalShaderOptionUpdatedEvent::Handler
{
[this](const AZ::Name&, AZ::RPI::ShaderOptionValue) { m_forceRebuildDrawPackets = true; }
};
AZ::RPI::ShaderSystemInterface::Get()->Connect(m_handleGlobalShaderOptionUpdate);
}
void TerrainFeatureProcessor::Initialize()
@ -139,11 +195,18 @@ namespace Terrain
void TerrainFeatureProcessor::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
{
if ((dataChangedMask & (TerrainDataChangedMask::HeightData | TerrainDataChangedMask::Settings)) == 0)
if ((dataChangedMask & (TerrainDataChangedMask::HeightData | TerrainDataChangedMask::Settings)) != 0)
{
return;
TerrainHeightOrSettingsUpdated(dirtyRegion);
}
if ((dataChangedMask & TerrainDataChangedMask::SurfaceData) != 0)
{
TerrainSurfaceDataUpdated(dirtyRegion);
}
}
void TerrainFeatureProcessor::TerrainHeightOrSettingsUpdated(const AZ::Aabb& dirtyRegion)
{
AZ::Aabb worldBounds = AZ::Aabb::CreateNull();
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
@ -177,10 +240,15 @@ namespace Terrain
m_areaData.m_sampleSpacing = queryResolution.GetX();
m_areaData.m_heightmapUpdated = true;
}
void TerrainFeatureProcessor::TerrainSurfaceDataUpdated(const AZ::Aabb& dirtyRegion)
{
m_dirtyDetailRegion.AddAabb(dirtyRegion);
}
void TerrainFeatureProcessor::OnTerrainMacroMaterialCreated(AZ::EntityId entityId, const MacroMaterialData& newMaterialData)
{
MacroMaterialData& materialData = FindOrCreateMacroMaterial(entityId);
MacroMaterialData& materialData = FindOrCreateByEntityId(entityId, m_macroMaterials);
UpdateMacroMaterialData(materialData, newMaterialData);
@ -197,14 +265,14 @@ namespace Terrain
void TerrainFeatureProcessor::OnTerrainMacroMaterialChanged(AZ::EntityId entityId, const MacroMaterialData& newMaterialData)
{
MacroMaterialData& data = FindOrCreateMacroMaterial(entityId);
MacroMaterialData& data = FindOrCreateByEntityId(entityId, m_macroMaterials);
UpdateMacroMaterialData(data, newMaterialData);
}
void TerrainFeatureProcessor::OnTerrainMacroMaterialRegionChanged(
AZ::EntityId entityId, [[maybe_unused]] const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion)
{
MacroMaterialData& materialData = FindOrCreateMacroMaterial(entityId);
MacroMaterialData& materialData = FindOrCreateByEntityId(entityId, m_macroMaterials);
for (SectorData& sectorData : m_sectorData)
{
bool overlapsOld = sectorData.m_aabb.Overlaps(materialData.m_bounds);
@ -236,7 +304,7 @@ namespace Terrain
void TerrainFeatureProcessor::OnTerrainMacroMaterialDestroyed(AZ::EntityId entityId)
{
MacroMaterialData* materialData = FindMacroMaterial(entityId);
const MacroMaterialData* materialData = FindByEntityId(entityId, m_macroMaterials);
if (materialData)
{
@ -255,13 +323,461 @@ namespace Terrain
}
m_areaData.m_macroMaterialsUpdated = true;
RemoveMacroMaterial(entityId);
RemoveByEntityId(entityId, m_macroMaterials);
}
void TerrainFeatureProcessor::UpdateTerrainData()
void TerrainFeatureProcessor::OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
// Validate that the surface tag is new
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
{
if (surface.m_surfaceTag == surfaceTag)
{
AZ_Error(TerrainFPName, false, "Already have a surface material mapping for this surface tag.");
return;
}
}
uint16_t detailMaterialId = CreateOrUpdateDetailMaterial(material);
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, detailMaterialId });
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
}
void TerrainFeatureProcessor::OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
{
if (surface.m_surfaceTag == surfaceTag)
{
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);
return;
}
}
AZ_Error(TerrainFPName, false, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed().");
}
void TerrainFeatureProcessor::OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
bool found = false;
uint16_t materialId = CreateOrUpdateDetailMaterial(material);
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
{
if (surface.m_surfaceTag == surfaceTag)
{
found = true;
surface.m_detailMaterialId = materialId;
break;
}
}
if (!found)
{
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, materialId });
}
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
}
void TerrainFeatureProcessor::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);
}
uint16_t TerrainFeatureProcessor::CreateOrUpdateDetailMaterial(MaterialInstance material)
{
static constexpr uint16_t InvalidDetailMaterial = 0xFFFF;
uint16_t detailMaterialId = InvalidDetailMaterial;
for (DetailMaterialData& detailMaterial : m_detailMaterials.GetDataVector())
{
if (detailMaterial.m_assetId == material->GetAssetId())
{
UpdateDetailMaterialData(detailMaterial, material);
detailMaterialId = m_detailMaterials.GetIndexForData(&detailMaterial);
break;
}
}
if (detailMaterialId == InvalidDetailMaterial)
{
detailMaterialId = m_detailMaterials.GetFreeSlotIndex();
UpdateDetailMaterialData(m_detailMaterials.GetData(detailMaterialId), material);
}
return detailMaterialId;
}
void TerrainFeatureProcessor::UpdateDetailMaterialData(DetailMaterialData& materialData, MaterialInstance material)
{
if (materialData.m_materialChangeId != material->GetCurrentChangeId())
{
materialData = DetailMaterialData();
DetailTextureFlags& flags = materialData.m_properties.m_flags;
materialData.m_materialChangeId = material->GetCurrentChangeId();
materialData.m_assetId = material->GetAssetId();
auto getIndex = [&](const char* const indexName) -> AZ::RPI::MaterialPropertyIndex
{
const AZ::RPI::MaterialPropertyIndex index = material->FindPropertyIndex(AZ::Name(indexName));
AZ_Warning(TerrainFPName, 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())
{
using TypeRefRemoved = AZStd::remove_cvref_t<decltype(ref)>;
ref = material->GetPropertyValue(index).GetValue<TypeRefRemoved>();
}
};
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;
applyProperty(BaseColorMap, materialData.m_colorImage);
applyFlag(BaseColorUseTexture, DetailTextureFlags::UseTextureBaseColor);
applyProperty(BaseColorFactor, materialData.m_properties.m_baseColorFactor);
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);
}
applyProperty(MetallicMap, materialData.m_metalnessImage);
applyFlag(MetallicUseTexture, DetailTextureFlags::UseTextureMetallic);
applyProperty(MetallicFactor, materialData.m_properties.m_metalFactor);
applyProperty(RoughnessMap, materialData.m_roughnessImage);
applyFlag(RoughnessUseTexture, DetailTextureFlags::UseTextureRoughness);
if ((flags & DetailTextureFlags::UseTextureRoughness) > 0)
{
float lowerBound = 0.0;
float upperBound = 1.0;
applyProperty(RoughnessLowerBound, lowerBound);
applyProperty(RoughnessUpperBound, upperBound);
materialData.m_properties.m_roughnessBias = lowerBound;
materialData.m_properties.m_roughnessScale = upperBound - lowerBound;
}
else
{
materialData.m_properties.m_roughnessBias = 0.0;
applyProperty(RoughnessFactor, materialData.m_properties.m_roughnessScale);
}
applyProperty(SpecularF0Map, materialData.m_specularF0Image);
applyFlag(SpecularF0UseTexture, DetailTextureFlags::UseTextureSpecularF0);
applyProperty(SpecularF0Factor, materialData.m_properties.m_specularF0Factor);
applyProperty(NormalMap, materialData.m_normalImage);
applyFlag(NormalUseTexture, DetailTextureFlags::UseTextureNormal);
applyProperty(NormalFactor, materialData.m_properties.m_normalFactor);
applyFlag(NormalFlipX, DetailTextureFlags::FlipNormalX);
applyFlag(NormalFlipY, DetailTextureFlags::FlipNormalY);
applyProperty(DiffuseOcclusionMap, materialData.m_occlusionImage);
applyFlag(DiffuseOcclusionUseTexture, DetailTextureFlags::UseTextureOcclusion);
applyProperty(DiffuseOcclusionFactor, materialData.m_properties.m_occlusionFactor);
applyProperty(HeightMap, materialData.m_heightImage);
applyFlag(HeightUseTexture, DetailTextureFlags::UseTextureHeight);
applyProperty(HeightFactor, materialData.m_properties.m_heightFactor);
applyProperty(HeightOffset, materialData.m_properties.m_heightOffset);
applyProperty(HeightBlendFactor, materialData.m_properties.m_heightBlendFactor);
}
}
void TerrainFeatureProcessor::CheckUpdateDetailTexture(const Aabb2i& newBounds, const Vector2i& newCenter)
{
static const AZ::Name TerrainHeightmapName = AZ::Name(TerrainHeightmapChars);
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(TerrainFPName, m_detailTextureImage, "Failed to initialize the detail texture image.");
UpdateDetailTexture(newBounds, newBounds, newCenter);
}
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)
{
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;
UpdateDetailTexture(updateBounds, newBounds, newCenter);
}
// 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;
}
UpdateDetailTexture(updateBounds, newBounds, newCenter);
}
if (m_dirtyDetailRegion.IsValid())
{
// If any regions are marked as dirty, then they should be updated.
AZ::Vector3 currentMin = AZ::Vector3(newBounds.m_min.m_x * DetailTextureScale, newBounds.m_min.m_y * DetailTextureScale, -0.5f);
AZ::Vector3 currentMax = AZ::Vector3(newBounds.m_max.m_x * DetailTextureScale, newBounds.m_max.m_y * DetailTextureScale, 0.5f);
AZ::Aabb detailTextureCoverage = AZ::Aabb::CreateFromMinMax(currentMin, currentMax);
AZ::Vector3 previousMin = AZ::Vector3(m_detailTextureBounds.m_min.m_x * DetailTextureScale, m_detailTextureBounds.m_min.m_y * DetailTextureScale, -0.5f);
AZ::Vector3 previousMax = AZ::Vector3(m_detailTextureBounds.m_max.m_x * DetailTextureScale, m_detailTextureBounds.m_max.m_y * DetailTextureScale, 0.5f);
AZ::Aabb previousCoverage = AZ::Aabb::CreateFromMinMax(previousMin, previousMax);
// Area of texture not already updated by camera movement above.
AZ::Aabb clampedCoverage = previousCoverage.GetClamped(detailTextureCoverage);
// Clamp the dirty region to the area of the detail texture that is visible and not already updated.
clampedCoverage.Clamp(m_dirtyDetailRegion);
if (clampedCoverage.IsValid())
{
Aabb2i updateBounds;
updateBounds.m_min.m_x = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMin().GetX() / DetailTextureScale));
updateBounds.m_min.m_y = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMin().GetY() / DetailTextureScale));
updateBounds.m_max.m_x = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMax().GetX() / DetailTextureScale));
updateBounds.m_max.m_y = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMax().GetY() / DetailTextureScale));
if (updateBounds.m_min.m_x < updateBounds.m_max.m_x && updateBounds.m_min.m_y < updateBounds.m_max.m_y)
{
UpdateDetailTexture(updateBounds, newBounds, newCenter);
}
}
}
}
}
uint8_t TerrainFeatureProcessor::CalculateUpdateRegions(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel,
AZStd::array<Aabb2i, 4>& textureSpaceAreas, AZStd::array<Aabb2i, 4>& scaledWorldSpaceAreas)
{
Vector2i centerOffset = { centerPixel.m_x - DetailTextureSizeHalf, centerPixel.m_y - DetailTextureSizeHalf };
int32_t quadrantXOffset = centerPixel.m_x < DetailTextureSizeHalf ? DetailTextureSize : -DetailTextureSize;
int32_t quadrantYOffset = centerPixel.m_y < DetailTextureSizeHalf ? DetailTextureSize : -DetailTextureSize;
uint8_t numQuadrants = 0;
// For each of the 4 quadrants:
auto calculateQuadrant = [&](Vector2i quadrantOffset)
{
Aabb2i offsetUpdateArea = updateArea + centerOffset + quadrantOffset;
Aabb2i updateSectionBounds = textureBounds.GetClamped(offsetUpdateArea);
if (updateSectionBounds.IsValid())
{
textureSpaceAreas[numQuadrants] = updateSectionBounds - textureBounds.m_min;
scaledWorldSpaceAreas[numQuadrants] = updateSectionBounds - centerOffset - quadrantOffset;
++numQuadrants;
}
};
calculateQuadrant({ 0, 0 });
calculateQuadrant({ quadrantXOffset, 0 });
calculateQuadrant({ 0, quadrantYOffset });
calculateQuadrant({ quadrantXOffset, quadrantYOffset });
return numQuadrants;
}
void TerrainFeatureProcessor::UpdateDetailTexture(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel)
{
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 };
};
// Because the center of the detail texture may be offset, each update area may actually need to be split into
// up to 4 separate update areas in each sector of the quadrant.
AZStd::array<Aabb2i, 4> textureSpaceAreas;
AZStd::array<Aabb2i, 4> scaledWorldSpaceAreas;
uint8_t updateAreaCount = CalculateUpdateRegions(updateArea, textureBounds, centerPixel, textureSpaceAreas, scaledWorldSpaceAreas);
// Pull the data for each area updated and use it to construct an update for the detail material id texture.
for (uint8_t i = 0; i < updateAreaCount; ++i)
{
const Aabb2i& quadrantTextureArea = textureSpaceAreas[i];
const Aabb2i& quadrantWorldArea = scaledWorldSpaceAreas[i];
AZStd::vector<DetailMaterialPixel> pixels;
pixels.resize((quadrantWorldArea.m_max.m_x - quadrantWorldArea.m_min.m_x) * (quadrantWorldArea.m_max.m_y - quadrantWorldArea.m_min.m_y));
uint32_t index = 0;
for (int yPos = quadrantWorldArea.m_min.m_y; yPos < quadrantWorldArea.m_max.m_y; ++yPos)
{
for (int xPos = quadrantWorldArea.m_min.m_x; xPos < quadrantWorldArea.m_max.m_x; ++xPos)
{
AZ::Vector2 position = AZ::Vector2(xPos * DetailTextureScale, yPos * DetailTextureScale);
AzFramework::SurfaceData::SurfaceTagWeightList surfaceWeights;
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::GetSurfaceWeightsFromVector2, position, surfaceWeights, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, nullptr);
// Store the top two surface weights in the texture with m_blend storing the relative weight.
bool isFirstMaterial = true;
float firstWeight = 0.0f;
for (const auto& surfaceTagWeight : surfaceWeights)
{
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)
{
if (isFirstMaterial)
{
pixels.at(index).m_material1 = aznumeric_cast<uint8_t>(materialId);
firstWeight = surfaceTagWeight.m_weight;
// m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct.
isFirstMaterial = false;
}
else
{
pixels.at(index).m_material2 = aznumeric_cast<uint8_t>(materialId);
float totalWeight = firstWeight + surfaceTagWeight.m_weight;
float blendWeight = 1.0f - (firstWeight / totalWeight);
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
break;
}
}
}
else
{
break; // since the list is ordered, no other materials are in the list with positive weights.
}
}
++index;
}
}
const int32_t left = quadrantTextureArea.m_min.m_x;
const int32_t top = quadrantTextureArea.m_min.m_y;
const int32_t width = quadrantTextureArea.m_max.m_x - quadrantTextureArea.m_min.m_x;
const int32_t height = quadrantTextureArea.m_max.m_y - quadrantTextureArea.m_min.m_y;
AZ::RHI::ImageUpdateRequest imageUpdateRequest;
imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = aznumeric_cast<uint32_t>(left);
imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast<uint32_t>(top);
imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = width * sizeof(DetailMaterialPixel);
imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(DetailMaterialPixel);
imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = height;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = width;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = height;
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1;
imageUpdateRequest.m_sourceData = pixels.data();
imageUpdateRequest.m_image = m_detailTextureImage->GetRHIImage();
m_detailTextureImage->UpdateImageContents(imageUpdateRequest);
}
}
uint16_t TerrainFeatureProcessor::GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position)
{
for (const auto& materialRegion : m_detailMaterialRegions.GetDataVector())
{
if (materialRegion.m_region.Contains(AZ::Vector3(position.GetX(), position.GetY(), 0.0f)))
{
for (const auto& materialSurface : materialRegion.m_materialsForSurfaces)
{
if (materialSurface.m_surfaceTag == surfaceType)
{
return materialSurface.m_detailMaterialId;
}
}
}
}
return m_detailMaterials.NoFreeSlot;
}
void TerrainFeatureProcessor::UpdateTerrainData()
{
uint32_t width = m_areaData.m_updateWidth;
uint32_t height = m_areaData.m_updateHeight;
const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds;
@ -280,8 +796,10 @@ namespace Terrain
AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D(
AZ::RHI::ImageBindFlags::ShaderRead, width, height, AZ::RHI::Format::R16_UNORM
);
const AZ::Name TerrainHeightmapName = AZ::Name(TerrainHeightmapChars);
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!");
AZ_Error(TerrainFPName, m_areaData.m_heightmapImage, "Failed to initialize the heightmap image.");
}
AZStd::vector<uint16_t> pixels;
@ -366,6 +884,19 @@ namespace Terrain
m_heightmapPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::HeightmapImage));
AZ_Error(TerrainFPName, m_heightmapPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::HeightmapImage);
m_detailMaterialIdPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::DetailMaterialIdImage));
AZ_Error(TerrainFPName, m_detailMaterialIdPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::DetailMaterialIdImage);
m_detailCenterPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::DetailCenter));
AZ_Error(TerrainFPName, m_detailCenterPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::DetailCenter);
m_detailAabbPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::DetailAabb));
AZ_Error(TerrainFPName, m_detailAabbPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::DetailAabb);
m_detailHalfPixelUvPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::DetailHalfPixelUv));
AZ_Error(TerrainFPName, m_detailHalfPixelUvPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::DetailHalfPixelUv);
// Find any macro materials that have already been created.
TerrainMacroMaterialRequestBus::EnumerateHandlers(
[&](TerrainMacroMaterialRequests* handler)
{
@ -376,6 +907,30 @@ namespace Terrain
}
);
TerrainMacroMaterialNotificationBus::Handler::BusConnect();
// 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;
for (const auto& materialMapping : materialMappings)
{
if (materialMapping.m_materialInstance)
{
OnTerrainSurfaceMaterialMappingCreated(entityId, materialMapping.m_surfaceTag, materialMapping.m_materialInstance);
}
}
return true;
}
);
TerrainAreaMaterialNotificationBus::Handler::BusConnect();
}
void TerrainFeatureProcessor::UpdateMacroMaterialData(MacroMaterialData& macroMaterialData, const MacroMaterialData& newMaterialData)
@ -474,14 +1029,73 @@ namespace Terrain
}
}
}
else if (m_forceRebuildDrawPackets)
{
for (auto& sectorData : m_sectorData)
{
for (auto& drawPacket : sectorData.m_drawPackets)
{
drawPacket.Update(*GetParentScene(), true);
}
}
}
m_forceRebuildDrawPackets = false;
if (m_areaData.m_heightmapUpdated)
{
UpdateTerrainData();
const AZ::Data::Instance<AZ::RPI::Image> heightmapImage = m_areaData.m_heightmapImage;
const AZ::Data::Instance<AZ::RPI::Image> heightmapImage = m_areaData.m_heightmapImage; // cast StreamingImage to Image
m_materialInstance->SetPropertyValue(m_heightmapPropertyIndex, heightmapImage);
m_materialInstance->Compile();
}
AZ::Vector3 cameraPosition = AZ::Vector3::CreateZero();
for (auto& view : process.m_views)
{
if ((view->GetUsageFlags() & AZ::RPI::View::UsageFlags::UsageCamera) > 0)
{
cameraPosition = view->GetCameraTransform().GetTranslation();
break;
}
}
if (m_dirtyDetailRegion.IsValid() || !cameraPosition.IsClose(m_previousCameraPosition))
{
int32_t newDetailTexturePosX = aznumeric_cast<int32_t>(AZStd::roundf(cameraPosition.GetX() / DetailTextureScale));
int32_t newDetailTexturePosY = aznumeric_cast<int32_t>(AZStd::roundf(cameraPosition.GetY() / DetailTextureScale));
Aabb2i newBounds;
newBounds.m_min.m_x = newDetailTexturePosX - DetailTextureSizeHalf;
newBounds.m_min.m_y = newDetailTexturePosY - DetailTextureSizeHalf;
newBounds.m_max.m_x = newDetailTexturePosX + DetailTextureSizeHalf;
newBounds.m_max.m_y = newDetailTexturePosY + DetailTextureSizeHalf;
// Use modulo to find the center point in texture space. Care must be taken so negative values are
// handled appropriately (ie, we want -1 % 1024 to equal 1023, not -1)
Vector2i newCenter;
newCenter.m_x = (DetailTextureSize + (newDetailTexturePosX % DetailTextureSize)) % DetailTextureSize;
newCenter.m_y = (DetailTextureSize + (newDetailTexturePosY % DetailTextureSize)) % DetailTextureSize;
CheckUpdateDetailTexture(newBounds, newCenter);
m_detailTextureBounds = newBounds;
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
m_previousCameraPosition = cameraPosition;
const AZ::Data::Instance<AZ::RPI::Image> detailTextureImage = m_detailTextureImage; // cast StreamingImage to Image
m_materialInstance->SetPropertyValue(m_detailMaterialIdPropertyIndex, detailTextureImage);
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
);
m_materialInstance->SetPropertyValue(m_detailAabbPropertyIndex, detailAabb);
m_materialInstance->SetPropertyValue(m_detailHalfPixelUvPropertyIndex, 0.5f / DetailTextureSize);
AZ::Vector2 detailUvOffset = AZ::Vector2(float(newCenter.m_x) / DetailTextureSize, float(newCenter.m_y) / DetailTextureSize);
m_materialInstance->SetPropertyValue(m_detailCenterPropertyIndex, detailUvOffset);
}
if (m_areaData.m_heightmapUpdated || m_areaData.m_macroMaterialsUpdated)
@ -603,6 +1217,11 @@ namespace Terrain
}
}
}
if (m_materialInstance)
{
m_materialInstance->Compile();
}
}
void TerrainFeatureProcessor::InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata)
@ -746,9 +1365,10 @@ namespace Terrain
// larger but this will limit how much is rendered.
}
MacroMaterialData* TerrainFeatureProcessor::FindMacroMaterial(AZ::EntityId entityId)
template <typename T>
T* TerrainFeatureProcessor::FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<T>& container)
{
for (MacroMaterialData& data : m_macroMaterials.GetDataVector())
for (T& data : container.GetDataVector())
{
if (data.m_entityId == entityId)
{
@ -757,34 +1377,36 @@ namespace Terrain
}
return nullptr;
}
MacroMaterialData& TerrainFeatureProcessor::FindOrCreateMacroMaterial(AZ::EntityId entityId)
template <typename T>
T& TerrainFeatureProcessor::FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<T>& container)
{
MacroMaterialData* dataPtr = FindMacroMaterial(entityId);
T* dataPtr = FindByEntityId(entityId, container);
if (dataPtr != nullptr)
{
return *dataPtr;
}
const uint16_t slotId = m_macroMaterials.GetFreeSlotIndex();
AZ_Assert(slotId != m_macroMaterials.NoFreeSlot, "Ran out of indices for macro materials");
const uint16_t slotId = container.GetFreeSlotIndex();
AZ_Assert(slotId != AZ::Render::IndexedDataVector<T>::NoFreeSlot, "Ran out of indices");
MacroMaterialData& data = m_macroMaterials.GetData(slotId);
T& data = container.GetData(slotId);
data.m_entityId = entityId;
return data;
}
void TerrainFeatureProcessor::RemoveMacroMaterial(AZ::EntityId entityId)
template <typename T>
void TerrainFeatureProcessor::RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<T>& container)
{
for (MacroMaterialData& data : m_macroMaterials.GetDataVector())
for (T& data : container.GetDataVector())
{
if (data.m_entityId == entityId)
{
m_macroMaterials.RemoveData(&data);
container.RemoveData(&data);
return;
}
}
AZ_Assert(false, "Entity Id not found in m_macroMaterials.")
AZ_Assert(false, "Entity Id not found in container.")
}
template<typename Callback>
@ -798,4 +1420,60 @@ namespace Terrain
}
}
}
auto TerrainFeatureProcessor::Vector2i::operator+(const Vector2i& rhs) const -> Vector2i
{
Vector2i offsetPoint = *this;
offsetPoint += rhs;
return offsetPoint;
}
auto TerrainFeatureProcessor::Vector2i::operator+=(const Vector2i& rhs) -> Vector2i&
{
m_x += rhs.m_x;
m_y += rhs.m_y;
return *this;
}
auto TerrainFeatureProcessor::Vector2i::operator-(const Vector2i& rhs) const -> Vector2i
{
return *this + -rhs;
}
auto TerrainFeatureProcessor::Vector2i::operator-=(const Vector2i& rhs) -> Vector2i&
{
return *this += -rhs;
}
auto TerrainFeatureProcessor::Vector2i::operator-() const -> Vector2i
{
return {-m_x, -m_y};
}
auto TerrainFeatureProcessor::Aabb2i::operator+(const Vector2i& rhs) const -> Aabb2i
{
return { m_min + rhs, m_max + rhs };
}
auto TerrainFeatureProcessor::Aabb2i::operator-(const Vector2i& rhs) const -> Aabb2i
{
return *this + -rhs;
}
auto TerrainFeatureProcessor::Aabb2i::GetClamped(Aabb2i rhs) const -> Aabb2i
{
Aabb2i ret;
ret.m_min.m_x = AZ::GetMax(m_min.m_x, rhs.m_min.m_x);
ret.m_min.m_y = AZ::GetMax(m_min.m_y, rhs.m_min.m_y);
ret.m_max.m_x = AZ::GetMin(m_max.m_x, rhs.m_max.m_x);
ret.m_max.m_y = AZ::GetMin(m_max.m_y, rhs.m_max.m_y);
return ret;
}
bool TerrainFeatureProcessor::Aabb2i::IsValid() const
{
// Intentionally strict, equal min/max not valid.
return m_min.m_x < m_max.m_x && m_min.m_y < m_max.m_y;
}
}

@ -12,11 +12,13 @@
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <TerrainRenderer/TerrainMacroMaterialBus.h>
#include <TerrainRenderer/TerrainAreaMaterialRequestBus.h>
#include <Atom/RPI.Public/FeatureProcessor.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <Atom/RPI.Public/MeshDrawPacket.h>
#include <Atom/RPI.Public/Material/MaterialReloadNotificationBus.h>
#include <Atom/RPI.Public/Shader/ShaderSystemInterface.h>
#include <Atom/Feature/Utils/IndexedDataVector.h>
namespace AZ::RPI
@ -37,6 +39,7 @@ namespace Terrain
, private AZ::RPI::MaterialReloadNotificationBus::Handler
, private AzFramework::Terrain::TerrainDataNotificationBus::Handler
, private TerrainMacroMaterialNotificationBus::Handler
, private TerrainAreaMaterialNotificationBus::Handler
{
public:
AZ_RTTI(TerrainFeatureProcessor, "{D7DAC1F9-4A9F-4D3C-80AE-99579BF8AB1C}", AZ::RPI::FeatureProcessor);
@ -112,6 +115,109 @@ namespace Terrain
AZStd::fixed_vector<uint16_t, MaxMaterialsPerSector> m_macroMaterials;
};
enum DetailTextureFlags : uint32_t
{
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,
UseTextureRoughness = 0b0000'0000'0000'0000'0000'0000'0000'1000,
UseTextureOcclusion = 0b0000'0000'0000'0000'0000'0000'0001'0000,
UseTextureHeight = 0b0000'0000'0000'0000'0000'0000'0010'0000,
UseTextureSpecularF0 = 0b0000'0000'0000'0000'0000'0000'0100'0000,
FlipNormalX = 0b0000'0000'0000'0000'0000'0000'1000'0000,
FlipNormalY = 0b0000'0000'0000'0000'0000'0001'0000'0000,
BlendModeMask = 0b0000'0000'0000'0000'0000'0110'0000'0000,
BlendModeLerp = 0b0000'0000'0000'0000'0000'0000'0000'0000,
BlendModeLinearLight = 0b0000'0000'0000'0000'0000'0010'0000'0000,
BlendModeMultiply = 0b0000'0000'0000'0000'0000'0100'0000'0000,
BlendModeOverlay = 0b0000'0000'0000'0000'0000'0110'0000'0000,
};
struct DetailMaterialShaderProperties
{
// Uv
AZStd::array<float, 12> m_uvTransform
{
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
};
// Factor / Scale / Bias for input textures
float m_baseColorFactor{ 1.0f };
float m_normalFactor{ 1.0f };
float m_metalFactor{ 1.0f };
float m_roughnessScale{ 1.0f };
float m_roughnessBias{ 0.0f };
float m_specularF0Factor{ 1.0f };
float m_occlusionFactor{ 1.0f };
float m_heightFactor{ 1.0f };
float m_heightOffset{ 0.0f };
float m_heightBlendFactor{ 0.5f };
// Flags
DetailTextureFlags m_flags{ 0 };
float m_padding; // 16 byte aligned
};
struct DetailMaterialData
{
AZ::Data::AssetId m_assetId;
AZ::RPI::Material::ChangeId m_materialChangeId{AZ::RPI::Material::DEFAULT_CHANGE_ID};
AZ::Data::Instance<AZ::RPI::Image> m_colorImage;
AZ::Data::Instance<AZ::RPI::Image> m_normalImage;
AZ::Data::Instance<AZ::RPI::Image> m_roughnessImage;
AZ::Data::Instance<AZ::RPI::Image> m_metalnessImage;
AZ::Data::Instance<AZ::RPI::Image> m_specularF0Image;
AZ::Data::Instance<AZ::RPI::Image> m_occlusionImage;
AZ::Data::Instance<AZ::RPI::Image> m_heightImage;
DetailMaterialShaderProperties m_properties; // maps directly to shader
};
struct DetailMaterialSurface
{
AZ::Crc32 m_surfaceTag;
uint16_t m_detailMaterialId;
};
struct DetailMaterialListRegion
{
AZ::EntityId m_entityId;
AZ::Aabb m_region{AZ::Aabb::CreateNull()};
AZStd::vector<DetailMaterialSurface> m_materialsForSurfaces;
};
struct Vector2i
{
int32_t m_x{ 0 };
int32_t m_y{ 0 };
Vector2i operator+(const Vector2i& rhs) const;
Vector2i& operator+=(const Vector2i& rhs);
Vector2i operator-(const Vector2i& rhs) const;
Vector2i& operator-=(const Vector2i& rhs);
Vector2i operator-() const;
};
struct Aabb2i
{
Vector2i m_min;
Vector2i m_max;
Aabb2i operator+(const Vector2i& offset) const;
Aabb2i operator-(const Vector2i& offset) const;
Aabb2i GetClamped(Aabb2i rhs) const;
bool IsValid() const;
};
// AZ::RPI::MaterialReloadNotificationBus::Handler overrides...
void OnMaterialReinitialized(const MaterialInstance& material) override;
@ -124,6 +230,12 @@ namespace Terrain
void OnTerrainMacroMaterialChanged(AZ::EntityId entityId, const MacroMaterialData& material) override;
void OnTerrainMacroMaterialRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override;
void OnTerrainMacroMaterialDestroyed(AZ::EntityId entityId) override;
// TerrainAreaMaterialNotificationBus overrides...
void OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag) override;
void OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override;
void Initialize();
void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata);
@ -132,12 +244,26 @@ namespace Terrain
void UpdateTerrainData();
void PrepareMaterialData();
void UpdateMacroMaterialData(MacroMaterialData& macroMaterialData, const MacroMaterialData& newMaterialData);
void TerrainHeightOrSettingsUpdated(const AZ::Aabb& dirtyRegion);
void TerrainSurfaceDataUpdated(const AZ::Aabb& dirtyRegion);
uint16_t CreateOrUpdateDetailMaterial(MaterialInstance material);
void UpdateDetailMaterialData(DetailMaterialData& materialData, MaterialInstance material);
void CheckUpdateDetailTexture(const Aabb2i& newBounds, const Vector2i& newCenter);
void UpdateDetailTexture(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel);
uint16_t GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position);
uint8_t CalculateUpdateRegions(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel,
AZStd::array<Aabb2i, 4>& textureSpaceAreas, AZStd::array<Aabb2i, 4>& scaledWorldSpaceAreas);
void ProcessSurfaces(const FeatureProcessor::RenderPacket& process);
MacroMaterialData* FindMacroMaterial(AZ::EntityId entityId);
MacroMaterialData& FindOrCreateMacroMaterial(AZ::EntityId entityId);
void RemoveMacroMaterial(AZ::EntityId entityId);
template <typename T>
T* FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<T>& container);
template <typename T>
T& FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<T>& container);
template <typename T>
void RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<T>& container);
template<typename Callback>
void ForOverlappingSectors(const AZ::Aabb& bounds, Callback callback);
@ -147,8 +273,11 @@ namespace Terrain
// System-level parameters
static constexpr float GridSpacing{ 1.0f };
static constexpr uint32_t GridSize{ 64 }; // number of terrain quads (vertices are m_gridSize + 1)
static constexpr int32_t GridSize{ 64 }; // number of terrain quads (vertices are m_gridSize + 1)
static constexpr float GridMeters{ GridSpacing * GridSize };
static constexpr int32_t DetailTextureSize{ 1024 };
static constexpr int32_t DetailTextureSizeHalf{ DetailTextureSize / 2 };
static constexpr float DetailTextureScale{ 0.5f };
AZStd::unique_ptr<AZ::RPI::AssetUtils::AsyncAssetLoader> m_materialAssetLoader;
MaterialInstance m_materialInstance;
@ -160,8 +289,13 @@ namespace Terrain
AZ::RHI::ShaderInputImageIndex m_macroColorMapIndex;
AZ::RHI::ShaderInputImageIndex m_macroNormalMapIndex;
AZ::RPI::MaterialPropertyIndex m_heightmapPropertyIndex;
AZ::RPI::MaterialPropertyIndex m_detailMaterialIdPropertyIndex;
AZ::RPI::MaterialPropertyIndex m_detailCenterPropertyIndex;
AZ::RPI::MaterialPropertyIndex m_detailAabbPropertyIndex;
AZ::RPI::MaterialPropertyIndex m_detailHalfPixelUvPropertyIndex;
AZ::Data::Instance<AZ::RPI::Model> m_patchModel;
AZ::Vector3 m_previousCameraPosition = AZ::Vector3(AZStd::numeric_limits<float>::max(), 0.0, 0.0);
// Per-area data
struct TerrainAreaData
@ -178,12 +312,21 @@ namespace Terrain
bool m_macroMaterialsUpdated{ true };
bool m_rebuildSectors{ true };
};
TerrainAreaData m_areaData;
AZ::Aabb m_dirtyRegion{ AZ::Aabb::CreateNull() };
AZ::Aabb m_dirtyDetailRegion{ AZ::Aabb::CreateNull() };
Aabb2i m_detailTextureBounds;
Vector2i m_detailTextureCenter;
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_detailTextureImage;
AZ::RPI::ShaderSystemInterface::GlobalShaderOptionUpdatedEvent::Handler m_handleGlobalShaderOptionUpdate;
bool m_forceRebuildDrawPackets = false;
AZStd::vector<SectorData> m_sectorData;
AZ::Render::IndexedDataVector<MacroMaterialData> m_macroMaterials;
AZ::Render::IndexedDataVector<DetailMaterialData> m_detailMaterials;
AZ::Render::IndexedDataVector<DetailMaterialListRegion> m_detailMaterialRegions;
};
}

Loading…
Cancel
Save