Merge remote-tracking branch 'upstream/development' into amzn-tommy/gitflow_211116_o3de2

Signed-off-by: Tommy Walton <waltont@amazon.com>
This commit is contained in:
Tommy Walton
2021-11-17 09:04:04 -08:00
32 changed files with 1491 additions and 578 deletions
@@ -61,6 +61,10 @@ class TestAutomation(EditorTestSuite):
class AtomEditorComponents_HDRColorGradingAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_HDRColorGradingAdded as test_module
@pytest.mark.test_case_id("C32078116")
class AtomEditorComponents_HDRiSkyboxAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_HDRiSkyboxAdded as test_module
@pytest.mark.test_case_id("C32078117")
class AtomEditorComponents_LightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module
@@ -0,0 +1,177 @@
"""
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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
hdri_skybox_entity_creation = (
"HDRi Skybox successfully created",
"HDRi Skybox failed to be created")
hdri_skybox_component = (
"Entity has an HDRi Skybox component",
"Entity failed to find HDRi Skybox component")
cubemap_property_set = (
"Cubemap property set on HDRi Skybox component",
"Couldn't set Cubemap property on HDRi Skybox component")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_HDRiSkybox_AddedToEntity():
"""
Summary:
Tests the HDRi Skybox component can be added to an entity and has the expected functionality.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
Expected Behavior:
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create an HDRi Skybox with no components.
2) Add an HDRi Skybox component to HDRi Skybox.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Enter/Exit game mode.
6) Test IsHidden.
7) Test IsVisible.
8) Delete HDRi Skybox.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
:return: None
"""
import os
import azlmbr.legacy.general as general
from editor_python_test_tools.asset_utils import Asset
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Test steps begin.
# 1. Create an HDRi Skybox with no components.
hdri_skybox_entity = EditorEntity.create_editor_entity(
AtomComponentProperties.hdri_skybox())
Report.critical_result(Tests.hdri_skybox_entity_creation,
hdri_skybox_entity.exists())
# 2. Add an HDRi Skybox component to HDRi Skybox.
hdri_skybox_component = hdri_skybox_entity.add_component(
AtomComponentProperties.hdri_skybox())
Report.critical_result(
Tests.hdri_skybox_component,
hdri_skybox_entity.has_component(AtomComponentProperties.hdri_skybox()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not hdri_skybox_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, hdri_skybox_entity.exists())
# 5. Set Cubemap Texture on HDRi Skybox component.
skybox_cubemap_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
skybox_cubemap_material_asset = Asset.find_asset_by_path(skybox_cubemap_asset_path, False)
hdri_skybox_component.set_component_property_value(
AtomComponentProperties.hdri_skybox('Cubemap Texture'), skybox_cubemap_material_asset.id)
get_cubemap_property = hdri_skybox_component.get_component_property_value(
AtomComponentProperties.hdri_skybox('Cubemap Texture'))
Report.result(Tests.cubemap_property_set, get_cubemap_property == skybox_cubemap_material_asset.id)
# 6. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 7. Test IsHidden.
hdri_skybox_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, hdri_skybox_entity.is_hidden() is True)
# 8. Test IsVisible.
hdri_skybox_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, hdri_skybox_entity.is_visible() is True)
# 9. Delete hdri_skybox entity.
hdri_skybox_entity.delete()
Report.result(Tests.entity_deleted, not hdri_skybox_entity.exists())
# 10. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, hdri_skybox_entity.exists())
# 11. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not hdri_skybox_entity.exists())
# 12. Look for errors or asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_HDRiSkybox_AddedToEntity)
@@ -0,0 +1,25 @@
/*
* 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
namespace AzToolsFramework::EmbeddedPython
{
// When using embedded Python, some platforms need to explicitly load the python library.
// For any modules that depend on 3rdParty::Python package, the AZ::Module should inherit this class.
class PythonLoader
{
public:
PythonLoader();
~PythonLoader();
private:
void* m_embeddedLibPythonHandle{ nullptr };
};
} // namespace AzToolsFramework::EmbeddedPython
@@ -47,6 +47,7 @@ set(FILES
API/EntityCompositionRequestBus.h
API/EntityCompositionNotificationBus.h
API/EditorViewportIconDisplayInterface.h
API/PythonLoader.h
API/ViewPaneOptions.h
API/ViewportEditorModeTrackerInterface.h
Application/Ticker.h
@@ -0,0 +1,20 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/API/PythonLoader.h>
namespace AzToolsFramework::EmbeddedPython
{
PythonLoader::PythonLoader()
{
}
PythonLoader::~PythonLoader()
{
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/API/PythonLoader.h>
#include <AzCore/Debug/Trace.h>
#include <dlfcn.h>
namespace AzToolsFramework::EmbeddedPython
{
PythonLoader::PythonLoader()
{
constexpr char libPythonName[] = "libpython3.7m.so.1.0";
if (m_embeddedLibPythonHandle = dlopen(libPythonName, RTLD_NOW | RTLD_GLOBAL);
m_embeddedLibPythonHandle == nullptr)
{
char* err = dlerror();
AZ_Error("PythonLoader", false, "Failed to load %s with error: %s\n", libPythonName, err ? err : "Unknown Error");
}
}
PythonLoader::~PythonLoader()
{
if (m_embeddedLibPythonHandle)
{
dlclose(m_embeddedLibPythonHandle);
}
}
} // namespace AzToolsFramework::EmbeddedPython
@@ -7,4 +7,5 @@
#
set(FILES
AzToolsFramework/API/PythonLoader_Linux.cpp
)
@@ -7,4 +7,5 @@
#
set(FILES
../Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp
)
@@ -7,4 +7,5 @@
#
set(FILES
../Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp
)
@@ -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);
@@ -9,11 +9,13 @@
#pragma once
#include <AzCore/Module/Module.h>
#include <AzToolsFramework/API/PythonLoader.h>
namespace AtomToolsFramework
{
class AtomToolsFrameworkModule
: public AZ::Module
, public AzToolsFramework::EmbeddedPython::PythonLoader
{
public:
AZ_RTTI(AtomToolsFrameworkModule, "{B58B7CA8-98C9-4DC8-8607-E094989BBBE2}", AZ::Module);
@@ -49,8 +49,14 @@ namespace AZ::Render
RenderAABB(instance, renderActorSettings.m_staticAABBColor);
}
// Render skeleton
// Render simple line skeleton
if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_LINESKELETON])
{
RenderLineSkeleton(instance, renderActorSettings.m_lineSkeletonColor);
}
// Render advance skeleton
if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_SKELETON])
{
RenderSkeleton(instance, renderActorSettings.m_skeletonColor);
}
@@ -110,6 +116,29 @@ namespace AZ::Render
return aabbRadius * 0.01f;
}
float AtomActorDebugDraw::CalculateBoneScale(EMotionFX::ActorInstance* actorInstance, EMotionFX::Node* node)
{
// Get the transform data
EMotionFX::TransformData* transformData = actorInstance->GetTransformData();
const EMotionFX::Pose* pose = transformData->GetCurrentPose();
const size_t nodeIndex = node->GetNodeIndex();
const size_t parentIndex = node->GetParentIndex();
const AZ::Vector3 nodeWorldPos = pose->GetWorldSpaceTransform(nodeIndex).m_position;
if (parentIndex != InvalidIndex)
{
const AZ::Vector3 parentWorldPos = pose->GetWorldSpaceTransform(parentIndex).m_position;
const AZ::Vector3 bone = parentWorldPos - nodeWorldPos;
const float boneLength = bone.GetLengthEstimate();
// 10% of the bone length is the sphere size
return boneLength * 0.1f;
}
return 0.0f;
}
void AtomActorDebugDraw::PrepareForMesh(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM)
{
// Check if we have already prepared for the given mesh
@@ -145,7 +174,7 @@ namespace AZ::Render
auxGeom->DrawAabb(aabb, aabbColor, RPI::AuxGeomDraw::DrawStyle::Line);
}
void AtomActorDebugDraw::RenderSkeleton(EMotionFX::ActorInstance* instance, const AZ::Color& skeletonColor)
void AtomActorDebugDraw::RenderLineSkeleton(EMotionFX::ActorInstance* instance, const AZ::Color& skeletonColor)
{
RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
@@ -189,6 +218,42 @@ namespace AZ::Render
auxGeom->DrawLines(lineArgs);
}
void AtomActorDebugDraw::RenderSkeleton(EMotionFX::ActorInstance* instance, const AZ::Color& skeletonColor)
{
RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
const EMotionFX::TransformData* transformData = instance->GetTransformData();
const EMotionFX::Skeleton* skeleton = instance->GetActor()->GetSkeleton();
const EMotionFX::Pose* pose = transformData->GetCurrentPose();
const size_t numEnabled = instance->GetNumEnabledNodes();
for (size_t i = 0; i < numEnabled; ++i)
{
EMotionFX::Node* joint = skeleton->GetNode(instance->GetEnabledNode(i));
const size_t jointIndex = joint->GetNodeIndex();
const size_t parentIndex = joint->GetParentIndex();
// check if this node has a parent and is a bone, if not skip it
if (parentIndex == InvalidIndex)
{
continue;
}
const AZ::Vector3 nodeWorldPos = pose->GetWorldSpaceTransform(jointIndex).m_position;
const AZ::Vector3 parentWorldPos = pose->GetWorldSpaceTransform(parentIndex).m_position;
const AZ::Vector3 bone = parentWorldPos - nodeWorldPos;
const AZ::Vector3 boneDirection = bone.GetNormalizedEstimate();
const AZ::Vector3 centerWorldPos = bone / 2 + nodeWorldPos;
const float boneLength = bone.GetLengthEstimate();
const float boneScale = CalculateBoneScale(instance, joint);
const float parentBoneScale = CalculateBoneScale(instance, skeleton->GetNode(parentIndex));
const float cylinderSize = boneLength - boneScale - parentBoneScale;
// Render the bone cylinder, the cylinder will be directed towards the node's parent and must fit between the spheres
auxGeom->DrawCylinder(centerWorldPos, boneDirection, boneScale, cylinderSize, skeletonColor);
auxGeom->DrawSphere(nodeWorldPos, boneScale, skeletonColor);
}
}
void AtomActorDebugDraw::RenderEMFXDebugDraw(EMotionFX::ActorInstance* instance)
{
RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
@@ -37,9 +37,12 @@ namespace AZ::Render
private:
float CalculateBoneScale(EMotionFX::ActorInstance* actorInstance, EMotionFX::Node* node);
float CalculateScaleMultiplier(EMotionFX::ActorInstance* instance) const;
void PrepareForMesh(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM);
void RenderAABB(EMotionFX::ActorInstance* instance, const AZ::Color& aabbColor);
void RenderLineSkeleton(EMotionFX::ActorInstance* instance, const AZ::Color& skeletonColor);
void RenderSkeleton(EMotionFX::ActorInstance* instance, const AZ::Color& skeletonColor);
void RenderEMFXDebugDraw(EMotionFX::ActorInstance* instance);
void RenderNormals(
@@ -545,6 +545,7 @@ namespace EMStudio
->Attribute(AZ::Edit::Attributes::ChangeNotify, &RenderOptions::OnLineSkeletonColorChangedCallback)
->DataElement(AZ::Edit::UIHandlers::Default, &RenderOptions::m_skeletonColor, "Solid skeleton color",
"Solid skeleton color.")
->Attribute(AZ_CRC("AlphaChannel", 0xa0cab5cf), true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &RenderOptions::OnSkeletonColorChangedCallback)
->DataElement(AZ::Edit::UIHandlers::Default, &RenderOptions::m_selectionColor, "Selection gizmo color",
"Selection gizmo color")
@@ -1268,6 +1269,7 @@ namespace EMStudio
void RenderOptions::OnSkeletonColorChangedCallback() const
{
PluginOptionsNotificationsBus::Event(s_skeletonColorOptionName, &PluginOptionsNotificationsBus::Events::OnOptionChanged, s_skeletonColorOptionName);
CopyToRenderActorSettings(EMotionFX::GetRenderActorSettings());
}
void RenderOptions::OnSelectionColorChangedCallback() const
@@ -9,6 +9,8 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Module/Module.h>
#include <AzToolsFramework/API/PythonLoader.h>
#include <PythonSystemComponent.h>
#include <PythonReflectionComponent.h>
#include <PythonMarshalComponent.h>
@@ -18,6 +20,7 @@ namespace EditorPythonBindings
{
class EditorPythonBindingsModule
: public AZ::Module
, public AzToolsFramework::EmbeddedPython::PythonLoader
{
public:
AZ_RTTI(EditorPythonBindingsModule, "{851B9E35-4FD5-49B1-8207-E40D4BBA36CC}", AZ::Module);
@@ -9,12 +9,15 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Module/Module.h>
#include <AzToolsFramework/API/PythonLoader.h>
#include <PythonAssetBuilderSystemComponent.h>
namespace PythonAssetBuilder
{
class PythonAssetBuilderModule
: public AZ::Module
, public AzToolsFramework::EmbeddedPython::PythonLoader
{
public:
AZ_RTTI(PythonAssetBuilderModule, "{35C9457E-54C2-474C-AEBE-5A70CC1D435D}", AZ::Module);
@@ -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 };
};
}
@@ -10,6 +10,7 @@
#include <${Name}ModuleInterface.h>
#include <${Name}EditorSystemComponent.h>
#include <AzToolsFramework/API/PythonLoader.h>
void Init${SanitizedCppName}Resources()
{
@@ -21,6 +22,7 @@ namespace ${SanitizedCppName}
{
class ${SanitizedCppName}EditorModule
: public ${SanitizedCppName}ModuleInterface
, public AzToolsFramework::EmbeddedPython::PythonLoader
{
public:
AZ_RTTI(${SanitizedCppName}EditorModule, "${ModuleClassId}", ${SanitizedCppName}ModuleInterface);
+1 -1
View File
@@ -43,6 +43,6 @@ ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-linux
ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-linux TARGETS azslc PACKAGE_HASH 6d7dc671936c34ff70d2632196107ca1b8b2b41acdd021bfbc69a9fd56215c22)
ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev5-linux TARGETS ZLIB PACKAGE_HASH 9be5ea85722fc27a8645a9c8a812669d107c68e6baa2ca0740872eaeb6a8b0fc)
ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-linux TARGETS squish-ccr PACKAGE_HASH 85fecafbddc6a41a27c5f59ed4a5dfb123a94cb4666782cf26e63c0a4724c530)
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-linux TARGETS astc-encoder PACKAGE_HASH 2ba97a06474d609945f0ab4419af1f6bbffdd294ca6b869f5fcebec75c573c0f)
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev2-linux TARGETS astc-encoder PACKAGE_HASH 71549d1ca9e4d48391b92a89ea23656d3393810e6777879f6f8a9def2db1610c)
ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-linux TARGETS ISPCTexComp PACKAGE_HASH 065fd12abe4247dde247330313763cf816c3375c221da030bdec35024947f259)
ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-linux TARGETS lz4 PACKAGE_HASH 5de3dbd3e2a3537c6555d759b3c5bb98e5456cf85c74ff6d046f809b7087290d)
+1 -1
View File
@@ -40,7 +40,7 @@ ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac
ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-mac TARGETS libsamplerate PACKAGE_HASH b912af40c0ac197af9c43d85004395ba92a6a859a24b7eacd920fed5854a97fe)
ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev5-mac TARGETS ZLIB PACKAGE_HASH b6fea9c79b8bf106d4703b67fecaa133f832ad28696c2ceef45fb5f20013c096)
ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-mac TARGETS squish-ccr PACKAGE_HASH 155bfbfa17c19a9cd2ef025de14c5db598f4290045d5b0d83ab58cb345089a77)
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-mac TARGETS astc-encoder PACKAGE_HASH 96f6ea8c3e45ec7fe525230c7c53ca665c8300d8e28456cc19bb3159ce6f8dcc)
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev2-mac TARGETS astc-encoder PACKAGE_HASH 06f129d26995845824f1fb906a5135b2c71d44d66c768342af85fa28a175906f)
ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac TARGETS ISPCTexComp PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169)
ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac TARGETS lz4 PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b)
ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-mac TARGETS azslc PACKAGE_HASH a9d81946b42ffa55c0d14d6a9249b3340e59a8fb8835e7a96c31df80f14723bc)
@@ -46,7 +46,7 @@ ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-windows
ly_associate_package(PACKAGE_NAME Crashpad-0.8.0-rev1-windows TARGETS Crashpad PACKAGE_HASH d162aa3070147bc0130a44caab02c5fe58606910252caf7f90472bd48d4e31e2)
ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev5-windows TARGETS ZLIB PACKAGE_HASH 8847112429744eb11d92c44026fc5fc53caa4a06709382b5f13978f3c26c4cbd)
ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-windows TARGETS squish-ccr PACKAGE_HASH 5c3d9fa491e488ccaf802304ad23b932268a2b2846e383f088779962af2bfa84)
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-windows TARGETS astc-encoder PACKAGE_HASH 3addc6fc1a7eb0d6b7f3d530e962af967e6d92b3825ef485da243346357cf78e)
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev2-windows TARGETS astc-encoder PACKAGE_HASH 17249bfa438afb34e21449865d9c9297471174ae0cea9b2f9def2ee206038295)
ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-windows TARGETS ISPCTexComp PACKAGE_HASH b6fa6ea28a2808a9a5524c72c37789c525925e435770f2d94eb2d387360fa2d0)
ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-windows TARGETS lz4 PACKAGE_HASH 4ea457b833cd8cfaf8e8e06ed6df601d3e6783b606bdbc44a677f77e19e0db16)
ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-windows TARGETS azslc PACKAGE_HASH 44eb2e0fc4b0f1c75d0fb6f24c93a5753655b84dbc3e6ad45389ed3b9cf7a4b0)
@@ -83,7 +83,7 @@
"CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_PARALLEL_LINK_JOBS=4",
"CMAKE_LY_PROJECTS": "AutomatedTesting",
"CMAKE_TARGET": "all",
"CTEST_OPTIONS": "-E (AutomatedTesting::Atom_TestSuite_Main|AutomatedTesting::PhysicsTests_Main|AutomatedTesting::PrefabTests|AutomatedTesting::TerrainTests_Main|Gem::EMotionFX.Editor.Tests) -L (SUITE_smoke|SUITE_main) -LE (REQUIRES_gpu) --no-tests=error",
"CTEST_OPTIONS": "-E (AutomatedTesting::Atom_TestSuite_Main|AutomatedTesting::PrefabTests|AutomatedTesting::TerrainTests_Main|Gem::EMotionFX.Editor.Tests) -L (SUITE_smoke|SUITE_main) -LE (REQUIRES_gpu) --no-tests=error",
"TEST_RESULTS": "True"
}
},
@@ -96,7 +96,7 @@
"CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4",
"CMAKE_LY_PROJECTS": "AutomatedTesting",
"CMAKE_TARGET": "all",
"CTEST_OPTIONS": "-E (AutomatedTesting::Atom_TestSuite_Main|AutomatedTesting::PhysicsTests_Main|AutomatedTesting::PrefabTests|AutomatedTesting::TerrainTests_Main|Gem::EMotionFX.Editor.Tests) -L (SUITE_smoke|SUITE_main) -LE (REQUIRES_gpu) --no-tests=error",
"CTEST_OPTIONS": "-E (AutomatedTesting::Atom_TestSuite_Main|AutomatedTesting::PrefabTests|AutomatedTesting::TerrainTests_Main|Gem::EMotionFX.Editor.Tests) -L (SUITE_smoke|SUITE_main) -LE (REQUIRES_gpu) --no-tests=error",
"TEST_RESULTS": "True"
}
},