当前位置:编程学习 > wap >>

【转】一种简单有效的3D模型的动画多线程方案

摘要

              本文将介绍一种简单而有效的多线程方案,能加速大量的3D模型的动画渲染。此方案通过使用线程池、双缓冲、间隔更新等方法显著提升了骨骼动画的性能。尤其值得一提的是,此方案可以使基于CPU的骨骼动画在多核系统上获得与基于GPU动画接近的性能,从而在某些情况下可成为除基于GPU动画外的另一种良好选择。

介绍

              在现代游戏中,动画扮演着重要的角色,它使3D模型看上去更加真实。骨骼动画可能是今天我们使用的最高级的实时动画技术之一。在这种技术中,定义模型外形的蒙皮通常被称为皮肤,而支撑皮肤的结构通常被称为骨骼,皮肤的形状和位置随着骨骼的变化而变化。由于这种技术的算法涉及大量的矩阵运算[1],因此骨骼动画的计算密集性相当高。而且,大型多人在线角色扮演游戏中通常有一些大的游戏场景,比如集市,战场,闹市区等。这些场景中有很多动态的3D模型。因此处理这些模型的骨骼动画将成为游戏中一个显著的性能瓶颈。

              当前,已经有一些软硬件技术被用于优化骨骼动画的性能。比如对模型骨骼使用LOD(细节分层)技术能减少远处模型的动画复杂性。而在骨骼动画的算法实现中使用SSE指令能获得比原先C实现的代码至少高两倍的性能。对于支持顶点着色器(vertex shader)的显卡,大部分动画渲染工作能从CPU移到GPU上,并获得显著的性能提升。

              在过去的几年里,PC业界有两种趋势,一种是越来越多的台式机和笔记本的处理器是多核的,这意味着现在开发的游戏将在一个主流为多核处理器的市场上发布;另一个趋势是笔记本市场的增长速度明显超过了台式机。这意味主要配备集成显卡的笔记本将在游戏玩家中越来越普遍。因此,如何合理利用多核系统的CPU和GPU资源将是游戏开发者需要认真考虑的问题。

              本文提出了一种模型动画的多线程方案。此方案能通过利用多核处理器资源改善游戏的性能。此方案通过线程池、双缓冲和间隔更新的方法把骨骼动画的渲染管线并行化。而且,一个渲染大量动态3D模型的演示程序被开发用于验证此方案的有用性。测试表明此方案既有利于基于CPU的骨骼动画,又有利于基于GPU的骨骼动画,特别是对基于CPU的动画,通过使用这个方案,能使性能大大优于传统单线程的实现;并且,在某些高端显卡上,使用此方案的基于CPU的动画能体现了与基于GPU的动画近似的性能,而在主流的低端显卡上,更是显示了较明显的性能优势。

              本文接下来的章节组织如下:第二部分介绍骨骼动画的技术和基于CPU和基于GPU实现的优缺点;第三部分阐述此方案及其实现细节,然后第四部分将介绍开发的演示程序,第五部分分析此方案的性能。第六部分提出了一些额外的考虑。最后第七部分是本文的总结。

