Assimp:手动操纵装配网格的骨骼
Assimp: Manipulate bones of rigged mesh manually
我正在从事一个具有以下目标的项目:
- 使用 Assimp.NET
加载绑定的 3D 网格(例如人体骨骼)
- 操纵网格的骨骼使其适合您自己的body(使用 Microsoft Kinect v2)
- 执行顶点蒙皮
加载绑定网格和提取骨骼信息(希望如此)没有任何问题(基于本教程:http://www.richardssoftware.net/2013/10/skinned-models-in-directx-11-with.html)。每个骨骼(class "ModelBone")包含以下信息:
Assimp.Matrix4x4 LocalTransform
Assimp.Matrix4x4 GlobalTransform
Assimp.Matrix4x4 Offset
LocalTransform
直接从assimp节点中提取出来(node.Transform
)。
GlobalTransform
包括自己的 LocalTransform
和所有 parent 的 LocalTransform
(参见代码片段 calculateGlobalTransformation()
)。
Offset
是直接提取assimp骨(bone.OffsetMatrix
).
目前我没有实现 GPU 顶点蒙皮,但我遍历每个顶点并操纵它的位置和法向量。
foreach (Vertex vertex in this.Vertices)
{
Vector3D newPosition = new Vector3D();
Vector3D newNormal = new Vector3D();
for (int i=0; i < vertex.boneIndices.Length; i++)
{
int boneIndex = vertex.boneIndices[i];
float boneWeight = vertex.boneWeights[i];
ModelBone bone = this.BoneHierarchy.Bones[boneIndex];
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
// Calculate new vertex position and normal
newPosition += boneWeight * (finalTransform * vertex.originalPosition);
newNormal += boneWeight * (finalTransform * vertex.originalNormal);
}
// Apply new vertex position and normal
vertex.position = newPosition;
vertex.normal = newNormal;
}
正如我已经说过的,我想用 Kinect v2 传感器操纵骨骼,这样我就不必使用动画(例如插值关键帧,...)!但一开始我希望能够手动操作骨骼(例如,将网格的躯干旋转 90 度)。因此,我通过调用 Assimp.Matrix4x4.FromRotationX(1.5708f);
创建了一个 4x4 旋转矩阵(x-axis 周围 90 度)。然后我用这个旋转矩阵替换骨骼的 LocalTransform
:
Assimp.Matrix4x4 rotation = Assimp.Matrix4x4.FromRotationX(1.5708f);
bone.LocalTransform = rotation;
UpdateTransformations(bone);
在骨骼操作之后,我使用以下代码计算骨骼的新 GlobalTransform
并且它是 child 骨骼:
public void UpdateTransformations(ModelBone bone)
{
this.calculateGlobalTransformation(bone);
foreach (var child in bone.Children)
{
UpdateTransformations(child);
}
}
private void calculateGlobalTransformation(ModelBone bone)
{
// Global transformation includes own local transformation ...
bone.GlobalTransform = bone.LocalTransform;
ModelBone parent = bone.Parent;
while (parent != null)
{
// ... and all local transformations of the parent bones (recursively)
bone.GlobalTransform = parent.LocalTransform * bone.GlobalTransform;
parent = parent.Parent;
}
}
这种方法导致这种 image。转换似乎正确地应用于所有 child 骨骼,但是被操纵的骨骼围绕世界 space 原点旋转,而不是围绕它自己的本地 space :( 我已经尝试包括 GlobalTransform
将(GlobalTransform
的最后一行)翻译成旋转矩阵,然后将其设置为 LocalTransform
,但没有成功...
希望有人body能帮我解决这个问题!
提前致谢!
要变换骨骼,您应该使用它的偏移矩阵:
http://assimp.sourceforge.net/lib_html/structai_bone.html#a9ae5293b5c937436e4b338e20221cc2e
偏移矩阵从全局 space 转换为骨骼 space。如果您想围绕原点旋转骨骼,您应该:
- 转换为骨骼 space
- 应用旋转
- 转换为全局space
所以骨骼的全局变换可以这样计算:
bonesGlobalTransform = parentGlobalTransform *
bone.offset.inverse() *
boneLocalTransform *
bone.offset;
所以:
- 使用偏移矩阵
转换为骨骼space
- 应用局部变换
- 使用 offset.inverse() 矩阵
转换为全局 space
最后我找到了解决方案:)所有计算都是正确的,除了:
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
我的正确计算是:
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
finalTransform.transpose();
看来是行优先/列优先的问题。我最终的 CPU 顶点蒙皮代码是:
public void PerformSmoothVertexSkinning()
{
// Precompute final transformation matrix for each bone
List<Matrix4x4> FinalTransforms = new List<Matrix4x4>();
foreach (ModelBone bone in this.BoneHierarchy.Bones)
{
// Multiplying a vector (e.g. vertex position/normal) by finalTransform will (from right to left):
// 1. transform the vector from mesh space to bone space (by bone.Offset)
// 2. transform the vector from bone space to world space (by bone.GlobalTransform)
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
finalTransform.Transpose();
FinalTransforms.Add(finalTransform);
}
foreach (Submesh submesh in this.Submeshes)
{
foreach (Vertex vertex in submesh.Vertices)
{
Vector3D newPosition = new Vector3D();
Vector3D newNormal = new Vector3D();
for (int i = 0; i < vertex.BoneIndices.Length; i++)
{
int boneIndex = vertex.BoneIndices[i];
float boneWeight = vertex.BoneWeights[i];
// Get final transformation matrix to transform each vertex position
Matrix4x4 finalVertexTransform = FinalTransforms[boneIndex];
// Get final transformation matrix to transform each vertex normal (has to be inverted and transposed!)
Matrix4x4 finalNormalTransform = FinalTransforms[boneIndex];
finalNormalTransform.Inverse();
finalNormalTransform.Transpose();
// Calculate new vertex position and normal (average of influencing bones)
// Formula: newPosition += boneWeight * (finalVertexTransform * vertex.OriginalPosition);
// += boneWeight * (bone.GlobalTransform * bone.Offset * vertex.OriginalPosition);
// From right to left:
// 1. Transform vertex position from mesh space to bone space (by bone.Offset)
// 2. Transform vertex position from bone space to world space (by bone.GlobalTransform)
// 3. Apply bone weight
newPosition += boneWeight * (finalVertexTransform * vertex.OriginalPosition);
newNormal += boneWeight * (finalNormalTransform * vertex.OriginalNormal);
}
// Apply new vertex position and normal
vertex.Position = newPosition;
vertex.Normal = newNormal;
}
}
}
希望这篇文章对其他人有所帮助。感谢谢尔盖的帮助!
我正在从事一个具有以下目标的项目:
- 使用 Assimp.NET 加载绑定的 3D 网格(例如人体骨骼)
- 操纵网格的骨骼使其适合您自己的body(使用 Microsoft Kinect v2)
- 执行顶点蒙皮
加载绑定网格和提取骨骼信息(希望如此)没有任何问题(基于本教程:http://www.richardssoftware.net/2013/10/skinned-models-in-directx-11-with.html)。每个骨骼(class "ModelBone")包含以下信息:
Assimp.Matrix4x4 LocalTransform
Assimp.Matrix4x4 GlobalTransform
Assimp.Matrix4x4 Offset
LocalTransform
直接从assimp节点中提取出来(node.Transform
)。
GlobalTransform
包括自己的 LocalTransform
和所有 parent 的 LocalTransform
(参见代码片段 calculateGlobalTransformation()
)。
Offset
是直接提取assimp骨(bone.OffsetMatrix
).
目前我没有实现 GPU 顶点蒙皮,但我遍历每个顶点并操纵它的位置和法向量。
foreach (Vertex vertex in this.Vertices)
{
Vector3D newPosition = new Vector3D();
Vector3D newNormal = new Vector3D();
for (int i=0; i < vertex.boneIndices.Length; i++)
{
int boneIndex = vertex.boneIndices[i];
float boneWeight = vertex.boneWeights[i];
ModelBone bone = this.BoneHierarchy.Bones[boneIndex];
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
// Calculate new vertex position and normal
newPosition += boneWeight * (finalTransform * vertex.originalPosition);
newNormal += boneWeight * (finalTransform * vertex.originalNormal);
}
// Apply new vertex position and normal
vertex.position = newPosition;
vertex.normal = newNormal;
}
正如我已经说过的,我想用 Kinect v2 传感器操纵骨骼,这样我就不必使用动画(例如插值关键帧,...)!但一开始我希望能够手动操作骨骼(例如,将网格的躯干旋转 90 度)。因此,我通过调用 Assimp.Matrix4x4.FromRotationX(1.5708f);
创建了一个 4x4 旋转矩阵(x-axis 周围 90 度)。然后我用这个旋转矩阵替换骨骼的 LocalTransform
:
Assimp.Matrix4x4 rotation = Assimp.Matrix4x4.FromRotationX(1.5708f);
bone.LocalTransform = rotation;
UpdateTransformations(bone);
在骨骼操作之后,我使用以下代码计算骨骼的新 GlobalTransform
并且它是 child 骨骼:
public void UpdateTransformations(ModelBone bone)
{
this.calculateGlobalTransformation(bone);
foreach (var child in bone.Children)
{
UpdateTransformations(child);
}
}
private void calculateGlobalTransformation(ModelBone bone)
{
// Global transformation includes own local transformation ...
bone.GlobalTransform = bone.LocalTransform;
ModelBone parent = bone.Parent;
while (parent != null)
{
// ... and all local transformations of the parent bones (recursively)
bone.GlobalTransform = parent.LocalTransform * bone.GlobalTransform;
parent = parent.Parent;
}
}
这种方法导致这种 image。转换似乎正确地应用于所有 child 骨骼,但是被操纵的骨骼围绕世界 space 原点旋转,而不是围绕它自己的本地 space :( 我已经尝试包括 GlobalTransform
将(GlobalTransform
的最后一行)翻译成旋转矩阵,然后将其设置为 LocalTransform
,但没有成功...
希望有人body能帮我解决这个问题!
提前致谢!
要变换骨骼,您应该使用它的偏移矩阵: http://assimp.sourceforge.net/lib_html/structai_bone.html#a9ae5293b5c937436e4b338e20221cc2e 偏移矩阵从全局 space 转换为骨骼 space。如果您想围绕原点旋转骨骼,您应该:
- 转换为骨骼 space
- 应用旋转
- 转换为全局space
所以骨骼的全局变换可以这样计算:
bonesGlobalTransform = parentGlobalTransform *
bone.offset.inverse() *
boneLocalTransform *
bone.offset;
所以:
- 使用偏移矩阵 转换为骨骼space
- 应用局部变换
- 使用 offset.inverse() 矩阵 转换为全局 space
最后我找到了解决方案:)所有计算都是正确的,除了:
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
我的正确计算是:
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
finalTransform.transpose();
看来是行优先/列优先的问题。我最终的 CPU 顶点蒙皮代码是:
public void PerformSmoothVertexSkinning()
{
// Precompute final transformation matrix for each bone
List<Matrix4x4> FinalTransforms = new List<Matrix4x4>();
foreach (ModelBone bone in this.BoneHierarchy.Bones)
{
// Multiplying a vector (e.g. vertex position/normal) by finalTransform will (from right to left):
// 1. transform the vector from mesh space to bone space (by bone.Offset)
// 2. transform the vector from bone space to world space (by bone.GlobalTransform)
Matrix4x4 finalTransform = bone.GlobalTransform * bone.Offset;
finalTransform.Transpose();
FinalTransforms.Add(finalTransform);
}
foreach (Submesh submesh in this.Submeshes)
{
foreach (Vertex vertex in submesh.Vertices)
{
Vector3D newPosition = new Vector3D();
Vector3D newNormal = new Vector3D();
for (int i = 0; i < vertex.BoneIndices.Length; i++)
{
int boneIndex = vertex.BoneIndices[i];
float boneWeight = vertex.BoneWeights[i];
// Get final transformation matrix to transform each vertex position
Matrix4x4 finalVertexTransform = FinalTransforms[boneIndex];
// Get final transformation matrix to transform each vertex normal (has to be inverted and transposed!)
Matrix4x4 finalNormalTransform = FinalTransforms[boneIndex];
finalNormalTransform.Inverse();
finalNormalTransform.Transpose();
// Calculate new vertex position and normal (average of influencing bones)
// Formula: newPosition += boneWeight * (finalVertexTransform * vertex.OriginalPosition);
// += boneWeight * (bone.GlobalTransform * bone.Offset * vertex.OriginalPosition);
// From right to left:
// 1. Transform vertex position from mesh space to bone space (by bone.Offset)
// 2. Transform vertex position from bone space to world space (by bone.GlobalTransform)
// 3. Apply bone weight
newPosition += boneWeight * (finalVertexTransform * vertex.OriginalPosition);
newNormal += boneWeight * (finalNormalTransform * vertex.OriginalNormal);
}
// Apply new vertex position and normal
vertex.Position = newPosition;
vertex.Normal = newNormal;
}
}
}
希望这篇文章对其他人有所帮助。感谢谢尔盖的帮助!