Merging WrinkleMask support from 1.0 to main (#680)
Added a loop to the skin shader that will sample from wrinkle masks, multiply them by a weight, combine them, and use them instead of vertex colors for wrinkle map blending Added an array of masks, an array of weights, and a wrinkle mask count to the DefaultObjectSrg. -Will create a follow up task to handle this a better way. Removed motion vector (for now) from skin.materialtype since we're not using them, and removed depthtransparent since skin doesn't support transparency Added an interface to the MeshFeatureProcessor to get the object srg Wrapped srg->Compile in if(srg->IsQueuedForCompile()) to prevent compiling twice --This doesn't stop a race condition if both happen at the same time, but that is at least far less likely. It will need a better solution later. Added a function to the MorphTargetExporter that will check to see if a texture that matches the blend shape name exists in a particular folder, and adds a reference to that image to the MorphTargetMetaAsset --Only supports .tif, and doesn't automatically re-process the .fbx if the folder is updated. These can be improved in later iterations Added a null check in MaterialTypeSourceData.cpp to fix a crash I ran into Added a for loop in two places to look for the first submesh that has a morph target, instead of just using the first to check if a lod has morph targets or not. --I have a better fix for this, but it involves more areas of the code, so I'm saving that for another change. Modified AtomActorInstance to look for any morph targets that have a wrinkle mask reference Then each frame, for any morph targets with non-zero weights that also have wrinkle masks, it updates the mask array, weights, and count on the object srg.
This commit is contained in:
@@ -101,7 +101,7 @@ struct VSOutput
|
||||
float2 m_uv[UvSetCount] : UV1;
|
||||
float2 m_detailUv : UV3;
|
||||
|
||||
float4 m_blendMask : UV8;
|
||||
float4 m_wrinkleBlendFactors : UV8;
|
||||
};
|
||||
|
||||
#include <Atom/Features/Vertex/VertexHelper.azsli>
|
||||
@@ -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 -------
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -148,6 +148,8 @@ namespace AZ
|
||||
|
||||
Data::Instance<RPI::Model> GetModel(const MeshHandle& meshHandle) const override;
|
||||
Data::Asset<RPI::ModelAsset> GetModelAsset(const MeshHandle& meshHandle) const override;
|
||||
Data::Instance<RPI::ShaderResourceGroup> GetObjectSrg(const MeshHandle& meshHandle) const override;
|
||||
void QueueObjectSrgForCompile(const MeshHandle& meshHandle) const override;
|
||||
void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance<RPI::Material>& material) override;
|
||||
void SetMaterialAssignmentMap(const MeshHandle& meshHandle, const MaterialAssignmentMap& materials) override;
|
||||
const MaterialAssignmentMap& GetMaterialAssignmentMap(const MeshHandle& meshHandle) const override;
|
||||
|
||||
+8
@@ -61,6 +61,14 @@ namespace AZ
|
||||
virtual Data::Instance<RPI::Model> GetModel(const MeshHandle& meshHandle) const = 0;
|
||||
//! Gets the underlying RPI::ModelAsset for a meshHandle.
|
||||
virtual Data::Asset<RPI::ModelAsset> 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<RPI::ShaderResourceGroup> 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<RPI::Material>& material) = 0;
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace UnitTest
|
||||
MOCK_METHOD1(CloneMesh, MeshHandle(const MeshHandle&));
|
||||
MOCK_CONST_METHOD1(GetModel, AZStd::intrusive_ptr<AZ::RPI::Model>(const MeshHandle&));
|
||||
MOCK_CONST_METHOD1(GetModelAsset, AZ::Data::Asset<AZ::RPI::ModelAsset>(const MeshHandle&));
|
||||
MOCK_CONST_METHOD1(GetObjectSrg, AZStd::intrusive_ptr<AZ::RPI::ShaderResourceGroup>(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&));
|
||||
|
||||
@@ -231,6 +231,19 @@ namespace AZ
|
||||
return {};
|
||||
}
|
||||
|
||||
Data::Instance<RPI::ShaderResourceGroup> 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<RPI::Material>& material)
|
||||
{
|
||||
Render::MaterialAssignmentMap materials;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
|
||||
|
||||
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<AZ::RPI::StreamingImageAsset> m_wrinkleMask;
|
||||
|
||||
//! Boolean to indicate the presence or absence of color deltas
|
||||
bool m_hasColorDeltas = false;
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
|
||||
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
|
||||
|
||||
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
|
||||
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<const DataTypes::IBlendShapeData>& 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<RPI::StreamingImageAsset> MorphTargetExporter::GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const
|
||||
{
|
||||
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> 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
|
||||
|
||||
@@ -64,7 +64,11 @@ namespace AZ
|
||||
const AZStd::string& blendShapeName,
|
||||
const AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IBlendShapeData>& 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<RPI::StreamingImageAsset> GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const;
|
||||
};
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -422,7 +422,7 @@ namespace AZ
|
||||
const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex);
|
||||
|
||||
AZ::Name enumName = AZ::Name(property.m_value.GetValue<AZStd::string>());
|
||||
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());
|
||||
|
||||
@@ -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)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <MCore/Source/AzCoreConversions.h>
|
||||
|
||||
#include <Atom/RPI.Public/Scene.h>
|
||||
#include <Atom/RPI.Public/Image/StreamingImage.h>
|
||||
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/Component/EntityId.h>
|
||||
@@ -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<AZ::RPI::MorphTargetMetaAsset::MorphTarget>& 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<EMotionFX::MorphTargetStandard*>(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<RPI::StreamingImage> streamingImage = RPI::StreamingImage::FindOrCreate(metaData.m_wrinkleMask);
|
||||
if (streamingImage)
|
||||
{
|
||||
m_morphTargetWrinkleMaskMapsByLod[lodIndex][morphTarget] = streamingImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AtomActorInstance::UpdateWrinkleMasks()
|
||||
{
|
||||
if (m_meshHandle)
|
||||
{
|
||||
Data::Instance<RPI::ShaderResourceGroup> 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<Data::Instance<RPI::Image>>(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<uint32_t>(m_wrinkleMasks.size()));
|
||||
m_meshFeatureProcessor->QueueObjectSrgForCompile(*m_meshHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace Render
|
||||
} // namespace AZ
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <AzFramework/Visibility/BoundsBus.h>
|
||||
|
||||
#include <Integration/Rendering/RenderActorInstance.h>
|
||||
#include <EMotionFX/Source/MorphTargetStandard.h>
|
||||
|
||||
#include <LmbrCentral/Animation/SkeletalHierarchyRequestBus.h>
|
||||
|
||||
@@ -29,6 +30,8 @@
|
||||
#include <Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h>
|
||||
#include <Atom/Feature/SkinnedMesh/SkinnedMeshShaderOptions.h>
|
||||
#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
|
||||
#include <Atom/RHI.Reflect/ShaderResourceGroupLayoutDescriptor.h>
|
||||
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/std/smart_ptr/intrusive_base.h>
|
||||
|
||||
@@ -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<AZ::Render::SkinnedMeshInputBuffers> m_skinnedMeshInputBuffers = nullptr;
|
||||
AZStd::intrusive_ptr<SkinnedMeshInstance> m_skinnedMeshInstance;
|
||||
AZ::Data::Instance<AZ::RPI::Buffer> m_boneTransforms = nullptr;
|
||||
@@ -179,6 +188,12 @@ namespace AZ
|
||||
AZ::TransformInterface* m_transformInterface = nullptr;
|
||||
AZStd::set<Data::AssetId> m_waitForMaterialLoadIds;
|
||||
AZStd::vector<float> m_morphTargetWeights;
|
||||
|
||||
typedef AZStd::unordered_map<EMotionFX::MorphTargetStandard*, Data::Instance<RPI::Image>> MorphTargetWrinkleMaskMap;
|
||||
AZStd::vector<MorphTargetWrinkleMaskMap> m_morphTargetWrinkleMaskMapsByLod;
|
||||
|
||||
AZStd::vector<Data::Instance<RPI::Image>> m_wrinkleMasks;
|
||||
AZStd::vector<float> m_wrinkleMaskWeights;
|
||||
};
|
||||
|
||||
} // namespace Render
|
||||
|
||||
Reference in New Issue
Block a user