[Stage3D]GPU渲染的噴泉粒子


您需要把Flash Player升級到11版本才能正常播放示例程序。

如果播放動畫的幀率較低,那么有可能是您的顯卡不被Flash Player所支持,Flash Player會采用CPU來對畫面進行渲染。

源碼:http://files.cnblogs.com/flash3d/partic.rar

在詳細介紹該粒子效果編寫方法之前,先讓我們簡單了解下Stage3D開發基本流程與注意事項。

1. 獲取Stage3D對象

Stage3D對象用於顯示通過Stage3D技術渲染出來的圖像,我們可以通過類似

stage.stage3Ds[0]

的方式獲取Stage3D對象,Flash總共提供四個獨立的Stage3D對象。

2. 獲取Context3D對象

Context3D對象是我們使用Stage3D技術主要操作對象。該對象類似一個大工廠,我們可以向他申請空的頂點緩沖(VertexBuffer3D),頂點索引(IndexBuffer3D),着色器程序(Program3D)等,而后向全新的對象內裝入數據再通過Context3D上傳至GPU。

Context3D對象並不能通過new來創建,也不能立即申請到。要想獲取Context3D對象則需通過

stage3d.requestContext3D()

的方式發起申請請求,而后監聽Event.CONTEXT3D_CREATE事件發生才算申請成功。此時則可通過

stage3d.context3D

獲取Context3D對象了。這里需要注意Event.CONTEXT3D_CREATE事件可能多次發生,假如其他程序中斷了Flash Player的GPU使用權后Flash Player重新獲得其使用權,則會再次發生Event.CONTEXT3D_CREATE事件。假如你在Event.CONTEXT3D_CREATE事件的回調中做了一些初始化工作,那么就要小心了,如果你不及時移除監聽,就有重復初始化的隱患。

3. 創建Program3D對象並寫入程序

通過

context.createProgram()

的方式,可以獲得一個空的Program3D對象。Program3D對象負責儲存着色器程序。有些朋友可能對着色器程序的概念和工作原理還不是很清楚,這里我簡單介紹下。

着色器程序(AGAL寫的程序)分為兩部分:頂點着色器程序(Vertex Shader),段着色器程序(Fragment Shader)。他們均被上傳至GPU運行。頂點着色器程序負責處理頂點緩沖數據並將處理好的數據寫入到位置輸出寄存器(op)並且將部分必須的中間數據儲存到變量寄存器留給段着色器使用,段着色器則是根據從頂點着色器傳來的變量,結合紋理數據,計算合成某個像素需要輸出的值。

關於用AGAL着色器的語法,請參考http://bbs.9ria.com/viewthread.php?tid=79747

關於Stage3D的最簡單“Hello World”程序:http://www.pixelbender.cn/?p=381

所以,從程序的角度看,貌似頂點着色器先被執行,而后再執行段着色器。但是請注意,實際上在GPU中,頂點着色器與段着色器的運行次數是不一樣的,這也是為什么在Stage3D的“Hello World”程序中所繪制的是一個漸變圖像而不是四個像素點了。

實際上GPU是首先計算完全部的頂點緩沖(可能有並行運算),而后對計算出來的頂點數據投影到2D屏幕上(計算出來的頂點原本是三維點,期間可能還進行了一些消隱等操作),接下來對這些頂點圍成的三角形進行柵格化(變成位置確定的像素點,但尚未有顏色),最后才是對柵格化后的像素執行段着色器程序,而其所需的變量寄存器值則通過像素位置關系內插計算得到的漸變值。

如圖展示了渲染管線的處理過程

 如下示例展示了一個三角形的繪制過程

我們可以通過Program3D對象的upload方法上傳程序,該方法接收兩個參數,第一個是頂點着色器程序(Vertex Shader),另外一個是段着色器程序(Fragment Shader)。但是請注意,upload的兩個參數類型是字節數組(ByteArray)而非字符串(String),所以我們需要AGALMiniAssembler對象來幫助我們把字符串的着色器程序變成字節數組。通過AGALMiniAssembler對象的assemble方法可以達到上述功能。該方法第一個參數是程序類型,可能值是兩個,一個是"vertex",表示第二個參數是頂點着色器程序,另外一個是"fragment",代表第二個參數是段着色器程序。

