由于当前显卡的设计特点是基于3D加速的, 所以微软推出DirectX8.0后, 就放弃了性能并不高的DirectDraw组件。因此从D i r e c t X 8.0开始, 虽然仍然可以使用DirectDraw组件, 但已经不是推荐做法。那么在DirectX8.0后, 使用D3D来做2D渲染是正确的选择。
在2D游戏中, 大量的工作是要做精灵的渲染。站在D3D的角度看, 所谓精灵, 其实就是一个绑定了纹理的矩形。因此, 在做2D精灵渲染时, 关键就是怎样渲染这个绑定了纹理的矩形。下面将详细讨论在D3D中, 2D精灵的几种渲染方法, 并比较它们的性能。
1 使用D3DXSprite渲染 (方法一)
为了使D3D程序员在做2D渲染时更加容易, 微软在DirectX里提供了ID3DXSprite接口。通过该接口, 程序员不用去关心诸如建立观察矩阵、投影矩阵等概念, 而只需要使用简单的几个函数的调用就可以完成将精灵进行拉伸、旋转、平移、渲染等操作。
1.1 创建D3DXSprite对象
要使用ID3DXSpirte, 首先需要使用D3DXCreateSprite函数创建D3DXSprite对象。下面的代码段展示如何创建一个D 3 D X S p r i t e对象。在这段代码里假设pD3DDevice是已经创建好的D3D设备指针。
LPD3DXSPRITE pSprite;
if (FAILED (D3DXCreateSprite (pD3DDevice, &pSprite) ) )
return;
1.2 在屏幕指定位置渲染
使用D3DXSprite对象渲染时, 其坐标系是以屏幕左上角为坐标原点, 沿原点向左是X坐标增大的方向, 沿原点向下是y坐标增大的方向。D3DXSprite的绘图操作必须放在D3DXSprite对象的Begin () /End () 函数对之间完成。在调用其Begin函数时可以同时指定精灵渲染时的行为 (比如打开Alpha混合开关) , 而具体的操作则交给Draw函数来完成。如下:
pSprite->Begin (D3DXSPRITE_ALPH ABLEND) ;//打开渲染开关, 同时打开Alpha混合开关
HRESULT hr=p S p r i t e->D r a w (pTexture, //纹理指针
NULL, //纹理矩形, 如果是NULL, 则使用整个纹理图片
NULL, //参考点, 如果是NU LL, 则使用精灵左上角坐标,
//即 (0, 0, 0) 位置
&D3DXVECTOR3 (x, y, z) , //渲染的位置, 用x、y、z坐标指定
0xFFFFFFFF//混合的颜色
) ;
pSprite->End () ;//结束渲染
1.3 世界变换
世界变换包括了缩放、旋转、平移等操作。对于这些操作, 可以使用D3DXMatrix Scaling函数、D3DXMatrixTranslation函数、D3DXMatrixRotationZ计算出相应的变换矩阵, 再用D3DXMatrixMultiply函数按一定的变换顺序将它们累积起来。然后使用D3DXSprite对象的SetTransform函数设置精灵的世界变换。
如果使用了SetTransform函数来设置变换矩阵, 在Draw函数里, 渲染位置的那个参数应该设置为NULL, 如:
pSprite->Draw (pTexture, NULL, N U L L, N U L L, 0 x F F F F F F F F) ;
2 建立正交投影矩阵, 使用渲染基本图元的方法来渲染2D精灵 (方法二)
使用D3DXSprite可以简单实现精灵渲染, 但是其封装的细节并不公开, 在效率和可扩展性方面都存在一些问题, 所以在2D精灵渲染时可以采用D3D提供的渲染基本图元的方式来实现, 以求得到较高的效率和较好的扩展性。实际上一个2D精灵可以看作是一个绑定了纹理的矩形, 因此可以采用渲染矩形的方法来渲染2D精灵。
2.1 建立观察矩阵和投影矩阵
在实现2D精灵渲染时, 观察者的位置变化不影响2D精灵最终渲染效果, 所以观察矩阵可以设置为单位, 如下:
D3DXMATRIXmatView;
D3DXMatrixIdentity (&matView) ;
p D 3 D D e v i c e->S e t T r a n s f o r m (D3DTS_VIEW, &matView) ;//设置观察矩阵
同时, 2D精灵不会随着观察者的位置和视点的变化和产生近大远小的改变。因此在渲染2D精灵时, 不能建立透视投影矩阵, 而是要建立正交投影矩阵。在D3D中, 可以通过D3DXMatrixOrthoLH函数建立一个左手坐标系的正交矩阵, 也可以通过D3DXMatrixOrthoOffCenterLH函数建立一个原点不在屏幕中心的正交矩阵。2D游戏程序员通常习惯于把屏幕左上角定义为坐标原点, 沿原点向左是X坐标增大的方向, 沿原点向下是y坐标增大的方向。所以可以采用D3DXMatrixOrthoOffCenterLH函数按如下方式定义投影矩阵:
D3DXMATRIX matProj;
D3DXMatrixOrthoOffCenterLH (&matProj, //矩阵变量
0.0f,
//x坐标最小值
(float) rect Client.right, //x坐标最大值
(float) rect Client.bottom, //y坐标最小值
(float) rect Client.top, //y坐标最大值
0.0f, //z坐标最小值
1.0f) ;//z坐标最大值
p D 3 D D e v i c e->S e t T r a n s f o r m (D3DTS_VIEW, &matView) ;//设置投影矩阵
2.2 为渲染准备顶点结构、顶点缓冲和索引缓冲
每个2D精灵有自己的位置坐标和纹理坐标, 因此定义顶点结构如下:
typedef struct SpriteVertex
{
FLOA Tx, y, z;
FLOA Ttu, tv;
}SPRITEVERTEX, *PSPRITEVERTEX;
而D3D渲染的基本方法是使用索引缓冲指定的顶点顺序从顶点缓冲里取出顶点数据, 然后按照指定的方式用基本图元进行渲染。所以在使用d3D进行2D渲染时, 可以为每一个精灵对象分配一个顶点缓冲和一个索引缓冲。如:
LPDIRECT3DVERTEXBUFFER9pVB;
LPDIRECT3DINDEXBUFFER9 pIB;
if (FAILED (pD3DDevice->CreateVertex Buffer (4*sizeof (SPRITEVERTEX) ,
0, D3DFVF_SPRITE, D3DPOOL_DEFA U L T, &p V B, N U L L) ) )
return;
if (FAILED (pD3D Device->CreateIndexBuffer (6*sizeof (WORD) , 0, D3DFMT_INDEX16, D3DPOOL_DEFAUL T, &pIB, NULL) ) )
return;
然后锁定顶点缓冲和索引缓冲, 将每个精灵相应的顶点数据和索引数据拷贝到顶点缓冲里和索引缓冲里。
2.3 世界变换
此处的世界变换可以和方法一的世界变换一样通过矩阵变换函数设置相应的缩放、旋转和平移矩阵, 然后通过D3DXMatrix Multiply函数按一定的变换顺序将它们累积起来, 得到最终的世界变换矩阵, 再调用D3D设备指针指向的函数SetTransform来设置最终的变换结果。
2.4 渲染
在设置完了世界变换后, 就可以调用D3D设备指针指向的函数DrawIndexedPri mitive来渲染索引缓冲所指向的顶点数据。此处渲染时, 应该按照三角形列表来渲染。如:
pD3DDevice->DrawIndexedPrimitive (D3DPT_TRIANGLELIST, 0, 0, 4, 0, 2) ;
3 为所有精灵准备一个大的缓冲池, 实现批处理渲染 (方法三)
在方法二里, 是为每一个精灵准备一个顶点缓冲和索引缓冲, 每次渲染时, 只渲染一个精灵对象。而实际上, 现在的显卡都是为批处理渲染设计的, 具备了同时渲染大量三角形的能力。所以, 从效率的角度考虑, 更合适的渲染方法是为所有的精灵准备一个大的顶点缓冲和索引缓冲区, 每帧渲染时, 先把所有精灵对象的顶点数据拷贝到顶点缓冲里, 然后把这些三角形作批处理渲染。
3.1 建立观察矩阵和投影矩阵
同方法二一样的方式建立观察矩阵和投影矩阵
3.2 准备顶点缓冲和索引缓冲
为所有精灵准备一个较大的顶点缓冲和索引缓冲。
#define MAX_VERTEX_NUM 50000
if (FAILED (pD3DDevice->CreateVertex B u f f e r (M A X_V E R T E X_N U M*s i z e o f (SPRITEVERTEX) ,
0, D3DFVF_SPRITE, D3DPOOL_DEFA U L T, &p V B, N U L L) ) )
return;
i f (F A I L E D (p D 3 D D e v i c e->CreateIndexBuffer (X_VERTEX_NUM*6/4*sizeof (WORD) , 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &p I B, N U L L) ) )
return;
然后锁定索引缓冲, 按照顺时针序定义索引缓冲数据, 如
WORD*pIndices, n=0;
if (FAILED (pIB->Lock (0, 0, (void**) &p Indices, 0) ) )
return;
for (int i=0;i
*pIndices++=n;*pIndices++=n+1;*pIndices++=n+2;
*pIndices++=n+2;
*pIndices++=n+1;*pIndices++=n+3;
n+=4;
}
pIB->Unlock () ;
3.3 为渲染准备顶点数据及世界变换
因为D3D的渲染是基于状态机的, 设置了一种渲染状态后, 其后的渲染就按此状态执行。在这里要用到批处理的方式来渲染所有三角形, 就不能调用D3D设备指针指向的函数SetTransform每个精灵设置世界变换矩阵。为此, 需要为每个对象定义一个顶点数组, 在每一帧里将顶点的数据换算成已变换过后的坐标位置。同方法二一样定义顶点数组的数据类型, 然后为每个精灵定义顶点数组:S P R I T E V E R T E X vertexs[4];
在每一帧里, 根据精灵的位移、缩放、位移情况, 计算顶点数据的变化, 并存放到vertexs数组里。
3.4 渲染
渲染时, 先锁定顶点缓冲, 然后检查每个精灵的混合方式、纹理图片有没有发生变化, 如果没有发生变化, 则把相应的精灵的顶点数组的数据拷贝到顶点缓冲中, 并计算处理的精灵个数nCount。如图1。
数据拷贝完后, 则将顶点缓冲解锁, 在调用DrawIndexedPrimitive一次性的将nCount个精灵同时渲染出来, 如:
pD3DDevice->DrawIndexedPrimitive (D3DPT_TRIANGLELIST, 0, 0, nCount*4, 0, nCount*2) ;
如果在检查到精灵的混合方式或者纹理和之前的精灵不同, 则先调用DrawIndex edPrimitive函数将已拷贝数据的精灵先渲染, 然后重新锁定顶点缓冲, 再按照前述方式继续处理剩下的精灵。
4 性能比较
从理论上看, 方法一不需要建立观察坐标系和投影坐标系, 其封装程度很高。正是由于封装程度较高, 那么在性能上必然有所损失;方法二是由程序员自己接管了变换和渲染的细节, 其性能应该比方法一高;而方法三则在方法二的基础上有所改进, 充分利用了DrawIndexedPrimitive函数批处理的特点, 将所有精灵一次性渲染出来, 其性能是最高的。
为了测试这三种方法的性能, 编写了相应的程序来实现多个相同的精灵渲染, 所用精灵使用一张64×64大小的png图片作为纹理。在同一硬件平台和操作系统下, 运行这三个程序, 通过比较程序的FPS来判定这三种方法的性能高低。测试数据如下:
通过表1的测试数据可以看出, 当只有10个精灵时, 三个方法性能相差不大。但是当精灵数目增多时, 方法一的性能急剧下降。当系统中精灵数目继续增多时, 方法二的性能也逐渐下降, 而方法三性能下降却没那么剧烈, 即使系统中有10000个精灵, 方法三还能保持47帧/秒, 仍然能满足游戏的需要。
5 结语
方法一使用ID3DXSprite接口来渲染2D精灵, 方便、简单。在系统中同时渲染精灵数目不多, 对系统性能要求不高时可以使用这种方法。
方法二通过建立正交投影矩阵, 然后使用渲染基本图元的方法来渲染2D精灵, 可以获得较高的效率, 同时还可以使用D 3 D提供的矩阵运算函数来实现世界变换。在系统中对性能要求不是特别高, 渲染精灵不是特别多的情况下, 使用这种方式可以取得开发方便和性能之间的一个较好平衡。
方法三充分利用了D3D批处理渲染的机制, 在系统中如果存在大量纹理相同的精灵, 这种方法可以获得最高性能。但是使用这种方法时, 需要自己处理所有世界变换的数据。
因此, 在开发2D游戏时, 应该根据需要同时渲染的精灵数目和系统性能需求, 在兼顾开发方便和快捷的需求下, 从上述三种方法中选择恰当的方式。
注:测试结果中标记为X的是系统资源消耗很大, 无法得到渲染结果。
摘要:从DirectX8.0开始, 微软已经放弃了DirectDraw组件。也就是说, 从Directx8.0开始, 2D渲染的方式应该采用Direct3D组件来完成。通常情况下, 使用D3D渲染2D的基本方式可以分为以下三种: (1) 使用ID3DXSprite接口渲染; (2) 建立正交投影矩阵, 为每一个待渲染的精灵创建顶点缓冲来渲染; (3) 建立正交投影矩阵, 为所有精灵建立一个比较大的顶点缓冲池作批处理渲染。本文将对这三种方法的实现方式进行探讨, 并通过测试比较这三种方法的性能差异。
关键词:D3D,2D精灵,精灵渲染,性能
参考文献
[1] Kelly Dempski[著], 于忠德, 吴红艳, 林锋[译]DirectX实时渲染技术详解, 第1版[M].重庆:重庆大学出版社, 2006, 12:529.
[2] Frank D.Luna[著], 段菲[译]DirectX9.03D游戏开发编程基础, 第1版[M].北京:清华大学出版社, 2007, 4:73.
相关文章:
说课的几种模式范文02-05
初中英语的几种时态02-05
外链的几种表现形式02-05
声卡使用的几种模式02-05
心电图几种病理波02-05
地震仪天然地震信号采集系统02-05
餐厅销售经理述职报告02-05
卫生院居民健康档案建立工作自查报告02-05
班级管理的几种方法02-05
二自由度冗余驱动并联机器人奇异性分析02-05