From ff4412db7ca9978eda06ebce089704c743ef2bb8 Mon Sep 17 00:00:00 2001 From: Ken Pruiksma Date: Fri, 4 Feb 2022 11:01:47 -0600 Subject: [PATCH] Calculating uv transforms for terrain detail materials (#7375) Calculating uv transforms for terrain detail materials This adds support for uv transforms in terrain detail materials. To support this work, the code for generating a matrix from material properties was pulled out of the material functor and put into a new MaterialUtils file in Atom Utils. --- .../Source/Material/Transform2DFunctor.cpp | 58 +++-------------- .../Code/Source/Material/Transform2DFunctor.h | 11 +--- .../Material/Transform2DFunctorSourceData.cpp | 4 +- .../Material/Transform2DFunctorSourceData.h | 3 +- .../Code/Include/Atom/Utils/MaterialUtils.h | 46 ++++++++++++++ Gems/Atom/Utils/Code/Source/MaterialUtils.cpp | 62 +++++++++++++++++++ Gems/Atom/Utils/Code/atom_utils_files.cmake | 2 + .../Terrain/TerrainDetailHelpers.azsli | 33 +++++++--- .../TerrainDetailMaterialManager.cpp | 39 ++++++++++++ .../TerrainDetailMaterialManager.h | 2 +- 10 files changed, 190 insertions(+), 70 deletions(-) create mode 100644 Gems/Atom/Utils/Code/Include/Atom/Utils/MaterialUtils.h create mode 100644 Gems/Atom/Utils/Code/Source/MaterialUtils.cpp diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctor.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctor.cpp index e7baff7f93..e9fdd6e06e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctor.cpp @@ -39,59 +39,21 @@ namespace AZ ; } } - + void Transform2DFunctor::Process(RuntimeContext& context) { using namespace RPI; - auto center = context.GetMaterialPropertyValue(m_center); - auto scale = context.GetMaterialPropertyValue(m_scale); - auto scaleX = context.GetMaterialPropertyValue(m_scaleX); - auto scaleY = context.GetMaterialPropertyValue(m_scaleY); - auto translateX = context.GetMaterialPropertyValue(m_translateX); - auto translateY = context.GetMaterialPropertyValue(m_translateY); - auto rotateDegrees = context.GetMaterialPropertyValue(m_rotateDegrees); - - if (scaleX != 0.0f) - { - translateX *= (1.0f / scaleX); - } - - if (scaleY != 0.0f) - { - translateY *= (1.0f / scaleY); - } - - Matrix3x3 translateCenter2D = Matrix3x3::CreateIdentity(); - translateCenter2D.SetBasisZ(-center.GetX(), -center.GetY(), 1.0f); - - Matrix3x3 translateCenterInv2D = Matrix3x3::CreateIdentity(); - translateCenterInv2D.SetBasisZ(center.GetX(), center.GetY(), 1.0f); - - Matrix3x3 scale2D = Matrix3x3::CreateDiagonal(AZ::Vector3(scaleX * scale, scaleY * scale, 1.0f)); - - Matrix3x3 translate2D = Matrix3x3::CreateIdentity(); - translate2D.SetBasisZ(translateX, translateY, 1.0f); + UvTransformDescriptor desc; + desc.m_center = context.GetMaterialPropertyValue(m_center); + desc.m_scale = context.GetMaterialPropertyValue(m_scale); + desc.m_scaleX = context.GetMaterialPropertyValue(m_scaleX); + desc.m_scaleY = context.GetMaterialPropertyValue(m_scaleY); + desc.m_translateX = context.GetMaterialPropertyValue(m_translateX); + desc.m_translateY = context.GetMaterialPropertyValue(m_translateY); + desc.m_rotateDegrees = context.GetMaterialPropertyValue(m_rotateDegrees); - Matrix3x3 rotate2D = Matrix3x3::CreateRotationZ(AZ::DegToRad(rotateDegrees)); - - Matrix3x3 transform = translateCenter2D; - for (auto transformType : m_transformOrder) - { - switch (transformType) - { - case TransformType::Scale: - transform = scale2D * transform; - break; - case TransformType::Rotate: - transform = rotate2D * transform; - break; - case TransformType::Translate: - transform = translate2D * transform; - break; - } - } - transform = translateCenterInv2D * transform; + Matrix3x3 transform = CreateUvTransformMatrix(desc, m_transformOrder); context.GetShaderResourceGroup()->SetConstant(m_transformMatrix, transform); diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctor.h b/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctor.h index 2ef5af6913..58dcd7ebb7 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctor.h +++ b/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctor.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace AZ { @@ -24,14 +25,6 @@ namespace AZ public: AZ_RTTI(Transform2DFunctor, "{3E9C4357-6B2D-4A22-89DB-462441C9D8CD}", RPI::MaterialFunctor); - enum class TransformType - { - Invalid, - Scale, - Rotate, - Translate - }; - static void Reflect(ReflectContext* context); using RPI::MaterialFunctor::Process; @@ -57,6 +50,4 @@ namespace AZ } // namespace Render - AZ_TYPE_INFO_SPECIALIZE(Render::Transform2DFunctor::TransformType, "{D8C15D33-CE3D-4297-A646-030B0625BF84}"); - } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctorSourceData.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctorSourceData.cpp index 26e7983a4b..7436cb06b7 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctorSourceData.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctorSourceData.cpp @@ -85,13 +85,13 @@ namespace AZ functor->m_transformOrder = m_transformOrder; - AZStd::set transformSet{m_transformOrder.begin(), m_transformOrder.end()}; + AZStd::set transformSet{m_transformOrder.begin(), m_transformOrder.end()}; if (m_transformOrder.size() != transformSet.size()) { AZ_Warning("Transform2DFunctor", false, "transformOrder field contains duplicate entries"); } - if (transformSet.find(Transform2DFunctor::TransformType::Invalid) != transformSet.end()) + if (transformSet.find(TransformType::Invalid) != transformSet.end()) { AZ_Warning("Transform2DFunctor", false, "transformOrder contains invalid entries"); } diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctorSourceData.h b/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctorSourceData.h index db7e65fedc..ae97879884 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctorSourceData.h +++ b/Gems/Atom/Feature/Common/Code/Source/Material/Transform2DFunctorSourceData.h @@ -10,6 +10,7 @@ #include "./Transform2DFunctor.h" #include +#include namespace AZ { @@ -30,7 +31,7 @@ namespace AZ private: - AZStd::vector m_transformOrder; //!< Controls the order in which Scale, Translate, Rotate are performed + AZStd::vector m_transformOrder; //!< Controls the order in which Scale, Translate, Rotate are performed // Material property inputs... AZStd::string m_center; //!< material property for center of scaling and rotation diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/MaterialUtils.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/MaterialUtils.h new file mode 100644 index 0000000000..5c89b63182 --- /dev/null +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/MaterialUtils.h @@ -0,0 +1,46 @@ +/* + * 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 +#include +#include +#include + +//! This file holds useful material related utility functions. + +namespace AZ +{ + namespace Render + { + enum class TransformType + { + Invalid, + Scale, + Rotate, + Translate + }; + + struct UvTransformDescriptor + { + Vector2 m_center{ Vector2::CreateZero() }; + float m_scale{ 1.0f }; + float m_scaleX{ 1.0f }; + float m_scaleY{ 1.0f }; + float m_translateX{ 0.0f }; + float m_translateY{ 0.0f }; + float m_rotateDegrees{ 0.0f }; + }; + + // Create a 3x3 uv transform matrix from a set of input properties. + Matrix3x3 CreateUvTransformMatrix(const UvTransformDescriptor& desc, const AZStd::span transformOrder); + } + + AZ_TYPE_INFO_SPECIALIZE(Render::TransformType, "{D8C15D33-CE3D-4297-A646-030B0625BF84}"); +} diff --git a/Gems/Atom/Utils/Code/Source/MaterialUtils.cpp b/Gems/Atom/Utils/Code/Source/MaterialUtils.cpp new file mode 100644 index 0000000000..6ce9ed5629 --- /dev/null +++ b/Gems/Atom/Utils/Code/Source/MaterialUtils.cpp @@ -0,0 +1,62 @@ +/* + * 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 + +namespace AZ::Render +{ + + Matrix3x3 CreateUvTransformMatrix(const UvTransformDescriptor& desc, AZStd::span transformOrder) + { + float translateX = desc.m_translateX; + float translateY = desc.m_translateY; + + if (desc.m_scaleX != 0.0f) + { + translateX *= (1.0f / desc.m_scaleX); + } + + if (desc.m_scaleY != 0.0f) + { + translateY *= (1.0f / desc.m_scaleY); + } + + Matrix3x3 translateCenter2D = Matrix3x3::CreateIdentity(); + translateCenter2D.SetBasisZ(-desc.m_center.GetX(), -desc.m_center.GetY(), 1.0f); + + Matrix3x3 translateCenterInv2D = Matrix3x3::CreateIdentity(); + translateCenterInv2D.SetBasisZ(desc.m_center.GetX(), desc.m_center.GetY(), 1.0f); + + Matrix3x3 scale2D = Matrix3x3::CreateDiagonal(AZ::Vector3(desc.m_scaleX * desc.m_scale, desc.m_scaleY * desc.m_scale, 1.0f)); + + Matrix3x3 translate2D = Matrix3x3::CreateIdentity(); + translate2D.SetBasisZ(translateX, translateY, 1.0f); + + Matrix3x3 rotate2D = Matrix3x3::CreateRotationZ(AZ::DegToRad(desc.m_rotateDegrees)); + + Matrix3x3 transform = translateCenter2D; + for (auto transformType : transformOrder) + { + switch (transformType) + { + case TransformType::Scale: + transform = scale2D * transform; + break; + case TransformType::Rotate: + transform = rotate2D * transform; + break; + case TransformType::Translate: + transform = translate2D * transform; + break; + } + } + transform = translateCenterInv2D * transform; + return transform; + } + +} diff --git a/Gems/Atom/Utils/Code/atom_utils_files.cmake b/Gems/Atom/Utils/Code/atom_utils_files.cmake index c11c2e294d..31529c93be 100644 --- a/Gems/Atom/Utils/Code/atom_utils_files.cmake +++ b/Gems/Atom/Utils/Code/atom_utils_files.cmake @@ -21,6 +21,7 @@ set(FILES Include/Atom/Utils/ImGuiShaderMetrics.inl Include/Atom/Utils/ImGuiTransientAttachmentProfiler.h Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl + Include/Atom/Utils/MaterialUtils.h Include/Atom/Utils/PngFile.h Include/Atom/Utils/PpmFile.h Include/Atom/Utils/StableDynamicArray.h @@ -30,6 +31,7 @@ set(FILES Include/Atom/Utils/AssetCollectionAsyncLoader.h Source/DdsFile.cpp Source/ImageComparison.cpp + Source/MaterialUtils.cpp Source/PngFile.cpp Source/PpmFile.cpp Source/Utils.cpp diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailHelpers.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailHelpers.azsli index b566b727ff..9794eec4a3 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailHelpers.azsli +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailHelpers.azsli @@ -204,16 +204,33 @@ void GetDetailSurfaceForMaterial(inout DetailSurface surface, uint materialId, f { TerrainSrg::DetailMaterialData detailMaterialData = TerrainSrg::m_detailMaterialData[materialId]; + float3x3 uvTransform = (float3x3)detailMaterialData.m_uvTransform; + float2 transformedUv = mul(uvTransform, float3(uv, 1.0)).xy; + + // With different materials in the quad, we can't rely on ddx/ddy of the transformed uv because + // the materials may have different uv transforms. This would create visible seams where the wrong + // mip was being used. Instead, manually calculate the transformed ddx/ddy using the ddx/ddy of the + // original uv. + float2 uvDdx = ddx(uv); float2 uvDdy = ddy(uv); - surface.m_color = GetDetailColor(detailMaterialData, uv, uvDdx, uvDdy); - surface.m_normal = GetDetailNormal(detailMaterialData, uv, uvDdx, uvDdy); - surface.m_roughness = GetDetailRoughness(detailMaterialData, uv, uvDdx, uvDdy); - surface.m_specularF0 = GetDetailSpecularF0(detailMaterialData, uv, uvDdx, uvDdy); - surface.m_metalness = GetDetailMetalness(detailMaterialData, uv, uvDdx, uvDdy); - surface.m_occlusion = GetDetailOcclusion(detailMaterialData, uv, uvDdx, uvDdy); - surface.m_height = GetDetailHeight(detailMaterialData, uv, uvDdx, uvDdy); + float2 uvX = uv + uvDdx; + float2 uvY = uv + uvDdy; + + float2 transformedUvX = mul(uvTransform, float3(uvX, 1.0)).xy; + float2 transformedUvY = mul(uvTransform, float3(uvY, 1.0)).xy; + + float2 transformedUvDdx = transformedUvX - transformedUv; + float2 transformedUvDdy = transformedUvY - transformedUv; + + surface.m_color = GetDetailColor(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy); + surface.m_normal = GetDetailNormal(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy); + surface.m_roughness = GetDetailRoughness(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy); + surface.m_specularF0 = GetDetailSpecularF0(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy); + surface.m_metalness = GetDetailMetalness(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy); + surface.m_occlusion = GetDetailOcclusion(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy); + surface.m_height = GetDetailHeight(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy); } // Debugs the detail material by choosing a random color per material ID and rendering it without blending. @@ -279,7 +296,7 @@ bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdCoord, float2 textureSize; TerrainSrg::m_detailMaterialIdImage.GetDimensions(textureSize.x, textureSize.y); - // The detail material id texture wraps since the "center" point can be anywhere in the texture, so mod by texturesize + // The detail material id texture wraps since the "center" point can be anywhere in the texture, so mod by textureSize int2 detailMaterialIdTopLeft = ((int2(detailMaterialIdCoord) % textureSize) + textureSize) % textureSize; int2 detailMaterialIdBottomRight = (detailMaterialIdTopLeft + 1) % textureSize; diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp index 96b363d8c6..a00d2efe59 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp @@ -15,6 +15,8 @@ #include #include +#include + #include namespace Terrain @@ -56,6 +58,13 @@ namespace Terrain static const char* const HeightFactor("parallax.factor"); static const char* const HeightOffset("parallax.offset"); static const char* const HeightBlendFactor("parallax.blendFactor"); + static const char* const UvCenter("uv.center"); + static const char* const UvScale("uv.scale"); + static const char* const UvTileU("uv.tileU"); + static const char* const UvTileV("uv.tileV"); + static const char* const UvOffsetU("uv.offsetU"); + static const char* const UvOffsetV("uv.offsetV"); + static const char* const UvRotateDegrees("uv.rotateDegrees"); } namespace TerrainSrgInputs @@ -548,6 +557,36 @@ namespace Terrain applyProperty(HeightOffset, shaderData.m_heightOffset); applyProperty(HeightBlendFactor, shaderData.m_heightBlendFactor); + AZ::Render::UvTransformDescriptor transformDescriptor; + applyProperty(UvCenter, transformDescriptor.m_center); + applyProperty(UvScale, transformDescriptor.m_scale); + applyProperty(UvTileU, transformDescriptor.m_scaleX); + applyProperty(UvTileV, transformDescriptor.m_scaleY); + applyProperty(UvOffsetU, transformDescriptor.m_translateX); + applyProperty(UvOffsetV, transformDescriptor.m_translateY); + applyProperty(UvRotateDegrees, transformDescriptor.m_rotateDegrees); + + AZStd::array order = + { + AZ::Render::TransformType::Rotate, + AZ::Render::TransformType::Translate, + AZ::Render::TransformType::Scale, + }; + + AZ::Matrix3x3 uvTransformMatrix = AZ::Render::CreateUvTransformMatrix(transformDescriptor, order); + uvTransformMatrix.GetRow(0).StoreToFloat3(&shaderData.m_uvTransform[0]); + uvTransformMatrix.GetRow(1).StoreToFloat3(&shaderData.m_uvTransform[4]); + uvTransformMatrix.GetRow(2).StoreToFloat3(&shaderData.m_uvTransform[8]); + + // Store a hash of the matrix in element in an unused portion for quick comparisons in the shader + size_t hash64 = 0; + for (float value : shaderData.m_uvTransform) + { + AZStd::hash_combine(hash64, value); + } + uint32_t hash32 = uint32_t((hash64 ^ (hash64 >> 32)) & 0xFFFFFFFF); + shaderData.m_uvTransform[3] = *reinterpret_cast(&hash32); + m_detailMaterialBufferNeedsUpdate = true; } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h index 667b1b2e85..3f63812e47 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h @@ -77,7 +77,7 @@ namespace Terrain struct DetailMaterialShaderData { - // Uv + // Uv (data is 3x3, padding each row for explicit alignment) AZStd::array m_uvTransform { 1.0, 0.0, 0.0, 0.0,