现在的位置: 首页 > 综合 > 正文

SpecialEffect学习之粒子一[译]

2012年02月05日 ⁄ 综合 ⁄ 共 12570字 ⁄ 字号 评论关闭

第18章 雨 烟 魔法更多,快乐的粒子。

我们将在本章开始3D效果,看许多程序员考虑的大部分有用的特效技术学习怎样制作粒子系统。程序员使用粒子系统管理多样的效果。粒子系统可以创造出优秀的天气效果,像于或雪,还有魔法效果,烟,火,碎片,急流,甚至更多。实际上,你可以说粒子系统基于所有的3D效果。这就是为什么我们要学习它的原因了,我们撇开3D章节不管。

一个最酷的粒子系统用起来创建所有不同的效果通过调整系统里的变量。这意味着同样的代码会驱动着你的雨,雪效果和魔法效果或烟等。你甚至可以重用这些粒子系统的代码跨越几个游戏或者修改版,你可以让你的美工保存为粒子编辑器。所以它们可以快速的容易的产生和杀死效果,而不需要写代码。

Those are just a few of the reasons why particle systems rock. 我可以列出更多,但是你可以已经在玩游戏直接感受粒子系统的魔法效果,你甚至也许阅读了其他书籍关于怎样创建粒子系统的章节。

我打算花费两章的时间在粒子系统上,在本章,我们讲基本的粒子系统- 它们是什么,怎样创建它们。下一章,我们花一些时间制作粒子系统的编辑器。如果你已经知道使用它们了,写了一个编辑器了,你也许想跳到下一章学习怎样简化它们,制作逼真的效果,灵活的粒子系统,使用简单的脚本语言。

This is going to be a very long roller coaster ride, 所以 strap yourself in.立刻你的汽车已经从这里移向那里。你感觉到门在下面,开始运送小山。

什么是粒子系统?

在实际生活,粒子是一些小的片。例如,你用锤子打碎一块砖,你获取了砖块的粒子。一个雨滴,雪花,喷射的血液,燃烧的残骸也可以被认为是粒子。图18是粒子的截图。在视频游戏编程领域,一个粒子是通过纹理绑定一起的两维空间的我们,在前面已经讲到。这个quads是很小,程序员通常渲染他们使用Alpha混合,所以它们一部分是透明的。一些程序员,用DirectX doc)也参考使用点精灵因为它们本质是2D的精灵(图像)在特殊点上。

每个粒子的模型,你可以创造出复杂的效果。例如,你可以创建暴风雪使用一组粒子。每帧,你循环它们,调整每个粒子的位置。调整x位置,这样你创建了非常令人信服的暴风雪效果,因为你了解真实的现象的物理模型。你有一个大的粒子,你模拟了自然力量,在每个粒子上用纹理绑定,然后你可以制作出雪花效果了。

所有粒子系统都有它们的核心:

粒子数组 更新函数 渲染函数

粒子数组:所有粒子系统有一个粒子数组。在C++中,使用粒子类(CParticle非常通用的名字).雨滴和雪花是这个类的实例。少数的使用瀑布或泉水使用水滴,或者模拟烟柱的烟粒,甚至模拟魔法发射时的能量放射的spell粒子。