--------------------编程问答-------------------- 骨骼动画

              骨骼动画可能是今天最常用的高级实时动画技术之一。在骨骼动画中,称为“皮肤(skin)”的多边形网格随着称为“骨骼(skeleton)”的支持结构的改变而不断变换。这种根据骨骼的动画而实现的皮肤转换处理成为“蒙皮(skinning)”技术。骨骼动画系统使用转换矩阵表示一根骨骼的坐标系相对于其父骨骼的坐标系的转换关系。这些转换矩阵通常保存在模型空间中。骨架中所有骨骼的转换矩阵组成一个时间点的骨骼动画帧。每个动画集都由一系列时间点的动画帧组成。通常游戏逻辑决定当前时间内模型使用什么样的动画集,然后启动骨骼动画的渲染管线去渲染模型。这条渲染管线能被分为三个基本阶段。它们分别是“骨骼变换”、“蒙皮”和“绘制”。

              第一个阶段的主要任务是变换骨骼并获得在世界空间中的骨架转换矩阵。在此阶段中,首先从一个动画集中选择两个时间点的动画帧(骨骼转换矩阵),保证当前时间位于这两个时间点中间,然后当前时间的动画帧由前后两个时间点的动画帧通过插值获得。如果此模型关联多个动画集的话,则所有插值后的动画帧被混和形成当前动画帧。最后,递归地沿着骨架层次,当前动画帧的每个骨骼的转换矩阵被转化为相对与世界坐标系的转换矩阵。通常,第一阶段在CPU上执行。

              第二个阶段被称为“蒙皮(skinning)”。由于皮肤顶点相对于所在骨骼是静止的,所以可以通过变动骨骼获得新的皮肤形状。由于缺省的皮肤保存在模型空间中,因此需要根据骨骼的世界转换矩阵,把皮肤顶点也转换到世界空间中去。在阶段二的实现中,模型空间的皮肤顶点首先通过一个偏移量矩阵转换为相对于所在骨骼的坐标,然后根据第一阶段输出的骨骼的世界转换矩阵,转换为在世界空间中的皮肤顶点。有时一个皮肤顶点受多个骨骼的影响,此时要对骨骼矩阵和偏移量矩阵的乘积进行加权求和。“蒙皮”可以在CPU上执行,也可以在具有顶点着色器的GPU上执行。本文中,“基于CPU的骨骼动画”意味着由CPU处理蒙皮,而“基于GPU的骨骼动画”则表示由GPU处理蒙皮。

              随着GPU的速度迅速增加以及最大程度的平行性被开发出来,用GPU处理蒙皮在诸多情况下都是有利的。一般来说,在今天的中高端独立显卡上,基于GPU的蒙皮比基于CPU的蒙皮更快并且更加节省的系统内存。然而,对于某些应用,在CPU上执行蒙皮更有好处[1]。在CPU上执行蒙皮能提高应用对大部分系统的兼容性。老系统上可能有不支持基于GPU蒙皮的显卡(比如GeForce2 和GeForce4MX)。而且,许多应用为了阴影体(shadow volumes)生成和冲突检测需要使用转换后的数据,但当前的显卡并不能很好地支持从显卡中大量获得已经处理过的数据。而在CPU上蒙皮允许应用很容易地访问动画后的蒙皮数据,且允许GPU做光照和裁减等运算。当然,计算阴影体可以完全放到有些显卡上,但是这种方案在今天的主流显卡上并不是特别有效。允许在GPU上进行蒙皮的显卡也可能由于一些资源上的限制,要求大的骨骼模型被分为多块小的网格才能渲染,这样会降低效率。在CPU上进行蒙皮没有上述限制,并且可以通过使用SSE指令优化和多线程优化使性能接近基于GPU的实现。正由于这些原因,一些游戏如DOOM III和Quake 4采用了基于CPU的蒙皮,使得游戏能运行在最广泛的系统配置上。

              最后一个阶段是把皮肤顶点画到屏幕上。在此阶段中,在世界空间的皮肤顶点经过裁减、栅化、着色等步骤,最终画到屏幕上。此步骤由GPU完成。

--------------------编程问答-------------------- 方案

              本方案的主要目标是基于软件最大程度地挖掘骨骼动画渲染管线的并行性。方案的设计来自于一些对骨骼动画渲染管线的观察。一方面,由于涉及大量的矩阵运算,此管线是计算密集的;另一方面,不同动态模型的渲染过程通常是独立的。因此,多线程能有利于骨骼动画渲染管线的性能。而且,临近的很小时间间隔中的动画数据(例如骨架的形状)通常非常相似,因此在许多情况下(尤其是在MMORPG中),不一定需要每帧都更新模型动画。间隔地更新模型动画能减轻总线压力,改善CPU和GPU之间的并行性。

              此方案包含三个部分:“线程池”、“双缓冲”和“间隔更新”。线程池中的动画线程执行动画渲染管线的第一阶段和第二阶段[1]。第三阶段由主线程执行,因为这个绘制阶段包含大量对图形API(例如DirectX和OpenGL)的调用,由于性能方面的问题,这些调用通常不合适放在不同的线程中执行[4]。此方案使用双缓冲最大化阶段三和阶段一、二之间的并行性,且简化它们的同步。最后,“间隔更新”的技巧是可供选择的,它能控制动画更新的频率。

              下面的章节将描述这三大部分的细节,以及本方案的资源管理策略,此策略与传统的管理动画资源的策略略有不同。