4. 創建頂點緩沖數據

頂點緩沖數據可以看成n行m列的二維數組。其中每行數據可以產生一個三維模型中某個三角形的頂點,該行的數據值決定着該頂點輸出的坐標(具體如何決定則看頂點着色器程序,也有可能部分數據不起作用,而是間接對段着色器起作用)。而頂點緩沖數據的行數則決定頂點的個數。

我們可以通過

context.createVertexBuffer(行數, 列數)

來申請一個空的頂點緩沖包裝對象。之后我們需要一個“行數 * 列數”長度的Vector.<Number>類型數組來寫入我們的頂點數據。

可以通過

vertex.uploadFromVector(數組, 行偏移量, 行數)

上傳數據。通過

context.setVertexBufferAt(寄存器序號, vertex, 列偏移量, 數據長度)

設置一行中的頂點數據如何分配給寄存器。其中寄存器序號就是在agal中va后所帶的數字,如寄存器序號是1,則可通過va1訪問設置進去的數據。列偏移量表示要設置到寄存器中的數據區間其第一個浮點數是處在該行的第幾列(0開始),數據長度則表示要設置幾個浮點數到寄存器中,其值是個字符串,類似"float2","float3"等,最大是"float4",因為一個寄存器最多只能儲存四個浮點數。
以下例子演示如何將頂點數據設置到寄存器中並在AGAL中訪問。

這里我們建立一個最簡單的三角形作為模型,一個三角形擁有三個頂點,故頂點緩沖有三行數據,每行數據中,我們需要三角形的坐標,以及頂點處的顏色(數據的數量,意義,作用看具體程序而定),於是我們決定用兩個浮點數表示坐標位置(X,Y),用三個浮點數表示顏色值(R,G,B),所以頂點緩沖數據應該是3行5列的,其數據長度是15。通過如下代碼

vertex.uploadFromVector(new<Number>[0, 1, 0, 0.5, 1, -1, -1, 0.5, 1, 0, 1, -1, 1, 0, 0.5], 0, 3)

可以將如下數據設置到頂點緩沖中去了。

0 1 0 0.5 1
-1 -1 0.5 1 0
1 -1 1 0 0.5

通過如下兩行代碼

context.setVertexBufferAt(0, vertex, 0, "float2");
context.setVertexBufferAt(1, vertex, 2, "float3");

可以將藍色部分的數據設置到va0中,把紅色部分數據設置到va1中。

假設頂點着色器程序正在處理第三行,此時訪問va0.x為1,va0.y為-1,va1.x為1,va1.y為0,va1.z為0.5。

着色器寄存器可容納四個浮點數,當設置進去的數據長度不夠時(比如設置的是"float2"),則系統默認幫你填上默認值,寄存器前三個浮點數的默認值均為0,第四個浮點數默認值為1。所以此時va0.z為0,va0.w為1。請注意,這個默認值是執行setVertexBufferAt的時候就設置進去的,而非到達GPU后才產生。GPU中的臨時寄存器在未被賦值之前是訪問不到其值的(直接掛了)。所以對於未被設置過的vt0

mov vt0.xy, va0.xy
mov vt1.x, vt0.z //掛了
mov vt0, va0
mov vt1.x, vt0.z //沒掛

以上程序產生不同的結果。

頂點着色器程序從頭到尾運行一次只處理一行數據,而且只能訪問本行數據,各行數據無法相互影響。

5. 創建紋理數據

紋理數據在段着色器運行時被采樣,比較常見用處是UV映射,將紋理作為模型貼圖,更加高級的用法則比如通過在同個或者多個紋理的多個點上采集像素並通過某種算法合成而產生特殊效果。

