硬件支持下骨骼蒙皮动画的实现

1623 篇文章 22 订阅
1277 篇文章 12 订阅
 

硬件支持下骨骼蒙皮动画的实现
作者:Octane3d
MSN:Octane3d@hotmail.com
电邮:Octane3d@tom.com

一 引言
角色动画是计算机动画技术的一个重要组成部分,在计算机辅助动画电影制作和各类广告制作中一直扮演着重要的角色。动画制作种所使用的角色动画技术的一个重要特点是动画渲染需要耗费大量时间,因此动画作品必须预先制作,渲染,然后作为视频文件播放,也就是说,是非实时。但是,虚拟现实,电子游戏,甚至是传统的动画制作软件对实时角色动画同样有很大的需求。现在,随着计算机硬件技术的发展,特别是消费级别的带有硬件加速功能的显卡技术的发展,实时角色动画逐渐获得了越来越广泛的应用。本文将在简单回顾实时角色动画发展的基础上,结合 SkeAni 演示程序,介绍在现有硬件基础上骨骼蒙皮动画的基本原理和实现方法。
实时角色动画技术主要有三种类型。
第一类是关节动画。关节动画中的角色由若干独立的部分组成。每一个部分对应着一个独立的网格模型,不同的部分按照角色的特点组织成一个层次结构。比如说,一个人体模型可以由头,上身,左上臂,左前臂,左手,右上臂,右前臂,右手,左大腿,左小腿,左脚,右大腿,右小腿,右脚等各部分组成。而某个部分,可能是另一个部分的子节点,同时又是另一个部分的父节点。比如上面的人体模型中,右前臂就是右上臂的子节点,同时也是右手的父节点。而右上臂是上身的子节点,后者则是躯体的子节点。通过改变不同部分之间的相对位置,比如夹角,位移等等,就可以实现所需要的各种动画效果。这类动画的优点很多。首先,在动画序列关键帧中只需要存储节点间的相对变化,因此动画文件占用的空间很小。其次,可以实现很多复杂的动画效果,如果应用程序支持反向动力学还可以动态实现预先存储的动画序列之外的新的动画效果。当然这类动画也有不少缺点。其中之一是由于角色模型是一个层次模型,要获得某一个部分相对于世界坐标的位置,必须从根结点开始遍历该节点所有的祖先节点累计计算模型 --- 世界变换。但最关键的问题是在不同部分的结合处往往会有很明显的接缝,这会严重的影响模型的真实感。
第二类是单一网格模型动画。这种动画中的角色由一个完整的网格模型构成。在动画序列的关键帧中记录着组成网格的各个顶点的新位置或者是相对于原位置的改变量。通过在相邻关键帧之间插值来直接改变该网格模型中各个顶点的位置就可以实现动画效果。相对于关节动画,单一网格模型动画的角色看上去更真实,也不会有关节动画所面临的接缝问题。由于没有使用层次模型,获得模型网格顶点在世界坐标中位置的计算量也很小。但是,这类动画的适应性很弱,角色很难通过实时计算来与环境进行良好的互动,以获得预先存储的动画序列之外的动画效果。另一方面,由于关键帧要存储网格模型所有的顶点信息,动画文件占用的空间特别大。
第三类是骨骼蒙皮动画,本文的第二部分将重点讨论这类动画。
二 骨骼蒙皮动画
骨骼蒙皮动画可以看作是关节动画和单一网格模型动画的结合。在骨骼蒙皮动画中,一个角色由作为皮肤的单一网格模型和按照一定层次组织起来的骨骼组成。骨骼层次描述了角色的结构,就像关节动画中的不同部分一样,骨骼蒙皮动画中的骨骼按照角色的特点组成一个层次结构。相邻的骨骼通过关节相连,并且可以作相对的运动。通过改变相邻骨骼间的夹角,位移,组成角色的骨骼就可以做出不同的动作,实现不同的动画效果。皮肤则作为一个网格蒙在骨骼之上,规定角色的外观。这里的皮肤不是固定不变的刚性网格,而是可以在骨骼影响下变化的一个可变形网格。组成皮肤的每一个顶点都会受到一个或者多个骨骼的影响。在顶点受到多个骨骼影响的情况下,不过的骨骼按照与顶点的几何,物理关系确定对该顶点的影响权重,这一权重可以通过建模软件计算,也可以手工设置。通过计算影响该顶点的不同骨骼对它影响的加权和就可以得到该顶点在世界坐标系中的正确位置。动画文件中的关键帧一般保存着骨骼的位置,朝向等信息。通过在动画序列中相邻的两个关键帧间插值可以确定某一时刻各个骨骼的新位置和新朝向。然后按照皮肤网格各个顶点中保存的影响它的骨骼索引和相应权重信息可以计算出该顶点的新位置。这样就实现了在骨骼驱动下的单一皮肤网格变形动画。或者简单地说骨骼蒙皮动画。骨骼蒙皮动画的效果比关节动画和单一网格动画更逼真,更生动。而且,随着3D硬件性能的提高,越来越多的相关计算可以通过硬件来完成,骨骼蒙皮动画已经成为各类实时动画应用中使用最广泛的动画技术。
下面讨论骨骼蒙皮动画实现的技术细节。在一个典型的骨骼蒙皮动画模型文件中,会保存如下信息:网格信息,骨骼信息和动画信息。网格信息是角色的多边形模型。该多边形模型一般由三角形面片组成,每一三角形面片有三个指向模型的顶点表的索引。通过该索引,可以确定该三角形的三个顶点。顶点表中的每一顶点除了带有位置,法向量,材质,纹理等基本信息外,还会指出有哪些骨骼影响了该顶点,影响权重又是多少。影响一个顶点的最大骨骼数一般取决于模型的设计和目标硬件平台的限制。比如,对于一个典型的人体骨架,一般只有在关节附近的顶点才会受到相邻几块骨骼的影响,而同时影响某一顶点的骨骼数,也不会超过四块。骨骼信息包括全部骨骼的数量和每一骨骼的具体信息。所有的骨骼按照父子关系组织成一棵树。树根代表整个骨架,其余每一节点包括叶子节点代表一根骨骼。每一根骨骼包括该骨骼在父骨骼坐标系中的变换矩阵,通过该变化矩阵确定了该骨骼在父骨骼坐标系中的位置。在动画信息中则保存了若干关键帧。每一关键帧指出了每一骨骼在该时刻相对于父骨骼坐标系的变换矩阵,当然也可以是该骨骼相对于父骨骼的位置,朝向等变动。在播放动画序列中的任一时刻:
1) 首先确定该时刻之前和之后的两个关键帧,然后按照该时刻与前后两个关键帧时刻的时间值插值计算出该时刻该骨骼相对于父骨骼的新位置,新朝向,新变换矩阵等等。
2) 然后从根骨骼开始遍历骨架,计算每一个骨骼相对于世界坐标的变换矩阵。计算该矩阵是一个递归过程,用伪码描述如下:
CalcBoneWorldMarix (pCurBone, pParentBone)
{
 pCurBone->WorldMatrix = pParentBone->WorldMatrix *
                               pCurBone->LocalMatrix;
    pChildBone = pCurBone->pFirstChildBone;   
while (pChildBone)
{
 CalcBoneWorldMatrix (pChildBone, pCurBone);
    PChildBone = pChildBone->pNextSiblingBone;
}
   }
   