3.1 线程池

              在本方案中,当渲染一个动态模型时,有一个被称为动画线程的独立线程会分配给这个模型去处理骨骼变换和蒙皮。为了并行渲染大量的动态模型,本方案采用了线程池。这主要基于如下一些优点:首先,线程池可以避免频繁创建和销毁动画线程所致的沉重代价。其次,通过使用线程池,动画线程的数目可以随着动态模型的数目和处理器核心的数目的变化,具有良好伸缩性。另外,线程池提供了一个简洁的编程接口,通过它,主线程只需要把动态模型提交到线程池的任务队列中,而不需要处理线程调度等方面的问题,这些都由线程池自动完成。使用一个良好封装的线程池,比如WIN32系统线程池,你只需要对模型类的定义做少量修改即可象如下代码所示那样实现多线程。

--------------------编程问答--------------------

从上面的代码中可以看到,在类方法Animate中,通过调用一个WIN32线程池API,QueueUserWorkItem[2],模型被提交到线程池。并且,通过使用这个API的第三个参数,可以说明线程池中的最大线程数。一般来说,一个应用中的活动线程数应该不大于处理器核心的数目。由于线程的切换开销,过多的线程并不会带来更多性能。

              在骨骼动画的渲染管线中,由动画线程执行的阶段一和阶段二的输出,将被作为由主线程执行的阶段三的输入。因此主线程必须与动画线程同步以避免数据竞争(data racing )。上面的代码中建议了一种简单有效的同步方法:定义一个计数器计算线程池中的模型数,主线程和动画线程通过轻量级的WIN32“Interlock”同步方法[3]互斥地访问这个计数器。只有当这个计数器为零的时候,主线程才能开始绘制模型。

3.2 双缓冲

              尽管上面的同步方法避免了多线程化的管线中的数据竞争。但它使阶段三和阶段一、二只能串行执行。为了使得阶段三和阶段一、二能并行执行,本方案采用了双缓冲技术。

              在模型动画中可以使用双缓冲主要基于如下假设:动画数据,例如骨架姿势和皮肤顶点,在临近的帧中是非常接近的。因此主线程可以使用前帧产生的动画数据,而不必等当前帧的最新数据。对于动画的视觉效果来说,一帧或几帧延迟的可预测的一致性行为不会影响游戏体验。

              在双缓冲的实现中,被主线程和动画线程共享的所有模型动画数据都有两个拷贝存放在双缓冲中。一个缓冲被称为读缓冲,在这里主线程把动画数据读到管线的阶段三中;另一个缓冲被称为写缓冲,在这里动画线程写入管线的阶段一、二的输出数据(见图一)。两个缓冲每隔一帧或几帧被交换一次。

--------------------编程问答-------------------- 说我恶意刷楼,冤枉啊~~~非要我使出杀手锏不可!!



图1: 双缓冲的机制

 

              当模型第一次进入摄像机的视域,模型的读缓冲中的动画数据可能要么没有准备好、要么太陈旧以至不能被主线程用来进行绘制。为了解决这个问题,每个缓冲都附带一个生命周期。当其中的动画数据被更新后,缓冲将被赋予一个新的生命周期,然后在接下来的每帧中,这个生命值会被递减。而主线程只使用生命值非负的缓冲去绘制模型。

              下面的代码描述了双缓冲在模型类中的实现:




