**** EDIT2:**
请避免指出效率/优化问题;这根本不是最终的代码。这只是我尝试基础知识:)。一旦我把一切都搞定了,我就会去清理和优化。
编辑:我决定用更简单的术语重新表述这个问题,看看是否有人可以帮我解决这个问题。
基本上,我将网格,骨架和动作从混合器导出到我正在处理的各种引擎中。但我让动画错了。我可以看出正在遵循的基本运动路径,但总是一个平移或旋转的轴是错误的。我认为问题很可能不在我的引擎代码(基于OpenGL)中,而是在我对骨架动画/皮肤化背后的理论的某些部分的误解,或者我在导出器脚本中从blender导出适当的关节矩阵的方式
我将解释理论,引擎动画系统和我的blender导出脚本,希望有人可能会在其中一个或全部中发现错误。
理论:(我使用了列主要排序,因为我在引擎中使用它导致它基于OpenGL)
鉴于上述情况,为了在关键帧n处皮肤顶点v。我需要:
所以上面的数学实现将是: 的 V' = Cj * Bj ^ -1 * v 。 实际上,我有一个疑问。我说v所属的网格有一个变换M,它从模型空间到世界空间。我还在一些教科书中读到,它需要从模型空间转变为联合空间。但我也在1中说过v需要从世界转变为联合空间。所以基本上我不确定我是否需要做 v' = Cj * Bj ^ -1 * v 或 v' = Cj * Bj ^ -1 * M * v 。现在我的实现倍增v'通过M而不是v。但是我已经尝试过更改它,它只是以不同的方式搞砸了,因为还有其他错误。
现在进行实施(Blender方面):
假设以下网格由1个立方体组成,其顶点绑定到单关节骨架中的单个关节:
假设还有一个60帧,3个关键帧动画,速度为60 fps。动画本质上是:
我在混合器方面的第一个混淆源是它的坐标系(与OpenGL的默认设置相反)以及可通过python api访问的不同矩阵。
现在,这就是我的导出脚本关于将 blender的坐标系转换为OpenGL的标准系统所做的事情:
# World transform: Blender -> OpenGL
worldTransform = Matrix().Identity(4)
worldTransform *= Matrix.Scale(-1, 4, (0,0,1))
worldTransform *= Matrix.Rotation(radians(90), 4, "X")
# Mesh (local) transform matrix
file.write('Mesh Transform:\n')
localTransform = mesh.matrix_local.copy()
localTransform = worldTransform * localTransform
for col in localTransform.col:
file.write('{:9f} {:9f} {:9f} {:9f}\n'.format(col[0], col[1], col[2], col[3]))
file.write('\n')
所以,如果你愿意,我的世界"矩阵基本上是将搅拌器坐标系更改为默认GL的行为,其中+ y向上,+ x向右和-z进入查看体积。然后我也预乘(在我们到达引擎的时候完成它,而不是在矩阵乘法顺序方面的post或pre意义上)网格矩阵M,这样我就不会这样做了需要在引擎中每次绘制调用时再次乘以它。
关于从Blender关节中提取的可能矩阵(Blender说法中的骨骼),我正在执行以下操作:
对于关节绑定姿势:
def DFSJointTraversal(file, skeleton, jointList):
for joint in jointList:
bindPoseJoint = skeleton.data.bones[joint.name]
bindPoseTransform = bindPoseJoint.matrix_local.inverted()
file.write('Joint ' + joint.name + ' Transform {\n')
translationV = bindPoseTransform.to_translation()
rotationQ = bindPoseTransform.to_3x3().to_quaternion()
scaleV = bindPoseTransform.to_scale()
file.write('T {:9f} {:9f} {:9f}\n'.format(translationV[0], translationV[1], translationV[2]))
file.write('Q {:9f} {:9f} {:9f} {:9f}\n'.format(rotationQ[1], rotationQ[2], rotationQ[3], rotationQ[0]))
file.write('S {:9f} {:9f} {:9f}\n'.format(scaleV[0], scaleV[1], scaleV[2]))
DFSJointTraversal(file, skeleton, joint.children)
file.write('}\n')
请注意,我实际上抓住了我认为的绑定姿势变换Bj的反转。这是我不需要在引擎中反转它。还要注意我去了matrix_local,假设这是Bj。另一种选择是简单的"矩阵",据我所知,只有不均匀的。
对于关节当前/关键帧姿势:
for kfIndex in keyframes:
bpy.context.scene.frame_set(kfIndex)
file.write('keyframe: {:d}\n'.format(int(kfIndex)))
for i in range(0, len(skeleton.data.bones)):
file.write('joint: {:d}\n'.format(i))
currentPoseJoint = skeleton.pose.bones[i]
currentPoseTransform = currentPoseJoint.matrix
translationV = currentPoseTransform.to_translation()
rotationQ = currentPoseTransform.to_3x3().to_quaternion()
scaleV = currentPoseTransform.to_scale()
file.write('T {:9f} {:9f} {:9f}\n'.format(translationV[0], translationV[1], translationV[2]))
file.write('Q {:9f} {:9f} {:9f} {:9f}\n'.format(rotationQ[1], rotationQ[2], rotationQ[3], rotationQ[0]))
file.write('S {:9f} {:9f} {:9f}\n'.format(scaleV[0], scaleV[1], scaleV[2]))
file.write('\n')
请注意,这里我使用skeleton.pose.bones而不是data.bones,我可以选择3个矩阵:matrix,matrix_basis和matrix_channel。根据python API文档中的描述,我并不十分清楚我应该选择哪一个,尽管我认为它是普通矩阵。另请注意,在这种情况下我不会反转矩阵。
实施(Engine / OpenGL方面):
我的动画子系统在每次更新时都会执行以下操作(我省略了更新循环的部分内容,在这里我们知道哪些对象需要更新,并且为了简单起见,时间是硬编码的):
static double time = 0;
time = fmod((time + elapsedTime),1.);
uint16_t LERPKeyframeNumber = 60 * time;
uint16_t lkeyframeNumber = 0;
uint16_t lkeyframeIndex = 0;
uint16_t rkeyframeNumber = 0;
uint16_t rkeyframeIndex = 0;
for (int i = 0; i < aClip.keyframesCount; i++) {
uint16_t keyframeNumber = aClip.keyframes[i].number;
if (keyframeNumber <= LERPKeyframeNumber) {
lkeyframeIndex = i;
lkeyframeNumber = keyframeNumber;
}
else {
rkeyframeIndex = i;
rkeyframeNumber = keyframeNumber;
break;
}
}
double lTime = lkeyframeNumber / 60.;
double rTime = rkeyframeNumber / 60.;
double blendFactor = (time - lTime) / (rTime - lTime);
GLKMatrix4 bindPosePalette[aSkeleton.jointsCount];
GLKMatrix4 currentPosePalette[aSkeleton.jointsCount];
for (int i = 0; i < aSkeleton.jointsCount; i++) {
F3DETQSType& lPose = aClip.keyframes[lkeyframeIndex].skeletonPose.joints[i];
F3DETQSType& rPose = aClip.keyframes[rkeyframeIndex].skeletonPose.joints[i];
GLKVector3 LERPTranslation = GLKVector3Lerp(lPose.t, rPose.t, blendFactor);
GLKQuaternion SLERPRotation = GLKQuaternionSlerp(lPose.q, rPose.q, blendFactor);
GLKVector3 LERPScaling = GLKVector3Lerp(lPose.s, rPose.s, blendFactor);
GLKMatrix4 currentTransform = GLKMatrix4MakeWithQuaternion(SLERPRotation);
currentTransform = GLKMatrix4TranslateWithVector3(currentTransform, LERPTranslation);
currentTransform = GLKMatrix4ScaleWithVector3(currentTransform, LERPScaling);
GLKMatrix4 inverseBindTransform = GLKMatrix4MakeWithQuaternion(aSkeleton.joints[i].inverseBindTransform.q);
inverseBindTransform = GLKMatrix4TranslateWithVector3(inverseBindTransform, aSkeleton.joints[i].inverseBindTransform.t);
inverseBindTransform = GLKMatrix4ScaleWithVector3(inverseBindTransform, aSkeleton.joints[i].inverseBindTransform.s);
if (aSkeleton.joints[i].parentIndex == -1) {
bindPosePalette[i] = inverseBindTransform;
currentPosePalette[i] = currentTransform;
}
else {
bindPosePalette[i] = GLKMatrix4Multiply(inverseBindTransform, bindPosePalette[aSkeleton.joints[i].parentIndex]);
currentPosePalette[i] = GLKMatrix4Multiply(currentPosePalette[aSkeleton.joints[i].parentIndex], currentTransform);
}
aSkeleton.skinningPalette[i] = GLKMatrix4Multiply(currentPosePalette[i], bindPosePalette[i]);
}
最后,这是我的顶点着色器:
#version 100
uniform mat4 modelMatrix;
uniform mat3 normalMatrix;
uniform mat4 projectionMatrix;
uniform mat4 skinningPalette[6];
uniform lowp float skinningEnabled;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 tCoordinates;
attribute vec4 jointsWeights;
attribute vec4 jointsIndices;
varying highp vec2 tCoordinatesVarying;
varying highp float lIntensity;
void main()
{
tCoordinatesVarying = tCoordinates;
vec4 skinnedVertexPosition = vec4(0.);
for (int i = 0; i < 4; i++) {
skinnedVertexPosition += jointsWeights[i] * skinningPalette[int(jointsIndices[i])] * position;
}
vec4 skinnedNormal = vec4(0.);
for (int i = 0; i < 4; i++) {
skinnedNormal += jointsWeights[i] * skinningPalette[int(jointsIndices[i])] * vec4(normal, 0.);
}
vec4 finalPosition = mix(position, skinnedVertexPosition, skinningEnabled);
vec4 finalNormal = mix(vec4(normal, 0.), skinnedNormal, skinningEnabled);
vec3 eyeNormal = normalize(normalMatrix * finalNormal.xyz);
vec3 lightPosition = vec3(0., 0., 2.);
lIntensity = max(0.0, dot(eyeNormal, normalize(lightPosition)));
gl_Position = projectionMatrix * modelMatrix * finalPosition;
}
结果是动画在方向方面显示错误。也就是说,它不是上下摆动而是进出(根据我在导出剪辑中的变换,我认为是Z轴)。并且旋转角度是逆时针而不是顺时针。
如果我尝试使用多个关节,那么它几乎就像第二个关节在其自身的不同坐标空间中旋转一样,并且不会跟随父母的100%变换。我认为它应该来自我的动画子系统,我假设它依次遵循我为不止一个关节的情况所解释的理论。
有什么想法吗?