[GEiv]第六章:粒子特效 绚丽的火焰与爆炸

第六章:粒子特效

绚丽的火焰与爆炸

本章节主要介绍粒子特效设计的方法论,其中有相当的知识量是平台无关的;在本文中会以“爆炸”这个实际的例子为线索,进行详细的设计讲解,并最终使用GEiv实现它。

[为什么要使用”粒子”]

实现粒子特效的首要目的,是对一些环境效果进行模拟仿真,常见的环境效果,例如火焰、爆炸、雨、雪、雾等,都是无数微小的粒子以某些规律共同作用的结果。而对于计算机来讲,虽然没有足够的运算能力对每一个自然粒子进行抽象,但我们可以借鉴其原理,使用相对更少的粒子对这些自然现象进行模拟和仿真,以达到近似的效果。

[需要设计哪些内容]

[粒子属性]

首先需要设计的是单个粒子的属性,这里我们以粒子个体作为考虑的焦点,考虑的内容往往是粒子的共有属性,属性的内容可以是图形样式、大小、颜色等等。

[投射规律]

投射规律考虑粒子以何种方式投射到屏幕上,这里以粒子群为考虑的焦点,考虑的内容会涉及到实际的物理规律,例如粒子在空间中的角度分布、速度分布以及颗粒大小分布等情况。

[演变规律]

演变规律是抛射后的粒子随着时间变化的规律,它同样会涉及到物理规律的模拟,只不过这次是针对单个粒子的设计,例如速度、自旋角度、颜色等属性的变化规律。

[实例-模拟爆炸]

爆炸特效在游戏中的使用相当广泛,属于经典的粒子系统。现在我们从零开始,设计一个爆炸的粒子特效。

在想要模拟爆炸前先来观察一个实际的爆炸例子:

从图片中我们能够概况一些基本的物理规律:

首先,在一个爆炸中,粒子的大小显然是不同的,而且,简单的想,粒子的大小与其质量成正比,所以粒子速度应该与其大小负相关,你可以看到颗粒状的小型碎片已经飞到了火焰之外的区域,这是动量守恒定律所确定的。

其次,在爆炸的中心,能量较高,呈现出亮白色;而在爆炸的外围,与空气接触后热量明显下降,火焰呈现出暗红色,在这个过程中,颜色也呈现出了明显的变化规律:亮白-》黄色-》红色-》暗红。

还有,速度的变化规律:在爆炸发生后,粒子的速度并不会一直不变,它还要受到空气阻力的作用,根据流体力学的相关内容,空气阻力与速度的平方成正比,与物体在运动方向的正投影面积成正比,所以其速度变化应该表现为某种受到阻尼的运动状态。

最后,在能量耗尽的暗红色区域,粒子逐渐消失,也就是说其颜色通道系数应该以某种非线性(先慢后快)的方式衰减。

[属性设计]

粒子图元:首先需要确定的问题,我们如何选择粒子的图形呢,使用点?圆形?方块?还是使用某种贴图呢……其实设计粒子的基本形态很值得一说,我们暂且使用圆形来设计,在最后您可以看到更改粒子形态对整体特效的影响。

粒子的颜色:由白到红,初始值使用白色。

粒子的大小:为了较为明确的产生大小两种粒子,我将使用一定的概率分布策略随机产生大小(详见投射设计部分)。

自旋角度:在圆周上均匀分布,由于一开始我们使用圆形作为图元,所以这个自旋这个属性不会显露出来。

通道:Alph初始值设置为1.0。

[投射设计]

产生:在我们给定爆炸点之后,假定粒子围绕着给定点进行+/-5位置浮动的随机的产生。

大小分布:以50%的概率产生6~35大小的粒子,否则产生6~24大小的粒子,这里只是一个简单方案,你也可以考虑使用高斯分布等。

速度方向分布:以产生点进行360度均匀分布。

速度大小分布:为了简化选择了恒定值,但是,空气阻力模型在演变中起到作用,故仍可观察到非常近似地模拟结果。

[演变设计]

速度衰减:

对于每一帧:v -= a*w^2*v;其中,w是粒子大小,a是衰减系数,v是当前速度,也就是说,速度进行阻尼衰减,并且大碎片的速度衰减的更快。

颜色衰减:

↑衰减时间图

↑衰减过程均匀抽样

首先,RGB中的红色分量是不变的。

假如把时间t变量规格化到0~1之间。

那么,蓝色分量应该最快衰减,因为爆炸主色调至少应该是一个暖色调。所以蓝色线使用的是t^16。

绿色分量暂时设置为伴随t的线性衰减,其实,G分量衰减速度可以依据大小而定以获得更逼真的效果。

