From 775b7d048b3f740939c308e62ddde6b4c94d858a Mon Sep 17 00:00:00 2001 From: jiaweig Date: Wed, 19 May 2021 00:36:24 -0700 Subject: [PATCH] Add support for tangent stream pairing with the first UV from the model. --- .../Types/EnhancedPBR_DepthPass_WithPS.azsl | 13 ++- .../Types/EnhancedPBR_ForwardPass.azsl | 17 ++-- .../Types/EnhancedPBR_Shadowmap_WithPS.azsl | 14 +++- .../Common/Assets/Materials/Types/Skin.azsl | 15 ++-- ...tandardMultilayerPBR_DepthPass_WithPS.azsl | 16 ++-- .../StandardMultilayerPBR_ForwardPass.azsl | 15 ++-- ...tandardMultilayerPBR_Shadowmap_WithPS.azsl | 14 +++- .../Types/StandardPBR_DepthPass_WithPS.azsl | 14 +++- .../Types/StandardPBR_ForwardPass.azsl | 15 ++-- .../Types/StandardPBR_Shadowmap_WithPS.azsl | 13 ++- .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 3 +- .../ShaderResourceGroups/DefaultDrawSrg.azsli | 8 +- .../ShaderLib/Atom/RPI/TangentSpace.azsli | 8 +- .../Include/Atom/RPI.Public/Model/ModelLod.h | 42 +++++++++- .../Code/Source/RPI.Public/MeshDrawPacket.cpp | 53 ++++++------ .../Code/Source/RPI.Public/Model/ModelLod.cpp | 81 ++++++++++++++++++- Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp | 28 +++++++ 17 files changed, 288 insertions(+), 81 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl index db99ee7e24..48d919ccab 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl @@ -77,10 +77,15 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) if(ShouldHandleParallaxInDepthShaders()) { - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl index a91e88afad..17bf7568a0 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl @@ -118,18 +118,21 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float { // ------- Tangents & Bitangets ------- - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering && MaterialSrg::m_parallaxUvIndex != 0) || (o_normal_useTexture && MaterialSrg::m_normalMapUvIndex != 0) || (o_clearCoat_enabled && o_clearCoat_normal_useTexture && MaterialSrg::m_clearCoatNormalMapUvIndex != 0) || (o_detail_normal_useTexture && MaterialSrg::m_detail_allMapsUvIndex != 0)) { - // Generate the tangent/bitangent for UV[1+] - const int startIndex = 1; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, startIndex); + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } // ------- Depth & Parallax ------- @@ -260,7 +263,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // Convert the angle from [0..1] = [0 .. 180 degrees] to radians [0 .. PI] const float anisotropyAngle = MaterialSrg::m_anisotropicAngle * PI; const float anisotropyFactor = MaterialSrg::m_anisotropicFactor; - surface.anisotropy.Init(surface.normal, tangents[0], bitangents[0], anisotropyAngle, anisotropyFactor, surface.roughnessA); + surface.anisotropy.Init(surface.normal, IN.m_tangent, IN.m_bitangent, anisotropyAngle, anisotropyFactor, surface.roughnessA); } // ------- Lighting Data ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl index 80aedcd6f4..a4665ccec8 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl @@ -80,10 +80,16 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) { static const float ShadowMapDepthBias = 0.000001; - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; + + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl index 84095ac163..0d0be496d6 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl @@ -181,16 +181,19 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) // ------- Tangents & Bitangets ------- - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; if ( (o_normal_useTexture && MaterialSrg::m_normalMapUvIndex != 0) || (o_detail_normal_useTexture && MaterialSrg::m_detail_allMapsUvIndex != 0)) { - // Generate the tangent/bitangent for UV[1+] - const int startIndex = 1; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, startIndex); + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } Surface surface; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl index ae156d7313..2517205bac 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl @@ -103,11 +103,17 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) if(ShouldHandleParallaxInDepthShaders()) { - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; + + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); + GetDepth_Setup(IN.m_blendMask); float depth; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index bc32aa1370..9d02891fd5 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -136,9 +136,14 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Tangents & Bitangets ------- - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering && MaterialSrg::m_parallaxUvIndex != 0) || (o_layer1_o_normal_useTexture && MaterialSrg::m_layer1_m_normalMapUvIndex != 0) @@ -149,9 +154,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float || (o_layer3_o_clearCoat_normal_useTexture && MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex != 0) ) { - // Generate the tangent/bitangent for UV[1+] - const int startIndex = 1; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, startIndex); + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } // ------- Debug Modes ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl index c76dd15975..d0bbf0c0a1 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl @@ -102,10 +102,16 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) if(ShouldHandleParallaxInDepthShaders()) { - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; + + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); GetDepth_Setup(IN.m_blendMask); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl index 28708c12d5..619feab204 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl @@ -78,10 +78,16 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) if(ShouldHandleParallaxInDepthShaders()) { - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; + + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index f362349a7b..7a12a5e854 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -109,18 +109,21 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float { // ------- Tangents & Bitangets ------- - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering && MaterialSrg::m_parallaxUvIndex != 0) || (o_normal_useTexture && MaterialSrg::m_normalMapUvIndex != 0) || (o_clearCoat_enabled && o_clearCoat_normal_useTexture && MaterialSrg::m_clearCoatNormalMapUvIndex != 0) ) { - // Generate the tangent/bitangent for UV[1+] - const int startIndex = 1; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, startIndex); + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } // ------- Depth & Parallax ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl index 7f24b29700..51533090d0 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl @@ -81,10 +81,15 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) { static const float ShadowMapDepthBias = 0.000001; - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); + // We support two UV streams, but only a single stream of tangent/bitangent. + // By default, the first UV stream is applied and the default tangent/bitangent are used. + // If anything uses the second UV stream, and it is not a duplication of the first stream, + // generated tangent/bitangent will be applied. + // (As it implies, cases may occur where all/none of the UV steams use the default TB.) + // Whether a UV stream can use the tangent/bitangent are encoded in DrawSrg. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 29f0636e9e..d3f9ea2b77 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -728,7 +728,8 @@ namespace AZ // retrieve vertex/index buffers RPI::ModelLod::StreamBufferViewList streamBufferViews; - [[maybe_unused]] bool result = modelLod->GetStreamsForMesh(inputStreamLayout, streamBufferViews, shaderInputContract, meshIndex); + AZ::RPI::UvStreamTangentIndex dummyUvStreamTangentIndex; + [[maybe_unused]] bool result = modelLod->GetStreamsForMesh(inputStreamLayout, streamBufferViews, dummyUvStreamTangentIndex, shaderInputContract, meshIndex); AZ_Assert(result, "Failed to retrieve mesh stream buffer views"); // note that the element count is the size of the entire buffer, even though this mesh may only diff --git a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli index 69381ca0fc..ab2c2078db 100644 --- a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli +++ b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli @@ -16,7 +16,13 @@ ShaderResourceGroup DrawSrg : SRG_PerDraw { - float4 m_placeholder; // [GFX-TODO] [Atom-1727] Bug in AZSLc, empty SRGs cannot be shader variant fallbacks! // This SRG is unique per draw packet + + uint m_uvStreamTangentIndex; + + uint GetTangentIndexAtUv(uint uvIndex) + { + return 0xF;//(m_uvStreamTangentIndex >> (4 * uvIndex)) & 0xF; + } } diff --git a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/TangentSpace.azsli b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/TangentSpace.azsli index 13e3c652db..4eeb13500d 100644 --- a/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/TangentSpace.azsli +++ b/Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/TangentSpace.azsli @@ -190,12 +190,16 @@ void SurfaceGradientNormalMapping_GenerateTB(float2 uv, out float3 tangentWS, ou } //! Utility macro to nest SGBNM setup processes. -#define PrepareGeneratedTangent(normal, worldPos, isFrontFace, uvSets, uvSetCount, outTangents, outBitangents, startIndex) \ +#define PrepareGeneratedTangent(normal, worldPos, isFrontFace, uvSets, uvSetCount, outTangents, outBitangents) \ { \ SurfaceGradientNormalMapping_Init(normal, worldPos, !isFrontFace); \ [unroll] \ - for (int i = startIndex; i < uvSetCount; ++i) \ + for (uint i = 0; i < uvSetCount; ++i) \ { \ + if (DrawSrg::GetTangentIndexAtUv(i) == 0) \ + { \ + continue; \ + } \ SurfaceGradientNormalMapping_GenerateTB(uvSets[i], outTangents[i], outBitangents[i]); \ } \ } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h index 285cf80aca..a69aaac6ed 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h @@ -34,6 +34,8 @@ namespace AZ //! A map matches the UV shader inputs of this material to the custom UV names from the model. using MaterialModelUvOverrideMap = AZStd::unordered_map; + class UvStreamTangentIndex; + class ModelLod final : public Data::InstanceData { @@ -115,6 +117,7 @@ namespace AZ bool GetStreamsForMesh( RHI::InputStreamLayout& layoutOut, ModelLod::StreamBufferViewList& streamBufferViewsOut, + UvStreamTangentIndex& uvStreamTangentIndexOut, const ShaderInputContract& contract, size_t meshIndex, const MaterialModelUvOverrideMap& materialModelUvMap = {}, @@ -130,6 +133,8 @@ namespace AZ const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo, Mesh& meshInstance); + StreamInfoList::const_iterator FindFirstUvStreamFromMesh(size_t meshIndex) const; + StreamInfoList::const_iterator FindDefaultUvStream(size_t meshIndex, const MaterialUvNameMap& materialUvNameMap) const; // Finds a mesh vertex input stream that is the best match for a contracted stream channel. @@ -137,12 +142,16 @@ namespace AZ // @param materialModelUvMap a map of UV name overrides, which can be supplied to bind a specific mesh stream name to a different material shader stream name. // @param materialUvNameMap the UV name map that came from a MaterialTypeAsset, which defines the default set of material shader stream names. // @param defaultUv the default UV stream to use if a matching UV stream could not be found. Use FindDefaultUvStream() to populate this. + // @param firstUv the first UV stream from the mesh, which, by design, the tangent/bitangent stream belongs to. + // @param uvStreamTangentIndex a bitset indicating which tangent/bitangent stream (including generated ones) a UV stream will be using. StreamInfoList::const_iterator FindMatchingStream( size_t meshIndex, const MaterialModelUvOverrideMap& materialModelUvMap, const MaterialUvNameMap& materialUvNameMap, const ShaderInputContract::StreamChannelInfo& contractStreamChannel, - StreamInfoList::const_iterator defaultUv) const; + StreamInfoList::const_iterator defaultUv, + StreamInfoList::const_iterator firstUv, + UvStreamTangentIndex& uvStreamTangentIndexOut) const; // Meshes may share index/stream buffers in an LOD or they may have // unique buffers. Often the asset builder will prioritize shared buffers @@ -165,5 +174,36 @@ namespace AZ AZStd::mutex m_callbackMutex; }; + + //! An encoded bitset for tangent used by a UV stream. + //! It will be passed through DefaultDrawSrg. + class UvStreamTangentIndex + { + public: + uint32_t GetFullFlag() const; + uint32_t GetNextAvailableUvIndex() const; + uint32_t GetTangentIndexAtUv(uint32_t uvIndex) const; + + void ApplyTangentIndex(uint32_t tangentIndex); + + void Reset(); + + // The flag indicating generated tangent/bitangent will be used. + static constexpr uint32_t UnassignedTangentIndex = 0b1111u; + + private: + // Flag composition: + // The next available slot index (highest 4 bits) + tangent index (4 bits each) * 7 + // e.g. 0x200000F0 means there are 2 UV streams, + // the first UV stream uses 0th tangent stream, + // the second UV stream uses the generated tangent stream (0xF). + uint32_t m_flag = 0; + + static constexpr uint32_t BitsPerTangentIndex = 4; + static constexpr uint32_t BitsForUvIndex = 4; + + public: + static constexpr uint32_t MaxTangents = (sizeof(m_flag) * CHAR_BIT - BitsForUvIndex) / BitsPerTangentIndex; + }; } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/MeshDrawPacket.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/MeshDrawPacket.cpp index d0a277304e..e6fe8251c7 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/MeshDrawPacket.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/MeshDrawPacket.cpp @@ -169,7 +169,7 @@ namespace AZ const AZ::Data::Asset& drawSrgAsset = shader->GetAsset()->GetDrawSrgAsset(); // Set all unspecified shader options to default values, so that we get the most specialized variant possible. - // (because FindVariantStableId treats unspecified options as a request specificlly for a variant that doesn't specify those options) + // (because FindVariantStableId treats unspecified options as a request specifically for a variant that doesn't specify those options) // [GFX TODO][ATOM-3883] We should consider updating the FindVariantStableId algorithm to handle default values for us, and remove this step here. RPI::ShaderOptionGroup shaderOptions = *shaderItem.GetShaderOptions(); shaderOptions.SetUnspecifiedToDefaultValues(); @@ -198,6 +198,31 @@ namespace AZ const ShaderVariantId finalVariantId = shaderOptions.GetShaderVariantId(); const ShaderVariant& variant = r_forceRootShaderVariantUsage ? shader->GetRootVariant() : shader->GetVariant(finalVariantId); + RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor; + variant.ConfigurePipelineState(pipelineStateDescriptor); + + // Render states need to merge the runtime variation. + // This allows materials to customize the render states that the shader uses. + const RHI::RenderStates& renderStatesOverlay = *shaderItem.GetRenderStatesOverlay(); + RHI::MergeStateInto(renderStatesOverlay, pipelineStateDescriptor.m_renderStates); + + streamBufferViewsPerShader.push_back(); + auto& streamBufferViews = streamBufferViewsPerShader.back(); + + UvStreamTangentIndex uvStreamTangentIndex; + + if (!m_modelLod->GetStreamsForMesh( + pipelineStateDescriptor.m_inputStreamLayout, + streamBufferViews, + uvStreamTangentIndex, + variant.GetInputContract(), + m_modelLodMeshIndex, + m_materialModelUvMap, + m_material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap())) + { + return false; + } + Data::Instance drawSrg; if (drawSrgAsset) { @@ -210,31 +235,13 @@ namespace AZ drawSrg->SetShaderVariantKeyFallbackValue(shaderOptions.GetShaderVariantKeyFallbackValue()); } + RHI::ShaderInputNameIndex shaderUvStreamTangentIndex = "m_uvStreamTangentIndex"; + + drawSrg->SetConstant(shaderUvStreamTangentIndex, uvStreamTangentIndex.GetFullFlag()); + drawSrg->Compile(); } - RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor; - variant.ConfigurePipelineState(pipelineStateDescriptor); - - // Render states need to merge the runtime variation. - // This allows materials to customize the render states that the shader uses. - const RHI::RenderStates& renderStatesOverlay = *shaderItem.GetRenderStatesOverlay(); - RHI::MergeStateInto(renderStatesOverlay, pipelineStateDescriptor.m_renderStates); - - streamBufferViewsPerShader.push_back(); - auto& streamBufferViews = streamBufferViewsPerShader.back(); - - if (!m_modelLod->GetStreamsForMesh( - pipelineStateDescriptor.m_inputStreamLayout, - streamBufferViews, - variant.GetInputContract(), - m_modelLodMeshIndex, - m_materialModelUvMap, - m_material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap())) - { - return false; - } - // Use the default draw list tag from the shader variant. RHI::DrawListTag drawListTag = shader->GetDrawListTag(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp index 8747dac663..0d064142df 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp @@ -117,6 +117,17 @@ namespace AZ return RHI::ResultCode::Success; } + ModelLod::StreamInfoList::const_iterator ModelLod::FindFirstUvStreamFromMesh(size_t meshIndex) const + { + const Mesh& mesh = m_meshes[meshIndex]; + + auto firstUv = AZStd::find_if(mesh.m_streamInfo.begin(), mesh.m_streamInfo.end(), [](const StreamBufferInfo& info) { + return info.m_semantic.m_name.GetStringView().starts_with(RHI::ShaderSemantic::UvStreamSemantic); + }); + + return firstUv; + } + ModelLod::StreamInfoList::const_iterator ModelLod::FindDefaultUvStream(size_t meshIndex, const MaterialUvNameMap& materialUvNameMap) const { const Mesh& mesh = m_meshes[meshIndex]; @@ -160,7 +171,9 @@ namespace AZ const MaterialModelUvOverrideMap& materialModelUvMap, const MaterialUvNameMap& materialUvNameMap, const ShaderInputContract::StreamChannelInfo& contractStreamChannel, - StreamInfoList::const_iterator defaultUv) const + StreamInfoList::const_iterator defaultUv, + StreamInfoList::const_iterator firstUv, + UvStreamTangentIndex& uvStreamTangentIndexOut) const { const Mesh& mesh = m_meshes[meshIndex]; auto iter = mesh.m_streamInfo.end(); @@ -229,12 +242,18 @@ namespace AZ iter = defaultUv; } + if (IsUv) + { + uvStreamTangentIndexOut.ApplyTangentIndex(iter == firstUv ? 0 : UvStreamTangentIndex::UnassignedTangentIndex); + } + return iter; } bool ModelLod::GetStreamsForMesh( RHI::InputStreamLayout& layoutOut, StreamBufferViewList& streamBufferViewsOut, + UvStreamTangentIndex& uvStreamTangentIndexOut, const ShaderInputContract& contract, size_t meshIndex, const MaterialModelUvOverrideMap& materialModelUvMap, @@ -250,11 +269,14 @@ namespace AZ bool success = true; + // Searching for the first UV in the mesh, so it can be used to paired with tangent/bitangent stream + auto firstUv = FindFirstUvStreamFromMesh(meshIndex); auto defaultUv = FindDefaultUvStream(meshIndex, materialUvNameMap); + uvStreamTangentIndexOut.Reset(); for (auto& contractStreamChannel : contract.m_streamChannels) { - auto iter = FindMatchingStream(meshIndex, materialModelUvMap, materialUvNameMap, contractStreamChannel, defaultUv); + auto iter = FindMatchingStream(meshIndex, materialModelUvMap, materialUvNameMap, contractStreamChannel, defaultUv, firstUv, uvStreamTangentIndexOut); if (iter == mesh.m_streamInfo.end()) { @@ -340,6 +362,8 @@ namespace AZ const Mesh& mesh = m_meshes[meshIndex]; auto defaultUv = FindDefaultUvStream(meshIndex, materialUvNameMap); + auto firstUv = FindFirstUvStreamFromMesh(meshIndex); + UvStreamTangentIndex dummyUvStreamTangentIndex; for (auto& contractStreamChannel : contract.m_streamChannels) { @@ -350,7 +374,7 @@ namespace AZ AZ_Assert(contractStreamChannel.m_streamBoundIndicatorIndex.IsValid(), "m_streamBoundIndicatorIndex was invalid for an optional shader input stream"); - auto iter = FindMatchingStream(meshIndex, materialModelUvMap, materialUvNameMap, contractStreamChannel, defaultUv); + auto iter = FindMatchingStream(meshIndex, materialModelUvMap, materialUvNameMap, contractStreamChannel, defaultUv, firstUv, dummyUvStreamTangentIndex); ShaderOptionValue isStreamBound = (iter == mesh.m_streamInfo.end()) ? ShaderOptionValue{0} : ShaderOptionValue{1}; shaderOptions.SetValue(contractStreamChannel.m_streamBoundIndicatorIndex, isStreamBound); @@ -413,5 +437,56 @@ namespace AZ m_buffers.emplace_back(buffer); return static_cast(m_buffers.size() - 1); } + + uint32_t UvStreamTangentIndex::GetFullFlag() const + { + return m_flag; + } + + uint32_t UvStreamTangentIndex::GetNextAvailableUvIndex() const + { + return m_flag >> (sizeof(m_flag) * CHAR_BIT - BitsForUvIndex); + } + + uint32_t UvStreamTangentIndex::GetTangentIndexAtUv(uint32_t uvIndex) const + { + return (m_flag >> (BitsPerTangentIndex * uvIndex)) & 0b1111u; + } + + void UvStreamTangentIndex::ApplyTangentIndex(uint32_t tangentIndex) + { + uint32_t currentSlot = GetNextAvailableUvIndex(); + if (currentSlot >= MaxTangents) + { + AZ_Error("UV Stream", false, "Reaching the max of avaiblable stream slots."); + return; + } + + if (tangentIndex > UnassignedTangentIndex) + { + AZ_Warning( + "UV Stream", false, + "Tangent index must use %d bits as defined in UvStreamTangentIndex::m_flag. Unassigned index will be applied.", + BitsPerTangentIndex); + tangentIndex = UnassignedTangentIndex; + } + + uint32_t mask = 0b1111u << (BitsPerTangentIndex * currentSlot); + mask = ~mask; + + // Clear the writing bits in case + m_flag &= mask; + + // Write the bits to the slot + m_flag |= (tangentIndex << (BitsPerTangentIndex * currentSlot)); + + // Increase the index + m_flag += (1u << (sizeof(m_flag) * CHAR_BIT - BitsForUvIndex)); + } + + void UvStreamTangentIndex::Reset() + { + m_flag = 0; + } } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp index 980a9ac320..640a6b406a 100644 --- a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -917,6 +918,33 @@ namespace UnitTest } } + TEST_F(ModelTests, UvStream) + { + AZ::RPI::UvStreamTangentIndex uvStreamTangentIndex; + EXPECT_EQ(uvStreamTangentIndex.GetFullFlag(), 0u); + + uvStreamTangentIndex.ApplyTangentIndex(1u); + EXPECT_EQ(uvStreamTangentIndex.GetTangentIndexAtUv(0u), 1u); + EXPECT_EQ(uvStreamTangentIndex.GetNextAvailableUvIndex(), 1u); + + uvStreamTangentIndex.ApplyTangentIndex(5u); + EXPECT_EQ(uvStreamTangentIndex.GetTangentIndexAtUv(1u), 5u); + EXPECT_EQ(uvStreamTangentIndex.GetNextAvailableUvIndex(), 2u); + + uvStreamTangentIndex.ApplyTangentIndex(100u); + EXPECT_EQ(uvStreamTangentIndex.GetTangentIndexAtUv(2u), AZ::RPI::UvStreamTangentIndex::UnassignedTangentIndex); + EXPECT_EQ(uvStreamTangentIndex.GetNextAvailableUvIndex(), 3u); + + for (uint32_t i = 3; i < AZ::RPI::UvStreamTangentIndex::MaxTangents; ++i) + { + uvStreamTangentIndex.ApplyTangentIndex(0u); + } + + AZ_TEST_START_TRACE_SUPPRESSION; + uvStreamTangentIndex.ApplyTangentIndex(0u); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + // This class creates a Model with one LOD, whose mesh contains 2 planes. Plane 1 is in the XY plane at Z=-0.5, and // plane 2 is in the XY plane at Z=0.5. The two planes each have 9 quads which have been triangulated. It only has // a position and index buffer.