diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl index 84095ac163..456d7cbabe 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl @@ -101,7 +101,7 @@ struct VSOutput float2 m_uv[UvSetCount] : UV1; float2 m_detailUv : UV3; - float4 m_blendMask : UV8; + float4 m_wrinkleBlendFactors : UV8; }; #include @@ -132,11 +132,11 @@ VSOutput SkinVS(VSInput IN) if(o_blendMask_isBound) { - OUT.m_blendMask = IN.m_optional_blendMask; + OUT.m_wrinkleBlendFactors = IN.m_optional_blendMask; } else { - OUT.m_blendMask = float4(0,1,0,0); + OUT.m_wrinkleBlendFactors = float4(0,0,0,0); } VertexHelper(IN, OUT, worldPosition, false); @@ -214,7 +214,22 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex]; float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor; - + + // ------- Wrinkle Map Setup ------- + + // Combine the optional per-morph target wrinkle masks + float4 wrinkleBlendFactors = float4(0.0, 0.0, 0.0, 0.0); + for(uint wrinkleMaskIndex = 0; wrinkleMaskIndex < ObjectSrg::m_wrinkle_mask_count; ++wrinkleMaskIndex) + { + wrinkleBlendFactors += ObjectSrg::m_wrinkle_masks[wrinkleMaskIndex].Sample(MaterialSrg::m_sampler, normalUv) * ObjectSrg::GetWrinkleMaskWeight(wrinkleMaskIndex); + } + + // If texture based morph target driven masks are being used, use those values instead of the per-vertex colors + if(ObjectSrg::m_wrinkle_mask_count) + { + IN.m_wrinkleBlendFactors = saturate(wrinkleBlendFactors); + } + // Since the wrinkle normal maps should all be in the same tangent space as the main normal map, we should be able to blend the raw normal map // texture values before doing all the tangent space transforms, so we only have to do the transforms once, for better performance. @@ -223,12 +238,12 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) { normalMapSample = SampleNormalXY(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY); } - if(o_wrinkleLayers_enabled && o_blendMask_isBound && o_wrinkleLayers_normal_enabled) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_normal_enabled) { - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture1, normalMapSample, MaterialSrg::m_wrinkle_normal_texture1, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.r); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture2, normalMapSample, MaterialSrg::m_wrinkle_normal_texture2, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.g); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture3, normalMapSample, MaterialSrg::m_wrinkle_normal_texture3, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.b); - normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture4, normalMapSample, MaterialSrg::m_wrinkle_normal_texture4, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_blendMask.a); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture1, normalMapSample, MaterialSrg::m_wrinkle_normal_texture1, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.r); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture2, normalMapSample, MaterialSrg::m_wrinkle_normal_texture2, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.g); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture3, normalMapSample, MaterialSrg::m_wrinkle_normal_texture3, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.b); + normalMapSample = ApplyNormalWrinkleMap(o_wrinkleLayers_normal_useTexture4, normalMapSample, MaterialSrg::m_wrinkle_normal_texture4, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, IN.m_wrinkleBlendFactors.a); } if(o_detail_normal_useTexture) @@ -255,7 +270,7 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) float3 baseColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor, o_baseColor_useTexture); bool useSampledBaseColor = o_baseColor_useTexture; - if(o_wrinkleLayers_enabled && o_blendMask_isBound && o_wrinkleLayers_baseColor_enabled) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_baseColor_enabled) { // If any of the wrinkle maps are applied, we will use the Base Color blend settings to apply the MaterialSrg::m_baseColor tint to the wrinkle maps, // even if the main base color map is not used. @@ -272,10 +287,10 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) baseColor = float3(1,1,1); } - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture1, baseColor, MaterialSrg::m_wrinkle_baseColor_texture1, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.r); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture2, baseColor, MaterialSrg::m_wrinkle_baseColor_texture2, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.g); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture3, baseColor, MaterialSrg::m_wrinkle_baseColor_texture3, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.b); - baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture4, baseColor, MaterialSrg::m_wrinkle_baseColor_texture4, MaterialSrg::m_sampler, baseColorUv, IN.m_blendMask.a); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture1, baseColor, MaterialSrg::m_wrinkle_baseColor_texture1, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.r); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture2, baseColor, MaterialSrg::m_wrinkle_baseColor_texture2, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.g); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture3, baseColor, MaterialSrg::m_wrinkle_baseColor_texture3, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.b); + baseColor = ApplyBaseColorWrinkleMap(o_wrinkleLayers_baseColor_useTexture4, baseColor, MaterialSrg::m_wrinkle_baseColor_texture4, MaterialSrg::m_sampler, baseColorUv, IN.m_wrinkleBlendFactors.a); } @@ -283,13 +298,13 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) baseColor = ApplyTextureOverlay(o_detail_baseColor_useTexture, baseColor, MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, IN.m_detailUv, detailLayerBaseColorFactor); - if(o_wrinkleLayers_enabled && o_wrinkleLayers_showBlendMaskValues && o_blendMask_isBound) + if(o_wrinkleLayers_enabled && o_wrinkleLayers_showBlendMaskValues) { // Overlay debug colors to highlight the different blend weights coming from the vertex color stream. - if(o_wrinkleLayers_count > 0) { baseColor = lerp(baseColor, float3(1,0,0), IN.m_blendMask.r); } - if(o_wrinkleLayers_count > 1) { baseColor = lerp(baseColor, float3(0,1,0), IN.m_blendMask.g); } - if(o_wrinkleLayers_count > 2) { baseColor = lerp(baseColor, float3(0,0,1), IN.m_blendMask.b); } - if(o_wrinkleLayers_count > 3) { baseColor = lerp(baseColor, float3(1,1,1), IN.m_blendMask.a); } + if(o_wrinkleLayers_count > 0) { baseColor = lerp(baseColor, float3(1,0,0), IN.m_wrinkleBlendFactors.r); } + if(o_wrinkleLayers_count > 1) { baseColor = lerp(baseColor, float3(0,1,0), IN.m_wrinkleBlendFactors.g); } + if(o_wrinkleLayers_count > 2) { baseColor = lerp(baseColor, float3(0,0,1), IN.m_wrinkleBlendFactors.b); } + if(o_wrinkleLayers_count > 3) { baseColor = lerp(baseColor, float3(1,1,1), IN.m_wrinkleBlendFactors.a); } } // ------- Specular ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index b8951d69c7..101a03b907 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -987,15 +987,6 @@ { "file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader", "tag": "SkinnedMeshMotionVector" - }, - // Used by the light culling system to produce accurate depth bounds for this object when it uses blended transparency - { - "file": "Shaders/Depth/DepthPassTransparentMin.shader", - "tag": "DepthPassTransparentMin" - }, - { - "file": "Shaders/Depth/DepthPassTransparentMax.shader", - "tag": "DepthPassTransparentMax" } ], "functors": [ diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli index 50896cdf25..abc4ec7fc4 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli @@ -31,6 +31,16 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject return SceneSrg::GetObjectToWorldInverseTransposeMatrix(m_objectId); } + //[GFX TODO][ATOM-15280] Move wrinkle mask data from the default object srg into something specific to the Skin shader + uint m_wrinkle_mask_count; + float4 m_wrinkle_mask_weights[4]; + Texture2D m_wrinkle_masks[16]; + + float GetWrinkleMaskWeight(uint index) + { + return m_wrinkle_mask_weights[index / 4][index % 4]; + } + //! Reflection Probe (smallest probe volume that overlaps the object position) struct ReflectionProbeData { diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h index 7875e38fc0..0d61ef82d1 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h @@ -148,6 +148,8 @@ namespace AZ Data::Instance GetModel(const MeshHandle& meshHandle) const override; Data::Asset GetModelAsset(const MeshHandle& meshHandle) const override; + Data::Instance GetObjectSrg(const MeshHandle& meshHandle) const override; + void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const override; void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) override; void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const MaterialAssignmentMap& materials) override; const MaterialAssignmentMap& GetMaterialAssignmentMap(const MeshHandle& meshHandle) const override; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h index c2360068de..fb5bff5584 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h @@ -61,6 +61,14 @@ namespace AZ virtual Data::Instance GetModel(const MeshHandle& meshHandle) const = 0; //! Gets the underlying RPI::ModelAsset for a meshHandle. virtual Data::Asset GetModelAsset(const MeshHandle& meshHandle) const = 0; + //! Gets the ObjectSrg for a meshHandle. + //! Updating the ObjectSrg should be followed by a call to QueueObjectSrgForCompile, + //! instead of compiling the srg directly. This way, if the srg has already been queued for compile, + //! it will not be queued twice in the same frame. The ObjectSrg should not be updated during + //! Simulate, or it will create a race between updating the data and the call to Compile + virtual Data::Instance GetObjectSrg(const MeshHandle& meshHandle) const = 0; + //! Queues the object srg for compile. + virtual void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const = 0; //! Sets the MaterialAssignmentMap for a meshHandle, using just a single material for the DefaultMaterialAssignmentId. //! Note if there is already a material assignment map, this will replace the entire map with just a single material. virtual void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) = 0; diff --git a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h index 39fd7b4380..418ee0cfb8 100644 --- a/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Mocks/MockMeshFeatureProcessor.h @@ -23,6 +23,8 @@ namespace UnitTest MOCK_METHOD1(CloneMesh, MeshHandle(const MeshHandle&)); MOCK_CONST_METHOD1(GetModel, AZStd::intrusive_ptr(const MeshHandle&)); MOCK_CONST_METHOD1(GetModelAsset, AZ::Data::Asset(const MeshHandle&)); + MOCK_CONST_METHOD1(GetObjectSrg, AZStd::intrusive_ptr(const MeshHandle&)); + MOCK_CONST_METHOD1(QueueObjectSrgForCompile, void(const MeshHandle&)); MOCK_CONST_METHOD1(GetMaterialAssignmentMap, const AZ::Render::MaterialAssignmentMap&(const MeshHandle&)); MOCK_METHOD2(ConnectModelChangeEventHandler, void(const MeshHandle&, ModelChangedEvent::Handler&)); MOCK_METHOD3(SetTransform, void(const MeshHandle&, const AZ::Transform&, const AZ::Vector3&)); diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 29f0636e9e..4059d65cbb 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -231,6 +231,19 @@ namespace AZ return {}; } + Data::Instance MeshFeatureProcessor::GetObjectSrg(const MeshHandle& meshHandle) const + { + return meshHandle.IsValid() ? meshHandle->m_shaderResourceGroup : nullptr; + } + + void MeshFeatureProcessor::QueueObjectSrgForCompile(const MeshHandle& meshHandle) const + { + if (meshHandle.IsValid()) + { + meshHandle->m_objectSrgNeedsUpdate = true; + } + } + void MeshFeatureProcessor::SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance& material) { Render::MaterialAssignmentMap materials; diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp index 388f4112a0..cb2d6a69ef 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp @@ -71,16 +71,6 @@ namespace AZ } - void SkinnedMeshFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet) - { - AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Simulate"); - AZ_UNUSED(packet); - - SkinnedMeshFeatureProcessorNotificationBus::Broadcast(&SkinnedMeshFeatureProcessorNotificationBus::Events::OnUpdateSkinningMatrices); - - } - void SkinnedMeshFeatureProcessor::Render(const FeatureProcessor::RenderPacket& packet) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); @@ -268,6 +258,8 @@ namespace AZ void SkinnedMeshFeatureProcessor::OnBeginPrepareRender() { m_renderProxiesChecker.soft_lock(); + + SkinnedMeshFeatureProcessorNotificationBus::Broadcast(&SkinnedMeshFeatureProcessorNotificationBus::Events::OnUpdateSkinningMatrices); } void SkinnedMeshFeatureProcessor::OnRenderEnd() diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h index bb3dd242a1..75d41742b2 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h @@ -49,7 +49,6 @@ namespace AZ // FeatureProcessor overrides ... void Activate() override; void Deactivate() override; - void Simulate(const FeatureProcessor::SimulatePacket& packet) override; void Render(const FeatureProcessor::RenderPacket& packet) override; void OnRenderEnd() override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h index 5b92047226..4aa3faa6c9 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/MorphTargetMetaAsset.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace AZ::RPI { @@ -56,6 +57,9 @@ namespace AZ::RPI float m_minPositionDelta; float m_maxPositionDelta; + //! Reference to the wrinkle mask, if it exists + AZ::Data::Asset m_wrinkleMask; + //! Boolean to indicate the presence or absence of color deltas bool m_hasColorDeltas = false; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp index 7aace50760..3d0cbca8e6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include + namespace AZ::RPI { using namespace AZ::SceneAPI; @@ -114,7 +117,7 @@ namespace AZ::RPI meshNodeName, sourceMesh.m_name.GetCStr()); const DataTypes::MatrixType globalTransform = Utilities::BuildWorldTransform(sceneGraph, sceneNodeIndex); - BuildMorphTargetMesh(vertexOffset, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform, coordSysConverter); + BuildMorphTargetMesh(vertexOffset, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform, coordSysConverter, scene.GetSourceFilename()); } } } @@ -157,7 +160,8 @@ namespace AZ::RPI const AZStd::string& blendShapeName, const AZStd::shared_ptr& blendShapeData, const DataTypes::MatrixType& globalTransform, - const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter) + const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter, + const AZStd::string& sourceSceneFilename) { const float tolerance = CalcPositionDeltaTolerance(sourceMesh); AZ::Aabb deltaPositionAabb = AZ::Aabb::CreateNull(); @@ -288,6 +292,8 @@ namespace AZ::RPI metaData.m_maxPositionDelta = maxValue; } + metaData.m_wrinkleMask = GetWrinkleMask(sourceSceneFilename, blendShapeName); + metaAssetCreator.AddMorphTarget(metaData); AZ_Assert(uncompressedPositionDeltas.size() == compressedDeltas.size(), "Number of uncompressed (%d) and compressed position delta components (%d) do not match.", @@ -312,4 +318,47 @@ namespace AZ::RPI AZ_Assert((packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex) == numMorphedVertices, "Vertex index range (%d) in morph target meta data does not match number of morphed vertices (%d).", packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex, numMorphedVertices); } + + Data::Asset MorphTargetExporter::GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const + { + AZ::Data::Asset imageAsset; + + // See if there is a wrinkle map mask for this mesh + AZStd::string sceneRelativeFilePath; + bool relativePathFound = true; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(relativePathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, sourceSceneFullFilePath, sceneRelativeFilePath); + + if (relativePathFound) + { + AZ::StringFunc::Path::StripFullName(sceneRelativeFilePath); + + // Get the folder the masks are supposed to be in + AZStd::string folderName; + AZ::StringFunc::Path::GetFileName(sourceSceneFullFilePath.c_str(), folderName); + folderName += "_wrinklemasks"; + + // Note: for now, we're assuming the mask is always authored as a .tif + AZStd::string blendMaskFileName = blendShapeName + "_wrinklemask.tif.streamingimage"; + + AZStd::string maskFolderAndFile; + AZ::StringFunc::Path::Join(folderName.c_str(), blendMaskFileName.c_str(), maskFolderAndFile); + + AZStd::string maskRelativePath; + AZ::StringFunc::Path::Join(sceneRelativeFilePath.c_str(), maskFolderAndFile.c_str(), maskRelativePath); + AZ::StringFunc::Path::Normalize(maskRelativePath); + + // Now see if the file exists + AZ::Data::AssetId maskAssetId; + Data::AssetCatalogRequestBus::BroadcastResult(maskAssetId, &Data::AssetCatalogRequests::GetAssetIdByPath, maskRelativePath.c_str(), AZ::Data::s_invalidAssetType, false); + + if (maskAssetId.IsValid()) + { + // Flush asset manager events to ensure no asset references are held by closures queued on Ebuses. + AZ::Data::AssetManager::Instance().DispatchEvents(); + + imageAsset.Create(maskAssetId, AZ::Data::AssetLoadBehavior::PreLoad, false); + } + } + return imageAsset; + } } // namespace AZ::RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h index d968a803d6..4845d7d1da 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MorphTargetExporter.h @@ -64,7 +64,11 @@ namespace AZ const AZStd::string& blendShapeName, const AZStd::shared_ptr& blendShapeData, const AZ::SceneAPI::DataTypes::MatrixType& globalTransform, - const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter); + const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter, + const AZStd::string& sourceSceneFilename); + + // Find a wrinkle mask for this morph target, if it exists + Data::Asset GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const; }; } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 2ab6ee92e9..109af70166 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -422,7 +422,7 @@ namespace AZ const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); AZ::Name enumName = AZ::Name(property.m_value.GetValue()); - uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName); + uint32_t enumValue = propertyDescriptor ? propertyDescriptor->GetEnumValue(enumName) : MaterialPropertyDescriptor::InvalidEnumValue; if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue) { materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr()); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp index 313e0bea31..3c0f832807 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp @@ -28,6 +28,7 @@ namespace AZ::RPI ->Field("numVertices", &MorphTargetMetaAsset::MorphTarget::m_numVertices) ->Field("minPositionDelta", &MorphTargetMetaAsset::MorphTarget::m_minPositionDelta) ->Field("maxPositionDelta", &MorphTargetMetaAsset::MorphTarget::m_maxPositionDelta) + ->Field("wrinkleMask", &MorphTargetMetaAsset::MorphTarget::m_wrinkleMask) ->Field("hasColorDeltas", &MorphTargetMetaAsset::MorphTarget::m_hasColorDeltas) ; } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index d0116452b7..9079f639ba 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -39,6 +40,8 @@ namespace AZ { namespace Render { + static constexpr uint32_t s_maxActiveWrinkleMasks = 16; + AZ_CLASS_ALLOCATOR_IMPL(AtomActorInstance, EMotionFX::Integration::EMotionFXAllocator, 0) AtomActorInstance::AtomActorInstance(AZ::EntityId entityId, @@ -413,6 +416,10 @@ namespace AZ EMotionFX::MorphSetup* morphSetup = m_actorInstance->GetActor()->GetMorphSetup(lodIndex); if (morphSetup) { + // Track all the masks/weights that are currently active + m_wrinkleMasks.clear(); + m_wrinkleMaskWeights.clear(); + uint32_t morphTargetCount = morphSetup->GetNumMorphTargets(); m_morphTargetWeights.clear(); for (uint32_t morphTargetIndex = 0; morphTargetIndex < morphTargetCount; ++morphTargetIndex) @@ -437,11 +444,28 @@ namespace AZ const EMotionFX::MorphTargetStandard::DeformData* deformData = morphTargetStandard->GetDeformData(deformDataIndex); if (deformData->mNumVerts > 0) { - m_morphTargetWeights.push_back(morphTargetSetupInstance->GetWeight()); + float weight = morphTargetSetupInstance->GetWeight(); + m_morphTargetWeights.push_back(weight); + + // If the morph target is active and it has a wrinkle mask + auto wrinkleMaskIter = m_morphTargetWrinkleMaskMapsByLod[lodIndex].find(morphTargetStandard); + if (weight > 0 && wrinkleMaskIter != m_morphTargetWrinkleMaskMapsByLod[lodIndex].end()) + { + // Add the wrinkle mask and weight, to be set on the material + m_wrinkleMasks.push_back(wrinkleMaskIter->second); + m_wrinkleMaskWeights.push_back(weight); + } } } } m_skinnedMeshRenderProxy->SetMorphTargetWeights(lodIndex, m_morphTargetWeights); + + // Until EMotionFX and Atom lods are synchronized [ATOM-13564] we don't know which EMotionFX lod to pull the weights from + // Until that is fixed, just use lod 0 [ATOM-15251] + if (lodIndex == 0) + { + UpdateWrinkleMasks(); + } } } } @@ -453,6 +477,8 @@ namespace AZ MaterialComponentRequestBus::EventResult(materials, m_entityId, &MaterialComponentRequests::GetMaterialOverrides); CreateRenderProxy(materials); + InitWrinkleMasks(); + TransformNotificationBus::Handler::BusConnect(m_entityId); MaterialComponentNotificationBus::Handler::BusConnect(m_entityId); MeshComponentRequestBus::Handler::BusConnect(m_entityId); @@ -573,5 +599,77 @@ namespace AZ { CreateSkinnedMeshInstance(); } + + void AtomActorInstance::InitWrinkleMasks() + { + EMotionFX::Actor* actor = m_actorAsset->GetActor(); + m_morphTargetWrinkleMaskMapsByLod.resize(m_skinnedMeshInputBuffers->GetLodCount()); + m_wrinkleMasks.reserve(s_maxActiveWrinkleMasks); + m_wrinkleMaskWeights.reserve(s_maxActiveWrinkleMasks); + + for (size_t lodIndex = 0; lodIndex < m_skinnedMeshInputBuffers->GetLodCount(); ++lodIndex) + { + EMotionFX::MorphSetup* morphSetup = actor->GetMorphSetup(lodIndex); + if (morphSetup) + { + const AZStd::vector& metaDatas = actor->GetMorphTargetMetaAsset()->GetMorphTargets(); + // Loop over all the EMotionFX morph targets + uint32_t numMorphTargets = morphSetup->GetNumMorphTargets(); + for (uint32_t morphTargetIndex = 0; morphTargetIndex < numMorphTargets; ++morphTargetIndex) + { + EMotionFX::MorphTargetStandard* morphTarget = static_cast(morphSetup->GetMorphTarget(morphTargetIndex)); + for (const RPI::MorphTargetMetaAsset::MorphTarget& metaData : metaDatas) + { + // Find the metaData associated with this morph target + if (metaData.m_morphTargetName == morphTarget->GetNameString() && metaData.m_wrinkleMask && metaData.m_numVertices > 0) + { + // If the metaData has a wrinkle mask, add it to the map + Data::Instance streamingImage = RPI::StreamingImage::FindOrCreate(metaData.m_wrinkleMask); + if (streamingImage) + { + m_morphTargetWrinkleMaskMapsByLod[lodIndex][morphTarget] = streamingImage; + } + } + } + } + } + } + } + + void AtomActorInstance::UpdateWrinkleMasks() + { + if (m_meshHandle) + { + Data::Instance wrinkleMaskObjectSrg = m_meshFeatureProcessor->GetObjectSrg(*m_meshHandle); + if (wrinkleMaskObjectSrg) + { + RHI::ShaderInputImageIndex wrinkleMasksIndex = wrinkleMaskObjectSrg->FindShaderInputImageIndex(Name{ "m_wrinkle_masks" }); + RHI::ShaderInputConstantIndex wrinkleMaskWeightsIndex = wrinkleMaskObjectSrg->FindShaderInputConstantIndex(Name{ "m_wrinkle_mask_weights" }); + RHI::ShaderInputConstantIndex wrinkleMaskCountIndex = wrinkleMaskObjectSrg->FindShaderInputConstantIndex(Name{ "m_wrinkle_mask_count" }); + if (wrinkleMasksIndex.IsValid() || wrinkleMaskWeightsIndex.IsValid() || wrinkleMaskCountIndex.IsValid()) + { + AZ_Error("AtomActorInstance", wrinkleMasksIndex.IsValid(), "m_wrinkle_masks not found on the ObjectSrg, but m_wrinkle_mask_weights and/or m_wrinkle_mask_count are being used."); + AZ_Error("AtomActorInstance", wrinkleMaskWeightsIndex.IsValid(), "m_wrinkle_mask_weights not found on the ObjectSrg, but m_wrinkle_masks and/or m_wrinkle_mask_count are being used."); + AZ_Error("AtomActorInstance", wrinkleMaskCountIndex.IsValid(), "m_wrinkle_mask_count not found on the ObjectSrg, but m_wrinkle_mask_weights and/or m_wrinkle_masks are being used."); + + if (m_wrinkleMasks.size()) + { + wrinkleMaskObjectSrg->SetImageArray(wrinkleMasksIndex, AZStd::array_view>(m_wrinkleMasks.data(), m_wrinkleMasks.size())); + + // Set the weights for any active masks + for (size_t i = 0; i < m_wrinkleMaskWeights.size(); ++i) + { + wrinkleMaskObjectSrg->SetConstant(wrinkleMaskWeightsIndex, m_wrinkleMaskWeights[i], i); + } + AZ_Error("AtomActorInstance", m_wrinkleMaskWeights.size() <= s_maxActiveWrinkleMasks, "The skinning shader supports no more than %d active morph targets with wrinkle masks.", s_maxActiveWrinkleMasks); + } + + wrinkleMaskObjectSrg->SetConstant(wrinkleMaskCountIndex, aznumeric_cast(m_wrinkleMasks.size())); + m_meshFeatureProcessor->QueueObjectSrgForCompile(*m_meshHandle); + } + } + } + } + } //namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index 1002fcbde1..e05280e896 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -17,6 +17,7 @@ #include #include +#include #include @@ -29,6 +30,8 @@ #include #include #include +#include + #include #include @@ -41,6 +44,7 @@ namespace AZ::RPI { class Model; class Buffer; + class StreamingImage; } namespace AZ @@ -168,6 +172,11 @@ namespace AZ // SkinnedMeshOutputStreamNotificationBus void OnSkinnedMeshOutputStreamMemoryAvailable() override; + // Check to see if the skin material is being used, + // and if there are blend shapes with wrinkle masks that should be applied to it + void InitWrinkleMasks(); + void UpdateWrinkleMasks(); + AZStd::intrusive_ptr m_skinnedMeshInputBuffers = nullptr; AZStd::intrusive_ptr m_skinnedMeshInstance; AZ::Data::Instance m_boneTransforms = nullptr; @@ -179,6 +188,12 @@ namespace AZ AZ::TransformInterface* m_transformInterface = nullptr; AZStd::set m_waitForMaterialLoadIds; AZStd::vector m_morphTargetWeights; + + typedef AZStd::unordered_map> MorphTargetWrinkleMaskMap; + AZStd::vector m_morphTargetWrinkleMaskMapsByLod; + + AZStd::vector> m_wrinkleMasks; + AZStd::vector m_wrinkleMaskWeights; }; } // namespace Render