在前面部分,我介绍了D3D的初始化和固定渲染流水线。这一章,将它们用于实践。
我们需要解决的事情是:
1、在D3D中如何存储顶点和索引数据;
2、怎样使用渲染状态来改变渲染结果;
3、学习怎样渲染场景;
4、学习怎样用D3DXCreate*函数创建更多的3D物体。
一、顶点缓冲区、索引缓冲区的概念
顶点缓冲区是一块连续的存储了顶点数据的内存。
索引缓冲区是一块连续的存储了索引数据的内存。
我们使用顶点缓冲区和索引缓冲区是来保存我们的数据是 因为它们能被放置在显存中。渲染显存中的数据要比渲染系 统内存中的数据
快的多。
在D3D中定义顶点缓冲区是通过IDirect3DVertexBuffer9 接口来定义的;索引缓冲区是通过IDirect3DIndexBuffer9 接口来定义的。
在程序中创建顶点缓冲区和索引缓冲区给我们提供了两个 方法:
1、CreateVertexBuffer();
2、CreateIndexBuffer();
以上这两个函数的详细说明,请参阅D3DAPI。
二、静态缓冲区、动态缓冲区的区别
静态存储区存储的是不经常更改的数据,原因这块数据被 放置在显存中,读和写变的非常的低效,访问 显存很低效。 但是渲染显存中
的数据却是很快的。这里需要注意下这个双面性。
将D3DUSAGE_DYNAMIC存储的是动态缓冲区。动态缓冲区通 常被放在AGP内存中,这种内存中的数据能够被很快的更新 。处理动态缓冲区的中的数据没有在显存中处理的快,因为 这些数据在渲染前,必须要从内存中转移到显存中,动态缓 冲区的目的,它们能够被稍微快一点的被刷新。对于动态缓 冲区,粒子系统肯能是最好的例子,并且它们每一帧都会被 更新(比CPU写快)。
注意:在程序中读取显存和AGP内存都是非常慢的,因此 假如你在运行时需要读取你的几何物体,最好的解决方案是 指定一个系统内存,都在其中拷贝并且读取数据。
IDirect3DVertexBuffer9* vb;
g_pDevice->CreateVertexBuffer
(
8*sizeof(Vertex),
0,
D3DFVF_XYZ,
D3DPOOL_MANAGER,
&vb,
NULL
)
三、访问缓冲内存,实际上就是应用加锁解锁函数。
为了访问一个顶点\索引缓冲区,我们需要得到一个指针 。那如何获得这个指针呢,很简单,你只要通过顶点缓冲 的对象来进行加
锁就可以了,里边有一个参数就可以得到 了!
原型:
HRESULT IDirect3DVertexBuffer::Lock
{
UINT offsetToLock,
UINT SizeToLock,
BYTE** pPvertex,
DWORD Flags
}
对于索引缓冲区的Locked原型跟它参数一样,只不过调 用者不同罢了。
具体参数意义,请参阅D3DAPI帮助文档。
下边演示如何往顶点缓冲区域内写数据(也就是往里边读 写数据的基本样板操作。)
Vertex *vertex;
IDirect3DVertexBuffer9::Lock
{
0, //偏移量
0, //指定锁定的内存数据大小,0,指全部
&vertex, //锁定内存的起始地址
0 //标记,这里为什么是0,我还不清楚
}
vertex[0] = vertex(..,..,..,..);
vertex[0] = vertex(..,..,..,..);
vertex[0] = vertex(..,..,..,..);
vertex[0] = vertex(..,..,..,..);
...
...
IDirect3DVertexBuffer9::Unlock();
该函数通知D3D将要多顶点缓冲区进行内存操作,并获得 指向顶点缓冲区的内存指针(首地址),顶点数据复制完成后 ,顶点缓冲区通
知Unlock()通知D3D内存操作结束。示例如 下:
void *ptr;
IDirect3DVertexBuffer::Lock
{
0,
sizeof(顶点格式结构体变量),
(void **)&ptr,
0
}
memcpy(ptr,顶点格式结构体变量,sizeof(顶点格式结 构体变量));//***核心代码***
IDirect3DVertexBuffer9::UnLock();
四、如何找回顶点和索引缓冲区的信息(选看)
有时我们需要得到顶点或者索引缓冲区信息。那如何从 新获取呢:
D3DVERTEXBUFFER_DESC VbDesc;
IDirect3DVertexBuffer9::GetDesc(VbDesc);
D3DINDEXBUFFER_DESC IbDesc;
IDirect3DVertexBuffer9::GetDesc(IbDesc);
D3DVERTEXBUFFER_DESC\D3DINDEXBUFER_DESC结构属性
请参阅D3DAPI帮助文档。
五、渲染状态
渲染状态影响集合体的渲染,这个你不用管,D3D为我们提供了好多渲染模式,你直接调用就OK,简单吧,那继续看下边吧。
D3D有默认的渲染状态,当你想自己指定渲染状态的时候,你尽可以改变它的渲染状态。通过D3D提供的函数SetRendState。
这里需要注意的是,当你指定一个渲染状态的时候,这个渲染状态会一直起作用直至你又重新设定了D3D的渲染状态。
在开始正式渲染前,我们要做3个准备工作,(1)资源流的设定(2)设置顶点的灵活顶点格式(3)设置索引缓冲区
使用顶点缓冲区和索引缓冲区配合工作,来进行我们的渲染之行,亢奋吧,呵呵!
IDirect3DDevice9::DrawPrimitive
{
绘制图元的类型,
待绘制图元的顶点编号,<相当给予了编程人员的控制权限,可以部分或者从开始处指定顶点编号>
绘制图元的数目
}
IDiect3DDevice9::DrawIndexPrimitive
{
绘制图元的类型,
。。。
}
以上结构体的具体参数,请参阅D3DAPI,很简单!
还有一点需要注意的事情是,所有的绘制方法,必须填写在:
IDirect3DDevice->BeginScene();
IDirect3DDevice->DrawPirmitive(...); 中间渲染代码
IDirect3DDevice->EndScene();
2010年8月18日 星期三 晚
阿舰 于日照
上一篇主要一块学习了D3D绘制图元的一些个理论知识,再一起回顾一下:讲述了两个缓冲区的概念,存储顶点数据的静态与动态缓冲区的区别,如何访问内存,如何得知顶点索引缓冲区的信息,在一个就是绘制图元的方法,以及在哪绘制的问题。 这一篇我将跟大家伙一起学习一下,各个概念之间的关联,下一篇,我将贴出源码,好了拭目以待吧!闲话少说,开干: 一、昨晚描述遗留的模糊问题: D3DUSAGE_WRITEONLY与D3DUSAGE_DYNAMIC间的关联,到底是怎么样的呢?为了描述清 楚这个问题,以及解决我思想上的包袱,今天中午我特意查了一下,望能给你解疑: 网上资料称,D3DUSAGE_DYNAMIC当被指定的时候,D3DUSAGE_WRITEONLY才具有存在的价值。 (1)D3DUSAGE_DYNAMIC+D3DUSAGE_WRITEONLY -->指定顶点数据被存储在显存中,读写速度并不快,但是有一个显著的特点就是渲染速 度很快! (2)D3DUSAGE_DYNAMIC 不附加D3DUSAGE_WRITEONLY的时候,其指定的是AGP内存,该内存的优点是数据能被很快的刷新!常用于粒子系 统那块! (3)D3DUSAGE_WRITEONLY 本身指定该内存为只写属性,任何试图从其中读数据的操作,均视为违法操作。 (4)不用D3DUSAGE_WRITEONLY标记的时候,指定的就是静态缓冲区中存储的数据,也就是存储在显存中,但是数据不宜频繁变动! 二、绘制图元的各个流程到底是在做些什么呢? 昨晚可能描述的不够准确或者是这些个知识点没有串联,因此你可能对我说的各个理论之间的关联并不是十分明朗,在这我简要的再 说一下这个流程: D3D的基本绘制图元是以三角形绘制的。众所周知点成线,线成面的概念。所以,我们可以假想任何复杂的物体都是一系列相关联的点 组成的线连接起来的。要想绘制它,需要经过这些个步骤: 1、指定各个顶点的属性,你比如说它的位置、法线、纹理我们借助C++中的结构体来实现它; 2、根据你所定义的顶点的各个属性,来定制属于你自己的灵活顶点格式; 3、到了这个阶段,你就得需要考虑,你到底需要绘制哪种类型的图元呢?点?线段?三角形?正方形?还是多面体?完成这些,你需 要多少个你所指定类型的点组成呢?(这里暂时不提索引缓冲的概念)。这时呢,你就需要根据你想创建的图元的类型,来填充你的 这个顶点结构体 4、当你自定义了那么多的顶点数据,你总得找个地方保存一下吧!这时,就是需要为其申请一段可以保存这些个顶点数据的内存段了 !通过D3D为我们提供的创建顶点缓冲区的函数CreateVertexBuffer来实现 5、当你申请了足够容纳你所定义的顶点数据结构体数组之后,你就需要访问这块内存了,并且将你的顶点数据填入到你所申请的内存 段中。这就需要在加锁解锁函数中来实现了。 6、设置渲染状态,指定渲染的模式,你比如说是否开启拣选模式等等。具体的参看D3DSETRENDERSTATETYPE。 7、你以上搞的这一套,还只是设计阶段,我们的最终目的是看到效果,看不到都是白扯,所以我们需要将我们以上的想法或者设计, 通过某种介质来呈献给绘制设备。于是就有了资源流这么个概念。就是与你的顶点缓冲区与渲染数据流连接用的媒介。 8、设置你的灵活顶点格式 9、最后就可以绘制你的D3D图元了 三、顶点缓冲区与索引缓冲区的优劣比较 索引缓冲区诞生的意义是减少系统内存的开销,提高程序的性能。不相信的话,我们一块来算笔账,事实为证。当我们假设绘制一个 正方形图元的时候,我们需要定义几个点吧,傻子都知道我们需要4个顶点:如下图A、B、C、D。 A--------B | / | | / | | / | C | /--------| D 1、节省系统内存 当我们仅仅用顶点缓冲直接绘制这个长方形图元的时候,我们拿出一种组织方式来分析一下:ABC,CBD。这时需要6个点来记录这些 组织方式我们才可以绘制出来这个图元:我们的图元坐标是int类型的,int占用4个字节,所以,需要的字节数:6*(3*4) = 72个字节 。如果我们用索引缓冲来实现的话,我们就只需剔除重复的点,这时我们只需要保存4个点就可以完成我们的绘制工作,这里呢,你要 借住索引来操作,索引也是占据空间的,但是它是16位的索引就可以搞定了就是WORD,一个WORD就是16位,即两个字节,所以依靠索 引来完成咱们的绘制的话,就需要:4*(3*4)+6*2 = 60个字节需要。 这里体现的节省系统内存开销,并不是很明显,如果你搞个复杂的图元来搞搞,那性价比就体现的淋漓尽致了! 2、提高程序的执行性能 使用顶点缓冲区来渲染的时候,D3D会对每个顶点进行计算,这样6个顶点需要计算;而如果你使用索引缓冲区来渲染的时候,你仅 仅需要让D3D处理4个顶点就可以了,这样可以让系统避免了不必要的性能开销,在一定程度上提高了程序的性能! 四、顶点数组如何与索引数组搭配呢? 前期在以上我所说的创建顶点缓冲区之前的那些操作,该怎么写还是怎么写,但是呢,在你填充顶点数据结构体后,需要一个16 位的索引数组,用于你用索引下标来组织你的三角形顶点的组织方式。 五、顶点缓冲区如何与索引缓冲区搭配来完成图元绘制呢? 1、依旧创建顶点缓冲区,需要注意的是,创建顶点缓冲区的大小那个参数,是你真正用到的点的个 数所需的内存大小。 2、新创建索引缓冲区,这里需要注意的是,那个索引数组的元素格式是D3DFMT_INDEX16。 3、操作内存,这里用索引数组来替换之前的顶点数组。 4、资源流的设定,你要借住顶点来与渲染流接轨。 5、依旧设置你的灵活顶点格式 6、通过SetIndices(IDirect3DIndexBuffer*),这个函数将你的索引指针与其代表的索引数组链接起来了。 7、绘制图元调用DrawIndexPrimitive(...)来正式渲染基于索引缓冲区模式的渲染。 2010年8月19日 星期四晚 阿舰 于日照 |