--------------------编程问答-------------------- 双缓冲避免了骨骼动画渲染管线中阶段三和阶段一、二之间的数据竞争,并且使主线程和动画线程之间的同步从帧内转移到帧间。因此在游戏循环开始的时候,主线程需要执行一些操作更新双缓冲的状态。下面的代码是双缓冲在游戏循环中的实现。



  双缓冲开发了主线程和动画线程之间的最大的并行性。而且,它能方便多线程编程。例如,程序员可以假设动画数据总是可被主线程使用;以及处理一个模型的动画线程能够从另一个模型的读缓冲中获得动画数据,这使得其他并行处理得以实现,比如并行的模型间冲突检测。双缓冲技术的主要缺点是额外的内存消耗。例如,当使用基于CPU的蒙皮技术渲染100个模型时,假设每个模型有5000个顶点,每个顶点有32字节,那么额外的内存开销有16兆字节。 --------------------编程问答-------------------- 3.3 间隔更新

              在一些MMORPG的游戏场景中有大量的动态3D模型,这些模型通常被称为PC (Player-Character,玩家角色) 或NPC (Non-Player-Character,非玩家角色)。NPC由游戏控制。在许多情况下,这些NPC的动作经常是简单的、并非快速移动的。由于临近的短时间间隔中的动画数据(例如骨架姿势)非常接近,因此,在这些情况下,并不必要每一帧都更新这些模型的动画。实事上,传统基于CPU的蒙皮常慢于基于GPU的蒙皮的一个主要原因是大量的皮肤顶点频繁地上传到显卡,以至于CPU与GPU之间的总线带宽成为瓶颈。本方案建议了一个技巧:每隔几帧才更新某些模型的动画数据,这样能减轻总线流量的压力,且提高渲染的效率。下面的代码是间隔更新技巧在主循环中的实现。



下面的代码是间隔更新技巧在模型类中的实现。

--------------------编程问答-------------------- 巨汗,这帖子发了半小时还没发完,郁闷~!!还得不停的借别人的号才能继续发帖~~!强烈鄙视!!

实际的更新模型动画的频率等于帧速率除以更新间隔。当间隔为1,本方案即与传统方法一样每帧更新动画。当间隔增加时,帧速率通常也会相应增加,但更新频率有可能下降。因此在实际游戏中,开发者需要选择合适的更新间隔,使动画更新的频率保持在一个合理的范围内。

              除了减轻总线传输的压力外,间隔更新与多线程结合时还体现了另一个好处。由于间隔更新允许动画任务的执行时间跨越多个帧,这使动画线程能有足够的时间处理一些耗时的动画任务,并且很少影响主线程的速度。

3.4 资源管理

              3D 模型的资源包含顶点缓冲,索引缓冲和纹理等。在处理模型动画时,CPU 和 GPU 会经常访问这些资源,因此这些资源的位置和使用方式将对骨骼动画渲染管线的性能产生重要的影响[6]。

              对于基于 GPU 的动画,渲染管道的大部分负载由显卡承担。在DirectX9中,这些资源通常作为“默认资源”或“托管资源”保存在本地显存中,使GPU能快速存取。此策略仍适用于本文建议的方案。 

              对基于CPU的动画,诸如网格顶点缓冲的资源会被CPU频繁更新。通常情况下,最好将这类资源作为“动态默认资源”置于非本地显存(如 AGP 显存)中。例如,在具体实现中,可以使用DirectX的POOL_DEFAULT标记和USAGE_DYNAMIC提示创建模型网格的顶点缓冲[5]。尽管上述资源管理策略最有利于传统基于CPU的动画,但获得的性能在大多数情况下依然无法和基于GPU的动画相比,这是由几方面的因素导致的。在资源管理方面有两个主要原因:1)GPU对“本地”显存的存取速度要远高于“非本地”显存;2)基于CPU动画每帧都需要上传网格顶点数据到GPU,这容易使总线成为瓶颈。

              本文建议的方案能间隔地更新模型动画。这样网格顶点等资源不需要每帧都上传到显卡中。通过间隔更新,此方案使模型动画的动态资源转变为半动态的资源。通常把这类资源作为DirectX的“托管资源”而不是“动态默认资源”来管理更加合理。DirectX会把这类资源放在恰当的位置,使CPU和GPU能高效地访问它们。

              使用“托管资源”的另一个理由是它使DirectX与本方案的多线程上下文具有更好的兼容性。