3)  对于皮肤网格中的每一个顶点,计算它在世界坐标中新的位置和朝向。首先找到影
响该顶点的所有骨骼。然后计算每一骨骼对该顶点的影响。也就时说,计算在该骨骼独立作用下顶点的新位置。计算按照如下公式:            
顶点的新位置 =  骨骼的世界变换矩阵 *
最初状态骨骼世界变矩阵的逆矩阵 * 最初状态顶点的位置 (I)
        然后将所有这些新位置按照每一骨骼的影响权重加权求和。注意所有权重的和应该恰好为 1。在公式(I)中,最初状态顶点的位置首先要与最初状态骨骼世界变矩阵的逆矩阵相乘是因为骨骼的世界变换矩阵是将骨骼从世界坐标系的原点移动到它在某一时刻所处的新位置,相应的,它所控制的顶点也应该是从包围位于原点位置的骨骼的那一位置移动到新位置。而最初状态顶点的位置已经被最初状态骨骼世界变矩阵移动到了世界坐标系中的某一点,为此,必须要首先把这一顶点连同控制它的最初状态骨骼一起移回到世界坐标系的原点。顶点朝向按照相同方式计算,但是不考虑平移效果。
    4)  根据网格模型顶点的新位置和朝向绘制角色网格。
            
三 硬件支持
  观察骨骼蒙动画的计算过程可以发现,在一个顶点受到许多骨骼影响的时候,求一个顶点的新位置会牵涉到大量的矩阵与乘法运算。比如如果一个顶点受到 16 根骨骼的影响,那么就要进行 16 次矩阵与向量相乘的运算,然后乘以影响权重又需要 16 次浮点数相乘,最后还需要 15 次浮点数相加来求得最终的顶点位置。对于一个复杂的角色模型,比如精细的人体模型,上万的顶点数和同时有几十根影响骨骼并不少见。骨骼蒙皮动画过大的计算量一度限制了它的广泛应用。幸运的是,随着3D硬件技术加速的发展,特别是硬件支持的 Vertex Shader 的出现,这一情况得到了很大的改观。
 2001年 nVidia 公司推出了突破性的三维加速卡 GeForce3, 介绍了Vertex Shader 和 Pixel Shader 这两个重要的功能,把开发者从 T&L 的固定渲染流水线中解放出来。因此,这两个 Shader 功能又常被称作可编程渲染流水线。一个 Shader 程序本质上是一段由应用开发者编写,运行在3D加速卡上的短小程序。这段程序可以使用汇编语言编写,也可以使用高级语言编写,但最终都要由相关的工具转换成机器代码,然后由3D加速卡在渲染图形时运行。Vertex Shader程序对顶点进行处理,Pixel Shader则对纹素进行处理。在渲染一个场景的时候,用户可以设定当前 Vertex Shader 程序,设定该 Shader 程序使用的常量 (比如世界观察变换矩阵)。之后在渲染的过程中,每一个顶点都会经过该 Shader 程序的处理,而该 Shader 程序每次也只能处理一个顶点。当用户不准备使用一些通用的 3D API (比如 OpenGL, Direct3D) 提供的变换和光照计算功能的时候 —— 虽然这些计算现在也是由加速卡完成的 —— 就可以自己编写 Vertex Shader 程序来完成这个功能。由于目前OpenGL对 Shader 的支持还没有很好的标准化,下面的讨论主要以 Direct3D 8.1提供的 Shader 接口为主展开。在Direct3D 8.1中,Vertex Shader处理器提供了一个整数地址寄存器a0.x,96个常量寄存器,12个临时寄存器,16个顶点寄存器作为输入寄存器以及输出寄存器oD0,oD1,oFog,oPos,oPts,oTo – oT7。除了 a0.x和oPts,其余所有的寄存器都具有四个浮点分量,可以用x,y,z,w来访问。Vertex Shader处理器还提供了若干汇编指令。更详细的说明可以参考Direct3D参考手册。在渲染每一帧时,计算每一根骨骼对顶点的影响和求这些影响的加权和可以完全通过Vertex Shader程序来完成,同时 Vertex Shader 程序也还要完成光照计算以及实现其它顶点效果所需要的计算。Pixel Shader 不是实现骨骼蒙皮动画所必需的,因此本文不予讨论。
