[OpenGL] 骨骼动画混合效果

        本文主要讨论两个骨骼动画过渡时的混合效果。

切换动作效果演示

        在游戏中,动画往往被切分成多个片段(clip),通过组合拼接来构建最终的表现效果。为了确保切换动作时的平滑过渡,使得整体动作更加流畅,需要对前后动作通过插值进行混合。

        概念引入

        在讨论具体的混合计算之前,我想依然有必要明确一下整个动画系统体系的一些细节。

        其中一个需要探讨的问题在于:动画运行时应该维护哪些数据,并以怎样的形式换算进入最终的渲染流程。在原先的骨骼动画demo中,我实际上是按帧存储了每帧每个骨骼的蒙皮矩阵,作为骨骼动画演示已经绰绰有余了。

        但对于实际应用而言,这里起码存在了两个问题:一是逐帧的动画数据一般是烘焙的结果,在dcc工具中动画数据一般为关键帧+曲线类型存储,实时插值本身并不耗时,并且可以压缩带宽,所以动画往往都会进行压缩存储;

        二是我们除了播放动画外,有时候还需要对动作做一些后处理——比如本篇文章讨论的动画混合,又比如反向动力学效果,此时仅有蒙皮矩阵是不利于运算的,我们应该根据自己的实际需求,存储为局部变换矩阵或全局(模型空间)变换矩阵。特别地,如果存储为局部变换矩阵,在解算的过程中存在不断查找父节点连乘矩阵的步骤,此处合理地安排骨骼的排序可以避免重复的运算。

        另外一个需要考虑的问题是,混合是否应该影响到动作的播放长度?我们假设动作A长度为ta帧,B为tb帧,混合帧数为tc,那么动作A,B混合后,总帧长应该为ta+tb,还是ta+tb+tc,又或是介于两者之间?

        针对这一问题,我认为比较友好的设计为:混合时间不应该影响原播放长度,混合这一功能本身仅仅是为了更好的表现效果,它不应当破坏原有的体系。那么此处就必然有一个动作“牺牲”一部分姿态。一个比较常见的思路是让目标动作的前tc帧转换为过渡帧,每一帧的矩阵与源动作最后一帧按照当前时间进行权重插值。

        具体实现

        为了更好地进行混合操作,我们可以将动画存储的数据修改为模型空间下的transform值,而不是最终的蒙皮矩阵。所谓的transform也就是分别存储平移旋转缩放,之后再根据RST进行矩阵构造:

struct STransform
{
    QVector3D position     = QVector3D(0, 0, 0);
    QVector3D scale        = QVector3D(1, 1, 1);
    QQuaternion rotation   = QQuaternion(0, 0, 0, 1);
};

        在实际的插值运算中,我们也是针对每个骨骼模型空间的平移、旋转、缩放分别进行插值(由于项目中骨骼不存在缩放,实际的工程中并没有计算缩放插值)

        在切换到新动作,当我们检测到新动作需要混合时,我们根据当前动作localtime,采样动作的transform值,作为缓存:


void CAnimationEngine::PlayAnimation(Object* obj, const string& path)
{
    if(m_animators.find(path) == m_animators.end())
    {
        return;
    }
    int frame = -1;
    string oldPath;
    // check need blend, save cache pose
    if(m_events.find(obj) != m_events.end() && m_animators.find(m_events[obj].m_path) != m_animators.end())
    {
        if(g_animParam.m_nBlendFrame)
        {
            oldPath = m_events[obj].m_path;
            frame = min(m_animators[oldPath].GetFrameNum(), static_cast<int>(m_events[obj].m_time * FRAME_PER_MS));
        }
    }
    m_events[obj] = SEvent(path, g_animParam.m_bLoop, g_animParam.m_nBlendFrame, g_animParam.m_eBlendCurve, g_animParam.m_fSpeed);
    if(!oldPath.empty())
    {
        CAnimator& animator = m_animators[oldPath];
        m_events[obj].m_cachePose = animator.GetTransform(frame);
    }
}

        接下来,在更新骨骼动画的代码中,我们对当前帧数下的采样动作和缓存动作的平移、旋转分别进行插值,混合权重以时间t单位,包含线性混合(t),以及非线性混合(3 * t * t - 2 * t * t)。

        插值结束后,我们重新构造模型空间的全局变换矩阵,并乘以绑定矩阵逆矩阵构造蒙皮矩阵,传递给着色器。

bool CAnimationEngine::UpdateAnimation(Object* obj, QOpenGLShaderProgram* program)
{
    // ...

    if (event.m_blendFrame > 0 && frame <= event.m_blendFrame && event.m_cachePose.size() > 0)
    {
        vector<QMatrix4x4> final;
        float ratio = static_cast<float>(frame + 1) / (event.m_blendFrame + 1);
        if (event.m_eBlendCurve == EBlendCurve::Smooth)
        {
            ratio = ratio * ratio * (-2 * ratio + 3);
        }

        for(int i = 0;i < size; i++)
        {
            STransform& transform = animator.GetTransform(frame, i);

            QQuaternion quat = QQuaternion::slerp(event.m_cachePose[i].rotation, transform.rotation, ratio);
            QVector3D trans = Lerp(event.m_cachePose[i].position, transform.position, ratio);

            QMatrix4x4& invBindPose = CAnimationEngine::Inst()->GetBone(i)->m_invBindPose;
            QMatrix4x4 mat;
            mat.translate(trans);
            mat.rotate(quat);
            mat = mat * invBindPose;

            final.push_back(mat);
        }
        program->setUniformValueArray(location,final.data(), size);
    }
    else
    {
        // ...
    }


    // ...
    return true;
}

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课程简介:本课程详细讲解基于Assimp C++库的模型读取模块,并且做了关于动画理论、关键帧插值、骨骼动画矩阵原理、骨骼动画读取与播放等知识的详细讲解,对于游戏行业或者三维可视化从业人员会有比较大的帮助。目前很多公司已经开始构建自己的底层图形引擎,其中动画就是重要的一个版块,本课程可以让学员从原理层面以及底层代码层面了解FBX、OBJ模型的读取本质,并且梳理程序架构,编写骨骼动画。2 课程解决优势:很多同学学习骨骼动画苦于无法找到详细的资料,其中卡主的问题点也比比皆是,比如FBX内嵌材质的读取,骨骼动画各类矩阵的应用,理论结合模型读取库读出来的数据如何一一对应等。我们的课程可以带领大家从原理+实践的角度进行学习,每一个知识点都会:a 推导基础公式及原理 b 一行一行进行代码实践从而能够保证每位同学都学有所得,能够看得懂,学得会,用得上,并且能够培养自主研究的能力。3 学习课程所得:学习本课程完毕之后,学员可以全方位的完全了解基于Assimp库的模型读取结构,了解每一个变量背后的含义,并且课程拥有随堂附赠的源代码,保证同学可以随时根据老师的代码纠正自己的错误。跟随课程一行一行写完代码的同学,可以获得自己的模型读取代码库,并且深度理解骨骼动画的原理与模型读取原理 本课程含有全源代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值