更新函数:在粒子数组中,更新函数循环调用,执行对偶每个的处理。典型的函数移动粒子的位置。使用物理变量计算,像中立,风方向,粒子的宽度等。更新函数也许很混乱的设置粒子属性(例如颜色,不透明性像烟上升,它更清晰。

渲染函数:这个函数很仔细的设置渲染状态,这样转换alpha混合等)输出粒子到屏幕。在系统中循环的播放,而且进行适当的图形调用(SetTexture和 DrawPrimitive需要获取这些粒子到正确的位置。

全部的粒子过程呢?

典型的,在C++粒子系统里,你可以有一个CParticleSystem类仔细控制所有的CParticles。更新渲染函数设置为public,CParticle数组被设置为私有或保护只让CParticleSystem可以修改它。下面的代码概要的展示了一下。

现在,当然,我们将从难的部分-执行更新和渲染。其他的将在剩余章里讲述。

制作粒子系统复杂吗?

当你设计粒子系统时,你问的第一个问题:我想灵活的使用它?粒子系统可被重复使用的渲染那些粒子。通常,更新代码在物理计算上移除代码。这些物理计算可以很简单,也可以很复杂。

简单的粒子系统可以很容易理解,你通常更漂亮的更新它。在同样的时间里,简单的粒子系统也许是你想要的,或者你的游戏足够灵活。

例如,我们说执行每个简单的粒子系统设计雪。你的更新函数大量的循环每个粒子,使用重力,让其下落。没问题,简单容易理解,但是不很灵活。

你想在雪粒子系统里添加喷泉效果。现在要要选择:执行新的粒子系统-意味着重做所有的管理工作(很讨厌!) 或者,扩充一个函数在雪系统里所以可以处理泉水。这也许意味着要移除重力的代码,替换为你设置的变量。

你肯更多的东西想让粒子系统处理,就复杂了。我想开始就超过简单,但是你到最后的章节,到最后你将执行非常复杂的操作-系统的处理是大批的在那里。

粒子属性核心

开始了我们的朋友,粒子先生。核心思想是粒子诞生在一个3D的世界里。系统函数围绕它们移动,它们生存期很短,快乐。甚至很多原因它们死了。所以正确的定义我们粒子的生存期。lifetime

大部分粒子系统执行粒子的生存期以秒为单位。更新函数来维持更新的时间-如果超越了生存期,粒子就死了。作为选择,一些粒子也死了如果他们撞到墙或者其他游戏对象(例如,雪花水通常撞击后消失了。

所以,我们需要粒子的生存期,我们还需要3D向量来可控制粒子的位置,但是还需要什么呢?这里全部列出粒子属性我考虑的。我们将添加更多的从简单到复杂:

位置 生存期 速度 颜色 大小 重力 纹理 喷射速度

但是等一等!我们的系统不需要所有的属性。记注,我们更新函数必须创建新的粒子。当更新函数创建一个新的粒子时候,它为每个粒子分配属性颜色,生存期。所以我们的系统需要变量将告诉更新函数怎样选择值。一种方式我们的系统更新随机值确定范围。如果我们设置最新粒子的最值和最小值属性,它不能拾取新的随机值。这意味着我们将需要下面的属性:

最大最小生存期,最大最小速度,最大,最小颜色,最大最小大小。现在我们有我们需要粒子系统的属性。我们还要确定可能的附加的属性。我们添加更多属性,移动到章节中。

写粒子系统的核心

我们开始获得了足够的概念。这一点上,你可以看Ch18p1_SimpleParticles简单程序,再使用下面的代码。

CParticle

让我们开始基本粒子系统的定义。我们使用 CParticle来描绘一个粒子。

class CParticle{ public: CParticle(){ m_fSize = 1.0f; m_fLifetime = 1.0f; m_fAge = 0.0f; } virtual ~CParticle(){} float m_fSize; float m_fLifetime; float m_fAge; D3DXCOLOR m_Color; D3DXCOLOR m_ColorStep; D3DXVECTOR3 m_vPos; D3DXVECTOR3 m_vDir; inline bool Update(float fTimeDelta) { } }

技巧D3DXCOLOR和D3DXVECTOR3是 D3DX提供的帮助类,封装了颜色和向量。它们对运算符进行了重载用起来很方便,例如你可以使用+来叠加。

一个好的面向对象设计(OOD)说你可以暴露成员变量你将增加函数。例如,你取带m_fSize一个公共成员,我将设置为m_fSize保护类型,通过GetSize和SetSize函数允许用户改变粒子属性。

为了可读性,我没有选择附属函数。在真正的粒子系统里,你可能用到。

正如你看到的,CParticle不是很复杂。它有一串公有变量,粒子的属性-通过构造函数设置默认值,内联函数执行更新。不要担心更新函数-你将知道它怎样工作在后面。现在让我们看CParticle的妈妈CParticleEmitter发射器。

CParticleEmitter

这个类包含我们的粒子数组,使用了粒子属性(重力,纹理等)

class CParticleEmitter

{ public: CParticleEmitter(); virtual ~CParticleEmitter(); virtual void Update(float fElapsedTime,float fTimeDelta); virtual HRESULT Render(); virtual HRESULT RestoreDeviceObjects(LPDIRECT3DDEVICE8 pDev, const char *strTextureFilename); virtual void InvalidateDeviceObjects(); private: D3DXVECTOR3 m_vGravity; D3DXVECTOR3 m_vPos; float m_fMinEmitRate; float m_fMaxEmitRate; D3DXCOLOR m_Color1; D3DXCOLOR m_Color2; float m_fMinSize; float m_fMaxSize; D3DXVECTOR3 m_vSpawnDir1; D3DXVECTOR3 m_vSpawnDir2; int m_iVBSize; LPDIRECT3DDEVICE8 m_pd3dDevice; LPDIRECT3DVERTEXBUFFER8 m_vbParticles; LPDIRECT3DTEXTURE8 m_texParticle; CRecyclingArray m_Particles; }

最重要的方法是更新和渲染。

RestoreDeviceObjects和InvalidateDeviceObjects 是初始化和释放资源的,使用命名的DirectX粒子的框架。我们的类成员每个都定义为虚函数,记注现在的成员函数还不是系统属性。

技巧:我删除了访问函数我在CParticle中不使用访问函数,大使我决定在这里使用它们。我们怎样经常的客户端代码CParticleEmitter. 它可能很稀罕的发生当客户端改变时,将直接改变属性,大部分时间,仅仅接触类将是 CParticleEmitter。然而,客户端将接触CParticleEmitter的属性是经常性的,如过不总是。这意味着没有访问函数,意味深长的偶然致使如果我改变CParticleEmitter的属性,我不得不修改附加代码文件(不是好的).所以,我花费少量时间添加了访问函数。

CParticleEmitter喷出Direct3D设备在m_pd3dDevice,使用m_vbParticles和m_texParticle 持续追踪顶点缓冲渲染粒子(我们将看到每秒),在粒子上使用纹理。

我保存防止搞乱到最后。时间告诉一点有魔力的小问题隐藏在你阅读的后面:m_Particles地球上粒子是什么?前面的代码,m_Particles是CRecyclingArray.CRecyclingArray是一个新类,看下面。

CRecyclingArray是基于模板构造的,如果不知道C++ 模板可以看后面附录。现在你的大脑要围绕模板,让我们描绘一下CRecyclingArray。

CRecyclingArray工作像std::vector,一样,但有些关键的不同:它不使用动态内存分配。取带的你可以设置一个最大值。客户代码,CRecyclingArray像一个vector-你可以添加对象给它,删除。在后台,然而CRecyclingArray不使用动态内存-它使用一些 Bool的标志来告诉静态成员的分配,每个可能的值。当你新使用CRecyclingArray时,数组获得下一个自由的对象给你。当你删除时,CRecyclingArray 标志在你删除上。有实际的内存分配或存储单元分配;取代的,CRecyclingArray重新使用内存创建和销毁。因此叫CRecyclingArray.现在你问。是不是使用new和delete?两个东西:

内存分配是慢的,

但是,堆碎片可以引导性能(,系统可以拖着走,更坏的内存错误。不是玩笑!

所以有足够的理由使用CRecyclingArray.它给我们最好的方式-性能分配自由对象(像标准的动态分配),没有速度或堆分配的烦恼。Whoohoo!

Using a Recycling Array使用Recycling数组

现在你知道我为什么创建CRecyclingArray了,我希望你喜欢使用它。之后你知道怎样使用它,我给你展示下它是内在工作。

第一课程:怎样创建recycling数组。

CRecyclingArray有两个模板参数。数组对象类型,最大的数组大小(那样的话,最大值对象数组将被处理。) 例如,下面代码将创建包含50个CMyClass对象的recycling数组。

CRecyclingArray m_MyArray;

所以,一般的好。下节课:怎样创建和销毁对象。创建对象,调用new方法。我不喜欢重载new运算符,所以我选择取代的方法,清晰的。你将在你的对象里找回新的指针,想下面的代码:

警告

仔细设置最大值。如果你分配了50个对象,你试着分配更多,CRecyclingArray将抛出异常。我喜欢C++的结构化异常处理。

CMyClass *newclass = m_MyArray.New();

删除一个对象,调用delete-有 delete方法,在粒子数组里,实际上是个指针:

警告:CRecyclingArray不调用new的构造函数。取带的是,它使用新的空的对象来取带,所以确定你的操作工作=重载一个对象你使用 CRecyclingArray.

m_MyArray.Delete(5);

m_MyArray.Delete(newclass);

Keep in mind that the Delete function doesn't NULL the pointer you pass to it.

表18.1展示了其他的用途,但是自己结实, CRecyclingArray的方法。

CRecyclingArray类方法

GetNumFreeElements()

GetNumUsedElements()

GetTotalElements()

GetAt(index)

IsAlive(index)

DeleteAll()

CRecyclingArray怎样工作

现在我们浏览下,让我们整体看下CRecyclingArray.

事实上非常简单的系统。内在的,CRecyclingArray有一个 Booleans等价的大小最大的对象值。真意味着对象被分配,假以为这释放。

我们看看New方法,复杂的数组:

TArrayElement *New(){ if(GetNumFreeElements()<1) throw(std::out_of_range( *CRecyclingArray::New: too many objects!")); int i = m_iLastNew; for(int q=0;q=iNumElements) i=0; } } m_aElements[i] = m_NewTempalte; m_iUsedElements++; m_bAlive[i] = true; m_iLastNew = i; return (&m_aElements[i]); }

New方法从最后一次开始,寻找假的标识。它找到假的标识后,它设置为真,通过对象计数增长,设置对象等于一个空对象(看图18.5).

每有构造函数被调用;取代的是,New依赖与相等的清楚对象的状态。

其他的方法是简单的,所以我不想在这里讲它们。

仅仅警告Delete重载指针对象用一个索引- 有些指针arithmetic going on there to determine the index of the object.I tried to make this pointer math as obvious as possible,难看的东西转换指针到int等。如果你看这的代码,我鼓励你去调试和跳过-所有东西将变的清晰。

CRecyclingArray简介

CRecyclingArray可不像清晰你的狗或者刷牙一样,但是它可以非常容易的管理你视频游戏众多的对象- 你将不在为它的交换的速度而担心内存分配时间释放时间。你可以从不但是随机的数组越界问题-because all the memory is allocated up front,如果你需要支持 5000个子弹,但是系统你运行没有更多的内存,你将知道when you create your array,not at some random point inside your game.我使用 CRecyclingArray在游戏中许多地方-我希望你能感觉到它的好处。

设置粒子系统

关于CRecyclingArray的资料已经足够了,让我们回到手头上的任务-够简单的粒子系统。

第一步将是设置粒子系统中Direct3D资源。这种情况下,意味着放置一些代码在粒子系统的初始化函数中创建纹理和我们粒子的顶点缓冲。

Neither task is anything worth losing sleep over.让我们看所有的光荣的初始化函数:

HRESULT CPartcileEmitter::RestoreDeviceObjects(LPDIRECT3DDEVICE8 pDev, const char *strTextureFilename) { HRESULT hr; m_pd3dDevice = pDev; m_Particles.DeleteAll(); if(FAILED(hr = D3DXCreateTextureFromFile(m_pd3dDevice,strTextureFilename,&m_texParticle))){ return(hr); } if(FAILED(hr = m_pd3dDevice->CreateVertexBuffer( m_iVBSize * sizeof(VERTEX_PARTICLE), D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY|D3DUSAGE_POINTS, D3DFVF_PARTICLE,D3DPOOL_DEFAULT,&m_vbParticles))) return(hr); return S_OK; }

首先我们存下了我们将使用的Direct3D设备。其次,我们清空了粒子数组,将其清除。简单的调用D3DXCreateTextureFromFile我们加载一个纹理 (使用CParticleSystem,纹理文件到RestoreDeviceObjects),然后调用CreateVertexBuffer创建我们的顶点缓冲。

一些东西不同的创建了顶点缓冲,然而。作为开始,有D3DUSAGE_DYNAMIC和D3DUSAGE_POINTS标识。告诉Direct3D指定关于我们如何使用顶点缓冲。

D3DUSAGE_DYNAMIC标识告诉Direct3D 在顶点缓冲将有动态的经常不断的改变D3DUSAGE_POINTS标识别告诉我们数据元素在描述顶点(在Direct3D里叫做) 点精灵。

技巧:是的,有许多东西打击你,但是有事实上有意义的东西嵌入在DirectX中。粒子系统really are that common.

点精灵和新顶点格式

我们使用其他不同的东西来创建顶点格式,我们有顶点结构叫做VERTEX_XYZ_DIFFUSE_TEX2的位置,颜色,纹理坐标:

typedef struct{ D3DXVECTOR3 position; D3DCOLOR color; FLOAT tu,tv; }VERTEX_XYZ_DIFFUSE_TEX1;

顶点结构工作有许多东西,但是粒子系统我们需要新的结构,包括不同的信息:

typedef struct{ D3DXVECTOR3 position; float pointsize; D3DCOLOR color; }VERTEX_PARTICLE;

你可以看到,新的顶点结构,VERTEX_PARTICLE,非常类似VERTEX_XYZ_DIFFUSE_TEX1,两个主要的不同:我们添加了纹理坐标和点大小

移除纹理坐标是简单的。有渲染状态调用D3DRS_POINTSPRITEENABLE。当渲染状态是真时, Direct3D自动的绘制整个纹理在每个点上。换句话说,D3DRS_POINTSPRITEENABLE设置为真,我们的顶点纹理结构不在描绘一个三角形点-它描绘出三角形的中心。Direct3D完成了不是我们的涉及的-它也许扩展粒子位置在2D,纹理坐标(0.0,0.0),(1.0f,0.0), (1.0,1.0)和(0.0,1.0)一维里四个顶点。或者 3D卡支持点精灵在硬件上,Direct3D may just pass along the information it gets,along with a note that says,"嘿先生 图形卡,这些粒子中心点,请画出它们使用这些整个的纹理,如果它们有quad

所以,因为这样,我们不再不得不维持纹理坐标在顶点结构。我们可以rest assureed that Direct3D知道 enough to map our entire texture onto the particle,and to put the center point of that partcle at the positoin we specify.惊讶的,不是吗?

Okay,所以什么是点大小?本质上, pointsize告诉Direct3D在点大小我们的点精灵; 换句话说大点的粒子将在摄影机空间单元。粒子有点大小2.0是大点的两倍。它需要你在顶点结构中有点的大小;然而,putting one in there gives you the freedom to make your particles differrent sizes.

我们的新顶点结构。记得,你可以任意的改变顶点结构-通常,我们将使用这些结构渲染我们的顶点,但是之后使用VERTEX_XYZ_DIFFUSE_TEX1(我们的"normal"结构)不再渲染几何。

解决渲染神秘的粒子,我们创建顶点缓冲。现在我们知道怎样初始化系统,让我们怎样渲染的。

渲染粒子

如果你不知道更好,你可能用两种方式写你的系统:

一次调用一次DrawPrimitive给每个粒子

一次调用DrawPrimitive给所有分支粒子 - 这种步骤,你可以锁定顶点缓冲,然后循环激活每个粒子。你解锁,然后调用DrawPrimitive一次渲染粒子系统的实体。这种情形后,你需要大的顶点缓冲-一次放下你的所有分之粒子系统的粒子。

产生后,不是最理想的。This situation is what I call a "Goldilocks Situation"you've got one bowl of porridge that's way too hot, and one that's way too cold. What you need is a bowl that's just right".

选择1不是好的,因为你浪费了一些时间来调用DrawPrimitive-你将在每帧潜在的调用数千个时间。不好。选择2也可以有较少的处理,因为你的图形卡同时和你的CPU一起工作。This situation would be 类to hiring an artist to work on your game, and then telling him he could't start on the artwork untill the code was 100% complete.

The "just right" situation is to hand small chunks of particles to the graphics card.这种方式,没有空闲-the graphics card begins processing the first chunk of particles while the CPU moves on and 开始准备下一次渲染用的顶点缓冲。

下个问题 involves how big the chunks should be.有可能有精确的最佳值somewhere-我同意通过练习来寻找。我使用了10%的代码,我找到了当前帧速度。如果优化或不优化。

所以,获取最好的性能,我们的渲染代码必须像跳芭蕾舞一样的设置渲染状态,锁定顶点缓冲,pumping in a bunch of particle system coordinates,解锁缓冲,调用DrawPrimitive,然后重复这一过程。

设置渲染状态

现在我们准备解决实体渲染的函数。I'm going to walk you ghrough the Render方法在Ch18pl_SimpleParticles.cpp中。取而代之渲染代码在big chunk,渲染代码是一大段的,我写了很多注释,所以很容易理解。

这是第一段

HRESULT CParticleEmitter::Render(){ HRESULT hr; m_pd3dDevice->SetRenderState(D3DRS_POINTSPRITEENABLE,TRUE); m_pd3dDeivce->SetRenderState(D3DRS_POINTSCALEENABLE,TRUE); m_pd3dDevice->SetRenderState(D3DRS_POINTSIZE_MIN,FtoDW(0.00f)); m_pd3dDevice->SetRenderState(D3DRS_POINTSCALE_A,FtoDW(0.00f)); m_pd3dDevice->SetRenderState(D3DRS_POINTSCALE_B,FtoDW(0.00f)); m_pd3dDevice->SetRenderState(D3DRS_POINTSCALE_C,FtoDW(1.00f));

是的,这里设置一些渲染状态是令人兴奋的事情。不要但是关于FtoDW函数-它是来转换参数从float转换到DWORD.For the morbidly curious,这里的FtoDW看起来像:

inline DWORD FtoDW(FLOAT f)(return *((DWORD*)&f);}

}

 

That's it-nothing more than a way to wrap an ugly cast into an innocent-looking function. 现在表18.2解释了所有的渲染状态。

设置纹理和激活纹理

我们设置之后的渲染状态,我们设置少量的其他将被更熟悉的渲染状态

m_pd3dDevice->SetStreamSource(0,m_vbParticles,sizeof(VERTEX_PARTICLE));

m_pd3dDevice->SetVertexShader(D3DFVF_PARTICLE);

m_pd3dDevice->SetTexture(0,m_texParticle);

这三个函数调用了我们的纹理缓冲和激活文理。注意所有的调用我们指定了D3DFVF_PARTICLE:

#define D3DFVF_PARTICLE (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_PSIZE)

我们需要设置Direct3D知道的不同的顶点结构的格式,有一个位置,发散的颜色,和点大小,因此标志D3DFVF_XYZ,D3DFVF_DIFFUSE D3DFVF_PSIZE,如果不清晰看第6章。

锁定顶点缓冲

现在我们处理下面的任务。理解锁定我们顶点缓冲区的:

VERTEX_PARTICLE *pVertices;

DWORD dwNumParticlesToRender = 0;

if(FAILED(hr = m_vbParticles->Lock(0,m_iVBSize * sizeof(VERTEX_PARTICLE),(BYTE**)&pVertices,D3DLOCK_DISCARD))) return hr;

这里有个成员变量m_iVBSize指示粒子顶点数目我们同时工作。然后,我说,有10%的粒子,所以系统说,2000个粒子,系统处理头200个,交给图形卡,然后处理下个200。

将粒子放入顶点缓冲

现在很有趣了。之后我们锁定顶点缓冲,我们需要开始填充。我们需要循环的确定。

for(int q=0;qposition = part.m_vPos; pVertices->pointsize = 1.0f; pVertices->color = (DWORD)part.m_Color; pVertices++; } }

我们循环处理每个粒子,确定它们是否被激活。如果是的话,意味着我们需要渲染它,所以我们需要它的位置,颜色,我们顶点缓冲大小信息(pVertices).我们然后增长顶点指针,所以我们移动下个结构在顶点缓冲。

紧记我们没有关闭循环

将顶点压入图形卡

现在处理下段片段

if(++dwNumParticlesToRender == m_iVBSize){ m_vbParticles->Unlock(); if(FAILED(hr = m_pd3dDevice->DrawPrimitive(D3DPT_POINTLIST,0,dwNumParticlesToRender))) return hr; if(FAILED(hr= m_vbParticles->Lock(0,m_iVBSize * sizeof(VERTEX_PARTICLE),(BYTE**)&pVertices,D3DLOCK_DISCARD))) return hr; dwNumParticlesToRender = 0; } }

之后我们增长指针,我们需要确定顶点缓冲仍然在 room(例如下个顶点),如果在room里,没有问题,我们继续前进在下个粒子上。然而,if there isn't room,我们需要一小点工作。

难以在最初就preceding section of code.这里,我们减少dwNumParticlesToRender计数器,然后比较顶点缓冲的大小。如果count等于size,这意味着填满了缓冲,现在 time to push that chunk of vertex data out to the 3D card via call to DrawPrimitive.

As you can see from the code inside the if block,一旦我们确定顶点缓冲是满的,我们解锁它,描绘它(D3DPT_POINTLIST标识,告诉Direct3D压入点数据,不要使用三角形列表和带。之后我们完成绘画,我们再锁定缓冲区,指定D3DLOCK_DISCARD标志(现在我们发送chunk顶点数据到卡,我们没有任何需要了,所以我们丢弃它).

清理:Push Out the Stragglers

在我们完成前的最后的一点代码

m_vbParticles->Unlock()

if(dwNumParticlesToRender(){ if(FAILED(hr = m_pd3dDevice->DrawPrimitive( D3DPT_POINTLIST,0,dwNumParticlesToRender))) return hr; } m_pd3dDevice->SetRenderState(D3DRS_POINTSPRITEENABLE,FALSE);

m_pd3dDEvice->SetRenderState(D3DRS_POINTSCALEENABLE,FALSE);

抱歉!评论已关闭.