2021SC@SDUSC

目录

分析概括

模块功能

IK算法

关键代码分析

SkeletonIkMover.h分析

CCD求解骨骼运动算法

SkeletonIkMover.cpp分析


分析概括

模块功能

该模块主要用于骨骼的运动控制,主要作用于Dust3D中的姿势模块,可以通过控制骨骼来控制几何体的姿势和动作

IK算法

骨络动画中有两种骨骼绑定模式:FK(正向运动学,Forward Kinematics)和IK(逆向运动学, Inverse Kinematics)两种。Dust3D中主要采用了IK控制骨骼运动,先确定子骨骼的方位,反向推导出其继承链上n级父骨骼方位。

k3d有容器版本的嘛 knots3d_转置

k3d有容器版本的嘛 knots3d_迭代_02

IK的计算方法可分为两种:ccd方法和雅可比转置矩阵法。

1.循环坐标下降法(Dust3D所用的方法,详情见后文)

2.可以使用雅可比转置矩阵,它定义了末端效应器如何基于整个系统的瞬时变换而变化(末端效应器就是指整个骨骼链的末端),求得的矩阵示转置,就可以得到要求的矩阵。

 

k3d有容器版本的嘛 knots3d_转置_03

关键代码分析

SkeletonIkMover.h分析

struct SkeletonIkNode
{//IK节点信息
    QUuid id;
    QVector3D position;
    QVector3D newPosition;
};

class SkeletonIkMover : public QObject
{
    Q_OBJECT
public:
    SkeletonIkMover();
    ~SkeletonIkMover();
    void setTarget(const QVector3D &target);//设定目标
    void setUpdateVersion(unsigned long long version);//设置更新
    unsigned long long getUpdateVersion();//接收更新
    void addNode(QUuid id, QVector3D position);//添加节点
    const std::vector<SkeletonIkNode> &ikNodes();//设置IK节点
signals:
    void finished();
public slots:
    void process();
private:
    void resolve();
private:
    unsigned long long m_updateVersion = 0;
    std::vector<SkeletonIkNode> m_ikNodes;
    QVector3D m_target;
};

CCD求解骨骼运动算法

Dust3D中的CCD的计算方法主要通过CcdIkSolver类来实现,其中iterate()规定了在进行IK运动求解时的迭代规则:

1.从末端关节连接的父关节A开始,计算末端关节与目标末端关节位置与该关节形成的夹角并进行旋转

2.如果旋转后的末端关节未达到目标,则以A的父关节开始重复步骤1;

3.如果到达根关节仍未将末端关节达到目标,则结束一次迭代,重复步骤1、2,进入下一次迭代。
(参考文章:)

void CcdIkSolver::iterate()
{
    auto rotateChildren = [&](const QQuaternion &quaternion, int i) {
        const auto &origin = m_nodes[i];
        for (size_t j = i + 1; j <= m_nodes.size() - 1; j++) {
            //关节前后位置的变换
            auto &next = m_nodes[j];
            const auto offset = next.position - origin.position;
            next.position = origin.position + quaternion.rotatedVector(offset);
        }
    };
    
    for (int i = m_nodes.size() - 2; i >= m_fromNodeIndex; i--) {
        //节点之间的旋转迭代
        const auto &origin = m_nodes[i];
        const auto &endEffector = m_nodes[m_nodes.size() - 1];
        QVector3D from = (endEffector.position - origin.position).normalized();
        QVector3D to = (m_destination - origin.position).normalized();
        auto quaternion = QQuaternion::rotationTo(from, to);
        rotateChildren(quaternion, i);
        if (origin.axis.isNull())
            continue;
        QVector3D oldAxis = origin.axis;
        QVector3D newAxis = quaternion.rotatedVector(oldAxis);
        auto hingQuaternion = QQuaternion::rotationTo(newAxis, oldAxis);
        rotateChildren(hingQuaternion, i);
        // TODO: Support angle limit for other axis
        int parentIndex = i - 1;
        if (parentIndex < 0)
            continue;
        int childIndex = i + 1;
        if (childIndex >= m_nodes.size())
            continue;
        const auto &parent = m_nodes[parentIndex];
        const auto &child = m_nodes[childIndex];
        QVector3D angleFrom = (QVector3D(0.0, parent.position.y(), parent.position.z()) - 
            QVector3D(0.0, origin.position.y(), origin.position.z())).normalized();
        QVector3D angleTo = (QVector3D(0.0, child.position.y(), child.position.z()) - 
            QVector3D(0.0, origin.position.y(), origin.position.z())).normalized();
        float degrees = angleInRangle360BetweenTwoVectors(angleFrom, angleTo, QVector3D(1.0, 0.0, 0.0));
        if (degrees < origin.minLimitDegrees) {
            auto quaternion = QQuaternion::fromAxisAndAngle(QVector3D(1.0, 0.0, 0.0), origin.minLimitDegrees - degrees);
            rotateChildren(quaternion, i);
        } else if (degrees > origin.maxLimitDegrees) {
            auto quaternion = QQuaternion::fromAxisAndAngle(QVector3D(-1.0, 0.0, 0.0), degrees - origin.maxLimitDegrees);
            rotateChildren(quaternion, i);
        }
    }
}

SkeletonIkMover.cpp分析

SkeletonIkMover类中的函数是对CcdIkSolver类的具体应用,首先通过solveTo()函数确定目标节点应用范围并进行cck计算,之后再根据计算结果带入节点变量中

void SkeletonIkMover::addNode(QUuid id, QVector3D position)
{//在关节中添加IK节点
    SkeletonIkNode ikNode;//定义IK节点
    //将节点的信息赋予该IK点
    ikNode.id = id;
    ikNode.position = ikNode.newPosition = position;
    m_ikNodes.push_back(ikNode);
}

void SkeletonIkMover::resolve()
{
    CcdIkSolver solver;//定义ccdIK解算器
    for (auto i = 0u; i < m_ikNodes.size(); i++) {
        solver.addNodeInOrder(m_ikNodes[i].position);//在ccdIK解算器中添加关键点
    }
    solver.solveTo(m_target);//设置目标节点的影响范围和影响邻域骨骼,进行ccd计算
    for (auto i = 0u; i < m_ikNodes.size(); i++) {
        //确定好关节新位置,代入
        m_ikNodes[i].newPosition = solver.getNodeSolvedPosition(i);
    }
}