要使用Vertex Shader 来实现骨骼蒙皮动画的计算,首先要把每一根骨骼的变换矩阵 (骨骼的世界变换矩阵 * 最初状态骨骼世界变矩阵的逆矩阵) 写入3D加速卡的常量寄存器中。由于每一根骨骼对应的变换矩阵有4 X 4 共16个浮点数,因此每一骨骼需要4个常量寄存器(事实上可以只存储前三行元素,这样就只需要3个常量寄存器。这样不考虑其它常量所需要的常量寄存器,比如材质,透视变换矩阵,光源等等,一个Shader中可以访问的最大骨骼数不会超过 96/3 = 32块。因此对于一个使用了大量骨骼的角色,就需要分别渲染使用了不超过32块的同一骨骼集的不同部分)。然后交给Vertex Shader程序处理的每一个顶点还需要包括使用哪几根骨骼的信息(也就是指出使用哪一个常量寄存器的中保存的变换矩阵。)同时影响一个顶点的骨骼数同样受到硬件的限制,对于Direct3D 8.1来说,这一数字是4。顶点信息还应包括影响该顶点的每一块骨骼的影响权重。由于所有权重的和应该等于1,所以最后一块骨骼的影响权重可以通过计算获得,无需包含在顶点信息内。Vertex Shader程序在收到一个顶点后,会按照该顶点骨骼索引利用a0.x寄存器一一定位相应的骨骼变换矩阵,然后对顶点位置进行变换,然后乘以相应骨骼的影响权重,最后把求得的变换结果累加,这样就得到了最终顶点的新位置。本文第四部分将具体分析一个实现骨骼蒙皮动画得Vertex Shader程序。
四 SkeAni 的实现
 SkeAni 是一个简单的骨骼蒙皮动画演示程序。该程序使了用Direct3D 8.1提供了Vertex Shader 功能和显卡的3D加速功能。SkeAni 由三部分组成:SkeAni演示程序 SkeAni.exe,模型文件 modal.saf和Vertex Shader程序 skeani.vsh。SkeAni使用了Direct3D提供的D3D Framework来简化与Direct3D API的接口。与骨骼蒙皮动画相关的代码位于 SkeAni.cpp 和SkeAni.h两个文件之中。这两个文件完成读取模型文件,初始化内部状态,插值计算某一时刻骨骼所对应的局部变换矩阵和世界变换矩阵,设置Vertex Shader中的骨骼世界变换矩阵与其它常量,设置渲染状态和渲染网格模型等功能。计算每一顶点的新位置和相应的光照则由Vertex Shader程序skeani.vsh来完成。
 Modal.saf 是 SkeAni 所使用的模型文件。该模型文件包含了四部分信息:场景基本信息,骨骼信息,网格信息和动画信息。场景基本信息指出了模型在世界坐标系中的位置。骨骼信息指出了骨骼的数量,骨骼间的层次关系,每一骨骼相对于父骨骼的变换矩阵,每一骨骼最初状态世界变换矩阵的逆矩阵。网格信息指出了顶点数,三角形面片数,顶点表,面片顶点索引表和影响网格的骨骼数与骨骼名称。动画信息指出了动画序列数和动画序列。每一动画序列控制一块骨骼,它包含了一系列的关键帧。每一关键帧指出了关键帧对应的时间和变换矩阵,这一矩阵将赋给所影响骨骼的局部变化矩阵以实现骨骼的动画。Modal.saf中的网格是一个由三角形面片组成的长方体。该模型中一共有4块骨骼,其中有3块骨骼影响网格,而同一时间最多只有2块骨骼影响一个顶点。Modal.saf用如下方法获得。首先在3DS MAX 中利用基本建模功能和Character Studio 的Physique功能制作一个场景与相关动画,然后通过Direct3D 附带的 XSkinExp 插件输出为 X 文件,然后修改 Direct3D 附带的例子SkinnedMesh将从X文件中解码出来的各类数据写入Modal.saf。具体的读取Modsl.saf文件的代码可以参考 SkeAni.cpp 中的 CSkeAniApp:: LoadSkeAniModal 函数。
 SkeAni 演示程序代码的代码执行流程如下:
        CSkeAniApp:: RestoreDeviceObjects 设置渲染状态,创建 Vertex Shader程序,在