可以通過

context.createTexture(寬, 高, "rgba", false);

來獲得空的紋理包裝對象。

之后向紋理包裝對象上傳位圖

texture.uploadFromBitmapData(位圖)

最后通過

context.setTextureAt(寄存器序號, texture)

來設置到指定的紋理寄存器上。

在AGAL中,可以通過類似

tex ft1,v0,fs0<2d,linear,nomip>

的方法把像素采集到寄存器中,fs0是序號為0的紋理寄存器,可以訪問到通過setTextureAt設置到0的紋理。<2d,linear,nomip>是采樣屬性,其依次可能值為

尺寸:2d,3d,cube
投影:nomip,mipnone,mipenearest
濾鏡:nearest,linear
拷貝:repeat,wrap,clamp
v0代表要采樣的坐標,ft1則是儲存采樣結果。

6. 設置着色器程序常數

着色器程序的特性與數學函數非常類似,具有純數據計算的特性。故常數對於着色器的意義相當於常數對於數學函數的意義一樣。

比如函數“y = ax”展現出的是y隨x線性變化的特征,但是常數a卻控制着變化速度的快慢。

通過類似

context.setProgramConstantsFromVector("vertex", 0, new<Number>[0, -0.5, 0.2, 0])

的代碼可將常數設置到常數寄存器中。函數第一個參數代表設置的是頂點着色器的常數還是段着色器的常數,"fragment"代表段着色器,設置進入的常量可通過類似fc0的形式訪問,"vertex"代表頂點着色器,設置進入的常量可通過類似vc0的形式訪問,vc后的數字則是由這個函數的第二個參數確定,表示要設置到哪個標號的寄存器內。第三個參數是常量數據,他是一個不超過4個長度的Vector.<Number>類型數組。其四個值分別對應着vt0.x,vt0.y,vt0.z,vt0.w。

對於as來說,改變常量是非常省力的(一行代碼而已),而改變頂點緩沖數據則非常費勁(需要遍歷整個數組)。所以如果希望通過Stage3D技術產生流暢的動畫,則必須通過不斷改變常量來完成,而不能改變頂點緩沖數據的值,否則GPU加速也就沒有意義了,這也是最考驗着色器設計能力的地方。

7. 創建頂點索引

頂點索引數據是一個n行3列的數據。其一行可繪制一個三角形,一行內的三個數據分別是三角形三個頂點的索引,這里索引所引用的就是頂點緩沖數據里面的某一行(代表某個頂點),索引值便是頂點緩沖數據的行號。

可通過

context.createIndexBuffer(索引數)

來創建空的頂點索引包裝對象。這里注意其參數是索引數(即n*3),而非行數。

通過

index.uploadFromVector(Vector.<uint>類型數組, 起始索引偏移量, 索引數)

上傳索引數據。在繪制3D畫面時,可以將index對象傳入context.drawTriangles繪制出所有指定三角形。

 

以上簡單介紹了Stage3D技術開發的基本流程,當然Stage3D還有其他功能需要大家深入去研究。

有了上面的基礎,下面講一下Stage3D噴泉粒子效果的制作要點,可能講的不對或者有更好的方案,還請各路大大多多指教。

1. 粒子模型

Stage3D並非BitmapData,無法直接將粒子以像素的形式設置到畫面上,故考慮以一個小三角形作為粒子模型。所以總共的頂點數應該是粒子數的三倍。

2. 動畫的實現

考慮到要充分發揮Stage3D的優勢,所以動畫的實現,也就是粒子位置隨時間的連續性改變也不應該讓as去實現。否則粒子的位置坐標就必須由as來計算並設置,那么就必須遍歷粒子數組,開銷大大滴。

所以我的方案是給着色器傳一個時間常量,粒子的坐標位置隨時間沿着某個曲線運動。

由於是噴泉效果,所以采用的是拋物線軌跡(捂臉~~太沒科技含量了)

拋物線原型:y = - x^2

