Fix animated model skinning precision issues

#2460
This commit is contained in:
Wojtek Figat
2024-04-22 13:18:52 +02:00
parent 1072b90c5b
commit 568a69081d
5 changed files with 24 additions and 26 deletions

View File

@@ -430,7 +430,9 @@ float3x4 GetPrevBoneMatrix(int index)
float3 SkinPrevPosition(ModelInput_Skinned input) float3 SkinPrevPosition(ModelInput_Skinned input)
{ {
float4 position = float4(input.Position.xyz, 1); 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.y * GetPrevBoneMatrix(input.BlendIndices.y);
boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z);
boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w);
@@ -439,12 +441,6 @@ float3 SkinPrevPosition(ModelInput_Skinned input)
#endif #endif
// Cached skinning data to avoid multiple calculation
struct SkinningData
{
float3x4 BlendMatrix;
};
// Calculates the transposed transform matrix for the given bone index // Calculates the transposed transform matrix for the given bone index
float3x4 GetBoneMatrix(int index) float3x4 GetBoneMatrix(int index)
{ {
@@ -457,7 +453,9 @@ float3x4 GetBoneMatrix(int index)
// Calculates the transposed transform matrix for the given vertex (uses blending) // Calculates the transposed transform matrix for the given vertex (uses blending)
float3x4 GetBoneMatrix(ModelInput_Skinned input) 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.y * GetBoneMatrix(input.BlendIndices.y);
boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z);
boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); 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 // 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 // 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 // Unpack vertex tangent frame
float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; 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; float3 tangent = input.Tangent.xyz * 2.0 - 1.0;
// Apply skinning // Apply skinning
tangent = mul(data.BlendMatrix, float4(tangent, 0)); tangent = normalize(mul(boneMatrix, float4(tangent, 0)));
normal = mul(data.BlendMatrix, float4(normal, 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); return float3x3(tangent, bitangent, normal);
} }
@@ -501,10 +499,9 @@ VertexOutput VS_Skinned(ModelInput_Skinned input)
VertexOutput output; VertexOutput output;
// Perform skinning // Perform skinning
SkinningData data; float3x4 boneMatrix = GetBoneMatrix(input);
data.BlendMatrix = GetBoneMatrix(input); float3 position = SkinPosition(input, boneMatrix);
float3 position = SkinPosition(input, data); float3x3 tangentToLocal = SkinTangents(input, boneMatrix);
float3x3 tangentToLocal = SkinTangents(input, data);
// Compute world space vertex position // Compute world space vertex position
CalculateInstanceTransform(input); CalculateInstanceTransform(input);

View File

@@ -10,7 +10,7 @@
/// <summary> /// <summary>
/// Current materials shader version. /// Current materials shader version.
/// </summary> /// </summary>
#define MATERIAL_GRAPH_VERSION 161 #define MATERIAL_GRAPH_VERSION 162
class Material; class Material;
class GPUShader; class GPUShader;

View File

@@ -563,9 +563,10 @@ void MeshData::NormalizeBlendWeights()
ASSERT(Positions.Count() == BlendWeights.Count()); ASSERT(Positions.Count() == BlendWeights.Count());
for (int32 i = 0; i < Positions.Count(); i++) 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; const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f;
BlendWeights[i] *= invSum; weights *= invSum;
} }
} }

View File

@@ -113,7 +113,7 @@ void AnimatedModel::PreInitSkinningData()
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
{ {
auto& bone = skeleton.Bones[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); _skinningData.SetData(identityMatrices.Get(), true);

View File

@@ -720,23 +720,23 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
{ {
int vtxIndex = clusterIndices[j] - firstVertexOffset; int vtxIndex = clusterIndices[j] - firstVertexOffset;
float vtxWeight = (float)clusterWeights[j]; float vtxWeight = (float)clusterWeights[j];
if (vtxWeight <= 0 || vtxIndex < 0 || vtxIndex >= vertexCount) if (vtxWeight <= 0 || vtxIndex < 0 || vtxIndex >= vertexCount)
continue; continue;
Int4& indices = mesh.BlendIndices.Get()[vtxIndex];
auto& indices = mesh.BlendIndices[vtxIndex]; Float4& weights = mesh.BlendWeights.Get()[vtxIndex];
auto& weights = mesh.BlendWeights[vtxIndex];
for (int32 k = 0; k < 4; k++) for (int32 k = 0; k < 4; k++)
{ {
if (vtxWeight >= weights.Raw[k]) if (vtxWeight >= weights.Raw[k])
{ {
// Move lower weights by one down
for (int32 l = 2; l >= k; l--) for (int32 l = 2; l >= k; l--)
{ {
indices.Raw[l + 1] = indices.Raw[l]; indices.Raw[l + 1] = indices.Raw[l];
weights.Raw[l + 1] = weights.Raw[l]; weights.Raw[l + 1] = weights.Raw[l];
} }
// Set bone influence
indices.Raw[k] = boneIndex; indices.Raw[k] = boneIndex;
weights.Raw[k] = vtxWeight; weights.Raw[k] = vtxWeight;
break; break;