C2常量寄存器中设置透视变化矩阵,在C1常量寄存器中设置一个光源。
。。。。。。
     D3D Framework的Render3DEnvironment 执行,开始渲染一帧。
Render3DEnvironment  调用CSkeAniApp::FrameMove
    FrameMove 获得当前时间。
   FrameMove 以当前时间作为参数调用 CSkeAniApp::Animate
  Animate 遍历每一个动画序列,插值计算该动画序列所对应的骨骼对
应当前时间的本地变换矩阵
   Render3DEnvironment CSkeAniApp::Render
Render 调用 CSkeAniApp::CalculateBonesWorldMatrices 计算根骨骼的
世界变化矩阵
     CalculateBonesWorldMatrices 递归调用自身以计算所有骨骼的世界
变化矩阵 。。。。。。
    Render 初始化渲染状态,设置当前 Vertex Shader。
    Render 计算然后从 C9 开始连续设置3个骨骼的世界-视变换矩阵
    Render设置光照与骨骼索引计算所需要的Vertex Shader常量
Render 调用DrawIndexedPrimitive进行绘制,此时 skeani.vsh 会被3D卡执行以计算顶点的新位置和光照效果。
 Skeani.vsh 是SkeAni所使用的Vertex Shader程序的源程序。SkeAni读取,编译该源程序,然后创建Vertex Shader渲染所需的程序,在需要的时候,设置此Vertex Shader程序为当前Vertex Shader程序,这时候,系统的固定几何变换与光照流水线会被关闭。Vertex Shader必须完成相应的几何变换与光照计算。Skeani.vsh 基于 D3D例子SkinnedMesh 中的 skinmesh2.vsh,略有修改。其工作原理如下:
 首先执行第一条语句计算影响该顶点的骨骼在常量寄存器中的索引。执行完该条语句后,r1.x保存的是影响顶点的第一根骨骼的变换矩阵的常量寄存器索引,r1.y则保存着第二根骨骼的索引。然后执行第二,第三条语句计算第二根骨骼的权重,该权重将保存在r0.w中。然后第四至第六条语句用第一根骨骼改变该顶点的位置和朝向。第七,第八条语句将结果乘以第一根骨骼的权重。这样,r4,45分别保存第一根骨骼对顶点的位置和朝向的影响。第九至第十三条语句计算第二根骨骼的对顶点的影响并且累加到r4,r5寄存器。第十四和第十五条语句对顶点进行投影运算,然后将顶点得归一化投影坐标通过oPos输出。第十六至第十八条语句归一化顶点的法向量,为后面的光照计算作准备。最后几条语句则计算泛光和镜面高光然后通过oD0,oD1输出。Vertex Shader的输出结果将会交给渲染流水线的后续功能部件进行处理并最终在屏幕上显示出来。由此便实现了硬件支持下的骨骼蒙皮动画。
五 结论
 骨骼蒙皮动画早已是动画制作软件中广泛使用的角色动画技术。现在,随着计算机计算能力特别是3D硬件处理能力的不断提高,实时骨骼蒙皮动画也已经从早期的试验阶段走向成熟,并且获得越来越广泛的应用。可以想象,在不久的未来,越来越多只在传统动画制作软件中应用的各种技术,比如反向动力学,自由形态变形等等,会成为实时动画中广泛采用的主流技术,从而使得实时动画应用实现能够以往只能在动画电影中才能看到的各种生动逼真的动画效果。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值