From 568a69081d4f404c5f4e47d790e7751a8159fc35 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 22 Apr 2024 13:18:52 +0200 Subject: [PATCH] Fix animated model skinning precision issues #2460 --- .../Editor/MaterialTemplates/Surface.shader | 33 +++++++++---------- .../Graphics/Materials/MaterialShader.h | 2 +- Source/Engine/Graphics/Models/ModelData.cpp | 5 +-- Source/Engine/Level/Actors/AnimatedModel.cpp | 2 +- .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 8 ++--- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index 1e8589ff2..29d803545 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -430,7 +430,9 @@ float3x4 GetPrevBoneMatrix(int index) float3 SkinPrevPosition(ModelInput_Skinned input) { float4 position = float4(input.Position.xyz, 1); - float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); + float weightsSum = input.BlendWeights.x + input.BlendWeights.y + input.BlendWeights.z + input.BlendWeights.w; + float mainWeight = input.BlendWeights.x + (1.0f - weightsSum); // Re-normalize to account for 16-bit weights encoding erros + float3x4 boneMatrix = mainWeight * GetPrevBoneMatrix(input.BlendIndices.x); boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); @@ -439,12 +441,6 @@ float3 SkinPrevPosition(ModelInput_Skinned input) #endif -// Cached skinning data to avoid multiple calculation -struct SkinningData -{ - float3x4 BlendMatrix; -}; - // Calculates the transposed transform matrix for the given bone index float3x4 GetBoneMatrix(int index) { @@ -457,7 +453,9 @@ float3x4 GetBoneMatrix(int index) // Calculates the transposed transform matrix for the given vertex (uses blending) float3x4 GetBoneMatrix(ModelInput_Skinned input) { - float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); + float weightsSum = input.BlendWeights.x + input.BlendWeights.y + input.BlendWeights.z + input.BlendWeights.w; + float mainWeight = input.BlendWeights.x + (1.0f - weightsSum); // Re-normalize to account for 16-bit weights encoding erros + float3x4 boneMatrix = mainWeight * GetBoneMatrix(input.BlendIndices.x); boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); @@ -465,13 +463,13 @@ float3x4 GetBoneMatrix(ModelInput_Skinned input) } // Transforms the vertex position by weighted sum of the skinning matrices -float3 SkinPosition(ModelInput_Skinned input, SkinningData data) +float3 SkinPosition(ModelInput_Skinned input, float3x4 boneMatrix) { - return mul(data.BlendMatrix, float4(input.Position.xyz, 1)); + return mul(boneMatrix, float4(input.Position.xyz, 1)); } // Transforms the vertex position by weighted sum of the skinning matrices -float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) +float3x3 SkinTangents(ModelInput_Skinned input, float3x4 boneMatrix) { // Unpack vertex tangent frame float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; @@ -479,10 +477,10 @@ float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) float3 tangent = input.Tangent.xyz * 2.0 - 1.0; // Apply skinning - tangent = mul(data.BlendMatrix, float4(tangent, 0)); - normal = mul(data.BlendMatrix, float4(normal, 0)); + tangent = normalize(mul(boneMatrix, float4(tangent, 0))); + normal = normalize(mul(boneMatrix, float4(normal, 0))); - float3 bitangent = cross(normal, tangent) * bitangentSign; + float3 bitangent = normalize(cross(normal, tangent) * bitangentSign); return float3x3(tangent, bitangent, normal); } @@ -501,10 +499,9 @@ VertexOutput VS_Skinned(ModelInput_Skinned input) VertexOutput output; // Perform skinning - SkinningData data; - data.BlendMatrix = GetBoneMatrix(input); - float3 position = SkinPosition(input, data); - float3x3 tangentToLocal = SkinTangents(input, data); + float3x4 boneMatrix = GetBoneMatrix(input); + float3 position = SkinPosition(input, boneMatrix); + float3x3 tangentToLocal = SkinTangents(input, boneMatrix); // Compute world space vertex position CalculateInstanceTransform(input); diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index d79ef49b0..7a5b842e2 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 161 +#define MATERIAL_GRAPH_VERSION 162 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 02cb9e6eb..b42288f7f 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -563,9 +563,10 @@ void MeshData::NormalizeBlendWeights() ASSERT(Positions.Count() == BlendWeights.Count()); for (int32 i = 0; i < Positions.Count(); i++) { - const float sum = BlendWeights[i].SumValues(); + Float4& weights = BlendWeights.Get()[i]; + const float sum = weights.SumValues(); const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f; - BlendWeights[i] *= invSum; + weights *= invSum; } } diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 18bd7fc87..17b9acc11 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -113,7 +113,7 @@ void AnimatedModel::PreInitSkinningData() for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) { auto& bone = skeleton.Bones[boneIndex]; - identityMatrices[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex]; + identityMatrices.Get()[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex]; } _skinningData.SetData(identityMatrices.Get(), true); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index a9f06d239..cf124f977 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -720,23 +720,23 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* { int vtxIndex = clusterIndices[j] - firstVertexOffset; float vtxWeight = (float)clusterWeights[j]; - if (vtxWeight <= 0 || vtxIndex < 0 || vtxIndex >= vertexCount) continue; - - auto& indices = mesh.BlendIndices[vtxIndex]; - auto& weights = mesh.BlendWeights[vtxIndex]; + Int4& indices = mesh.BlendIndices.Get()[vtxIndex]; + Float4& weights = mesh.BlendWeights.Get()[vtxIndex]; for (int32 k = 0; k < 4; k++) { if (vtxWeight >= weights.Raw[k]) { + // Move lower weights by one down for (int32 l = 2; l >= k; l--) { indices.Raw[l + 1] = indices.Raw[l]; weights.Raw[l + 1] = weights.Raw[l]; } + // Set bone influence indices.Raw[k] = boneIndex; weights.Raw[k] = vtxWeight; break;