DX10终极杀人武器!Crysis全方位测试
DirectX10 游戏的美好未来
● DX10新特点概要
Microsoft发布的DirectX 10代表了自从可编程Shader出现以来在3D API方面的最巨大的进步。通过一番脱胎换骨般的重建,DirectX 10展现出一系列非常醒目的新特性,包括高度优化的运行时,强大的Geometry Shader,纹理数组等等;这些特性将引领PC实时三维图形进入一个全新的世界。
在过去的十年中,DirectX已经稳步成为了Microsoft Windows平台上进行游戏开发的首选API。每一代的DirectX都带来对新的图形硬件特性的支持,因此每次都能帮助游戏开发者们迈出惊人的一步。
● DX10的架构特性以及带来的好处
DirectX之所以在广大的开发者中流行,是得益于它的简单易用和丰富的功能特性。然而,DirectX一直被一个主要的问题所困扰,那就是高CPU负载。
在图形编程API出现之前,三维程序直接向图形硬件发送图形命令来完成绘制工作。虽然这样绘制效率相当高,但是程序中要应对各种不同硬件上的不同命令,这使得开发工作十分困难,且程序中很容易出错。当越来越多不同的图形硬件冒出来的时候,这就成了一件十分不能忍的事。
于是便出现了像DirectX和OpenGL这样的图形API。它们在图形硬件和应用程序之间架起了一个中间层,这样,应用程序可以使用统一的图形编程代码,而对底层各种硬件的适应,则由图形API来完成。这就将游戏程序员们从与大量的图形硬件交换意见的恶梦中解救出来,使他们能够将精力集中在“制作伟大的游戏作品”上面。
但是这样就完美了么?不是的。每次DirectX从应用程序那里接收到一条命令,它就需要先对这条命令进行分析和处理,再向图形硬件发送相对应的硬件命令。由于这个分析和处理的过程是在CPU上完成的,这就意味着每一条3D绘图命令都会带来CPU的负载。这种负载给3D图象带来两个负面影响:限制了画面中可以同时绘制的物体数量;限制了可以在一个场景中使用的独立的特效的数量。这就使得游戏画面中的细节数量受到了很大的限制。而使图像具有真实感的重要因素,偏偏是细节量。
DirectX 10的一个主要目标就是最大地降低CPU负载。它主要通过三个途径来达到这个目的:第一,修改API核心,使得绘制物体和切换材质特效时的消耗降低;第二,引入新的机制,降低图形运算操作对CPU的依赖性,使更多的运算在GPU中完成;第三,使大量的物体可以通过调用单条DirectX绘制命令进行批量绘制。下面我们就来仔细的看一下这三种方式:
1.降低绘制消耗
第一种方式的一个重要例子就是DirectX 10中对三维数据和绘制命令进行验证过程的修改。所谓三维数据和命令的验证,是指在DirectX绘制图形之前,对传给它的图形数据和绘制命令进行格式和数据完整性的检查,以保证它们被送到图形硬件时不会导致硬件出问题;这是很必要的一步操作,但是不幸的是它会带来很大的性能开销。
从上表我们可以很容易的看出,在DirectX 9中,每次绘制一帧画面之前,都会对即将使用的相关数据进行一次验证。而DirectX 10中,仅当这些数据被创建后验证一次。这很明显是可以大大提高游戏进行中的效率的。
2.降低CPU依赖性
在降低CPU依赖性方面,DirectX 10 引入的三个重要机制就是:纹理阵列(texture arrays)、绘制断言(predicated draw)和数据流输出(stream out)。不要被这三个晦涩的名词吓倒,实际上它们是三个不难理解的机制。
首先我们来看纹理阵列。传统的DirectX在多张纹理中进行切换的操作是一个很消耗CPU的操作,因为每切换一次,都要调用一次DirectX的API函数。而每绘制一个使用新纹理的物体,就要进行一次这样的切换操作;有时为了实现特殊的材质特效,绘制一个物体时可能就要切换好几次纹理,开销很大。
所以,以前游戏美工们经常索性将大量的小纹理拼合到一张大的纹理中,通过给不同的三维物体分配这张大纹理的不同局部以期减少纹理切换,提高游戏运行效率。然而,想想也知道,这件事做起来会非常麻烦,而且DirectX 9中对纹理的尺寸的限制是4048×4048像素,也就是说,如果要容下更多的小纹理块,可能就得开辟很多张这样的大纹理。
DirectX 10引入的新的纹理阵列机构,将允许在一个由显卡维护的阵列中容纳512张单独的纹理,而且,在shader程序中可以使用一条新的指令来获取这个阵列中的任意一张纹理。而这种shader指令是运行在GPU中的;这样,就把原来要消耗很多CPU时间的纹理切换工作轻松地转给了GPU。由于纹理一般是直接放在显存中的,所以这件事由同显存一起放在显卡上的GPU来完成真是天经地义,大快人心。现在,在应用程序的层面,只要一开始设置好纹理阵列中的纹理,然后每次绘制一个物体时为它指定一个纹理的索引号,并同物体三维数据一起传递到shader中,就可以放心的让GPU来给物体选纹理了。
然后说说绘制断言。在一般的三维场景里,很多物体都是完全被别的物体挡在后面的。这时候如果要显卡绘制这些物体就是白费力气。尽管高级的GPU可以通过硬件算法将场景画面中被挡住的像素(注意是像素)预先剔除,但是仍然会有一些处理消耗。例如,一个完全被挡住的复杂的角色模型,它的身上可能有几千个顶点,需要做复杂的骨骼皮肤动画处理、顶点光照运算等等——然而,GPU是在处理完这些顶点之后,并要把这个角色模型一个像素一个像素地画到画面中时,才开始判断每个像素是否需要画——当所有的像素都被剔除了时,之前做的顶点处理也就全白费了。于是,游戏开发者们想出了一个方法来解决这个问题,这就是绘制断言。
简言之就是用一个可以代表某个复杂物体的简单物体来判断这个物体是否被全部挡住了——例如用一个可以罩住刚才那个角色的大盒子,当绘制这个盒子时,如果所有的像素都被P掉了,也就说明这个盒子肯定完全看不见,更甭说里边的角色了,也就不用做什么骨骼皮肤运算啦之类的操作了。一个盒子顶多有八个顶点,这学过初中几何的人都知道,相比处理几千个顶点,开销小得多。但是,以前这步中有些部分也是要在CPU中完成的。现在,在DirectX 10中,已经彻彻低低的交由GPU来做了。真是很周到啊。
最后来看看数据流输出。这是一个DirectX 10中的非常重要的特性,它允许GPU上的Vertex shader或Geometry shader向显存中添加数据!您也许觉得这感觉没什么,然而在以往的vertex shader中是比较独特的。以前的DirectX中,vertex shader只能读取显存中已有的顶点数据;而DirectX 10中引入的新的Geometry shader,不但能读取显存中的顶点数据、几何(点、线段、三角形)数据,还可以生成新的几何数据放回显存。鉴于其重要性,这一机制我们将在第四节:几何Shader以及Stream Out中单独介绍。
3.批量绘制
在DirectX 9中,对渲染状态的管理一直是一个十分消耗CPU时间的操作。所谓渲染状态,是指显卡进行一次绘制操作时所需要设置的各种数据和参数。例如,要绘制一个人物角色,就需要先设置他的几何模型数据的数据格式、纹理过滤模式、半透明混合模式等等——每设置一项,都要调用一次DirectX API,占用大量CPU时间,极大的约束了渲染的性能。
为了使这些操作能够批量的进行,DirectX 10中引入了两个新的结构——状态对象(state object)和常量缓冲(constant buffers)。
状态对象就是将以前的零散状态按照功能归结为几个整体,这样,当要设置一系列相关状态时,无需为每一个状态来调用一次DirectX API,只需要调用一次将这些状态统统设置到显卡中去。
而常量缓冲是另一个十分有意义的机制。在绘制模型前的准备工作中,渲染状态的设置只是一小部分。还是拿绘制人物角色来说,能照亮这个人的光源的颜色、位置、类型、范围等等,都要提前设给显卡;为了通过骨骼来带动他的皮肤做出姿势,还要设置骨骼的位置信息等等——总之是很多东西;而这些东西主要都是通过GPU中的常量寄存器(constant registers)来传递给它的。每个常量寄存器可以存储一个4维的浮点型向量(即四个浮点数)。常量寄存器是游戏程序向GPU输入游戏场景中数据的重要途径。在DirectX 9中,这种常量寄存器的数量是十分有限的,这在下文中的对比图表中我们也可以看到;而且每次更新一个寄存器,都需要调用一次DirectX API函数。而DirectX 10中,使用了常量缓冲(constant buffer)这种结构;在每个constant buffer中都可以容纳4096个常量,而且只需调用一次API就可以更新一大批常量。
采用constant buffer绘制的大量克隆物体
图片来源:Microsoft DirectX 10 SDK
举一个具体些的例子来对比一下:在以前,如果程序中想在场景里画很多的树木和杂草,可以采用一个类似于“克隆”的方法:先做好一棵或几棵树、草的三维模型,然后在画一帧画面时,不停的在不同的位置、方向,用不同的大小为参数,调用DirectX API的绘制函数来画这些模型,就可以画出很多草木来;但是每画一棵,都要设置一大堆参数后调用一次API;这是很耗CPU时间的,所以在以前的游戏中很难有大规模且细节丰富的森林场景。
而在DirectX 10中,我们可以先把树、草的几个模型设给显卡,然后将所有要画的树木的位置、方向和大小一次性的写入到constant buffer中,然后告诉DirectX——画!显卡就一下把所有的树木和草都一起绘制出来了。 这样,像Crysis那样的游戏,才能营造出十分逼真的森林效果。
总结
总之,DirectX 10降生到这个世上,就是为了带走由于CPU负载过大而给游戏图形效果带来的苦难。通过提前数据验证、纹理阵列、绘制断言、数据流输出、状态对象、常量缓冲等机制,帮助游戏的效果和效率上升到一个新的高度。