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]);elsem_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)——蒙皮相关推荐

  1. 源码分析学习记录(5)——骨骼存储与建立

    2021SC@SDUSC 文章目录 骨骼数据结构 updateMatrix 建立骨骼树形结构 骨骼决定了模型整体在世界坐标系中的位置和方位. 在渲染静态模型时, 由于模型的顶点都是定义在模型坐标系中的 ...

  2. 源码分析学习记录(9)——PBR材质

    2021SC@SDUSC Dust3D中的材质采用PBR模型.PBR就是Physically-Based Rendering的缩写,意为基于物理的渲染.它提供了一种光照和渲染方法,能够更精确的描绘光和 ...

  3. 源码分析学习记录(11)——半边结构

    2021SC@SDUSC Dust3D在网格无缝缝合时使用了半边数据结构存储相关数据. 表示多边形网格的一个常用方式就是使用共享的顶点列表和面的列表.这样的表示方法在许多情况下都非常方便和高效,但是在 ...

  4. 源码分析学习记录(12)——自动UV展开

    2021SC@SDUSC 文章目录 UV 展开 创建割缝 UV展开的扭曲情况 UvUnwrap UV 展开 参数曲面的参数域变量一般用UV字母来表达,比如参数曲面F(u,v).所以一般叫的三维曲面本质 ...

  5. 分析jQuery源码时记录的一点感悟

    分析jQuery源码时记录的一点感悟       1.  链式写法       这是jQuery语法上的最大特色,也许该改改POJO里的set方法,和其他的非get方法什么的,可以把多行代码合并,减去 ...

  6. r8169驱动源码阅读记录

    r8169驱动源码阅读记录 初始化 发包 收包 源码地址:linux-4.19.90\drivers\net\ethernet\realtek\r8169.c 源码阅读环境:Windows 搭建 op ...

  7. Spark-Core源码学习记录 3 SparkContext、SchedulerBackend、TaskScheduler初始化及应用的注册流程

    Spark-Core源码学习记录 该系列作为Spark源码回顾学习的记录,旨在捋清Spark分发程序运行的机制和流程,对部分关键源码进行追踪,争取做到知其所以然,对枝节部分源码仅进行文字说明,不深入下 ...

  8. dubbo源码分析学习---dubbo 重要内容Invoker 和服务注册过程

    这篇文章主要续接上一篇文章的基础上做的分析学习.前面没有分析 Invoker,我们来简单看看 Invoker 到底是一个啥东西. 一.Invoker 是什么 从前面的分析来看,服务的发布分三个阶段: ...

  9. spark源码编译记录

    spark在项目中已经用了一段时间了,趁现在空闲,下个源码编译在IDEA里面阅读下,特此记录过程. 前提已经安装maven和git 1.上官网下载源码的包: 2.然后解压到一个文件夹 3.编译,编译的 ...

最新文章

  1. 剑指offer-----Python-----栈
  2. 用java编写运行的小游戏_第一次用Java编写小游戏!
  3. Model层的两种写法
  4. How to become an expert in the IP industry? Here is where you should start
  5. mybatis的插件分析
  6. 探讨继承与实现(二)
  7. 输入学生的个数,姓名,成绩,然后按照学生的成绩的降序来打印学生的姓名
  8. java多线程_Java多线程
  9. GNU make 汇总
  10. 叮咚志汇超级外卖餐饮 6.3.8 + 超级跑腿 v2.0.3 打包下载 小程序模块
  11. LTE的基础知识与关键技术
  12. 数据结构与算法—队列详解
  13. Linux内核UDP收包为什么效率低?能做什么优化?
  14. 2022最新软件测试面试题
  15. 简单对象协议(SOAP)简介
  16. Magento支付宝手机网站支付插件V6.0旗舰版发布,支持在微信中使用支付宝支付,订单重新支付功能!...
  17. 后端开发——Flask框架从入门到入坟(终章)
  18. ES系列四、ES6.3常用api之文档类api
  19. Oracle中WITH ...... OPTION权限对于权限授予和收回的级联影响
  20. Verilog专题(九)DFF、Dlatch、JK flip-flop

热门文章

  1. Redis-server.exe闪退问题
  2. NVIDIA A100云服务器
  3. 区间素数 由N(N<=10000)个整数组成的数组,其中连续K(K<=200)个元素构成一个区间,称为K区间。一个K区间中所有素数的和记为Sk,请计算整个数组中,所有K区间中的最大Sk值,并输出。
  4. 电脑通过手机(摩托E2)上网
  5. Linux后台开发工具箱-葵花宝典
  6. STM32L475 硬件SPI+软件SPI驱动ST7789V2
  7. 想要打造专属社交电商平台?一站式解决方案营销社交化
  8. 《上帝掷骰子吗》人物八卦之费因曼
  9. linux kdump 分析,利用Kdump分析内核奔溃原因(1)
  10. 04 项目分析阶段(二)