引入一個常量使其變形經過原點的上拋曲線:y - c^2 = -(x - c)^2

由於還考慮到噴泉中每個粒子的噴發角度不一樣故還引入常量d:y = c^2 - (d * x - c)^2

d表示x軸縮放比例的倒數,若d為負則拋物線方向向左。

有了曲線方程,則下面需要x關於時間的方程了,根據物理規律,由於噴灑的高度相同,故粒子從升起到落地的時間相同,那么對於x來說就是從0位置移動到2c/d的時間相同。那么當給定0到1范圍的時間變量,無論2c/d是多少,經過相同的時間完成x與2c/d的比例也是相同的,於是有了這個方程:x = 2 * c / d * t

由於我們需要不同的粒子處在拋物線運動的不同時刻,這樣才會看起來有前后次序的感覺,於是我們還需要給這個t再加上一個時間常量得到x = 2 * c / d * (t + o)

再者需考慮t + o需要在0至1范圍內,並且超過1后回歸為0,這樣才會有粒子的生命結束與重生。所以這里動用到agal里frc這個運算符,其功能是對源操作數取小數,最后得到的公式為:

x = 2 * c / d * frc(t + o)

將該時間方程代入上述曲線方程得到y與時間的關系方程:y = c^2 - (2 * c * frc(t + o) - c)^2

此方程中常量c確定拋物線的高度,由於全部粒子最大高度一致,所以c可設置為全局常量;常量d確定粒子的運動方向與跨度,由於各粒子不相同,所以作為曲線描述信息保存到頂點緩沖中;o為初始時間偏移量,故各粒子也不同,均保存到頂點緩沖中。

3. 粒子模型的形狀保持

粒子的運動概念上是以三角形為單位,所以三角形的三個頂點相對位置應該保持不變才能防止粒子形狀發生改變,也就是說,每次運動粒子的三個頂點必須加上相同的該變量。

所以我設定的頂點緩沖數據結構是這樣的

va0 va1
p1.x p1.y o d
p2.x p2.y
p3.x p3.y

p1 p2 p3分別是三角形的三個頂點坐標,接下來的每三行,其值都與該三行的va0值對應相同,這三個點的數據確定了粒子的形狀。但是va1作為描述運動規律的數據,則是三行都相同,但是與其他的三行不相同。這體現了這三行的數據是共同描述一個粒子,而粒子的每個頂點其運動規律都相同,才不至於形狀改變。

結合上文推導的運動方程,我們得到如下頂點着色器代碼

mov vt2, vc1
mov vt0, va0
add vt1.x, vc0.x, va1.x //t + o
frc vt1.x, vt1.x //frc(t + o)
mov v0, vt1.x //將運動百分比傳到段着色器
mul vt1.x, vt1.x, vt2.y //c * frc(t + o)
mul vt1.x, vt1.x, vt2.w //2 * c * frc(t + o)
div vt0.x, vt1.x, va1.y //x = 2 * c / d * frc(t + o)
sub vt1.x, vt1.x, vt2.y //2 * c * frc(t + o) - c
pow vt1.x, vt1.x, vt2.w //(2 * c * frc(t + o) - c)^2
pow vt1.y, vt2.y, vt2.w //c^2
sub vt0.y, vt1.y, vt1.x //y = c^2 - (2 * c * frc(t + o) - c)^2
add vt0.xy, vt0.xy, va0.xy //粒子坐標累加到頂點坐標
add vt0.xy, vt0.xy, vc0.yz //原點做一定偏移
mov op, vt0 //輸出

而段着色器上,只是做了一點顏色漸變。主要是根據運動百分比來線性增加顏色的亮度,符合水噴出去就白了的規律(囧~)

mov ft0, fc //獲取設定的顏色常量
//給三原色加上運動百分比
add ft0.x, ft0.x, v0.x
add ft0.y, ft0.y, v0.x
add ft0.z, ft0.z, v0.x
mov oc, ft0 //輸出

程序源碼在最上面。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM