≡
导航
搜索
教程
插件
模型
模板
博客
交易
朋友
编程语言分享讨论总汇吧
已关注 | 取消
+关注
关注:
10
帖子:
1,222
签到
05月06日 尚未签到
看帖
图片
精品
视频
共有回帖数
0
个
构架游戏中的粒子系统 附:现场直播+图文教程
只看楼主
收藏
回复
回望空城
等级:
Expression GameEngine Particle System的实现效果
话说今天刚刚完成Expression GameEngine的粒子系统与粒子脚本解析部分的构架,高兴之余将这个凡出色游戏(什么,俄罗斯方块与贪吃蛇,本菜菜孤陋寡闻,那是什么东西,可以吃吗?) 中都不可或缺的粒子系统的构架方法与简化版的实现与大家分享,教程现场直播,附带本渣渣的临时手绘说明与本菜菜的菜逼级讲解。
教程须知
1.虽然是本菜菜讲解的菜逼级教程,但是,我不保证刚学C/C++一两年的朋友能够完全听懂,你可以收藏,可以马克,也可以开喷,但是请不要尝试和LZ我辩驳面码大爱这一个真理
2.教程需要相关数学与图形学的知识,建议了解过线性代数,矩阵构建,投影变换(我会粗略讲解).......但不知道到底有什么用处的朋友观看。
3.自从我可以删除我自己帖子的东西的时候,我已经不怕插楼了
4.本教程基于C++ with DirectX SDK,想亲手尝试的朋友可以下载DirectX SDK到电脑上,注意至少是9.0c以上版本
5.我会贴出上图源代码吗,答案是不会,因为本程序基于本菜逼的心血菜鸟作Expression,所以不会公开源代码,但是基于本菜菜的敬业精神我将贴上简化版的粒子系统源代码与方法,稍作修改也可以实现以上效果
6.除了C++外,还涉及到HLSL脚本语言,不了解的可以晚上看“固定渲染管线(CPU滴干活)”与“可编程渲染管线(GPU滴干活)”,另外本菜菜在HLSL吧中贴上了一些常用HLSL源码,有兴趣可以去看看
7.因为是现场直播现场码字,现场手绘渣渣讲解,所以可能有点慢,稍安毋躁,本菜菜说了,今天至少会直播到11点
1.DirectX 的配置
————第一步,安装DirectX SDK,下载安装过程自行解决,个人感觉凡好的书好的教程开头都不会花上二三十页教你如何安装一个软件,但是,我大天朝的教程书似乎特别喜欢教你如何下载安装一个软件,讲的甚至比关键的知识点还详细,安装完了之后,请留意以下文件夹
至于什么是DirectX,有什么用,相信DirectX与openGL大家都听过,在这里我不想多作介绍
用百度的话来说
DirectX,是微软公司创建的多媒体编程接口。用C++编程制作并遵循COM。广泛使用于游戏开发,openGL也是相似的同类货色
这么说吧通过DirectX能够直接绘制而不要经过windows坑爹的机制,所以用DX或openGL能够快速地绘制图形(那些还徘徊在用GDI或GDI+这类龟速接口还万分得意地开发坦克大战的早点弃暗投明吧),但是GDI绝非一无是处,比如开发GUI控件的时候,假如你不想自己重新假设一个字模引擎并且和本菜菜一样懒的话,GDI内置的绘制字体函数还是你的好基友,实际上DXFont内部也是采用了GDI,但是,采用GDI绘制文字过于频繁绝对也会导致帧率杀手,那么,我推荐你使用免费的字模引擎FreeType.......扯远了,自挽
下面打开VS开始编程之旅吧
首先自然是配置,假如你没有采用我上面所叙述的方法,你得把DirectX SDK里的include 和lib添加到项目的搜寻目录中,这些惯例大家应该都懂,然后包含头文件
然后我们创建D3D设备,第一个Direct3DCreate9 是初始化的惯例参数看名字大家都懂的它将创建一个d3d对象
注意D3DDISPLAYMODE,他将获得计算机的硬件显示模式,这非常的重要,他意味着你是否能使用DX实现一系列相应的功能,初始化dx很有必要对他进行检查
之后就是CreateDevice了,在创建device前,我们先创建一个结构体
双缓冲是解决屏幕闪烁的方式,就是创建一个和显存buf想适应的缓冲区而避免了直接写显存,若其他绘图操作都是对后备的buf进行操作的,当要显示图像时候,将后备buf直接复制到显存buf(当然不一定是复制),因为内存间操作很快,所以避免了一遍又一遍写显存而导致的闪烁。
注意,创建的buf最好与窗口大小相适应,因为他与你的窗口属于一种映射关系,比如你拉大窗口,里面的图形也会跟着拉伸,像这样
这些都是就拿我的范例程序来说,每一朵花就是一个粒子元,粒子系统就是绘制成百上千个这样的花到屏幕上,伴随着各种运动与各种效果,包括重力空气阻力风向加速度初速度等等等等,学过物理的都知道,通过这些参数我们可以计算出一个粒子的运动轨迹,粒子需要成百上千,所以只要你计算轨迹的算法有一个是龟速级别的,都可以让你的粒子动画卡成幻灯片,其实粒子可以归结为(1.位置 2.颜色 )就这么简单的两个,通常我们有如下方法计算一个粒子该有的位置与轨迹
1.全部由cpu处理,就是硬编码到你的C++代码中,这显然是不够聪明的方式,但是在cpu如此高速的今天,这种方法显然看上去也能让人满意(但实际上你在一个游戏中大量用这种方法,你会发现玩你的游戏就是一场悲剧)
这种方式显然聪明点,至少不会让你卡成码,实际上本教程就是使用这种效果,希望能达到抛砖引玉的效果
方法3,通过可编程管线直接送给cpu处理
不管怎么说,GPU他顶得住而且很快速,这种方法本菜菜最喜欢
首先需要建立一个粒子脚本,将脚本送入脚本解析引擎进行解析,然后控制粒子系统加载特定的纹理,HLSL与提供相应的轨迹计算方案,实际上脚本解析将会造成不可避免的性能损失,因此聪明点的做法是将轨迹计算代码封装入C或汇编编写的dll里,在需要计算轨迹的时候加载它
在本教程中,我们省略了脚本解析这一部分,但将保留如何加载hlsl与如何进行cpu的轨迹运算
我们开始定义顶点(Vertex)
DirectX提供了用户自定义顶点结构,我们管它叫 FVF
你可以自定义属于你自己的顶点结构,但是!!!,但是!!!!,一定要与你定义的FVF相对应
在本粒子系统中,我们无需用三角形扇,三角形面之类的映射粒子纹理,我们仅仅需要的是一个点(这句话不好理解的话请以后面的介绍对应下你就会轻松理解了)
下面代码摘抄自csdn上的一个源码
首先定义一个叫FVF_PS的顶点结构声明
XYZ就代表点位置了,NORMAL原来是法向量的意思,粒子根本用不到这个属性,但是我们可以定义它然后用它来记录别的信息,比如旋转角度,uv坐标等,PSZIE就是PointSize了,Diffuse漫反射光,你可以直接理解为颜色(漫反射光与颜色的关系请参照图形学,加上镜面反射,环境光,聚光七七八八的有点多,本教程不作讨论)
肯定有人会困惑,这样映射有什么意义吗
有,大大地有
这些顶点参数经过了GPU渲染管线,你可以使用GPU提供的寄存器访问到他们因此可编程管线才变得有意义(HLSL中我们用称为“语义”的玩意间接访问它们),详情建议看看GPU汇编
本文后面将会进行粗略的介绍
好了,顶点定义完了是时候定义我们自己的粒子结构了
粒子的结构尽可能精炼,毕竟想想看,粒子的数目成千上万
我来解释一下,首先D3DVECTOR3代表一个向量(没别的意思,上次有一个初中辍学的问学编程,我回了两个字:呵呵!),都知道向量代表着很多意义,方向模长什么的,这里的向量与高中理解的向量必须纠正下理解,向量记录着三个成员变量,x,y,z,他可以用来记录位置,就这样,别全都非要和方向搞上什么关系,好了,第一个向量记录位置,第二个就是真正的向量了,它代表了当前的速度,有大小,有方向,第三个是粒子的出生时间,第四个是颜色,然后分别是大小,每秒旋转角度,当前旋转角度,uv坐标。最后一个代表这个粒子是否还“活着”
tips:alive是粒子系统维护的灵魂,将那些达到生存时间的,脱离视角的,不需要的粒子及时的释放或回收掉(建议回收),这关系到粒子系统的渲染计算速度与内存的节约
一个常规的粒子生成器像3D MAX或者说HEG游戏引擎中内置的粒子生成器都会给出一个菜单来配置粒子的环境变量,这些环境变量将有助于你快速地创建出一个粒子发射器并得到期望的效果,这种快速配置的方式虽然不能完完全全地创造出你期望的所有效果,但是它却非常的适用于大多数的粒子配置,我们甚至可以考虑将他硬编码到我们的程序当中或者用它编写相应的HLSL文档
依照老前辈们的总结,粒子的环境大致可以如此地配置
tips:假如风向,重力都不需要,请记得将他们初始化为D3DXVECTOR3(0.0,0.0,0.0);
到现在为止,我们已经有了粒子的结构与环境的信息,依照Linden的说法程序开发应先让总体结构运行,在这里,我们率先将粒子的管理方法贴上
说道粒子的内存管理,很多人可能想到的是链表,将每一个粒子用链表的形式链接起来,这确实是一个可取的办法,除此之外,我们还能用vector(本教程不做谈论,应该是老熟人了吧),添加一个粒子就push进去一个,但是出于运行效率考虑,我建议对vector进行reserve以预留空间提高push的效率,当然,假如你对你的能力有信心的话,你可以专门为你的粒子系统制作一个内存池,这种方法使用得当同样高效
在本实例中,我们创建两个vector来对粒子进行管理
首先,粒子系统的Update首先会进行检查,看看当前活着的粒子是否达到了粒子的最大数目,假如没有它将会搜索m_vParticleFree,假如m_vparticleFree的元素不为空,他将取出那个已经free掉的粒子的索引号,再在m_vparticle中对该索引的粒子重置从而实现回收,假如m_vfreeParticle为空,就push_back一个新的粒子到m_vParticle;
而render的时候将会对m_vparticle进行一次遍历,假如粒子是alive的,那么就将他绘制到屏幕(缓存区)中;
update将会遍历m_vparticle的元素,依靠元素的速度与环境变量结合运算出粒子新的位置(颜色)
运用的知识是高中物理的知识,比如s=1/2gt平方之类的,可悲的是,LZ高中尽干类似这事儿了
好了,上代码
BOOL CParticleSystemDEMO::Update(DWORD Time){
D3DXVECTOR3 vOldPosition;
UINT AliveCount=0;
m_dwCurTime = Time;
m_dwElpasedTime = m_dwCurTime - m_dwLastTime;
m_dwLastTime = m_dwCurTime;
DWORD dwElpasedTime=m_dwElpasedTime;
m_dwCurrentTime += dwElpasedTime;
UINT ParticleNum=m_vParticle.size();
float fElpaseTime=dwElpasedTime*0.001f;
for(UINT i=0;iParticleNum;i++ )
{
DWORD dwTimePassed = m_dwCurrentTime - m_vParticle.at(i).m_dwTime;
if( DieCase(dwElpasedTime,m_vParticle.at(i)))
{ m_vParticle.at(i).isAlive=FALSE;
m_vParticleFree.push_back(i); }
else
{
if(m_vParticle.at(i).isAlive)
{
m_vParticle.at(i).m_ParticleCurVel += m_vGravity * fElpaseTime;
if( m_bAirResistence == TRUE )
m_vParticle.at(i).m_ParticleCurVel += (m_vWind - m_vParticle.at(i).m_ParticleCurVel) * fElpaseTime;
vOldPosition = m_vParticle.at(i).m_ParticleCurPos;
m_vParticle.at(i).m_ParticleCurPos += m_vParticle.at(i).m_ParticleCurVel * fElpaseTime;
m_vParticle.at(i).CurAngle+=m_vParticle.at(i).angle*fElpaseTime;
if (m_vParticle.at(i).CurAngle=360.0)
{
m_vParticle.at(i).CurAngle-=360.0;
}
AliveCount++;
}
}
DWORD _ElpaseTime=m_dwCurTime-m_dwLastUpdate;
int AddNum; if(_ElpaseTimem_dwCreateInterval) {
m_dwLastUpdate = m_dwCurTime;
if(m_dwCreateInterval!=0)
AddNum=_ElpaseTime/m_dwCreateInterval;
else
AddNum=m_dwMaxParticles;
for(int Add=0;AddAddNum;Add++)
if( AliveCount m_dwMaxParticles )
{ CreateParticleElement();
++AliveCount; }
}
return TRUE;
}
Tips:性能提示
访问局部变量的速度比访问类的成员变量速度快两倍
所以,这种访问方式避免
for(UINT i=0;iClassA.m_Num;i++)
改成
UINT x=ClassA.m_Num
for(UINT i=0;ix;i++)
现在,我们有了该有的粒子结构,是时候运用DirectX将我们的粒子绘制到屏幕上了
你可能需要了解下矩阵的相关知识,希望有兴趣的读者自行百度或谷歌
还在用c的graphics或win的GDI编写俄罗斯方块和贪吃蛇的眼中也许只有xy二维坐标系的概念,将纹理贴在屏幕坐标xy上实现2D动画,同样的事干多了,YY起来都没点感觉,在这里,我引入世界坐标系这个概念
DirectX中绘制位置一般情况下都世界坐标系的方式也就是三维的方式(当然,SpriteDraw是D3D中一个很方便的绘制2D贴图的方式),这其实不难理解,GDI是在一个二维的平面内绘制图形,这里无非就多了个z轴,绘制的思想还是大同小异的,这个Z轴和深度缓存相关,按我们现实的话来说,z越大(就是越远),看起来越小,z越小的将会遮挡住z越大的
Tips:别搞错了,虽然世界坐标系是三维的,按我们的显示屏幕总还是二维的啊,这就关系到投影变换的知识了
我非常乐意地复制粘贴一段:
写3d图形程序,就一定会做坐标变换。而谈到坐标变换,就不得不提起投影变换,因为它是所有变换中最不容易弄懂的。但有趣的是,各种关于透视变换的文档却依然是简之又简,甚至还有前后矛盾的地方。看来如此这般光景,想要弄清楚它,非得自己动手不可了。所以在下面的文章里,作者尝试推导一遍这个难缠的透视变换,然后把它套用到 DX和 PS2lib 的实例中去。
一般概念
所谓透视投影变换,就是view 空间到project 空间的带透视性质的坐标变换步骤(这两
个空间的定义可以参考其他文档和书籍)。我们首先来考虑它应该具有那些变换性质。很显然,它至少要保证我们在view空间中所有处于可视范围内的点通过变换之后,统统落在project空间的可视区域内。好极了,我们就从这里着手——先来看看两个空间的可视区域。
由于是透视变换,view空间中的可见范围既是常说的视平截体(view frustum)。如图,
楼主 2015-11-12 20:21
回复
共有回帖数
0
个
回 帖
表情
图片
视频
欢迎来到本吧,您可以在此发帖和众多大咖交流学习.
选择或直接输入昵称
Tips:支持QQ截图直接粘贴
发表
登录直线网账号
自动登录
忘记密码
免费注册
本吧信息
查看详情
吧主:
禾木
本吧公告
好好学习,天天向上!
我常逛的吧
我管理的吧
Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号
意见反馈
|
关于直线
|
版权声明
|
会员须知