***比起不做而后悔,不如做了再后悔 ——《游戏人生》***
平常咱们常见的特效:
诸如此类。
今天将学习一种特殊的特效——“粒子特效”。
粒子特效
粒子是什么?粒子是一种微小的物体,比如像我们周边环境中的雪花,火星等物体。因此在游戏中一般都用粒子特效来模拟咱们现实生活中的许多自然现象。
粒子系统
粒子系统是众多粒子的集合。一般具有具有粒子的更新,显示,销毁及其创建粒子等性质。不同的粒子系统具有不同的粒子行为,所以所具有的性质会略有区别。
使用粒子技术
1、从基础开始
2、使用四边形来绘制粒子
3、使用点精灵来进行绘制
-
从基础开始
粒子通常是一个四边形,用一小组纹理渲染,因此绘制一个粒子只需要使用两个三角形(构成一个四边形)。但必须根据公告板(billboarding)的渲染来确保多边形总是面向观察者。所谓公告板(billboarding)就是一个四边形,通过对其自身世界变换矩阵的控制,使其总是面向摄像机。
如图:
两个三角形组合在一起构成一个四方形,从上往下看,公告板确保多边形总是面向观察者的。最初,一个公告板(billboarding)对象被创建出来指向的是Z轴负向,但随着观察者的移动,公告板对象旋转以使它总是面向观察者。
如图:
位于左边的四边形初始时指向Z轴负向,当绘制时不断发生旋转,使它面向观察者。
因此使用公告板技术,主要可以节省存储器并加快渲染的速度,不过显示的粒子有点让人大跌眼镜。
- 使用四边形绘制粒子
绘制粒子就如同绘制多边形一样,使用两个三角形组合创建一个粒子。
如图创建了一个10单位大小的粒子:
一个10单位大小的粒子,它在原点上沿着x轴和y轴分别延伸5个单位。(由于做粒子动画,公告板技术(billboarding)的效果不如后面所介绍的***点精灵***好,所以没仔细了解,下次补充)
-
使用点精灵
什么是点精灵?
点精灵就是增加了渲染的公告板(billboarding),点精灵中,每个粒子被表示为一个单一的三维坐标(粒子的中心)和粒子的尺寸值,因此存储和处理的时候只需要处理一个顶点而非4个,这相比上面的公告板(billboarding)技术就节省了内存和大量的时间。结构格式
struct Particle
{
D3DXVECTOR3 position; //粒子的位置
D3DCOLOR _color; //粒子的颜色,可选可不选,看个人需求
float x,y; //纹理映射坐标
static const DWORD FVF; //灵活顶点格式
};
const DWORD Particle::D3DFVF_POINTVERTEX = D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1; //每一个顶点格式和该结构体中的每一类信息一一对应
点精灵的绘制状态
Status | bool | describe |
---|---|---|
D3DRS_POINTSPRITEENABLE | true | 规定整个当前纹理被映射到点精灵上 |
false | 规定点精灵的纹理坐标根据所指定的纹理单元被映射到点精灵上 | |
D3DRS_POINTSCALEENABLE | true | 规定点的尺寸将用观察坐标系的单位来度量(点精灵的尺寸将根据近大远小的原则进行相应的比例变换) |
false | 规定点的尺寸将用屏幕坐标系的单位(像素)来度量 |
Status | type | describe |
---|---|---|
D3DRS_POINTSIZE | D3DRENDERSTATETYPE | 用于指定点精灵的尺寸 |
D3DRS_POINTSIZE_MIN | D3DRENDERSTATETYPE | 指定点精灵可取的最小尺寸 |
D3DRS_POINTSIZE_MAX | D3DRENDERSTATETYPE | 规定点精灵可取的最大尺寸 |
使用样例:
//当前纹理映射到点精灵上
IDirect3DDevice9 *g_Device=NULL;
g_Device->SetRenderState(D3DRS_POINTSPRITEENABLE, true);
//点的尺寸用观察坐标系的单位来度量
g_Device->SetRenderState(D3DRS_POINTSCALEENABLE, true);
//指定点精灵的尺寸
g_Device->SetRenderState(D3DRS_POINTSIZE,FloatoD(1.0f));
//因为SetRenderState函数要求传入的类型必须是DWORD类型而不是float类型的
DWORD FloatoD(float f)
{
return *((DWORD*)&f); //采取强制转换
}
//指定最小 OR 最大尺寸,也和指定点精灵的尺寸类似,不过是将第一个参数进行修改
粒子所具备的属性:
粒子除了位置和其颜色之外应该还具有其他属性,比如粒子应该具有一定的速度或者加速度,某些特别的粒子还应具有旋转速度,生存周期等属性。
因此我们可以定义如下粒子类
//Demo 粒子基类
class Particle
{
public:
D3DXVECTOR3 m_Pos; //粒子的位置
D3DXVECTOR3 m_velocity; //粒子的速度
FLOAT m_RotationX; //X轴旋转速度
FLOAT m_RotationY; //Y轴旋转速度
FLOAT m_FallSpeed; //下降速度
FLOAT m_RotationSpeed; //旋转速度
INT m_TextureIndex; //纹理索引
FLOAT m_size; //粒子的大小
DWORD m_vbSize; //顶点缓存中存储顶点的个数
DWORD m_vbOffset; //偏移量
DWORD m_vbBatchSize; //每批粒子的数目
IDirect3DDevice9 *m_d3dDevice; //设备对象
LPDIRECT3DVERTEXBUFFER9 m_pVertexBuffer; //粒子顶点缓存
public:
Particle() {}
virtual ~Particle() {}
};
有了这个粒子类,我们还需要定义某个具体的粒子系统类,该类的任务是操作粒子,完成粒子的产生,更新,渲染,消失再到生成的过程。
定义如下:
//继承粒子类,形成新的雪花粒子类
class SNOW :public Particle
{
public:
SNOW(IDirect3DDevice9* _d3dDevice);
SNOW(){}
virtual ~SNOW();
HRESULT InitSnowParticle(); //粒子系统初始化函数
HRESULT UpdateSnowParticle(float fElapsedTime); //粒子系统更新函数
VOID PreRenderSnowParticle(); //粒子系统渲染准备
HRESULT RenderSnowParticle(); //粒子系统渲染函数
LPDIRECT3DTEXTURE9 m_pTexture[6]; //雪花纹理数组//
};
看看各个成员函数之间的内容
雪花粒子初始化:(Initialize)
HRESULT SNOW::InitSnowParticle()
{
//利用随机数函数,给雪花粒子的坐标位置,旋转速度,下降速度,及其纹理赋予初值
srand(GetTickCount());
for (int i=0;i<PARTICLE_MAXNUM;i++)
{
FLOAT X = (FLOAT)(rand() % SNOW_LENGTH_X-SNOW_LENGTH_X/3);
FLOAT Y = (FLOAT)(rand() % SNOW_WIDTH_Z-SNOW_WIDTH_Z/3);
FLOAT Z = (FLOAT)(rand() % SNOW_HEIGHT_Y-SNOW_HEIGHT_Y/3);
snows[i].m_Pos = D3DXVECTOR3(X, Y, Z);
snows[i].m_RotationX = (rand() % 180)*0.020f*D3DX_PI;
snows[i].m_RotationY = (rand() % 180)*0.020f*D3DX_PI;
snows[i].m_FallSpeed = (rand() % 300) + 100.0F;
snows[i].m_RotationSpeed = 3.0f + rand() % 10 * 0.1f;
snows[i].m_TextureIndex = rand() % 6;
}
//创建雪花粒子顶点缓存
m_d3dDevice->CreateVertexBuffer(4 * sizeof(_POINT), 0,
_POINT::D3DFVF_POINTVERTEX, D3DPOOL_MANAGED, &m_pVertexBuffer, NULL);
//填充雪花粒子顶点缓存
_POINT vertices[] =
{
{D3DXVECTOR3(-30.0F,0.0F,0.0F),0.0F,1.0F,},
{D3DXVECTOR3(-30.0F,60.0F,0.0F),0.0F,0.0F,},
{D3DXVECTOR3(30.0F,0.0F,0.0F),1.0F,1.0F},
{D3DXVECTOR3(30.0F,60.0F,0.0F),1.0F,0.0F}
};
//加锁
VOID* pVertices;
m_pVertexBuffer->Lock(0, sizeof(vertices), (void**)&pVertices, 0);
//访问
memcpy(pVertices, vertices, sizeof(vertices));
//解锁
m_pVertexBuffer->Unlock();
//创建6种雪花纹理
D3DXCreateTextureFromFile(m_d3dDevice, L"snow1.jpg", &m_pTexture[0]);
D3DXCreateTextureFromFile(m_d3dDevice, L"snow2.jpg", &m_pTexture[1]);
D3DXCreateTextureFromFile(m_d3dDevice, L"snow3.jpg", &m_pTexture[2]);
D3DXCreateTextureFromFile(m_d3dDevice, L"snow4.jpg", &m_pTexture[3]);
D3DXCreateTextureFromFile(m_d3dDevice, L"snow5.jpg", &m_pTexture[4]);
D3DXCreateTextureFromFile(m_d3dDevice, L"snow6.jpg", &m_pTexture[5]);
return S_OK;
}
雪花粒子的属性更新,形成动画效果:(Update)
HRESULT SNOW::UpdateSnowParticle(float fElapsedTime)
{
//一个for循环,更新每个雪花粒子的当前位置和角度
for (int i = 0; i < PARTICLE_MAXNUM; i++)
{
FLOAT Y = snows[i].m_FallSpeed*fElapsedTime;
D3DXVECTOR3 temp = D3DXVECTOR3(0, Y, 0);
snows[i].m_Pos -= temp;
//如果雪花粒子落到地面, 重新将其高度设置为最大
if (snows[i].m_Pos.y <=-1000)
{
snows[i].m_Pos.y = SNOW_HEIGHT_Y;
}
//更改自旋角度
snows[i].m_RotationX += snows[i].m_RotationSpeed * fElapsedTime;
snows[i].m_RotationY += snows[i].m_RotationSpeed * fElapsedTime;
}
return S_OK;
}
雪花粒子渲染前准备:(Previous Render)
//设置好各种状态
VOID SNOW::PreRenderSnowParticle()
{
//禁用照明效果
m_d3dDevice->SetRenderState(D3DRS_LIGHTING, false);
//设置纹理状态
m_d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); //将纹理颜色混合的第一个参数的颜色值用于输出
m_d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); //纹理颜色混合的第一个参数的值就取纹理颜色值
m_d3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); //缩小过滤状态采用线性纹理过滤
m_d3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); //放大过滤状态采用线性纹理过滤
//设置Alpha混合系数
m_d3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true); //打开Alpha混合
m_d3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); //源混合系数设为1
m_d3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); //目标混合系数设为1
m_d3dDevice->SetRenderState(D3DRS_ZENABLE, false); //关闭深度缓存
//设置剔出模式为不剔除任何面
m_d3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
}
最后开始渲染粒子:(Render Particles)
HRESULT SNOW::RenderSnowParticle()
{
PreRenderSnowParticle();
//渲染雪花
for (int i = 0; i < PARTICLE_MAXNUM; i++)
{
//构造并设置当前雪花粒子的世界矩阵
static D3DXMATRIX matYaw, matPitch, matTrans, matWorld;
D3DXMatrixRotationY(&matYaw, snows[i].m_RotationY);
D3DXMatrixRotationX(&matPitch, snows[i].m_RotationX);
D3DXMatrixTranslation(&matTrans, snows[i].m_Pos.x, snows[i].m_Pos.y, snows[i].m_Pos.z);
matWorld = matYaw * matPitch * matTrans;
m_d3dDevice->SetTransform(D3DTS_WORLD, &matWorld);
//渲染当前雪花粒子
m_d3dDevice->SetTexture(0, m_pTexture[snows[i].m_TextureIndex]); //设置纹理
m_d3dDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(_POINT)); //把包含的几何体信息的顶点缓存和渲染流水线相关联
m_d3dDevice->SetFVF(_POINT::D3DFVF_POINTVERTEX); //设置FVF灵活顶点格式
m_d3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); //绘制
}
//恢复相关渲染状态:Alpha混合 、剔除状态、光照
m_d3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
m_d3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
m_d3dDevice->SetRenderState(D3DRS_LIGHTING, true);
return S_OK;
}
好了,基本粒子的核心部分算是完了。
运行效果图:
代码下载链接:
码云源代码: