【发布时间】:2020-10-10 19:22:01
【问题描述】:
我正在使用自己的 Collada 解析器编写动画系统,但遇到了一个我无法解决的问题。
我收集了我的网格/蒙皮信息(顶点、法线、jointIds、权重等)、我的骨架信息(关节、它们的局部变换、反向绑定位置、层次结构) ,以及我的动画(每个关节的关键帧变换位置,时间戳)。
我的问题是,计算所有内容,然后在着色器中实现(权重总和乘以联合变换和顶点位置) - 我得到以下结果:
当我移除权重乘法时,网格仍然完好无损 - 但是皮肤实际上并没有跟随动画。我很迷茫,因为我觉得数学好像是正确的,但很明显我在某个地方出错了。有人能阐明我误解的方面吗?
这是我目前的理解和实现:
- 在收集了所有关节的局部变换和层次结构后,我计算了它们的逆绑定变换矩阵。为此,我将每个关节 localTransform 与它们的 parentLocalTransform 相乘以获得一个 bindTransform。反转该 bindTransform 会导致它们的 inverseBindTransform。下面是我的代码:
// Recursively collect each Joints InverseBindTransform -
// root joint's local position is an identity matrix.
// Function is only called once after data collection.
void Joint::CalcInverseBindTransform(glm::mat4 parentLocalPosition)
{
glm::mat4 bindTransform = parentLocalPosition * m_LocalTransform;
m_InverseBindPoseMatrix = glm::inverse(bindTransform);
for (Joint child : Children) {
child.CalcInverseBindTransform(bindTransform);
}
}
- 在动画制作过程中,对于每个关节,我将两个 JointTransform 用于两个帧,我的 currentTime 介于两者之间,并计算插值的 JointTransform。 (JointTransform 简单地有一个用于位置的 vec3 和用于旋转的四元数)。我对每个关节执行此操作,然后通过再次递归地将新的 frameLocalTransform 与它们的 parentLocalTransform 相乘,将这些插值应用于每个关节。我采用该 bindTransform 并将其乘以 invBindTransform ,然后转置矩阵。下面是代码:
std::unordered_map<int, glm::mat4> Animator::InterpolatePoses(float time) {
std::unordered_map<int, glm::mat4> poses;
if (IsPlaying()) {
for (std::pair<int, JointTransform> keyframe : m_PreviousFrame.GetJointKeyFrames()) {
JointTransform previousFrame = m_PreviousFrame.GetJointKeyFrames()[keyframe.first];
JointTransform nextFrame = m_NextFrame.GetJointKeyFrames()[keyframe.first];
JointTransform interpolated = JointTransform::Interpolate(previousFrame, nextFrame, time);
poses[keyframe.first] = interpolated.getLocalTransform();
}
}
return poses;
}
void Animator::ApplyPosesToJoints(std::unordered_map<int, glm::mat4> newPose, Joint* j, glm::mat4 parentTransform)
{
if (IsPlaying()) {
glm::mat4 currentPose = newPose[j->GetJointId()];
glm::mat4 modelSpaceJoint = parentTransform * currentPose;
for (Joint child : j->GetChildren()) {
ApplyPosesToJoints(newPose, &child, modelSpaceJoint);
}
modelSpaceJoint = glm::transpose(j->GetInvBindPosition() * modelSpaceJoint);
j->SetAnimationTransform(modelSpaceJoint);
}
}
- 然后我为每个关节收集所有新的 AnimatedTransform 并将它们发送到着色器:
void AnimationModel::Render(bool& pass)
{
[...]
std::vector<glm::mat4> transforms = GetJointTransforms();
for (int i = 0; i < transforms.size(); ++i) {
m_Shader->SetMat4f(transforms[i], ("JointTransforms[" + std::to_string(i) + "]").c_str());
}
[...]
}
void AnimationModel::AddJointsToArray(Joint current, std::vector<glm::mat4>& matrix)
{
glm::mat4 finalMatrix = current.GetAnimatedTransform();
matrix.push_back(finalMatrix);
for (Joint child : current.GetChildren()) {
AddJointsToArray(child, matrix);
}
}
- 在着色器中,我只是按照在研究这个主题时可以在网上找到的求和公式:
for (int i = 0; i < total_weight_amnt; ++i) {
mat4 jointTransform = JointTransforms[jointIds[i]];
vec4 newVertexPos = jointTransform * vec4(pos, 1.0);
total_pos += newVertexPos * weights[i];
[...]
--------- 回复归一化权重------------
有几个权重总和超过 1,但在解决了我的代码中的错误后,模型看起来像这样:
为了计算权重——我循环遍历向量中所有预先添加的权重,如果我发现一个权重小于我要添加的权重——我在那个位置替换那个权重。否则,我将权重附加到向量的末尾。如果我的向量中的权重比我指定的 max_weights(即 4)少 - 我用 0 填充剩余的权重/jointIds。
【问题讨论】:
-
您的体重值总和是否为适当的值(通常为 0、1 或 N)?
total_pos计算后是否需要调整(除以N)? -
我进行了权重计算 - 我确实注意到检查权重向量总和高于 1 的代码中存在错误。我修复了它并确认没有超过 1 的权重向量。出现了更多的几何图形-但是在我的数学中似乎直到其他地方出现错误。我会在帖子里发一张照片。
标签: c++ animation opengl graphics 3d