好友
阅读权限 20
听众
最后登录 1970-1-1
本帖最后由 Victory.ms 于 2012-9-12 18:57 编辑
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滴干活)”
好的教程离不开一个渣渣封面
注:凭本菜逼记忆的教程,可能不完全准确,有疑问请咨询MSDN
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添加到项目的搜寻目录中,这些惯例大家应该都懂,然后包含头文件
之后就是CreateWindow了,这个我不想多说,游戏开发我绝对不建议采用MFC(但也不反对),以前本菜菜还是大菜逼的时候,常常看网上说XX游戏是MFC做的,到今天发现我那是简直就被当猴耍
上面的代码应该已经被各种程序蹂X了很多次了,假如你不熟悉,不然你也许还要加强才能了解本教程,不然你对win的开发不感冒
创建了窗口后我们切入主题,初始化DirectX
我们先定义以上全局变量(Tips:在实际的游戏引擎构架中,DIRECT3DDEVICCE9将被封装在一个Graphics类中,其他的类通过这个Graphics实现对图形的绘制,而这些绘制纹理,网格的类我们管它叫图形核心,听起来是不是很带劲,在这里这个简化版的粒子系统这么定义没问题)
然后我们创建D3D设备,第一个Direct3DCreate9 是初始化的惯例参数看名字大家都懂的它将创建一个d3d对象
注意D3DDISPLAYMODE,他将获得计算机的硬件显示模式,这非常的重要,他意味着你是否能使用DX实现一系列相应的功能,初始化dx很有必要对他进行检查
之后就是CreateDevice了,在创建device前,我们先创建一个结构体
这里就已经涉及到了一系列的图形学知识,鉴于篇幅问题我挑重点的讲,首先是那个Backbuffer,这个和双缓冲技术有点像
双缓冲是解决屏幕闪烁的方式,就是创建一个和显存buf想适应的缓冲区而避免了直接写显存,若其他绘图操作都是对后备的buf进行操作的,当要显示图像时候,将后备buf直接复制到显存buf(当然不一定是复制),因为内存间操作很快,所以避免了一遍又一遍写显存而导致的闪烁。
注意,创建的buf最好与窗口大小相适应,因为他与你的窗口属于一种映射关系,比如你拉大窗口,里面的图形也会跟着拉伸,像这样
因此,请挑选合适的缓存区大小,backbufcount就是指缓冲区数目了,这涉及到了DX多缓存的一系列操作与效果,不过这里填1就行了,之后就是关于是否全屏,缓冲交换方式等,源代码上打上了注释,在本教程里建议就采用上面的方式,欲深入了解请百度,当CreateDevice完成后,DX的初始化工作就算完成的七七八八了
下面我们开始对粒子系统进行一些准备工作
首先了解下粒子系统如何实现
首先你可以看本菜菜的程序贴图,你可以看到《未闻花名》里的经典花海,不仅仅如此,你玩的3D游戏中那些非常cool的魔法效果,爆炸,雾,云等等等等,很大一部分都是由粒子系统所实现的,比如太空里的星云
火焰
这些都是就拿我的范例程序来说,每一朵花就是一个粒子元,粒子系统就是绘制成百上千个这样的花到屏幕上,伴随着各种运动与各种效果,包括重力空气阻力风向加速度初速度等等等等,学过物理的都知道,通过这些参数我们可以计算出一个粒子的运动轨迹,粒子需要成百上千,所以只要你计算轨迹的算法有一个是龟速级别的,都可以让你的粒子动画卡成幻灯片,其实粒子可以归结为(1.位置 2.颜色 )就这么简单的两个,通常我们有如下方法计算一个粒子该有的位置与轨迹
1.全部由cpu处理,就是硬编码到你的C++代码中,这显然是不够聪明的方式,但是在cpu如此高速的今天,这种方法显然看上去也能让人满意(但实际上你在一个游戏中大量用这种方法,你会发现玩你的游戏就是一场悲剧)
2.CPU与GPU联合工作
这种方式显然聪明点,至少不会让你卡成码,实际上本教程就是使用这种效果,希望能达到抛砖引玉的效果
方法3,通过可编程管线直接送给cpu处理
不管怎么说,GPU他顶得住而且很快速,这种方法本菜菜最喜欢
关于构架一个粒子系统
这里不妨曝光一下本菜菜引擎的方案
首先需要建立一个粒子脚本,将脚本送入脚本解析引擎进行解析,然后控制粒子系统加载特定的纹理,HLSL与提供相应的轨迹计算方案,实际上脚本解析将会造成不可避免的性能损失,因此聪明点的做法是将轨迹计算代码封装入C或汇编编写的dll里,在需要计算轨迹的时候加载它
在本教程中,我们省略了脚本解析这一部分,但将保留如何加载hlsl与如何进行cpu的轨迹运算
好了,说到这里请注意我上面说的一句话
在3D的图形世界中,mesh(网格)组成了模型的框架,mesh由N个三角形构成(这就是为什么我们看到的模型就像是三角形拼起来的很酷的感觉,但游戏中我们需要尽可能的减少三角形面的数目以减少运算,记住,在rander中任何一个龟速的函数都是不能容忍的。。。。额,我扯哪去了),而三角形又由点构成,点由位置标识,并附带着颜色等属性,(真的建议看一看相关计算机图形学知识,爽爆了),那么图形(我们更喜欢叫它纹理。。就是texture)是如何映射到面上的呢,我们有一个uv坐标,然后像这样
u为水平,v为垂直,两个代表1.0表示全图,若窦唯0.5就是左上方的四分之一了,映射纹理我们用DX的SetTexture函数,我之后介绍
我们开始定义顶点(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文档依照老前辈们的总结,粒子的环境大致可以如此地配置
1.粒子的创造间隔,很多情况下,你也许不会希望一大片的粒子突然出现在你的屏幕上,添加间隔,让粒子像水柱一般地喷射出来,而间隔为0,很多情况下被用在了爆炸的效果
2.设定粒子的最大数目----你不会希望无数的粒子占据了你整个屏幕并强X你的cpu资源的
3.粒子生存的时间,将那些在屏幕上活得太久的粒子释放或回收掉,比如爆炸造成的粒子,多数情况下你不会希望他们在你屏幕上常驻的
4.位置,这个情况是粒子都是由一个点发射出来的
5当前速度,都懂得,有大小有方向,用来计算下一个位置
6.速率
省下3个是环境变量
风向,重力,空气阻力,这些参数将直接影响环境中粒子的运行轨迹
tips:假如风向,重力都不需要,请记得将他们初始化为D3DXVECTOR3(0.0,0.0,0.0);
到现在为止,我们已经有了粒子的结构与环境的信息,依照Linden的说法程序开发应先让总体结构运行,在这里,我们率先将粒子的管理方法贴上
说道粒子的内存管理,很多人可能想到的是链表,将每一个粒子用链表的形式链接起来,这确实是一个可取的办法,除此之外,我们还能用vector(本教程不做谈论,应该是老熟人了吧),添加一个粒子就push进去一个,但是出于运行效率考虑,我建议对vector进行reserve以预留空间提高push的效率,当然,假如你对你的能力有信心的话,你可以专门为你的粒子系统制作一个内存池,这种方法使用得当同样高效
在本实例中,我们创建两个vector来对粒子进行管理
其中,m_vParticle用于容纳所有的粒子,包括活着的和挂掉的,而m_vParticleFree用于记录那些挂掉的粒子的索引号,上面我说过了,建议将粒子回收而不是删除(实际上在vector中进行粒子的删除或插入工作会让你的粒子系统变成一个大大的悲剧)
首先,粒子系统的Update首先会进行检查,看看当前活着的粒子是否达到了粒子的最大数目,假如没有它将会搜索m_vParticleFree,假如m_vparticleFree的元素不为空,他将取出那个已经free掉的粒子的索引号,再在m_vparticle中对该索引的粒子重置从而实现回收,假如m_vfreeParticle为空,就push_back一个新的粒子到m_vParticle;
而render的时候将会对m_vparticle进行一次遍历,假如粒子是alive的,那么就将他绘制到屏幕(缓存区)中;好了,管理方式有了我们来实现update
首先,粒子系统的Update首先会进行检查,看看当前活着的粒子是否达到了粒子的最大数目,假如没有它将会搜索m_vParticleFree,假如m_vparticleFree的元素不为空,他将取出那个已经free掉的粒子的索引号,再在m_vparticle中对该索引的粒子重置从而实现回收,假如m_vfreeParticle为空,就push_back一个新的粒子到m_vParticle;
而render的时候将会对m_vparticle进行一次遍历,假如粒子是alive的,那么就将他绘制到屏幕(缓存区)中;好了,管理方式有了我们来实现update
好了,上代码
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;i<ParticleNum;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(_ElpaseTime>m_dwCreateInterval) {
m_dwLastUpdate = m_dwCurTime;
if(m_dwCreateInterval!=0)
AddNum=_ElpaseTime/m_dwCreateInterval;
else
AddNum=m_dwMaxParticles;
for(int Add=0;Add<AddNum;Add++)
if( AliveCount < m_dwMaxParticles )
{ CreateParticleElement();
++AliveCount; }
}
return TRUE;
}
Tips:性能提示
访问局部变量的速度比访问类的成员变量速度快两倍
所以,这种访问方式避免
for(UINT i=0;i<ClassA.m_Num;i++)
改成
UINT x=ClassA.m_Num
for(UINT i=0;i<x;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)。如图,
它就是由前后两个截面截成的这个棱台。
从view空间的x正半轴看过去是下图这个样子。
接下来是project空间的可视范围。这个空间应当是处于你所见到的屏幕上。实际上将屏幕表面视作project空间的xoy平面,再加一条垂直屏幕向里(或向外)的z轴(这取决于你的坐标系是左手系还是右手系),这样就构成了我们想要的坐标系。好了,现在我们可以用视口(view port)的大小来描述这个可视范围了。比如说全屏幕640*480的分辨率,原点在屏幕中心,那我们得到的可视区域为一个长方体,它如下图(a)所示。
(图3)
但是,这样会带来一些设备相关性而分散我们的注意力,所以不妨先向DirectX文档学学,将project空间的可视范围定义为x∈[-1,1], y∈[-1,1], z∈[0,1]的一个立方体(上图b)。这实际上可看作一个中间坐标系,从这个坐标系到上面我们由视口得出的坐标系,只需要对三个轴向做一些放缩和平移操作即可。另外,这个project坐标系对clip操作来说,也是比较方便的。
按我的话来说,类似于
Tips:按照DX的说法,实际是投到了一个边长为2的立方体中
那么,投影变换有如下类似两种
在本实例中,我建议采用平行投影
而它的定义在DX中是
其中matproj是一个矩阵(线代知识),D3DXMatrixOrthoLH将构造一个平行投影矩阵并将矩阵写入matProj
D3DXMATRIX * D3DXMatrixOrthoLH(
D3DXMATRIX * pOut,
FLOAT w,
FLOAT h,
FLOAT zn,
FLOAT zf
);Parameters
pOut
[in, out] Pointer to the resulting D3DXMATRIX.w
[in] Width of the view volume.h
[in] Height of the view volume.zn
[in] Minimum z-value of the view volume which is referred to as z-near.zf
[in] Maximum z-value of the view volume which is referred to as z-far.
1.为输出矩阵
2.视宽
3.视高
4.最近的z大小(粒子坐标小于zn的都不会显示)
5.最远的z大小(大于的都不会显示)
然后我们用SetTransform将之作为当前的变换应用,D3DTS _Projection的意思大概就是投影变换方式
现在
假设你处在一个3维的世界中(额,难道我们不是在一个三维的世界?,YY了....)你会如何确定你想看到的景象
答案自然是用眼睛看罗,人通过旋转身体,移动位置从而看到不同空间的不同景象,在Direct3D的世界中,我们通过定义 眼睛的位置(D3DXVECTOR3),眼睛的朝向(D3DVECTOR3),向上的位置(D3DVECTOR3),视角大小(因为是平行投影变换,这里默认就行了不做设置)
这样,显示到你屏幕上的东西将是这个视角内的东西
同样构造一矩阵
D3DXMATRIX *WINAPI D3DXMatrixLookAtLH(
D3DXMATRIX *pOut, CONST D3DXVECTOR3 *pEye, CONST D3DXVECTOR3 *pAt, CONST D3DXVECTOR3 *pUp);
参数:
pOut
[in, out] 指向 D3DXMATRIX 结构的返回结果的矩阵。
pEye
[in] 指向D3DXVECTOR3 结构的眼睛所有在位置向量。这个值会用来作平移。
pAt
[in] 指向 D3DXVECTOR3 结构的摄像机观察目标位置向量。
pUp
[in] 指向D3DXVECTOR3 结构的当前世界坐标系向上方向向量。通常用[0, 1, 0]向量。
然后用SetTransform将之设置为当前视角,D3DTS_VIEW就是视角了
下面是绘制部分
在我们之前提到的双缓冲技术中,我们知道在内存中开辟一个与目标相对应的缓冲区,绘制对缓存区进行操作
在DirectX中,我们采用
创建一个DirectX的顶点缓冲区
并且用
来初始化这个缓冲区,参数1代表缓冲区大小,第二个是缓冲区访问模式,第三个是我们定义的顶点格式,第四个是内存池管理模式
思想,DirectX绘制的大概可以这样描述
1.Clear清除后备缓存
2.BeginScene
3.设置渲染模式并映射Texture 分别用SetRenderState与setTexture,用它来确定程序是否开启ZBuffer缓冲,是否开启Alpha混合,是否允许点精灵缩放......
4.开启Effect,设置对应的technique并执行相应的Pass(HLSL知识)
5.锁定顶点缓冲区
6.写入顶点数据
7.解锁缓冲区
8.绘制缓冲区
9.EndScene
我重点讲讲SetRenderState这一个部分的一小部分(因为太多了,所以只挑和本程序有关的知识点讲解)
要绘制一个如上图的粒子系统,首先需要对
SetRenderState进行开启Alpha测试,
第一行代码表示开启alpha测试
之后就是设置alpha混合模式了,不同的混合模式将产生不同的混合效果,比如我贴上的第一张效果就是第二行代码的混合模式
而我贴上的第二张效果就是
以这种方式设定的
在此处有对alpha混合模式的详细介绍
我贴上关键的一段
alpha混合原理
在前面介绍的示例程序中,绘制图形的颜色总是替换当前颜色缓冲区中存在的颜色,这样后面的物体总是覆盖在原有的物体上。但是当想要绘制类似于玻璃、水等具有透明效果的物体时,这种方法显然满足不了要求。通过定义一个表示物体半透明度的alpha值和一个半透明计算公式,可以将要绘制的物体颜色与颜色缓冲区中存在的颜色相混合,从而绘制出具有半透明效果的物体。Direct3D计算alpha颜色混合的方法如下:
color = (RGBsrc * Ksrc) OP (RGBdst * Kdst)
其中color表示alpha混合后的颜色值,RGBsrc表示源颜色值,即将要绘制的图元的颜色值;Ksrc表示源混合系数,通常赋值为表示半透明程度的alpha值,也可以是属于枚举类型D3DBLEND的任意值,用来和RGBsrc相乘。RGBdst表示目标颜色值,即当前颜色缓冲区中的颜色值,Kdst表示目标混合系数,可以是属于枚举D3DBLEND的任意值,用来和RGBdst相乘。OP表示源计算结果与颜色缓冲区计算结果的混合方法,默认状态下OP为D3DBLEND_ADD,即源计算结果与颜色缓冲区计算结果相加。
图形显示中,对alpha混合最普遍的用法是:把Ksrc赋值为D3DBLEND_SRCALPHA,即当前绘制像素的alpha值;把Kdst赋值为D3DBLEND_INVSRCALPHA,即1减去当前绘制像素的alpha值;把OP赋值为D3DBLEND_ADD,使源计算结果和颜色缓冲区计算结果相加,这样一来,alpha混合颜色的公式变为:
color = (RGBsrc * Ksrc) + (RGBdst * Kdst)
Effect翻译过来就叫影响了,这种影响是在Gpu内的操作,而创建这种Effect我们通过加载HLSL文件或GPU汇编来产生
首先创建一个Effect指针
然后加载文件创建它
在Render中我们可以对effect文件的变量赋值,比如这代码对里面的一个矩阵进行了赋值以便进行相关的运算
第一个参数是变量名,第二个是要赋值给他的值
然后我们依靠名称加载一个technique句柄
通过technique句柄执行该technique下的全部Pass
到此渲染结束,调用
EndScene结束渲染,然后调用Present让结果显示出来
在本实例中的HLSL代码如下(实现花的旋转效果)
float4x4 g_matWorldViewProj; // 世界/视图/投影变换矩阵
float g_texCutNumU = 1.0; //u坐标
float g_texCutNumV = 1.0; //v坐标
struct Particle {
float angle;
float texIndexU;
float texIndexV;
};
struct VS_INPUT { //顶点结构输入
float4 position : POSITION;
Particle particle : NORMAL;
float size : PSIZE;
float4 color : COLOR0;
};
struct VS_OUTPUT { //顶点输出结构
float4 position : POSITION;
float size : PSIZE;
float4 color : COLOR0;
float4 cossin : COLOR1;
float2 uv0 : TEXCOORD0;
}; const float PI = 3.1415926535;
VS_OUTPUT vs_main(VS_INPUT input) { VS_OUTPUT output;
output.position = mul(input.position, g_matWorldViewProj);
output.size = input.size;
output.color = input.color;
float angle = -input.particle.angle;
float cosne = cos(angle);
float sinne = sin(angle);
if(cosne >= 0.0){
output.cossin.x = cosne;
output.cossin.z = 0;
}else{
output.cossin.x = -cosne;
output.cossin.z = 1;
}
if(sinne >= 0.0){
output.cossin.y = sinne;
output.cossin.w = 0;
}else{
output.cossin.y = -sinne;
output.cossin.w = 1;
}
output.uv0 = 0.5; // 要有
return output;
} //const float PI = 3.14159265; sampler samp0 = sampler_state {
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
AddressU = CLAMP;
AddressV = CLAMP;
};
struct PS_INPUT { //像素着色器输入
float4 color : COLOR0;
float4 cossin : COLOR1;
float2 uv0 : TEXCOORD0;
};
float4 ps_main(PS_INPUT input) : COLOR0 {
float cosne = input.cossin.x;
float sinne = input.cossin.y;
if(input.cossin.z != 0.0){
cosne = -cosne;
}
if(input.cossin.w != 0.0){
sinne = -sinne;
}
float u = input.uv0.x - 0.5;
float v = input.uv0.y - 0.5;
float2 uv;
uv.x = u * cosne - v * sinne + 0.5; //计算旋转的关键代码
uv.y = u * sinne + v * cosne + 0.5;
return input.color * tex2D(samp0, uv); //纹理采样
}
technique techParticle { //技术定义
pass pass0 { //这里是Pass
VertexShader = compile vs_2_0 vs_main();
PixelShader = compile ps_2_0 ps_main();
}
}
DrawParticle()函数用于绘制粒子,也就是实现
的过程
具体请在本菜发布的源代码中查看,在这里提供
5.锁定顶点缓冲区
6.写入顶点数据
7.解锁缓冲区
8.绘制缓冲区
的关键函数,本菜不再详细解释(看函数名都看得出来)
别忘了最后的资源释放
特别是这个别忘记了
源代码发布:右键保存下面的图片,用winrar打开这个图片或修改后缀为.zip
tips:编译源代码需要DirectX SDK
发帖前要善用【论坛搜索 】 功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。