Java教程

源码分析学习记录(6)——蒙皮

本文主要是介绍源码分析学习记录(6)——蒙皮,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

2021SC@SDUSC

文章目录

  • 蒙皮算法
    • 刚性绑定算法
    • 柔性绑定算法
  • SkinnedMeshCreator
  • createMeshFromTransform

蒙皮算法

骨骼的蒙皮算法,又称为骨骼与皮肤的绑定法或骨骼子空间变形法(SSD),是骨骼动画中的重要部分,是研究骨骼如何带动皮肤网格运动、实时更新皮肤顶点位置的方法。在模型骨架进行运动时,我们可以使皮肤网格上的顶点随着骨骼一起做同样的运动,这样就产生了皮肤网格的变形效果。)

刚性绑定算法

刚性绑定算法采用位于底层的骨骼承载运动,每个骨骼关节对应控制一个皮肤顶点,得到皮肤网格顶点变换后的新位置信息。皮肤和顶点是一对一控制的。
刚性绑定算法公式:

  • V:顶点变换前世界坐标系下的位置
  • V经过矩阵Li转换成从皮肤顶点初始位置到相关联的那个关节初始位置的位移矢量,再经过骨骼的绝对转换矩阵Mi得到世界坐标系下皮肤顶点的新位置V’

柔性绑定算法

Dust3D采用的应该是柔性绑定算法,它与刚性绑定算法最大的区别在于:每个皮肤顶点都可能受到一个或者更多个骨骼关节影响,在确定皮肤顶点变换后的新位置时,需要由这些产生影响的骨骼关节来共同决定。
请添加图片描述
网格结构Mesh为顶点的集合,存放顶点序列、三角面片索引、权重值、纹理索引等信息,其中与蒙皮直接相关的是权重值信息weight。蒙皮信息的作用是使各个节点按照不同的权重weight同时对顶点施加影响,根据权重的不同,各个节点的影响大小,表现在模型上产生的效果就是多个节点对皮肤网格中的顶点相互拉扯,在建模时通过指定合理的不同区域mesh的权重值,使mesh在连接处产生平滑过渡,而不是关节动画中的生硬断裂,从而避免了裂缝的产生。

SkinnedMeshCreator

SkinnedMeshCreator::SkinnedMeshCreator(const Object &object,
        const std::map<int, RigVertexWeights> &resultWeights) :
    m_object(object),
    m_resultWeights(resultWeights)
{
    m_verticesOldIndices.resize(m_object.triangles.size());
    m_verticesBindNormals.resize(m_object.triangles.size());
    m_verticesBindPositions.resize(m_object.triangles.size());
    const std::vector<std::vector<QVector3D>> *triangleVertexNormals = m_object.triangleVertexNormals();
    //对于每个原始网格上的三角形面片,存储其索引、顶点索引、顶点法线
    for (size_t triangleIndex = 0; triangleIndex < m_object.triangles.size(); triangleIndex++) {
        for (int j = 0; j < 3; j++) {
            int oldIndex = m_object.triangles[triangleIndex][j];
            m_verticesOldIndices[triangleIndex].push_back(oldIndex);
            m_verticesBindPositions[triangleIndex].push_back(m_object.vertices[oldIndex]);
            if (nullptr != triangleVertexNormals)
                m_verticesBindNormals[triangleIndex].push_back((*triangleVertexNormals)[triangleIndex][j]);
            else
                m_verticesBindNormals[triangleIndex].push_back(QVector3D());
        }
    }
    
    //三角面片的填充颜色
    std::map<std::pair<QUuid, QUuid>, QColor> sourceNodeToColorMap;
    for (const auto &node: object.nodes)
        sourceNodeToColorMap.insert({{node.partId, node.nodeId}, node.color});
    
    m_triangleColors.resize(m_object.triangles.size(), Theme::white);
    const std::vector<std::pair<QUuid, QUuid>> *triangleSourceNodes = object.triangleSourceNodes();
    if (nullptr != triangleSourceNodes) {
        for (size_t triangleIndex = 0; triangleIndex < m_object.triangles.size(); triangleIndex++) {
            const auto &source = (*triangleSourceNodes)[triangleIndex];
            m_triangleColors[triangleIndex] = sourceNodeToColorMap[source];
        }
    }
}

createMeshFromTransform

线性混合蒙皮算法是最常使用的蒙皮算法,它求得一个顶点在每个骨骼影响下的一系列新的位置,然后对这些位置数据进行加权平均计算后得到最后的结果:

其中,V表示顶点变换前的世界坐标系中的位置,V’表示顶点变换后的位置,i表示同时影响该顶点的骨骼数量,wi表示第i个骨骼对该顶点施加的影响权重,取0~1之间的值;M表示在模型初始参考姿势下,与顶点相关的
第i个骨骼由本地坐标转换为世界坐标的转换矩阵,通过矩阵Mi能将骨骼i从初始位置换到动画数据来到时的新位置上,Mi×V表示V在骨骼i的单独影响下的位置。

Model *SkinnedMeshCreator::createMeshFromTransform(const std::vector<QMatrix4x4> &matricies)
{
    std::vector<std::vector<QVector3D>> transformedPositions = m_verticesBindPositions;
    std::vector<std::vector<QVector3D>> transformedPoseNormals = m_verticesBindNormals;
    
    if (!matricies.empty()) {
        for (size_t i = 0; i < transformedPositions.size(); ++i) {
            for (size_t j = 0; j < 3; ++j) {
                const auto &weight = m_resultWeights[m_verticesOldIndices[i][j]];
                QMatrix4x4 mixedMatrix;
                transformedPositions[i][j] = QVector3D();
                transformedPoseNormals[i][j] = QVector3D();
                for (int x = 0; x < MAX_WEIGHT_NUM; x++) {
                    float factor = weight.boneWeights[x];
                    if (factor > 0) {
                        //线性混合蒙皮公式
                        transformedPositions[i][j] += matricies[weight.boneIndices[x]] * m_verticesBindPositions[i][j] * factor;
                        transformedPoseNormals[i][j] += matricies[weight.boneIndices[x]] * m_verticesBindNormals[i][j] * factor;
                    }
                }
            }
        }
    }
    
    ShaderVertex *triangleVertices = new ShaderVertex[m_object.triangles.size() * 3];
    int triangleVerticesNum = 0;
    for (size_t triangleIndex = 0; triangleIndex < m_object.triangles.size(); triangleIndex++) {
        for (int i = 0; i < 3; i++) {
            //变换后的Mesh信息
            ShaderVertex &currentVertex = triangleVertices[triangleVerticesNum++];
            const auto &sourcePosition = transformedPositions[triangleIndex][i];
            const auto &sourceColor = m_triangleColors[triangleIndex];
            const auto &sourceNormal = transformedPoseNormals[triangleIndex][i];
            currentVertex.posX = sourcePosition.x();
            currentVertex.posY = sourcePosition.y();
            currentVertex.posZ = sourcePosition.z();
            currentVertex.texU = 0;
            currentVertex.texV = 0;
            currentVertex.colorR = sourceColor.redF();
            currentVertex.colorG = sourceColor.greenF();
            currentVertex.colorB = sourceColor.blueF();
            currentVertex.normX = sourceNormal.x();
            currentVertex.normY = sourceNormal.y();
            currentVertex.normZ = sourceNormal.z();
            currentVertex.metalness = Model::m_defaultMetalness;
            currentVertex.roughness = Model::m_defaultRoughness;
        }
    }
    
    return new Model(triangleVertices, triangleVerticesNum);
}
这篇关于源码分析学习记录(6)——蒙皮的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!