Terrain detail textures support with bindless arrays (#5460)

* Added buffer for material properties of detail mateirals, storing them in a multi-indexed data vector. Updated shader with relevant struct and buffer, but the buffer will need to be moved out of the mateiral SRG to work.

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

* Added buffer for material properties of detail mateirals, storing them in a multi-indexed data vector. Updated shader with relevant struct and buffer, but the buffer will need to be moved out of the mateiral SRG to work.

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

* Added buffer for material properties of detail mateirals, storing them in a multi-indexed data vector. Updated shader with relevant struct and buffer, but the buffer will need to be moved out of the mateiral SRG to work.

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

* - Moved settings related to the detail material to a partial view srg owned by the terrain gem.
- Added support for base color in detail materials.
- Hooked up basic base color rendering of detail materials.
- Corrected the way the material data was stored.
- Added ref counting for detail materials so they can be released when no longer used.

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

* Added buffer for material properties of detail mateirals, storing them in a multi-indexed data vector. Updated shader with relevant struct and buffer, but the buffer will need to be moved out of the mateiral SRG to work.

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

* - Moved settings related to the detail material to a partial view srg owned by the terrain gem.
- Added support for base color in detail materials.
- Hooked up basic base color rendering of detail materials.
- Corrected the way the material data was stored.
- Added ref counting for detail materials so they can be released when no longer used.

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

* Detail materials now put textures into bindless array that's accessed in the shader. Shader now pulls all the detail materal information for a single mateiral but does no blending.

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

* Correcting rebase merge problem.

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

* Fix detail roughness fade out with distance.

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

* Adding tests for new MultiIndexedDataVector functions

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

* Updates to move bindless array to separate SRG
- Exposed BindSrg() in renderpass so it's possible to add additional SRGs to a pass
- Created a TerrainSrg for use by the terrain forward shader
- Moved the bindless array out of the partial view SRG to the TerrainSrg

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

* Moved more properties out of the view srg to the terrain srg.

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

* Spelling fixes

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

* Fixing bug where the roughness min/max value were inverted. Also fixed bug where bad data would show for areas where there was no macro material.

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

* Updates from PR review

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

* Fixing case issues and updating function name due to a recent fix.

Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>
This commit is contained in:
Ken Pruiksma
2021-11-17 10:30:06 -06:00
committed by GitHub
parent 9b6e2ed51d
commit 8bccd36d03
12 changed files with 1140 additions and 571 deletions
@@ -12,4 +12,5 @@
#ifdef AZ_COLLECTING_PARTIAL_SRGS
#include <Atom/Feature/Common/Assets/ShaderResourceGroups/ViewSrg.azsli>
#include <Terrain/Assets/Shaders/Terrain/ViewSrg.azsli> // Temporary until gem partial view srgs can be included automatically.
#endif
@@ -199,6 +199,28 @@ namespace AZ
{
return m_indices.at(index);
}
template<size_t Index, typename DataType>
IndexType GetIndexForData(const DataType* data) const
{
if (data >= &AZStd::get<Index>(m_data).front() && data <= &AZStd::get<Index>(m_data).back())
{
return m_dataToIndices.at(data - &AZStd::get<Index>(m_data).front());
}
return NoFreeSlot;
}
template<size_t Index, typename LambdaType>
void ForEach(LambdaType lambda) const
{
for (auto& item : AZStd::get<Index>(m_data))
{
if (!lambda(item))
{
break;
}
}
}
private:
using Fn = void(&)(AZStd::vector<Ts>& ...);
@@ -31,7 +31,7 @@ namespace UnitTest
{
DestroyAllocator();
}
private:
void CreateAllocator()
@@ -60,6 +60,12 @@ namespace UnitTest
TEST_F(IndexedDataVectorTests, TestInsert)
{
enum Types
{
IntType = 0,
DoubleType = 1,
};
MultiIndexedDataVector<int, double> myVec;
constexpr int NumToInsert = 5;
@@ -69,38 +75,48 @@ namespace UnitTest
{
auto index = myVec.GetFreeSlotIndex();
indices.push_back(index);
myVec.GetData<0>(index) = i;
myVec.GetData<1>(index) = (double)i;
myVec.GetData<IntType>(index) = i;
myVec.GetData<DoubleType>(index) = (double)i;
}
for (size_t i = 0; i < NumToInsert; ++i)
{
auto index = indices[i];
EXPECT_EQ(i, myVec.GetData<0>(index));
EXPECT_EQ((double)i, myVec.GetData<1>(index));
EXPECT_EQ(i, myVec.GetData<IntType>(index));
EXPECT_EQ((double)i, myVec.GetData<DoubleType>(index));
}
}
TEST_F(IndexedDataVectorTests, TestSize)
{
enum Types
{
IntType = 0,
};
MultiIndexedDataVector<int> myVec;
constexpr int NumToInsert = 5;
for (int i = 0; i < NumToInsert; ++i)
{
auto index = myVec.GetFreeSlotIndex();
myVec.GetData<0>(index) = i;
myVec.GetData<IntType>(index) = i;
}
EXPECT_EQ(NumToInsert, myVec.GetDataCount());
EXPECT_EQ(NumToInsert, myVec.GetDataVector<0>().size());
EXPECT_EQ(NumToInsert, myVec.GetDataVector<IntType>().size());
myVec.Clear();
EXPECT_EQ(0, myVec.GetDataCount());
EXPECT_EQ(0, myVec.GetDataVector<0>().size());
EXPECT_EQ(0, myVec.GetDataVector<IntType>().size());
}
TEST_F(IndexedDataVectorTests, TestErase)
{
enum Types
{
IntType = 0,
};
MultiIndexedDataVector<int> myVec;
constexpr int NumToInsert = 200;
AZStd::unordered_map<int, uint16_t> valueToIndex;
@@ -109,7 +125,7 @@ namespace UnitTest
{
auto index = myVec.GetFreeSlotIndex();
valueToIndex[i] = index;
myVec.GetData<0>(index) = i;
myVec.GetData<IntType>(index) = i;
}
// erase every even number
@@ -133,12 +149,21 @@ namespace UnitTest
{
int val = iter.first;
uint16_t index = iter.second;
EXPECT_EQ(val, myVec.GetData<0>(index));
EXPECT_EQ(val, myVec.GetData<IntType>(index));
}
}
TEST_F(IndexedDataVectorTests, TestManyTypes)
{
enum Types
{
IntType = 0,
StringType = 1,
DoubleType = 2,
FloatType = 3,
CharType = 4,
};
MultiIndexedDataVector<int, AZStd::string, double, float, const char*> myVec;
auto index = myVec.GetFreeSlotIndex();
@@ -148,16 +173,173 @@ namespace UnitTest
constexpr float TestFloatVal = FLT_MAX;
const char* TestConstPointerVal = "This is a C array.";
myVec.GetData<0>(index) = TestIntVal;
myVec.GetData<1>(index) = TestStringVal;
myVec.GetData<2>(index) = TestDoubleVal;
myVec.GetData<3>(index) = TestFloatVal;
myVec.GetData<4>(index) = TestConstPointerVal;
myVec.GetData<IntType>(index) = TestIntVal;
myVec.GetData<StringType>(index) = TestStringVal;
myVec.GetData<DoubleType>(index) = TestDoubleVal;
myVec.GetData<FloatType>(index) = TestFloatVal;
myVec.GetData<CharType>(index) = TestConstPointerVal;
EXPECT_EQ(TestIntVal, static_cast<int>(myVec.GetData<IntType>(index)));
EXPECT_EQ(TestStringVal, static_cast<AZStd::string>(myVec.GetData<StringType>(index)));
EXPECT_EQ(TestDoubleVal, static_cast<double>(myVec.GetData<DoubleType>(index)));
EXPECT_EQ(TestFloatVal, static_cast<float>(myVec.GetData<FloatType>(index)));
EXPECT_STREQ(TestConstPointerVal, static_cast<const char*>(myVec.GetData<CharType>(index)));
}
MultiIndexedDataVector<int32_t, float> CreateTestVector(AZStd::vector<uint16_t>& indices)
{
enum Types
{
IntType = 0,
FloatType = 1,
};
MultiIndexedDataVector<int32_t, float> myVec;
constexpr int32_t Count = 10;
int32_t startInt = 10;
float startFloat = 2.0f;
// Create some initial values
for (uint32_t i = 0; i < Count; ++i)
{
uint16_t index = myVec.GetFreeSlotIndex();
indices.push_back(index);
myVec.GetData<IntType>(index) = startInt;
myVec.GetData<FloatType>(index) = startFloat;
startInt += 1;
startFloat += 1.0f;
}
return myVec;
}
void CheckIndexedData(MultiIndexedDataVector<int32_t, float>& data, AZStd::vector<uint16_t>& indices)
{
enum Types
{
IntType = 0,
FloatType = 1,
};
// For each index, get its data and make sure GetIndexForData returns the same
// index used to retrieve the data
for (uint32_t i = 0; i < data.GetDataCount(); ++i)
{
int32_t& intData = data.GetData<IntType>(indices.at(i));
uint16_t indexForData = data.GetIndexForData<IntType>(&intData);
EXPECT_EQ(indices.at(i), indexForData);
float& floatData = data.GetData<FloatType>(indices.at(i));
indexForData = data.GetIndexForData<FloatType>(&floatData);
EXPECT_EQ(indices.at(i), indexForData);
}
}
TEST_F(IndexedDataVectorTests, GetIndexForDataSimple)
{
AZStd::vector<uint16_t> indices;
MultiIndexedDataVector<int32_t, float> myVec = CreateTestVector(indices);
CheckIndexedData(myVec, indices);
}
TEST_F(IndexedDataVectorTests, GetIndexForDataComplex)
{
enum Types
{
IntType = 0,
FloatType = 1,
};
AZStd::vector<uint16_t> indices;
MultiIndexedDataVector<int32_t, float> myVec = CreateTestVector(indices);
// remove every other value to shuffle the data around
for (uint32_t i = 0; i < myVec.GetDataCount(); i += 2)
{
myVec.RemoveIndex(indices.at(i));
}
int32_t startInt = 100;
float startFloat = 20.0f;
// Add some data back in
const size_t count = myVec.GetDataCount();
for (uint32_t i = 0; i < count; i += 2)
{
uint16_t index = myVec.GetFreeSlotIndex();
indices.at(i) = index;
myVec.GetData<IntType>(index) = startInt;
myVec.GetData<FloatType>(index) = startFloat;
startInt += 1;
startFloat += 1.0f;
}
CheckIndexedData(myVec, indices);
}
TEST_F(IndexedDataVectorTests, ForEach)
{
enum Types
{
IntType = 0,
FloatType = 1,
};
MultiIndexedDataVector<int32_t, float> myVec;
constexpr int32_t Count = 10;
int32_t startInt = 10;
float startFloat = 2.0f;
AZStd::vector<uint16_t> indices;
AZStd::set<int32_t> intValues;
AZStd::set<float> floatValues;
// Create some initial values
for (uint32_t i = 0; i < Count; ++i)
{
uint16_t index = myVec.GetFreeSlotIndex();
indices.push_back(index);
myVec.GetData<IntType>(index) = startInt;
myVec.GetData<FloatType>(index) = startFloat;
intValues.insert(startInt);
floatValues.insert(startFloat);
startInt += 1;
startFloat += 1.0f;
}
uint32_t visitCount = 0;
myVec.ForEach<IntType>([&](int32_t value) -> bool
{
intValues.erase(value);
++visitCount;
return true; // keep iterating
});
// All ints should have been visited and found in the set
EXPECT_EQ(visitCount, Count);
EXPECT_EQ(intValues.size(), 0);
visitCount = 0;
myVec.ForEach<FloatType>([&](float value) -> bool
{
floatValues.erase(value);
++visitCount;
return true; // keep iterating
});
// All floats should have been visited and found in the set
EXPECT_EQ(visitCount, Count);
EXPECT_EQ(floatValues.size(), 0);
visitCount = 0;
myVec.ForEach<IntType>([&]([[maybe_unused]] int32_t value) -> bool
{
++visitCount;
return false; // stop iterating
});
// Since false is immediately returned, only one element should have been visited.
EXPECT_EQ(visitCount, 1);
EXPECT_EQ(TestIntVal, static_cast<int>(myVec.GetData<0>(index)));
EXPECT_EQ(TestStringVal, static_cast<AZStd::string>(myVec.GetData<1>(index)));
EXPECT_EQ(TestDoubleVal, static_cast<double>(myVec.GetData<2>(index)));
EXPECT_EQ(TestFloatVal, static_cast<float>(myVec.GetData<3>(index)));
EXPECT_STREQ(TestConstPointerVal, static_cast<const char*>(myVec.GetData<4>(index)));
}
}
@@ -62,6 +62,10 @@ namespace AZ
//! It may return nullptr if this pass is independent with any views.
ViewPtr GetView() const;
// Add a srg to srg list to be bound for this pass
void BindSrg(const RHI::ShaderResourceGroup* srg);
protected:
explicit RenderPass(const PassDescriptor& descriptor);
@@ -95,9 +99,6 @@ namespace AZ
// Clear the srg list
void ResetSrgs();
// Add a srg to srg list to be bound for this pass
void BindSrg(const RHI::ShaderResourceGroup* srg);
// Set srgs for pass's execution
void SetSrgsForDraw(RHI::CommandList* commandList);
void SetSrgsForDispatch(RHI::CommandList* commandList);
@@ -89,61 +89,6 @@
}
],
"settings": [
{
"id": "heightmapImage",
"displayName": "Heightmap Image",
"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",
@@ -177,178 +122,6 @@
"id": "m_detailFadeLength"
}
}
],
"baseColor": [
{
"id": "color",
"displayName": "Color",
"description": "Color is displayed as sRGB but the values are stored as linear color.",
"type": "Color",
"defaultValue": [ 1.0, 1.0, 1.0 ],
"connection": {
"type": "ShaderInput",
"id": "m_baseColor"
}
},
{
"id": "factor",
"displayName": "Factor",
"description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.",
"type": "Float",
"defaultValue": 1.0,
"min": 0.0,
"max": 1.0,
"connection": {
"type": "ShaderInput",
"id": "m_baseColorFactor"
}
},
{
"id": "textureMap",
"displayName": "Texture",
"description": "Base color texture map",
"type": "Image",
"connection": {
"type": "ShaderInput",
"id": "m_baseColorMap"
}
},
{
"id": "useTexture",
"displayName": "Use Texture",
"description": "Whether to use the texture.",
"type": "Bool",
"defaultValue": true
},
{
"id": "textureBlendMode",
"displayName": "Texture Blend Mode",
"description": "Selects the equation to use when combining Color, Factor, and Texture.",
"type": "Enum",
"enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ],
"defaultValue": "Overlay",
"connection": {
"type": "ShaderOption",
"id": "o_baseColorTextureBlendMode"
}
}
],
"normal": [
{
"id": "textureMap",
"displayName": "Texture",
"description": "Texture for defining surface normal direction.",
"type": "Image",
"connection": {
"type": "ShaderInput",
"id": "m_normalMap"
}
},
{
"id": "useTexture",
"displayName": "Use Texture",
"description": "Whether to use the texture, or just rely on vertex normals.",
"type": "Bool",
"defaultValue": true
},
{
"id": "flipX",
"displayName": "Flip X Channel",
"description": "Flip tangent direction for this normal map.",
"type": "Bool",
"defaultValue": false,
"connection": {
"type": "ShaderInput",
"id": "m_flipNormalX"
}
},
{
"id": "flipY",
"displayName": "Flip Y Channel",
"description": "Flip bitangent direction for this normal map.",
"type": "Bool",
"defaultValue": false,
"connection": {
"type": "ShaderInput",
"id": "m_flipNormalY"
}
},
{
"id": "factor",
"displayName": "Factor",
"description": "Strength factor for scaling the values",
"type": "Float",
"defaultValue": 1.0,
"min": 0.0,
"softMax": 2.0,
"connection": {
"type": "ShaderInput",
"id": "m_normalFactor"
}
}
],
"roughness": [
{
"id": "textureMap",
"displayName": "Texture",
"description": "Texture for defining surface roughness.",
"type": "Image",
"connection": {
"type": "ShaderInput",
"id": "m_roughnessMap"
}
},
{
"id": "useTexture",
"description": "Whether to use the texture, or just default to the Factor value.",
"type": "Bool",
"defaultValue": true
},
{
"id": "factor",
"displayName": "Factor",
"description": "Controls the roughness value",
"type": "Float",
"defaultValue": 1.0,
"min": 0.0,
"max": 1.0,
"connection": {
"type": "ShaderInput",
"id": "m_roughnessFactor"
}
}
],
"specularF0": [
{
"id": "textureMap",
"displayName": "Texture",
"description": "Texture for defining surface reflectance.",
"type": "Image",
"connection": {
"type": "ShaderInput",
"id": "m_specularF0Map"
}
},
{
"id": "useTexture",
"displayName": "Use Texture",
"description": "Whether to use the texture, or just default to the Factor value.",
"type": "Bool",
"defaultValue": true
},
{
"id": "factor",
"displayName": "Factor",
"description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.",
"type": "Float",
"defaultValue": 0.5,
"min": 0.0,
"max": 1.0,
"connection": {
"type": "ShaderInput",
"id": "m_specularF0Factor"
}
}
]
}
},
@@ -364,39 +137,5 @@
}
],
"functors": [
{
"type": "UseTexture",
"args": {
"textureProperty": "baseColor.textureMap",
"useTextureProperty": "baseColor.useTexture",
"dependentProperties": ["baseColor.textureBlendMode"],
"shaderOption": "o_baseColor_useTexture"
}
},
{
"type": "UseTexture",
"args": {
"textureProperty": "specularF0.textureMap",
"useTextureProperty": "specularF0.useTexture",
"shaderOption": "o_specularF0_useTexture"
}
},
{
"type": "UseTexture",
"args": {
"textureProperty": "normal.textureMap",
"useTextureProperty": "normal.useTexture",
"dependentProperties": ["normal.factor", "normal.flipX", "normal.flipY"],
"shaderOption": "o_normal_useTexture"
}
},
{
"type": "UseTexture",
"args": {
"textureProperty": "roughness.textureMap",
"useTextureProperty": "roughness.useTexture",
"shaderOption": "o_roughness_useTexture"
}
}
]
}
@@ -15,8 +15,6 @@
ShaderResourceGroup ObjectSrg : SRG_PerObject
{
row_major float3x4 m_modelToWorld;
struct TerrainData
{
float2 m_uvMin;
@@ -36,6 +34,8 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
uint m_mapsInUse;
};
row_major float3x4 m_modelToWorld;
TerrainData m_terrainData;
MacroMaterialData m_macroMaterialData[4];
@@ -43,7 +43,7 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
Texture2D m_macroColorMap[4];
Texture2D m_macroNormalMap[4];
// The below shouldn't be in this SRG but needs to be for now because the lighting functions depend on them.
//! Reflection Probe (smallest probe volume that overlaps the object position)
@@ -93,26 +93,10 @@ 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;
MagFilter = Linear;
MipFilter = Point;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};
Sampler m_sampler
{
AddressU = Wrap;
@@ -123,15 +107,6 @@ 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;
@@ -153,11 +128,6 @@ ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial
}
option bool o_useTerrainSmoothing = false;
option bool o_baseColor_useTexture = true;
option bool o_specularF0_useTexture = true;
option bool o_normal_useTexture = true;
option bool o_roughness_useTexture = true;
option TextureBlendMode o_baseColorTextureBlendMode = TextureBlendMode::Multiply;
struct VertexInput
{
@@ -240,12 +210,12 @@ float GetHeight(float2 origUv)
if (o_useTerrainSmoothing)
{
float2 textureSize;
TerrainMaterialSrg::m_heightmapImage.GetDimensions(textureSize.x, textureSize.y);
height = SampleBSpline5Tap(TerrainMaterialSrg::m_heightmapImage, TerrainMaterialSrg::HeightmapSampler, uv, textureSize, rcp(textureSize));
ViewSrg::m_heightmapImage.GetDimensions(textureSize.x, textureSize.y);
height = SampleBSpline5Tap(ViewSrg::m_heightmapImage, ViewSrg::HeightmapSampler, uv, textureSize, rcp(textureSize));
}
else
{
height = TerrainMaterialSrg::m_heightmapImage.SampleLevel(TerrainMaterialSrg::HeightmapSampler, uv, 0).r;
height = ViewSrg::m_heightmapImage.SampleLevel(ViewSrg::HeightmapSampler, uv, 0).r;
}
return ObjectSrg::m_terrainData.m_heightScale * (height - 0.5f);
@@ -0,0 +1,250 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Atom/Features/ColorManagement/TransformColor.azsli>
enum DetailTextureFlags
{
UseTextureBaseColor = 0x00000001, //0b0000'0000'0000'0000'0000'0000'0000'0001
UseTextureNormal = 0x00000002, //0b0000'0000'0000'0000'0000'0000'0000'0010
UseTextureMetallic = 0x00000004, //0b0000'0000'0000'0000'0000'0000'0000'0100
UseTextureRoughness = 0x00000008, //0b0000'0000'0000'0000'0000'0000'0000'1000
UseTextureOcclusion = 0x00000010, //0b0000'0000'0000'0000'0000'0000'0001'0000
UseTextureHeight = 0x00000020, //0b0000'0000'0000'0000'0000'0000'0010'0000
UseTextureSpecularF0 = 0x00000040, //0b0000'0000'0000'0000'0000'0000'0100'0000
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
};
struct DetailSurface
{
float3 m_color;
float3 m_normal;
float m_roughness;
float m_specularF0;
float m_metalness;
float m_occlusion;
float m_height;
};
option bool o_debugDetailMaterialIds = false;
DetailSurface GetDefaultDetailSurface()
{
DetailSurface surface;
surface.m_color = float3(0.5, 0.5, 0.5);
surface.m_normal = float3(0.0, 0.0, 1.0);
surface.m_roughness = 1.0;
surface.m_specularF0 = 0.5;
surface.m_metalness = 0.0;
surface.m_occlusion = 1.0;
surface.m_height = 0.5;
return surface;
}
// Detail material index getters
uint GetDetailColorIndex(TerrainSrg::DetailMaterialData materialData)
{
return materialData.m_colorNormalImageIndices & 0x0000FFFF;
}
uint GetDetailNormalIndex(TerrainSrg::DetailMaterialData materialData)
{
return materialData.m_colorNormalImageIndices >> 16;
}
uint GetDetailRoughnessIndex(TerrainSrg::DetailMaterialData materialData)
{
return materialData.m_roughnessMetalnessImageIndices & 0x0000FFFF;
}
uint GetDetailMetalnessIndex(TerrainSrg::DetailMaterialData materialData)
{
return materialData.m_roughnessMetalnessImageIndices >> 16;
}
uint GetDetailSpecularF0Index(TerrainSrg::DetailMaterialData materialData)
{
return materialData.m_specularF0OcclusionImageIndices & 0x0000FFFF;
}
uint GetDetailOcclusionIndex(TerrainSrg::DetailMaterialData materialData)
{
return materialData.m_specularF0OcclusionImageIndices >> 16;
}
uint GetDetailHeightIndex(TerrainSrg::DetailMaterialData materialData)
{
return materialData.m_heightImageIndex & 0x0000FFFF;
}
// Detail material value getters
float3 GetDetailColor(TerrainSrg::DetailMaterialData materialData, float2 uv)
{
float3 color = materialData.m_baseColor;
if ((materialData.m_flags & DetailTextureFlags::UseTextureBaseColor) > 0)
{
color = TerrainSrg::m_detailTextures[GetDetailColorIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).rgb;
}
return color * materialData.m_baseColorFactor;
}
float3 GetDetailNormal(TerrainSrg::DetailMaterialData materialData, float2 uv)
{
float2 normal = float2(0.0, 0.0);
if ((materialData.m_flags & DetailTextureFlags::UseTextureNormal) > 0)
{
normal = TerrainSrg::m_detailTextures[GetDetailNormalIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).rg;
}
// X and Y are inverted here to be consistent with SampleNormalXY in NormalInput.azsli.
if(materialData.m_flags & DetailTextureFlags::FlipNormalX)
{
normal.y = -normal.y;
}
if(materialData.m_flags & DetailTextureFlags::FlipNormalY)
{
normal.x = -normal.x;
}
return GetTangentSpaceNormal(normal, materialData.m_normalFactor);
}
float GetDetailRoughness(TerrainSrg::DetailMaterialData materialData, float2 uv)
{
float roughness = materialData.m_roughnessScale;
if ((materialData.m_flags & DetailTextureFlags::UseTextureRoughness) > 0)
{
roughness = TerrainSrg::m_detailTextures[GetDetailRoughnessIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r;
roughness = materialData.m_roughnessBias + roughness * materialData.m_roughnessScale;
}
return roughness;
}
float GetDetailMetalness(TerrainSrg::DetailMaterialData materialData, float2 uv)
{
float metalness = 1.0;
if ((materialData.m_flags & DetailTextureFlags::UseTextureMetallic) > 0)
{
metalness = TerrainSrg::m_detailTextures[GetDetailMetalnessIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r;
}
return metalness * materialData.m_metalFactor;
}
float GetDetailSpecularF0(TerrainSrg::DetailMaterialData materialData, float2 uv)
{
float specularF0 = 1.0;
if ((materialData.m_flags & DetailTextureFlags::UseTextureSpecularF0) > 0)
{
specularF0 = TerrainSrg::m_detailTextures[GetDetailSpecularF0Index(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r;
}
return specularF0 * materialData.m_specularF0Factor;
}
float GetDetailOcclusion(TerrainSrg::DetailMaterialData materialData, float2 uv)
{
float occlusion = 1.0;
if ((materialData.m_flags & DetailTextureFlags::UseTextureOcclusion) > 0)
{
occlusion = TerrainSrg::m_detailTextures[GetDetailOcclusionIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r;
}
return occlusion * materialData.m_occlusionFactor;
}
float GetDetailHeight(TerrainSrg::DetailMaterialData materialData, float2 uv)
{
float height = materialData.m_heightFactor;
if ((materialData.m_flags & DetailTextureFlags::UseTextureHeight) > 0)
{
height = TerrainSrg::m_detailTextures[GetDetailHeightIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r;
height = materialData.m_heightOffset + height * materialData.m_heightFactor;
}
return height;
}
void GetDetailSurfaceForMaterial(inout DetailSurface surface, uint materialId, float2 uv)
{
TerrainSrg::DetailMaterialData detailMaterialData = TerrainSrg::m_detailMaterialData[materialId];
surface.m_color = GetDetailColor(detailMaterialData, uv);
surface.m_normal = GetDetailNormal(detailMaterialData, uv);
surface.m_roughness = GetDetailRoughness(detailMaterialData, uv);
surface.m_specularF0 = GetDetailSpecularF0(detailMaterialData, uv);
surface.m_metalness = GetDetailMetalness(detailMaterialData, uv);
surface.m_occlusion = GetDetailOcclusion(detailMaterialData, uv);
surface.m_height = GetDetailHeight(detailMaterialData, uv);
}
void GetDebugDetailSurface(inout DetailSurface surface, uint material1, uint material2, float blend, float2 idUv)
{
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));
}
surface.m_color = lerp(material1Color, material2Color, blend);
float seamBlend = 0.0;
const float halfLineWidth = 1.0 / 2048.0;
if (any(abs(idUv) % 1.0 < halfLineWidth) || any(abs(idUv) % 1.0 > 1.0 - halfLineWidth))
{
seamBlend = 1.0;
}
surface.m_color = lerp(surface.m_color, float3(0.0, 0.0, 0.0), seamBlend); // draw texture seams
surface.m_color = pow(surface.m_color , 2.2);
surface.m_normal = float3(0.0, 0.0, 1.0);
surface.m_roughness = 1.0;
surface.m_specularF0 = 0.5;
surface.m_metalness = 0.0;
surface.m_occlusion = 1.0;
surface.m_height = 0.5;
}
bool GetDetailSurface(inout DetailSurface surface, float2 idUv, float2 uv)
{
uint4 material1 = TerrainSrg::m_detailMaterialIdImage.GatherRed(TerrainSrg::DetailSampler, idUv, 0).xyzw;
uint4 material2 = TerrainSrg::m_detailMaterialIdImage.GatherGreen(TerrainSrg::DetailSampler, idUv, 0).xyzw;
const float maxBlendAmount = 0xFF;
// convert integer of 0-255 to float of 0-1.
float4 blends = float4(TerrainSrg::m_detailMaterialIdImage.GatherBlue(TerrainSrg::DetailSampler, idUv, 0).xyzw) / maxBlendAmount;
if (o_debugDetailMaterialIds)
{
GetDebugDetailSurface(surface, material1.x, material2.x, blends.x, idUv);
return true;
}
if (material1.x == 0xFF)
{
return false;
}
GetDetailSurfaceForMaterial(surface, material1.x, uv);
return true;
}
@@ -7,8 +7,11 @@
*/
#include <Atom/Features/SrgSemantics.azsli>
#include <viewsrg.srgi>
#include <TerrainSrg.azsli>
#include <TerrainCommon.azsli>
#include <TerrainDetailHelpers.azsli>
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
#include <Atom/Features/PBR/ForwardPassOutput.azsli>
@@ -17,7 +20,6 @@
#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
{
@@ -28,8 +30,6 @@ struct VSOutput
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2;
};
option bool o_debugDetailMaterialIds = false;
VSOutput TerrainPBR_MainPassVS(VertexInput IN)
{
VSOutput OUT;
@@ -71,9 +71,9 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
{
// ------- Surface -------
Surface surface;
// Position
surface.position = IN.m_worldPosition.xyz;
surface.vertexNormal = normalize(IN.m_normal);
float viewDistance = length(ViewSrg::m_worldPosition - surface.position);
float detailFactor = saturate((viewDistance - TerrainMaterialSrg::m_detailFadeDistance) / max(TerrainMaterialSrg::m_detailFadeLength, EPSILON));
float2 detailUv = IN.m_uv * TerrainMaterialSrg::m_detailTextureMultiplier;
@@ -83,92 +83,80 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
// ------- Macro Color / Normal -------
float3 macroColor = TerrainMaterialSrg::m_baseColor.rgb;
[unroll] for (uint i = 0; i < 4 && (i < ObjectSrg::m_macroMaterialCount); ++i)
// There's a bug that shows up with an NVidia GTX 1660 Super card happening on driver versions as recent as 496.49 (10/26/21) in which
// the IN.m_uv values will intermittently "flicker" to 0.0 after entering and exiting game mode.
// (See https://github.com/o3de/o3de/issues/5014)
// This bug has only shown up on PCs when using the DX12 RHI. It doesn't show up with Vulkan or when capturing frames with PIX or
// RenderDoc. Our best guess is that it is a driver bug. The workaround is to use the IN.m_uv values in a calculation prior to the
// point that we actually use them for macroUv below. The "if(any(!isnan(IN.m_uv)))" seems to be sufficient for the workaround. The
// if statement will always be true, but just the act of reading these values in the if statement makes the values stable. Removing
// the if statement causes the flickering to occur using the steps documented in the bug.
if (any(!isnan(IN.m_uv)))
{
float2 macroUvMin = ObjectSrg::m_macroMaterialData[i].m_uvMin;
float2 macroUvMax = ObjectSrg::m_macroMaterialData[i].m_uvMax;
float2 macroUv = lerp(macroUvMin, macroUvMax, IN.m_uv);
if (macroUv.x >= 0.0 && macroUv.x <= 1.0 && macroUv.y >= 0.0 && macroUv.y <= 1.0)
[unroll] for (uint i = 0; i < 4 && (i < ObjectSrg::m_macroMaterialCount); ++i)
{
if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 1) > 0)
float2 macroUvMin = ObjectSrg::m_macroMaterialData[i].m_uvMin;
float2 macroUvMax = ObjectSrg::m_macroMaterialData[i].m_uvMax;
float2 macroUv = lerp(macroUvMin, macroUvMax, IN.m_uv);
if (macroUv.x >= 0.0 && macroUv.x <= 1.0 && macroUv.y >= 0.0 && macroUv.y <= 1.0)
{
macroColor = GetBaseColorInput(ObjectSrg::m_macroColorMap[i], TerrainMaterialSrg::m_sampler, macroUv, macroColor, true);
if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 1) > 0)
{
macroColor = GetBaseColorInput(ObjectSrg::m_macroColorMap[i], TerrainMaterialSrg::m_sampler, macroUv, macroColor, true);
}
if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 2) > 0)
{
bool flipX = ObjectSrg::m_macroMaterialData[i].m_flipNormalX;
bool flipY = ObjectSrg::m_macroMaterialData[i].m_flipNormalY;
bool factor = ObjectSrg::m_macroMaterialData[i].m_normalFactor;
macroNormal = GetNormalInputTS(ObjectSrg::m_macroNormalMap[i], TerrainMaterialSrg::m_sampler,
macroUv, flipX, flipY, CreateIdentity3x3(), true, factor);
}
break;
}
if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 2) > 0)
{
bool flipX = ObjectSrg::m_macroMaterialData[i].m_flipNormalX;
bool flipY = ObjectSrg::m_macroMaterialData[i].m_flipNormalY;
bool factor = ObjectSrg::m_macroMaterialData[i].m_normalFactor;
macroNormal = GetNormalInputTS(ObjectSrg::m_macroNormalMap[i], TerrainMaterialSrg::m_sampler,
macroUv, flipX, flipY, CreateIdentity3x3(), true, factor);
}
break;
}
}
float3 detailNormal = GetNormalInputTS(TerrainMaterialSrg::m_normalMap, TerrainMaterialSrg::m_sampler,
detailUv, TerrainMaterialSrg::m_flipNormalX, TerrainMaterialSrg::m_flipNormalY, CreateIdentity3x3(), o_normal_useTexture, TerrainMaterialSrg::m_normalFactor);
detailNormal = ReorientTangentSpaceNormal(macroNormal, detailNormal);
surface.normal = lerp(detailNormal, macroNormal, detailFactor);
surface.normal = normalize(surface.normal);
surface.vertexNormal = normalize(IN.m_normal);
// ------- Base Color -------
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)
DetailSurface detailSurface = GetDefaultDetailSurface();
float2 detailRegionMin = TerrainSrg::m_detailAabb.xy;
float2 detailRegionMax = TerrainSrg::m_detailAabb.zw;
float2 detailRegionUv = (surface.position.xy - detailRegionMin) / (detailRegionMax - detailRegionMin);
bool hasDetailSurface = false;
// Check to make sure we're inside the detail texture's bounds and within where detail textures should be drawn.
if (detailFactor < 1.0 && all(detailRegionUv > TerrainSrg::m_detailHalfPixelUv) && all(detailRegionUv < 1.0 - TerrainSrg::m_detailHalfPixelUv))
{
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);
}
detailRegionUv += TerrainSrg::m_detailMaterialIdImageCenter - (0.5);
hasDetailSurface = GetDetailSurface(detailSurface, detailRegionUv, detailUv);
}
// ------- Specular -------
float specularF0Factor = GetSpecularInput(TerrainMaterialSrg::m_specularF0Map, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_specularF0Factor, o_specularF0_useTexture);
specularF0Factor = lerp(specularF0Factor, 0.5, detailFactor);
surface.SetAlbedoAndSpecularF0(blendedColor, specularF0Factor, 0.0);
const float macroRoughness = 1.0;
const float macroSpecularF0 = 0.5;
const float macroMetalness = 0.0;
// ------- Roughness -------
surface.roughnessLinear = GetRoughnessInput(TerrainMaterialSrg::m_roughnessMap, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_roughnessFactor, 0.0, 1.0, o_roughness_useTexture);
surface.roughnessLinear = lerp(surface.roughnessLinear, 1.0, detailFactor);
surface.CalculateRoughnessA();
if (hasDetailSurface)
{
float3 blendedColor = lerp(detailSurface.m_color, macroColor, detailFactor);
float blendedSpecularF0 = lerp(detailSurface.m_specularF0, macroSpecularF0, detailFactor);
surface.SetAlbedoAndSpecularF0(blendedColor, blendedSpecularF0, detailSurface.m_metalness * (1.0 - detailFactor));
surface.roughnessLinear = lerp(detailSurface.m_roughness, macroRoughness, detailFactor);
surface.CalculateRoughnessA();
detailSurface.m_normal = ReorientTangentSpaceNormal(macroNormal, detailSurface.m_normal);
surface.normal = lerp(detailSurface.m_normal, macroNormal, detailFactor);
surface.normal = normalize(surface.normal);
}
else
{
surface.normal = macroNormal;
surface.SetAlbedoAndSpecularF0(macroColor, macroSpecularF0, macroMetalness);
surface.roughnessLinear = macroRoughness;
surface.CalculateRoughnessA();
}
// Clear Coat, Transmission (Not used for terrain)
surface.clearCoat.InitializeToZero();
@@ -184,6 +172,7 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
// Shadow, Occlusion
lightingData.shadowCoords = IN.m_shadowCoords;
lightingData.diffuseAmbientOcclusion = detailSurface.m_occlusion;
// Diffuse and Specular response
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
@@ -0,0 +1,74 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Atom/Features/SrgSemantics.azsli>
ShaderResourceGroupSemantic SRG_Terrain
{
FrequencyId = 7;
};
ShaderResourceGroup TerrainSrg : SRG_Terrain
{
Sampler DetailSampler
{
AddressU = Wrap;
AddressV = Wrap;
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
};
struct DetailMaterialData
{
// Uv
row_major float3x4 m_uvTransform;
float3 m_baseColor;
// Factor / Scale / Bias for input textures
float m_baseColorFactor;
float m_normalFactor;
float m_metalFactor;
float m_roughnessScale;
float m_roughnessBias;
float m_specularF0Factor;
float m_occlusionFactor;
float m_heightFactor;
float m_heightOffset;
float m_heightBlendFactor;
// Flags
uint m_flags; // see DetailTextureFlags
// Image indices
uint m_colorNormalImageIndices;
uint m_roughnessMetalnessImageIndices;
uint m_specularF0OcclusionImageIndices;
uint m_heightImageIndex; // only first 16 bits used
// 16 byte aligned
uint2 m_padding;
};
Texture2D<uint4> m_detailMaterialIdImage;
StructuredBuffer<DetailMaterialData> m_detailMaterialData;
Texture2D m_detailTextures[]; // bindless array of all textures for detail materials
float2 m_detailMaterialIdImageCenter;
float m_detailHalfPixelUv;
float4 m_detailAabb;
}
@@ -0,0 +1,80 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZ_COLLECTING_PARTIAL_SRGS
#error Do not include this file directly. Include the main .srgi file instead.
#endif
partial ShaderResourceGroup ViewSrg
{
Sampler HeightmapSampler
{
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Point;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};
Sampler DetailSampler
{
AddressU = Wrap;
AddressV = Wrap;
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
};
struct DetailMaterialData
{
// Uv
row_major float3x4 m_uvTransform;
float3 m_baseColor;
// Factor / Scale / Bias for input textures
float m_baseColorFactor;
float m_normalFactor;
float m_metalFactor;
float m_roughnessScale;
float m_roughnessBias;
float m_specularF0Factor;
float m_occlusionFactor;
float m_heightFactor;
float m_heightOffset;
float m_heightBlendFactor;
// Flags
uint m_flags; // see DetailTextureFlags
// Image indices
uint m_colorNormalImageIndices;
uint m_roughnessMetalnessImageIndices;
uint m_specularF0OcclusionImageIndices;
uint m_heightImageIndex; // only first 16 bits used
// 16 byte aligned
uint2 m_padding;
};
Texture2D m_heightmapImage;
Texture2D<uint4> m_detailMaterialIdImage;
StructuredBuffer<DetailMaterialData> m_detailMaterialData;
Texture2D m_detailTextures[]; // bindless array of all textures for detail materials
float2 m_detailMaterialIdImageCenter;
float m_detailHalfPixelUv;
float4 m_detailAabb;
}
@@ -31,6 +31,9 @@
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include <Atom/RPI.Public/Model/Model.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Public/Pass/PassFilter.h>
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
@@ -50,18 +53,24 @@ namespace Terrain
const char* TerrainDetailChars = "TerrainDetail";
}
namespace MaterialInputs
namespace ViewSrgInputs
{
// 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");
static const char* const HeightmapImage("m_heightmapImage");
}
namespace TerrainSrgInputs
{
static const char* const DetailMaterialIdImage("m_detailMaterialIdImage");
static const char* const DetailMaterialData("m_detailMaterialData");
static const char* const DetailMaterialIdImageCenter("m_detailMaterialIdImageCenter");
static const char* const DetailHalfPixelUv("m_detailHalfPixelUv");
static const char* const DetailAabb("m_detailAabb");
static const char* const DetailTextures("m_detailTextures");
}
namespace DetailMaterialInputs
{
static const char* const BaseColorColor("baseColor.color");
static const char* const BaseColorMap("baseColor.textureMap");
static const char* const BaseColorUseTexture("baseColor.useTexture");
static const char* const BaseColorFactor("baseColor.factor");
@@ -72,8 +81,8 @@ namespace Terrain
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 RoughnessLowerBound("roughness.lowerBound");
static const char* const RoughnessUpperBound("roughness.upperBound");
static const char* const SpecularF0Map("specularF0.textureMap");
static const char* const SpecularF0UseTexture("specularF0.useTexture");
static const char* const SpecularF0Factor("specularF0.factor");
@@ -126,6 +135,9 @@ namespace Terrain
void TerrainFeatureProcessor::Activate()
{
EnableSceneNotification();
CacheForwardPass();
Initialize();
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
@@ -138,6 +150,13 @@ namespace Terrain
void TerrainFeatureProcessor::Initialize()
{
// Load indices for the View Srg.
auto viewSrgLayout = AZ::RPI::RPISystemInterface::Get()->GetViewSrgLayout();
m_heightmapPropertyIndex = viewSrgLayout->FindShaderInputImageIndex(AZ::Name(ViewSrgInputs::HeightmapImage));
AZ_Error(TerrainFPName, m_heightmapPropertyIndex.IsValid(), "Failed to find view srg input constant %s.", ViewSrgInputs::HeightmapImage);
// Load the terrain material asynchronously
const AZStd::string materialFilePath = "Materials/Terrain/DefaultPbrTerrain.azmaterial";
m_materialAssetLoader = AZStd::make_unique<AZ::RPI::AssetUtils::AsyncAssetLoader>();
@@ -166,6 +185,7 @@ namespace Terrain
return;
}
OnTerrainDataChanged(AZ::Aabb::CreateNull(), TerrainDataChangedMask::HeightData);
}
void TerrainFeatureProcessor::Deactivate()
@@ -173,6 +193,8 @@ namespace Terrain
TerrainMacroMaterialNotificationBus::Handler::BusDisconnect();
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect();
AZ::RPI::MaterialReloadNotificationBus::Handler::BusDisconnect();
DisableSceneNotification();
m_patchModel = {};
m_areaData = {};
@@ -181,6 +203,7 @@ namespace Terrain
m_macroMaterials.Clear();
m_materialAssetLoader = {};
m_materialInstance = {};
}
void TerrainFeatureProcessor::Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet)
@@ -339,9 +362,47 @@ namespace Terrain
uint16_t detailMaterialId = CreateOrUpdateDetailMaterial(material);
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, detailMaterialId });
m_detailMaterials.GetData(detailMaterialId).refCount++;
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
}
void TerrainFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] AZ::RPI::RenderPipeline* renderPipeline)
{
CacheForwardPass();
}
void TerrainFeatureProcessor::CheckDetailMaterialForDeletion(uint16_t detailMaterialId)
{
auto& detailMaterialData = m_detailMaterials.GetData(detailMaterialId);
if (--detailMaterialData.refCount == 0)
{
uint16_t bufferIndex = detailMaterialData.m_detailMaterialBufferIndex;
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(bufferIndex);
for (uint16_t imageIndex :
{
shaderData.m_colorImageIndex,
shaderData.m_normalImageIndex,
shaderData.m_roughnessImageIndex,
shaderData.m_metalnessImageIndex,
shaderData.m_specularF0ImageIndex,
shaderData.m_occlusionImageIndex,
shaderData.m_heightImageIndex
})
{
if (imageIndex != InvalidDetailImageIndex)
{
m_detailImageViews.at(imageIndex) = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::Magenta)->GetImageView();
m_detailImageViewFreeList.push_back(imageIndex);
m_detailImagesNeedUpdate = true;
}
}
m_detailMaterialShaderData.Release(bufferIndex);
m_detailMaterials.RemoveIndex(detailMaterialId);
}
}
void TerrainFeatureProcessor::OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
@@ -350,6 +411,8 @@ namespace Terrain
{
if (surface.m_surfaceTag == surfaceTag)
{
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
if (surface.m_surfaceTag != materialRegion.m_materialsForSurfaces.back().m_surfaceTag)
{
AZStd::swap(surface, materialRegion.m_materialsForSurfaces.back());
@@ -373,13 +436,19 @@ namespace Terrain
if (surface.m_surfaceTag == surfaceTag)
{
found = true;
surface.m_detailMaterialId = materialId;
if (surface.m_detailMaterialId != materialId)
{
++m_detailMaterials.GetData(materialId).refCount;
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
surface.m_detailMaterialId = materialId;
}
break;
}
}
if (!found)
{
++m_detailMaterials.GetData(materialId).refCount;
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, materialId });
}
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
@@ -398,138 +467,196 @@ namespace Terrain
static constexpr uint16_t InvalidDetailMaterial = 0xFFFF;
uint16_t detailMaterialId = InvalidDetailMaterial;
for (DetailMaterialData& detailMaterial : m_detailMaterials.GetDataVector())
for (auto& detailMaterialData : m_detailMaterials.GetDataVector())
{
if (detailMaterial.m_assetId == material->GetAssetId())
if (detailMaterialData.m_assetId == material->GetAssetId())
{
UpdateDetailMaterialData(detailMaterial, material);
detailMaterialId = m_detailMaterials.GetIndexForData(&detailMaterial);
detailMaterialId = m_detailMaterials.GetIndexForData(&detailMaterialData);
UpdateDetailMaterialData(detailMaterialId, material);
break;
}
}
if (detailMaterialId == InvalidDetailMaterial)
AZ_Assert(m_detailMaterialShaderData.GetSize() < 0xFF, "Only 255 detail materials supported.");
if (detailMaterialId == InvalidDetailMaterial && m_detailMaterialShaderData.GetSize() < 0xFF)
{
detailMaterialId = m_detailMaterials.GetFreeSlotIndex();
UpdateDetailMaterialData(m_detailMaterials.GetData(detailMaterialId), material);
auto& detailMaterialData = m_detailMaterials.GetData(detailMaterialId);
detailMaterialData.m_detailMaterialBufferIndex = aznumeric_cast<uint16_t>(m_detailMaterialShaderData.Reserve());
UpdateDetailMaterialData(detailMaterialId, material);
}
return detailMaterialId;
}
void TerrainFeatureProcessor::UpdateDetailMaterialData(DetailMaterialData& materialData, MaterialInstance material)
void TerrainFeatureProcessor::UpdateDetailMaterialData(uint16_t detailMaterialIndex, MaterialInstance material)
{
if (materialData.m_materialChangeId != material->GetCurrentChangeId())
DetailMaterialData& materialData = m_detailMaterials.GetData(detailMaterialIndex);
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(materialData.m_detailMaterialBufferIndex);
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);
return; // material hasn't changed, nothing to do
}
materialData.m_materialChangeId = material->GetCurrentChangeId();
materialData.m_assetId = material->GetAssetId();
DetailTextureFlags& flags = shaderData.m_flags;
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())
{
// GetValue<T>() expects the actaul type, not a reference type, so the reference needs to be removed.
using TypeRefRemoved = AZStd::remove_cvref_t<decltype(ref)>;
ref = material->GetPropertyValue(index).GetValue<TypeRefRemoved>();
}
};
auto applyImage = [&](const char* const indexName, AZ::Data::Instance<AZ::RPI::Image>& ref, const char* const usingFlagName, DetailTextureFlags flagToSet, uint16_t& imageIndex) -> void
{
// Determine if an image exists and if its using flag allows it to be used.
const auto index = getIndex(indexName);
const auto useTextureIndex = getIndex(usingFlagName);
bool useTextureValue = true;
if (useTextureIndex.IsValid())
{
useTextureValue = material->GetPropertyValue(useTextureIndex).GetValue<bool>();
}
if (index.IsValid() && useTextureValue)
{
ref = material->GetPropertyValue(index).GetValue<AZ::Data::Instance<AZ::RPI::Image>>();
}
useTextureValue = useTextureValue && ref;
flags = DetailTextureFlags(useTextureValue ? (flags | flagToSet) : (flags & ~flagToSet));
// Update queues to add/remove textures depending on if the image is used
if (ref)
{
if (imageIndex == InvalidDetailImageIndex)
{
if (m_detailImageViewFreeList.size() > 0)
{
imageIndex = m_detailImageViewFreeList.back();
m_detailImageViewFreeList.pop_back();
}
else
{
imageIndex = aznumeric_cast<uint16_t>(m_detailImageViews.size());
m_detailImageViews.push_back();
}
}
m_detailImageViews.at(imageIndex) = ref->GetImageView();
m_detailImagesNeedUpdate = true;
}
else if (imageIndex != InvalidDetailImageIndex)
{
m_detailImageViews.at(imageIndex) = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::Magenta)->GetImageView();
m_detailImageViewFreeList.push_back(imageIndex);
m_detailImagesNeedUpdate = true;
imageIndex = InvalidDetailImageIndex;
}
};
auto applyFlag = [&](const char* const indexName, DetailTextureFlags flagToSet) -> void
{
const auto index = getIndex(indexName);
if (index.IsValid())
{
bool flagValue = material->GetPropertyValue(index).GetValue<bool>();
flags = DetailTextureFlags(flagValue ? flags | flagToSet : flags);
}
};
auto getEnumName = [&](const char* const indexName) -> const AZStd::string_view
{
const auto index = getIndex(indexName);
if (index.IsValid())
{
uint32_t enumIndex = material->GetPropertyValue(index).GetValue<uint32_t>();
const AZ::Name& enumName = material->GetMaterialPropertiesLayout()->GetPropertyDescriptor(index)->GetEnumName(enumIndex);
return enumName.GetStringView();
}
return "";
};
using namespace DetailMaterialInputs;
applyImage(BaseColorMap, materialData.m_colorImage, BaseColorUseTexture, DetailTextureFlags::UseTextureBaseColor, shaderData.m_colorImageIndex);
applyProperty(BaseColorFactor, shaderData.m_baseColorFactor);
const auto index = getIndex(BaseColorColor);
if (index.IsValid())
{
AZ::Color baseColor = material->GetPropertyValue(index).GetValue<AZ::Color>();
shaderData.m_baseColorRed = baseColor.GetR();
shaderData.m_baseColorGreen = baseColor.GetG();
shaderData.m_baseColorBlue = baseColor.GetB();
}
const AZStd::string_view& blendModeString = getEnumName(BaseColorBlendMode);
if (blendModeString == "Multiply")
{
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeMultiply);
}
else if (blendModeString == "LinearLight")
{
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeLinearLight);
}
else if (blendModeString == "Lerp")
{
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeLerp);
}
else if (blendModeString == "Overlay")
{
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeOverlay);
}
applyImage(MetallicMap, materialData.m_metalnessImage, MetallicUseTexture, DetailTextureFlags::UseTextureMetallic, shaderData.m_metalnessImageIndex);
applyProperty(MetallicFactor, shaderData.m_metalFactor);
applyImage(RoughnessMap, materialData.m_roughnessImage, RoughnessUseTexture, DetailTextureFlags::UseTextureRoughness, shaderData.m_roughnessImageIndex);
if ((flags & DetailTextureFlags::UseTextureRoughness) > 0)
{
float lowerBound = 0.0;
float upperBound = 1.0;
applyProperty(RoughnessLowerBound, lowerBound);
applyProperty(RoughnessUpperBound, upperBound);
shaderData.m_roughnessBias = lowerBound;
shaderData.m_roughnessScale = upperBound - lowerBound;
}
else
{
shaderData.m_roughnessBias = 0.0;
applyProperty(RoughnessFactor, shaderData.m_roughnessScale);
}
applyImage(SpecularF0Map, materialData.m_specularF0Image, SpecularF0UseTexture, DetailTextureFlags::UseTextureSpecularF0, shaderData.m_specularF0ImageIndex);
applyProperty(SpecularF0Factor, shaderData.m_specularF0Factor);
applyImage(NormalMap, materialData.m_normalImage, NormalUseTexture, DetailTextureFlags::UseTextureNormal, shaderData.m_normalImageIndex);
applyProperty(NormalFactor, shaderData.m_normalFactor);
applyFlag(NormalFlipX, DetailTextureFlags::FlipNormalX);
applyFlag(NormalFlipY, DetailTextureFlags::FlipNormalY);
applyImage(DiffuseOcclusionMap, materialData.m_occlusionImage, DiffuseOcclusionUseTexture, DetailTextureFlags::UseTextureOcclusion, shaderData.m_occlusionImageIndex);
applyProperty(DiffuseOcclusionFactor, shaderData.m_occlusionFactor);
applyImage(HeightMap, materialData.m_heightImage, HeightUseTexture, DetailTextureFlags::UseTextureHeight, shaderData.m_heightImageIndex);
applyProperty(HeightFactor, shaderData.m_heightFactor);
applyProperty(HeightOffset, shaderData.m_heightOffset);
applyProperty(HeightBlendFactor, shaderData.m_heightBlendFactor);
m_updateDetailMaterialBuffer = true;
}
void TerrainFeatureProcessor::CheckUpdateDetailTexture(const Aabb2i& newBounds, const Vector2i& newCenter)
@@ -765,7 +892,7 @@ namespace Terrain
{
if (materialSurface.m_surfaceTag == surfaceType)
{
return materialSurface.m_detailMaterialId;
return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex;
}
}
}
@@ -801,6 +928,7 @@ namespace Terrain
// World size changed, so the whole height map needs updating.
m_dirtyRegion = worldBounds;
m_imagesNeedUpdate = true;
}
int32_t xStart = aznumeric_cast<int32_t>(AZStd::ceilf(m_dirtyRegion.GetMin().GetX() / queryResolution));
@@ -889,21 +1017,48 @@ namespace Terrain
m_macroNormalMapIndex = layout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::MacroNormalMap));
AZ_Error(TerrainFPName, m_macroNormalMapIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::MacroNormalMap);
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_terrainSrg = {};
for (auto& shaderItem : m_materialInstance->GetShaderCollection())
{
if (shaderItem.GetShaderAsset()->GetDrawListName() == AZ::Name("forward"))
{
const auto& shaderAsset = shaderItem.GetShaderAsset();
m_terrainSrg = AZ::RPI::ShaderResourceGroup::Create(shaderItem.GetShaderAsset(), shaderAsset->GetSupervariantIndex(AZ::Name()), AZ::Name{"TerrainSrg"});
AZ_Error(TerrainFPName, m_terrainSrg, "Failed to create Terrain shader resource group");
break;
}
}
AZ_Error(TerrainFPName, m_terrainSrg, "Terrain Srg not found on any shader in the terrain material");
if (m_terrainSrg)
{
const AZ::RHI::ShaderResourceGroupLayout* terrainSrgLayout = m_terrainSrg->GetLayout();
m_detailMaterialIdPropertyIndex = terrainSrgLayout->FindShaderInputImageIndex(AZ::Name(TerrainSrgInputs::DetailMaterialIdImage));
AZ_Error(TerrainFPName, m_detailMaterialIdPropertyIndex.IsValid(), "Failed to find view srg input constant %s.", TerrainSrgInputs::DetailMaterialIdImage);
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);
m_detailCenterPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailMaterialIdImageCenter));
AZ_Error(TerrainFPName, m_detailCenterPropertyIndex.IsValid(), "Failed to find view srg input constant %s.", TerrainSrgInputs::DetailMaterialIdImageCenter);
m_detailHalfPixelUvPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailHalfPixelUv));
AZ_Error(TerrainFPName, m_detailHalfPixelUvPropertyIndex.IsValid(), "Failed to find view srg input constant %s.", TerrainSrgInputs::DetailHalfPixelUv);
m_detailAabbPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailAabb));
AZ_Error(TerrainFPName, m_detailAabbPropertyIndex.IsValid(), "Failed to find view srg input constant %s.", TerrainSrgInputs::DetailAabb);
m_detailTexturesIndex = terrainSrgLayout->FindShaderInputImageUnboundedArrayIndex(AZ::Name(TerrainSrgInputs::DetailTextures));
AZ_Error(TerrainFPName, m_detailTexturesIndex.IsValid(), "Failed to find view srg input constant %s.", TerrainSrgInputs::DetailTextures);
// Set up the gpu buffer for detail material data
AZ::Render::GpuBufferHandler::Descriptor desc;
desc.m_bufferName = "Detail Material Data";
desc.m_bufferSrgName = TerrainSrgInputs::DetailMaterialData;
desc.m_elementSize = sizeof(DetailMaterialShaderData);
desc.m_srgLayout = terrainSrgLayout;
m_detailMaterialDataBuffer = AZ::Render::GpuBufferHandler(desc);
}
// Find any macro materials that have already been created.
TerrainMacroMaterialRequestBus::EnumerateHandlers(
@@ -987,7 +1142,7 @@ namespace Terrain
auto objectSrg = AZ::RPI::ShaderResourceGroup::Create(shaderAsset, materialAsset->GetObjectSrgLayout()->GetName());
if (!objectSrg)
{
AZ_Warning("TerrainFeatureProcessor", false, "Failed to create a new shader resource group, skipping.");
AZ_Warning(TerrainFPName, false, "Failed to create a new shader resource group, skipping.");
continue;
}
@@ -1003,7 +1158,7 @@ namespace Terrain
// set the shader option to select forward pass IBL specular if necessary
if (!drawPacket.SetShaderOption(AZ::Name("o_meshUseForwardPassIBLSpecular"), AZ::RPI::ShaderOptionValue{ false }))
{
AZ_Warning("MeshDrawPacket", false, "Failed to set o_meshUseForwardPassIBLSpecular on mesh draw packet");
AZ_Warning(TerrainFPName, false, "Failed to set o_meshUseForwardPassIBLSpecular on mesh draw packet");
}
const uint8_t stencilRef = AZ::Render::StencilRefs::UseDiffuseGIPass | AZ::Render::StencilRefs::UseIBLSpecularPass;
drawPacket.SetStencilRef(stencilRef);
@@ -1053,11 +1208,14 @@ namespace Terrain
if (m_areaData.m_heightmapUpdated)
{
UpdateTerrainData();
const AZ::Data::Instance<AZ::RPI::Image> heightmapImage = m_areaData.m_heightmapImage; // cast StreamingImage to Image
m_materialInstance->SetPropertyValue(m_heightmapPropertyIndex, heightmapImage);
}
if (m_updateDetailMaterialBuffer)
{
m_updateDetailMaterialBuffer = false;
m_detailMaterialDataBuffer.UpdateBuffer(m_detailMaterialShaderData.GetRawData(), aznumeric_cast<uint32_t>(m_detailMaterialShaderData.GetSize()));
}
AZ::Vector3 cameraPosition = AZ::Vector3::CreateZero();
for (auto& view : process.m_views)
{
@@ -1068,7 +1226,7 @@ namespace Terrain
}
}
if (m_dirtyDetailRegion.IsValid() || !cameraPosition.IsClose(m_previousCameraPosition))
if (m_dirtyDetailRegion.IsValid() || !cameraPosition.IsClose(m_previousCameraPosition) || m_detailImagesNeedUpdate)
{
int32_t newDetailTexturePosX = aznumeric_cast<int32_t>(AZStd::roundf(cameraPosition.GetX() / DetailTextureScale));
int32_t newDetailTexturePosY = aznumeric_cast<int32_t>(AZStd::roundf(cameraPosition.GetY() / DetailTextureScale));
@@ -1091,8 +1249,6 @@ namespace Terrain
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,
@@ -1100,11 +1256,16 @@ namespace Terrain
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_terrainSrg)
{
m_terrainSrg->SetConstant(m_detailAabbPropertyIndex, detailAabb);
m_terrainSrg->SetConstant(m_detailHalfPixelUvPropertyIndex, 0.5f / DetailTextureSize);
m_terrainSrg->SetConstant(m_detailCenterPropertyIndex, detailUvOffset);
m_detailMaterialDataBuffer.UpdateSrg(m_terrainSrg.get());
}
}
if (m_areaData.m_heightmapUpdated || m_areaData.m_macroMaterialsUpdated)
@@ -1195,6 +1356,15 @@ namespace Terrain
sectorData.m_srg->Compile();
}
}
// Currently there seems to be a bug in unbounded image arrays where flickering can occur if this isn't updated every frame.
if (m_terrainSrg/* && m_detailImagesUpdated*/)
{
AZStd::array_view<const AZ::RHI::ImageView*> imageViews(m_detailImageViews.data(), m_detailImageViews.size());
[[maybe_unused]] bool result = m_terrainSrg->SetImageViewUnboundedArray(m_detailTexturesIndex, imageViews);
AZ_Error(TerrainFPName, result, "Failed to set image view unbounded array into shader resource group.");
m_detailImagesNeedUpdate = false;
}
}
for (auto& sectorData : m_sectorData)
@@ -1236,10 +1406,30 @@ namespace Terrain
}
}
if (m_detailTextureImage && m_areaData.m_heightmapImage && m_imagesNeedUpdate)
{
m_imagesNeedUpdate = false;
for (auto& view : process.m_views)
{
auto viewSrg = view->GetShaderResourceGroup();
viewSrg->SetImage(m_heightmapPropertyIndex, m_areaData.m_heightmapImage);
}
if (m_terrainSrg)
{
m_terrainSrg->SetImage(m_detailMaterialIdPropertyIndex, m_detailTextureImage);
}
}
if (m_materialInstance)
{
m_materialInstance->Compile();
}
if (m_terrainSrg && m_forwardPass)
{
m_terrainSrg->Compile();
m_forwardPass->BindSrg(m_terrainSrg->GetRHIShaderResourceGroup());
}
}
void TerrainFeatureProcessor::InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata)
@@ -1368,6 +1558,7 @@ namespace Terrain
void TerrainFeatureProcessor::OnMaterialReinitialized([[maybe_unused]] const MaterialInstance& material)
{
PrepareMaterialData();
for (auto& sectorData : m_sectorData)
{
for (auto& drawPacket : sectorData.m_drawPackets)
@@ -1375,6 +1566,8 @@ namespace Terrain
drawPacket.Update(*GetParentScene());
}
}
m_imagesNeedUpdate = true;
m_detailImagesNeedUpdate = true;
}
void TerrainFeatureProcessor::SetWorldSize([[maybe_unused]] AZ::Vector2 sizeInMeters)
@@ -1438,6 +1631,27 @@ namespace Terrain
}
}
}
void TerrainFeatureProcessor::CacheForwardPass()
{
auto rasterPassFilter = AZ::RPI::PassFilter::CreateWithPassClass<AZ::RPI::RasterPass>();
rasterPassFilter.SetOwnerScene(GetParentScene());
AZ::RHI::RHISystemInterface* rhiSystem = AZ::RHI::RHISystemInterface::Get();
AZ::RHI::DrawListTag forwardTag = rhiSystem->GetDrawListTagRegistry()->AcquireTag(AZ::Name("forward"));
AZ::RPI::PassSystemInterface::Get()->ForEachPass(rasterPassFilter,
[&](AZ::RPI::Pass* pass) -> AZ::RPI::PassFilterExecutionFlow
{
auto* rasterPass = azrtti_cast<AZ::RPI::RasterPass*>(pass);
if (rasterPass && rasterPass->GetDrawListTag() == forwardTag)
{
m_forwardPass = rasterPass;
return AZ::RPI::PassFilterExecutionFlow::StopVisitingPasses;
}
return AZ::RPI::PassFilterExecutionFlow::ContinueVisitingPasses;
}
);
}
auto TerrainFeatureProcessor::Vector2i::operator+(const Vector2i& rhs) const -> Vector2i
{
@@ -19,7 +19,9 @@
#include <Atom/RPI.Public/MeshDrawPacket.h>
#include <Atom/RPI.Public/Material/MaterialReloadNotificationBus.h>
#include <Atom/RPI.Public/Shader/ShaderSystemInterface.h>
#include <Atom/Feature/Utils/GpuBufferHandler.h>
#include <Atom/Feature/Utils/IndexedDataVector.h>
#include <Atom/Feature/Utils/SparseVector.h>
namespace AZ::RPI
{
@@ -29,6 +31,7 @@ namespace AZ::RPI
}
class Material;
class Model;
class RenderPass;
class StreamingImage;
}
@@ -125,17 +128,19 @@ namespace Terrain
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,
FlipNormalX = 0b0000'0000'0000'0001'0000'0000'0000'0000,
FlipNormalY = 0b0000'0000'0000'0010'0000'0000'0000'0000,
BlendModeMask = 0b0000'0000'0000'0000'0000'0110'0000'0000,
BlendModeMask = 0b0000'0000'0000'1100'0000'0000'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,
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,
};
struct DetailMaterialShaderProperties
static constexpr uint16_t InvalidDetailImageIndex = 0xFFFF;
struct DetailMaterialShaderData
{
// Uv
AZStd::array<float, 12> m_uvTransform
@@ -145,30 +150,50 @@ namespace Terrain
0.0, 0.0, 1.0, 0.0,
};
float m_baseColorRed{ 1.0f };
float m_baseColorGreen{ 1.0f };
float m_baseColorBlue{ 1.0f };
// 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
// Image indices
uint16_t m_colorImageIndex{ InvalidDetailImageIndex };
uint16_t m_normalImageIndex{ InvalidDetailImageIndex };
uint16_t m_roughnessImageIndex{ InvalidDetailImageIndex };
uint16_t m_metalnessImageIndex{ InvalidDetailImageIndex };
uint16_t m_specularF0ImageIndex{ InvalidDetailImageIndex };
uint16_t m_occlusionImageIndex{ InvalidDetailImageIndex };
uint16_t m_heightImageIndex{ InvalidDetailImageIndex };
// 16 byte aligned
uint16_t m_padding1;
uint32_t m_padding2;
uint32_t m_padding3;
};
struct DetailMaterialData
{
AZ::Data::AssetId m_assetId;
AZ::RPI::Material::ChangeId m_materialChangeId{AZ::RPI::Material::DEFAULT_CHANGE_ID};
uint32_t refCount = 0;
uint16_t m_detailMaterialBufferIndex{ 0xFFFF };
AZ::Data::Instance<AZ::RPI::Image> m_colorImage;
AZ::Data::Instance<AZ::RPI::Image> m_normalImage;
@@ -177,8 +202,6 @@ namespace Terrain
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
@@ -217,6 +240,12 @@ namespace Terrain
Aabb2i GetClamped(Aabb2i rhs) const;
bool IsValid() const;
};
struct DetailTextureLocation
{
uint16_t m_index;
AZ::Data::Instance<AZ::RPI::Image> m_image;
};
// AZ::RPI::MaterialReloadNotificationBus::Handler overrides...
void OnMaterialReinitialized(const MaterialInstance& material) override;
@@ -237,6 +266,9 @@ namespace Terrain
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;
// AZ::RPI::SceneNotificationBus overrides...
void OnRenderPipelinePassesChanged(AZ::RPI::RenderPipeline* renderPipeline) override;
void Initialize();
void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata);
bool InitializePatchModel();
@@ -249,7 +281,8 @@ namespace Terrain
void TerrainSurfaceDataUpdated(const AZ::Aabb& dirtyRegion);
uint16_t CreateOrUpdateDetailMaterial(MaterialInstance material);
void UpdateDetailMaterialData(DetailMaterialData& materialData, MaterialInstance material);
void CheckDetailMaterialForDeletion(uint16_t detailMaterialId);
void UpdateDetailMaterialData(uint16_t detailMaterialIndex, 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);
@@ -271,6 +304,8 @@ namespace Terrain
AZ::Outcome<AZ::Data::Asset<AZ::RPI::BufferAsset>> CreateBufferAsset(
const void* data, const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName);
void CacheForwardPass();
// System-level parameters
static constexpr float GridSpacing{ 1.0f };
static constexpr int32_t GridSize{ 64 }; // number of terrain quads (vertices are m_gridSize + 1)
@@ -281,6 +316,7 @@ namespace Terrain
AZStd::unique_ptr<AZ::RPI::AssetUtils::AsyncAssetLoader> m_materialAssetLoader;
MaterialInstance m_materialInstance;
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> m_terrainSrg;
AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex;
AZ::RHI::ShaderInputConstantIndex m_terrainDataIndex;
@@ -288,11 +324,13 @@ namespace Terrain
AZ::RHI::ShaderInputConstantIndex m_macroMaterialCountIndex;
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::RHI::ShaderInputImageIndex m_heightmapPropertyIndex;
AZ::RHI::ShaderInputImageIndex m_detailMaterialIdPropertyIndex;
AZ::RHI::ShaderInputBufferIndex m_detailMaterialDataIndex;
AZ::RHI::ShaderInputConstantIndex m_detailCenterPropertyIndex;
AZ::RHI::ShaderInputConstantIndex m_detailAabbPropertyIndex;
AZ::RHI::ShaderInputConstantIndex m_detailHalfPixelUvPropertyIndex;
AZ::RHI::ShaderInputImageUnboundedArrayIndex m_detailTexturesIndex;
AZ::Data::Instance<AZ::RPI::Model> m_patchModel;
AZ::Vector3 m_previousCameraPosition = AZ::Vector3(AZStd::numeric_limits<float>::max(), 0.0, 0.0);
@@ -312,17 +350,26 @@ namespace Terrain
TerrainAreaData m_areaData;
AZ::Aabb m_dirtyRegion{ AZ::Aabb::CreateNull() };
AZ::Aabb m_dirtyDetailRegion{ AZ::Aabb::CreateNull() };
bool m_updateDetailMaterialBuffer{ false };
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;
bool m_forceRebuildDrawPackets{ false };
bool m_imagesNeedUpdate{ false };
AZStd::vector<SectorData> m_sectorData;
AZ::Render::IndexedDataVector<MacroMaterialData> m_macroMaterials;
AZ::Render::IndexedDataVector<DetailMaterialData> m_detailMaterials;
AZ::Render::IndexedDataVector<DetailMaterialListRegion> m_detailMaterialRegions;
AZ::Render::SparseVector<DetailMaterialShaderData> m_detailMaterialShaderData;
AZ::Render::GpuBufferHandler m_detailMaterialDataBuffer;
AZ::RPI::RenderPass* m_forwardPass;
AZStd::vector<const AZ::RHI::ImageView*> m_detailImageViews;
AZStd::vector<uint16_t> m_detailImageViewFreeList;
bool m_detailImagesNeedUpdate{ false };
};
}