通道衰减:

↑衰减时间图

↑衰减过程均匀抽样

通道衰减过程先慢后快,这样,在特效开始的一段时间内,我们不会感到通道的变化,直到粒子快要消亡时才会有直观的视觉感受。

[编码实现]

接下来就是编码阶段了,我们也明确的看到,其实整个粒子特效的实现过程中,设计占了相当大的比例,在最后的阶段,只不过是要我们使用擅长的平台去实现罢了,其实很多软件开发都是这样的,编码只是个实现过程,不是什么高科技。

您可以到GitHub上找到本章中的例子。这里进入

在Geiv下,我们的粒子仅需要实现Individual接口,并使用个体的集群管理器进行管理即可(参阅第五章)。

//ExpIndividual.java:
package com.geiv.test;

import engineextend.crowdcontroller.Individual;
import geivcore.UESI;
import geivcore.enginedata.obj.Obj;
import java.awt.Color;
import com.thrblock.util.REPR;
import com.thrblock.util.RandomSet;

public class ExpIndividual implements Individual {
	UESI UES;
	Obj disp;

	float sTallms = 500;//这里设置了粒子从产生到消亡的总经历时间
	float allms = sTallms;
	float Dms = 17;//这里设置了每一帧的时间,你也可以用1000/UES.getFPS这中方法在构造器里填充

	float V = 4.5f;//运动的初始速度被固定为4.5像素每帧
	float ax, ay;
	float vx, vy;
	float Theta;//自选角度,本例中暂时使用圆形,所以是看不出的

	public ExpIndividual(UESI UES) {
		this.UES = UES;
		disp = UES.creatObj(UESI.XRIndex);//这里把图元产生在了XR层,前面的章节中介绍了该层次混合模式的特点。
		disp.addGLOval("FFFFFF",0,0,12,12,12);//画一个圆形
		disp.setGLFill(true);
		disp.setColor("FFFFFF");
		disp.setAlph(disp.getTopDivIndex(), 1.0f);

		allms = sTallms;
	}

	@Override
	public boolean isAvalible() {
		return !disp.isPrintable();//关于Individual请参考第五章的介绍
	}

	@Override
	public void getUse(Object[] ARGS, float... FARGS) {
		int Rad;//我们使用一定的分布方法产生Rad大小,RandomSet是内置的随机数发生器,其静态方法名称都比较好理解,就不在这里细细讲解了。
		if (RandomSet.getRate(50)) {//以50%的概率返回布尔值true
			Rad = RandomSet.getRandomNum(6, 35);//返回6~35随机数,均匀分布。
		} else {
			Rad = RandomSet.getRandomNum(6, 24);
		}
		disp.setWidth(Rad);
		disp.setHeight(Rad);
		//初始位置具有+/-5的浮动区域
		disp.setCentralX(FARGS[0] + RandomSet.getRandomNum(-5, 5));
		disp.setCentralY(FARGS[1] + RandomSet.getRandomNum(-5, 5));
		//初始自选角度,0~360均匀分布。
		disp.setAngle(RandomSet.getRandomFloatIn_1() * 360);
                //速度角,0~2PI均匀分布,使用弧度是为了方便调用Math下的三角函数。
		Theta = (float) Math.PI * 2 * RandomSet.getRandomFloatIn_1();
		vx = V * (float) Math.sin(Theta);//计算横纵向速度
		vy = -V * (float) Math.cos(Theta);
		ax = -0.0003f * (disp.getWidth() * disp.getWidth()) * vx;//计算加速度
		ay = -0.0003f * (disp.getHeight() * disp.getHeight()) * vy;
		disp.show();//显示到屏幕上(投射完成)
	}

	@Override
	public void doStp(int clock) {
		if (this.allms > Dms) {
			allms -= Dms;//allms记录当前剩余存活期,使用这个变量是为了将存活期规格化到0~1之间。
                        //REPR是内置的变换工具,可以将一个规格化后的线性量转化为自定义的常用非线性量
			//颜色变化
			disp.setColor(new Color(1.0f, REPR.Rep_POW_1_F(allms / sTallms, disp.getWidth() / 24), REPR.Rep_POW_F(allms / sTallms, 16)));
			//通道变化
			disp.setAlph(REPR.Rep_POW_1_F(allms / sTallms, disp.getWidth() / 12));
			//运算加速度
			ax = -0.0003f * (disp.getWidth() * disp.getWidth()) * vx;
			ay = -0.0003f * (disp.getHeight() * disp.getHeight()) * vy;
			//运算速度
			vx += ax;
			vy += ay;
			//运算位置
			disp.setDx(disp.getDx() + vx);
			disp.setDy(disp.getDy() + vy);
		} else {
                        //生命周期结束后,将粒子资源回收
			finish(Individual.SRC_INNER);
		}
	}

