[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");

經過改進的特效:

        

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

[總結]

        本章介紹了粒子特效設計的基本步驟,即屬性、投射、演化三部分。

        粒子特效是對自然的模擬,因此在設計時要充分地考慮到物理因素,這樣會得到更好的仿真結果。

        最後,恰當地選擇粒子圖元可以得到更好的結果,而粒子圖元的選擇與圖形的性質有關。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章