2021SC@SDUSC
骨骼的蒙皮算法,又称为骨骼与皮肤的绑定法或骨骼子空间变形法(SSD),是骨骼动画中的重要部分,是研究骨骼如何带动皮肤网格运动、实时更新皮肤顶点位置的方法。在模型骨架进行运动时,我们可以使皮肤网格上的顶点随着骨骼一起做同样的运动,这样就产生了皮肤网格的变形效果。
刚性绑定算法采用位于底层的骨骼承载运动,每个骨骼关节对应控制一个皮肤顶点,得到皮肤网格顶点变换后的新位置信息。皮肤和顶点是一对一控制的。
刚性绑定算法公式:
Dust3D采用的应该是柔性绑定算法,它与刚性绑定算法最大的区别在于:每个皮肤顶点都可能受到一个或者更多个骨骼关节影响,在确定皮肤顶点变换后的新位置时,需要由这些产生影响的骨骼关节来共同决定。
网格结构Mesh为顶点的集合,存放顶点序列、三角面片索引、权重值、纹理索引等信息,其中与蒙皮直接相关的是权重值信息weight。蒙皮信息的作用是使各个节点按照不同的权重weight同时对顶点施加影响,根据权重的不同,各个节点的影响大小,表现在模型上产生的效果就是多个节点对皮肤网格中的顶点相互拉扯,在建模时通过指定合理的不同区域mesh的权重值,使mesh在连接处产生平滑过渡,而不是关节动画中的生硬断裂,从而避免了裂缝的产生。
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]; } } }
线性混合蒙皮算法是最常使用的蒙皮算法,它求得一个顶点在每个骨骼影响下的一系列新的位置,然后对这些位置数据进行加权平均计算后得到最后的结果:
其中,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 ¤tVertex = 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); }