	@Override
	public void finish(int src) {
		disp.hide();
		//重置颜色与通道
		disp.setColor("FFFFFF");
		disp.setAlph(disp.getTopDivIndex(), 1.0f);
		//重置大小
		disp.setWidth(12);
		disp.setHeight(12);
		//重置存活时间。
		allms = sTallms;
	}

	@Override
	public void destroy() {
		disp.destroy();
	}
}
//Explosion.java:
package com.geiv.test;

import engineextend.crowdcontroller.CrowdController;
import geivcore.UESI;

public class Explosion{

	UESI UES;
	CrowdController cc;
	public Explosion(UESI UES)
	{
		this.UES = UES;
		cc = new CrowdController(UES, true);
		for(int i = 0;i < 512;i++)//装入了512个粒子资源
		{
			cc.addIndividual(new ExpIndividual(UES));
		}
	}

	public void doEffect(float dx,float dy) {
		for(int i = 0;i < 128;i++)//当每次调用时,分配128个粒子资源,同时也意味着,您可以同时在屏幕上产生4个异步的爆炸特效。
		{
			cc.getAvailible().getUse(null,dx,dy);
		}
	}

	public void forceClose() {
		cc.finishAllInd();
	}
}
//Main.java:
package com.geiv.test;

import geivcore.R;
import geivcore.UESI;

public class Main{
	public static void main(String[] args) {
		UESI UES = new R();
		Explosion exp = new Explosion(UES);
		for(;;){
			exp.doEffect(400,300); //产生一个爆炸
			UES.wait(3,1000); //延时1秒
		}
	}
}

执行效果:

[粒子特效的改进]

“一堆圆形一点儿也不像嘛”这是我同学看到程序后的第一句评价,的确,从粒子的行为模式上来讲,是有类似爆炸的性质了,不过一个爆炸也不能只让圆形组成不是吗?

↑给一个大点的图,可以更突出的发现这个问题

在属性设计时,我提到了关于粒子图元选择的问题,对于爆炸这个特效,显然均匀的圆形(或者其他图形)不是一种好的图元构成,我们需要一个形状并不均匀,甚至伴有随机性的图形来替换这个圆,于是笔者想到了“云”这个东西。

↑由于云是白色的,所以为了展示,把PS的衬底一起截下来了。

云本来是与爆炸毫不相干的东西,选择它是由它的图形性质决定的:边缘渐变、具有随机性、在颜色通道上也不均匀。而且,加上我们之前定义的自旋随机分布,加入自旋角的云看起来和彼此具有更大的差异。

为了使用云这个素材,先把它放在项目目录里:

之后找到ExpIndividual类,找到它的图元绘制部分:

我们把

disp.addGLOval("FFFFFF",0,0,12,12,12);

disp.setGLFill(true);

这两行改为:

disp.addGLImage(0, 0, 12, 12,".\\Effect\\PT_CLOUD1_POINT.png");

经过改进的特效:

↑可以跟圆形做一下对比,是不是好多了呢

[总结]

本章介绍了粒子特效设计的基本步骤,即属性、投射、演化三部分。

粒子特效是对自然的模拟,因此在设计时要充分地考虑到物理因素,这样会得到更好的仿真结果。

最后,恰当地选择粒子图元可以得到更好的结果,而粒子图元的选择与图形的性质有关。

时间: 2024-08-06 04:02:52

[GEiv]第六章:粒子特效 绚丽的火焰与爆炸的相关文章

cocos2dx学习笔记(5)——粒子特效CCParticleSystem

0.使用方法 拿 CCParticleExplosion 举例. //创建CCParticleExplosion特效 CCParticleSystem *p1 = CCParticleExplosion::create(); //设置特效贴图 p1->setTexture(CCTextureCache::sharedTextureCache()->addImage("cocos2dx.png")); //设置自动释放 p1->setAutoRemoveOnFinish

第六章 电磁新理论(修补章)

                第六章       电磁新理论(修补章)         关于麦克斯韦方程中的散度式的证明,网上资料很多,也较为简单.所以,本章只是对麦克斯韦方程中的旋度式做逻辑推导,作为第六章的补充.其实,现代电磁理论关于旋度式,通常只说是实验证明的,实在是感觉有点遗憾.我就勉为其难来做做吧.虽然,可以数学证明麦克斯韦的所谓"波动方程":但我并不认同有电磁波,而是认为所谓的"电磁波".不外是本源粒子--电子在空间中的能量传递.或说电磁力的传递吧.而