演示应用

              为了检验此方案是否行之有效,我们开发了一个渲染大量3D模型动画的演示应用。这个演示程序改进了DirectX的例程“MultiAnimation”[7],实现了基于CPU和基于GPU的骨骼动画,并且同时支持单线程和多线程版本。

              本演示展示了许多模型在一个平面地板上漫步的场景。用户或应用(在缺省状态下)可以控制这些模型。每个模型都拥有三个动画集,即闲走、行走和慢跑。动画控制器会将这些动画集融合在一起,确保每个动画可以平滑过渡至下一个动画。这些模型之间会做碰撞检测,可以互相阻碍彼此的运动。通过回调系统,模型可在动画中适当的情况下播放脚步声。在本演示中,动画线程执行的动画任务包括动画集融合、骨架变换和蒙皮(针对基于 CPU 的解决方案)三项工作。演示程序只处理在摄像机视截体中的模型的动画和绘制。在应用控制的模式下,能通过“箭头”键移动摄像机。在用户控制模式下,可以通过“A/W/D”键控制某个被选模型的移动,而摄像机则会跟随模型。



图 2: 演示应用的屏幕截图

 

              一个配置文件用于初始化演示应用进入各种运行模式。您可以采用下列 4 个选项配置演示程序:

Method:选择基于 CPU 的蒙皮或基于 GPU 的蒙皮;

Threads:线程池中的动画线程的数量。“非零”表示使用本文建议的多线程方案;“零”表示传统的单线程版本;

Models:初始的模型数量;

Interval:一个动画更新间隔所包含的帧数。此选项只在本文建议的方案中有效。

--------------------编程问答-------------------- 性能分析

表 1: 测试平台配置

 

性能测试和等级评定均使用特定的计算机系统和/或组件进行测量。这些测试反映了英特尔产品的大致性能。系统硬件、软件设计或配置的任何不同都有可能影响实际性能。

 

   首先,在此配置上对传统单线程的骨骼动画的实现进行性能评测,并将该评测结果作为基准。配置 A是一种采用了多核处理器和高端显卡的台式机配置。测试结果请参见表 2。测试1是基于CPU的骨骼动画(也就是在CPU上进行蒙皮)的测试结果,测试2是基于GPU的骨骼动画(也就是在GPU上进行蒙皮)的测试结果。显然,我们可以看到:传统的基于 CPU 的蒙皮速度远不及基于 GPU 的蒙皮速度。

表 2: 在配置A平台上测试单线程的骨骼动画性能

 

    其次,在配置A上测试使用本文建议的方案实现的多线程骨骼动画的性能。测试结果请参见表3。对于基于CPU的骨骼动画,此方案的性能比传统单线程方案好很多 (把测试3, 测试4, 测试5和测试1相比较),前者的速度约为后者的2.6到9倍。对于基于GPU的骨骼动画,虽然此方法带来的性能增幅不如基于CPU的动画那样高,但此方案也明显优于传统单线程方案(将测试 6、测试 7、测试 8 与测试 2 进行比较)。值得注意的是,此方案在某些情况下能使基于CPU的骨骼动画的性能与基于 GPU 的动画的性能几乎相当(将测试 4 与测试 7 进行比较),有时甚至更快(将测试 4 与测试 2 进行比较或将测试 5 与测试 8 进行比较)。上述测试结果的原因是,基于CPU的动画与基于GPU的动画相比有更多部分的动画渲染管线在CPU上,因此能更加充分利用此方案的优势,即通过多线程和有效降低总线压力来提高性能。

表 3: 在配置A上测试本方案的性能 ( 动画线程数 = 3)

 

     从表三中同样可以看出,应用的性能确实受益于间隔更新的技巧。然而,尽管更大的更新间隔能提高帧速率,却降低了动画更新的频率(由表 3 中的数据计算得出)。如果动画更新频率太低(如位于 15 以下),我们的视觉体验就会受到影响。因此,应用程序应采用适当的更新间隔,使动画更新频率保持在一个合理的范围内,并且取得令人满意的帧速率。在测试 4 中,演示程序采用了6帧的更新间隔,使基于 CPU 的动画获得了与基于GPU的动画接近的性能,并且使动画更新频率维持在21~36帧之间。

     我们可以使用英特尔® 线程分析器[8]来捕捉此方案的线程行为细节。英特尔® 线程分析器这一线程分析工具可插入英特尔® VTune™ 性能分析器中,可用来分析线程的负载均衡和任何潜在的线程开销。用线程分析器对此方案的演示程序进行分析,典型的输出结果(见图 4)表明,借助双缓冲,动画线程和主线程在极低的同步开销下几乎可以完全并行运行(由“全利用”色带表示);并且由于线程池的动态调度,动画线程之间的工作负载(由“活动”色带表示)能够达到完美的均衡。而主线程和动画线程的工作负载的差距意味着动画线程可以容纳更多重负载的任务。

 