【Stage3D学习笔记续】真正的3D世界(五):粒子特效

先看效果,按下空格键添加粒子特效: 一般而言粒子特效的实现都是比较复杂的,且不说实现粒子特效的编码和设计,光是编写一个粒子编辑器就不是简单的一件事,但是作者使用了很取巧的方式来完成,我们接下来深入代码看看作者是怎么处理的. Particle 在我还没有看这本书的这章之前我认为一个Particle应该是一个单一的粒子(或许是一个面片或者是一个简单的模型),而最终的粒子效果则是有成千上万个Particle组成渲染得出的,所以应该存在一个JSON或XML描述文件来指定每个粒子的运动轨迹和生命周期,不过

Qt移动应用开发(四):应用粒子特效

上一篇文章介绍了Qt Quick是怎样对帧动画进行支持的.帧动画的实现离不开状态机.而状态机.动画和状态切换(transitions)则是Qt框架的核心内容,也就是说它们可以建立在任何一个QObject对象中而不必非得依赖Qt的任何图形显示模块.拿一个例子说吧,如果你想实现背景音乐的平滑过渡,你可以不用写多余的代码,将背景音乐的音量作一下动画插值就可以达到效果了.事实上我制作的游戏<吃药了>就是这么实现效果的.而这一篇文章将要聚焦的是Qt Quick另外一个非常强大的系统--粒子系统. 原创文

第六章 电磁新理论

                     第六章       电磁新理论   声明        本人自称:三空道士.可看作是社会底层的无业游民,也无不妥.虽修行于俗世,甘于过平淡艰苦的隐居生活,但从不放弃对宇宙真理的探索.因学艺不精.情关难过,在我的博客文章会充满对现代理论物理的有理有据的怒骂,如果你是相对论.量子论等现代理论物理的信徒,就请不要看了:如因此而引起你的不适,在此.敬请原谅! 我从小到大的生活里,基本上都是别人骂我,因我在生活中所犯的小错太多.数不清:所以,砖家叫兽.科班们在我心

数据库系统实现 第六章 查询执行

第六章 查询执行 查询执行也就是操作数据库的算法 一次查询的过程: 查询-->查询编译(第七章)-->查询执行(第六章)-->数据 查询编译预览 查询编译可以分为三个步骤: a)分析:构造分析树,用来表达查询和它的结构 b)查询重写,分析树被转化为初始查询计划,通常是代数表达式,之后初始的查询计划会被优化为一个时间更小的计划 c)物理计划生成,将查询计划转化成物理的计划, 为了选择更好的查询计划,需要判断 1)查询哪一个代数的等价形式是最有效的 2)对选中形式的每一个操作,所使用的算法选

第六章:异常机制

第六章:异常机制 异常的定义 异常:在程序运行过程中出现的意外事件,导致程序中断执行. 异常处理 try...catch 语法:try{ //可能出现异常的代码}catch(异常类型 异常对象名){ //处理异常的代码:}执行过程:当try中的代码异常发生时抛出一个异常对象,该异常对象与catch中异常类型进行匹配,匹配成功进入catch块,否则不执行catch中代码(相当于异常未被处理).程序只有当异常处理成功后才能继续执行. try...catch...catch 语法:try{ //可能出

2017上半年软考 第六章 重要知识点

第六章 项目整体管理 []项目整体管理概述 [][]项目整体管理的含义.作用和过程 项目整体管理6个过程?p264 项目整体管理包括什么? 项目管理的核心是什么? 项目整体管理涉及哪几个方面?p265 [][]项目经理是整合者 项目经理作为整合者要做什么?p265 [][]整体管理的地位 []项目整体管理实现过程 [][]制定项目章程概述 项目章程的意义是什么? 项目章程包括什么? [][]制定项目章程 项目章程的作用? 项目章程的输入? 制定项目章程的工具和技术?p267 项目章程的输出?p2

ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第六章:管理产品图片:多对多关系(上)

这章介绍了怎样创建一个新的实体来管理图片,怎样使用HTML窗体来上传图片文件和使用多对多关系来使它们与产品相关,并且怎样来保存图片到文件系统中.这章也介绍了更多复杂的错误处理增加客户端错误到模型中为了把它们显示回给用户.在这章中播种数据库使用的产品图片可能在在第六章的从Apress网页站点下载代码中. 注意:如果你想遵从这章的代码,你必须完成第五章的代码或者从www.apress.com下载第五章的源代码作为一个起点. 创建实体保存图片文件名 这个项目,我们正要使用文件系统在Web项目中存储图片