--------------------编程问答-------------------- 测试结果表明,采用此方案的基于 CPU 的动画的性能大大优于基于 GPU 的动画(前者约是后者的 3.5~4.2 倍)。原因在于,在基于 GPU 的动画中,大部分动画渲染管线的负载都需要GPU来处理,这使得配置B中的集成显卡成为应用的性能瓶颈。而基于 CPU 的动画能够节省有限的 GPU 资源,并平衡CPU与GPU之间的负载,从而使应用的整体性能得到提高。此外,通过测试还可以发现,在配置 B 中,间隔更新方法对基于 CPU 和基于 GPU 的动画的性能几乎都没有影响。这是因为英特尔® 集成显卡采用了统一内存架构,GPU没有本地显存且总是访问主存中的数据。因此无论采用什么样的更新间隔,对总线流量都不会有明显的影响。

其他考虑

              一些额外的考虑可以扩展本文提出的方案。

              在实际游戏中,除了一些动作简单、节奏缓慢的模型,比如四处漫游的非玩家角色(NPC)之外,还有一些动作复杂,快速变化的模型,比如正在执行打斗动作的玩家角色(PC)。这两类模型常同时出现在同一个游戏场景中,但需要不同的动画更新频率。尽管上述demo中只实现了一种更新间隔,但本质上此方案支持多种更新间隔。最简单的实现两种更新间隔的方法是,对需要快速更新的模型,由主线程串行地执行渲染管线,仅对可以慢速更新的模型,放到线程池中处理;更加灵活和高效的方法是,定制一个具有两个优先级队列的线程池,取代缺省的系统线程池。高优先级队列存放需要快速更新动画的模型,低优先队列存放慢速更新的模型。线程池中的动画线程先处理高优先队列中所有模型,然后处理低优先队列。由于主线程每个间隔周期都会与相关的动画线程同步,所以不会发生低优先队列等待处理的饥饿现象。

              回顾此方案,尽管是为并行化骨骼动画的渲染管线,但它可以很容易推广到其他动画类型,例如变型动画(morphing),粒子动画,布料和柔体仿真等。除了进行骨骼变换和蒙皮外,此方案中的动画任务可以包含其他计算密集的任务,例如物理模拟、AI、冲突检测、阴影体生成等。在此方案的扩展应用中,根据具体任务特性,可以对此方案灵活地裁减,比如有些情况下不需要双缓冲,则可以保持传统的单缓冲,并依然利用本方案的并行性和性能。

总结

              本文提出了一种简单而有效的多线程方案,它能充分利用多核处理器的计算能力,提高骨骼动画的性能。此方案使用线程池、双缓冲和间隔更新的方法并行化了骨骼动画的渲染管线。此方案被证明对基于CPU和基于GPU的骨骼动画都有效。尤其对于基于CPU的动画,通过使用此方案,获得的性能比传统的单线程实现好很多。而且,与基于GPU的动画相比较,使用此方案实现的基于CPU的动画在某些高端显卡上的性能并不逊色,而且在主流集成显卡上反而显示了更好的性能。其原因是该方案能使基于CPU的动画更好地利用多核处理器的运算能力并且省下GPU资源可供渲染复杂的场景。因此,该方案特别有利于需要广泛的显卡兼容性的网络游戏,以及一些消耗大量GPU资源的视频游戏。而且,该方案能扩展应用到更多动画类型。此方案的主要不足是内存开销较大。尽管如此,该方案提供了一种模型动画的可选的优化方法,而且并不排斥传统方案。 因此,在开发一个实际游戏的过程中,开发者可以通过把此方案与其他优化方案结合起来,平衡CPU和GPU的负载,从而在多核系统上获得最佳的整体性能。

--------------------编程问答-------------------- 顶 --------------------编程问答-------------------- 楼主不容易,顶一下!
--------------------编程问答-------------------- 希望能看到跟多的内容啊!!!!!!!!!!! --------------------编程问答-------------------- 很好  
补充:移动开发 ,  超极本开发
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,