承接上篇,延伸說明 Foreach
取代 copy stamp
的使用與最佳化。
這邊用一些粒子從球狀變成方塊並往下位移的簡單效果來說明。
方塊與球的型變
建立一個 box
,設成 Polygon Mesh
,給予足夠的面數(範例是 10x10x10
)。
用 Attribute Wrangle
寫出型變:
vector size = getbbox_size(0); //取得大小 vector direction = normalize(v@P); //每個點的方向,因為在原點所以較為省略 vector target_pos = direction * size.x / 2.0; //大小除以二為半徑 float mix = 0; // 注意mix變數,後面會再提到。 v@P = lerp(v@P, target_pos, mix); // 便可以藉由mix變數 `0~1` 調整型變
結果如下:
建立點
接下來要建立 template points,這種概念很常用到,一般要做大量物件的自定義動態,都會先轉成 points ,做完動態後再 instance 回去。
這邊比起用 grid,更傾向於用 vex 製作,更為單純快速。
float dist = 0.1; //點之間的間隔 for (int x = 0; x < 30; x++) { for (int z = 0; z < 30; z++) { addpoint(0, set(x, 0, z) * dist); } }
這樣製造了900個間隔0.1單位的粒子。
設定動態
在製作動態時,一定是先把不需要重複計算的屬性都先設定完,最後一步再來依照屬性驅動動態,畢竟一動起來就是 time dependent
,效能會大幅降低。
自製公式
根據過往工作的經驗,歸納出一個常用的公式:
float getProgress(float order, animation, duration) { float start = (1 - duration) * order; float end = order + duration - duration * order; float result = fit(animation, start, end, 0.0, 1.0); return result; }
只要代入 order
、animation
、duration
,就可以取得每個點的 progress
來進行動畫。
- animation: 整個動畫的長度,基本上連結到 channel 由 key 驅動。
- order: 每個點的順序,代表
progress
開始的順序。 - duration: 每個點在整個動畫長度的佔比。
- progress: 結算出每個點在該
animation
時間段的的行進值。
以上數值皆為 0 ~ 1
。
主要的概念在於,只要調整 animation
的開始 0
到結尾 1
,就可以計算出每個點的進程。
附上圖表說明:
一整段 animation
,不同的點有不同的 order
順序。
animation
一開始,order
0.0
的點也會跟著開始。
animation
一結束,order
1.0
的點也會跟著結束。
而這 animation
的過程,就是根據每個點自己的 order
還有 duration
,算出 progress
。
如果 duration
變短,order
0.0
的點跟 order
1.0
的點在 animation
的間隔就會拉大,那點彼此之間的交互關係就較不明顯。
可能還有點抽象,先繼續實做下去。
取得order
要進行上面的公式,先取得 order
,範例的情況用亂數取得即可。
@order = rand(i@ptnum);
取得progress
藉由上面取得的 order
,再加上連結 channel 取得 animation
跟 duration
,animation
設定為第1格為 0
,第100格為 1
,是一個線性的100格動態。
duration
隨意,範例數值為 0.2
。
float getProgress(float order, animation, duration) { float start = (1 - duration) * order; float end = order + duration - duration * order; float result = fit(animation, start, end, 0, 1); return result; } float animation = ch('animation'); // 1~100格 0.0~1.0 的數值動態 float duration = ch('duration'); // 0.2 @progress = getProgress(@order, animation, duration); // 將取得的 progress 屬性輸出
驅動位移
有了 progress
屬性後,來做一個從 progress
0
時在高處,progress
1
時在原點的位移動畫。
float start_height = ch('start_height'); // progress 為0時的高點,這邊為2 v@P.y = lerp(start_height, v@P.y, @progress); // 因為y在原點,第一項數值就不相加了
這樣就完成了一個基本的動態,複製成方塊後的效果如下:
如果 duration
調成 0.6
,彼此之間 progress
行進過程拉長就會產生交疊,像這邊 animation
有100格則每個 progress
有 100 * 0.6 = 60
格:
那如果把 order
改成
@order = relbbox(0, v@P).z;
依照Z軸的順序,又是另一種效果:
驅動型變
可以發現有 progress
後,驅動動畫的操作直覺許多。
接下來就要來驅動型變,還記得我們剛剛第一段設定的 mix
值嗎?
如同上一篇設定了 type 來讓 foreach
迭代,這次我們設定 mix
屬性來迭代。
如果這邊是用 copy stamps
,會需要每一顆粒子都依照 mix
屬性去 instance 一個球到方塊的中間型態。
但用 foreach
的話,我們可以減少 mix
的差異化,用最少的迭代量去複製所有點。
照這想法設定 mix
屬性:
i@mix = (int)((1.0 - @progress) * 100);
注意這邊,我們將計算出的浮點數乘上100之後化為整數,這樣最多的差異也只會是一百種,等於最多也只會迭代100次,這是非常重要的最佳化,尤其在粒子有幾十百萬的時候還能保持時間軸可拖動的關鍵。
甚至依據鏡頭的不同,更可以減為 *10
,只迭代10次,畢竟整個球變成方塊的過程不會看得那麼細。
接上foreach
建立 foreach block
,並將 Piece Attribute
設為 mix
。
方塊的型變節點改成嵌在 foreach block
裡面,因應方才我們寫的架構做些小更動:
vector size = getbbox_size(0); vector direction = normalize(v@P); vector target_pos = direction * size.x / 2.0; float mix = point(1, 'mix', 0); // 採樣點的mix屬性 mix /= 100; // 因為前面乘了100,這邊要除回來 v@P = lerp(v@P, target_pos, mix);
這樣便完成了。
結論
foreach block
配合正確的迭代屬性是很重要的最佳化流程,甚至可以搭配 compiled block
去做異步執行。
當然,在做 foreach block
前一定要先想想,這件事沒辦法在 Attribute Wrangle
去執行嗎?在 vex
去 Run Over 絕對是最快的,真的沒辦法再用 foreach block
,那 copy stamp
就盡量不要碰了。
progress
的概念這邊只是初步介紹,延伸彈性很大,如下面影片,可以上了兩層 animation
、針對不同驅動去用 chramp
重新設定 progress
、對 order
、duration
去做多層次設定等延伸方法。
範例檔案
最後附上範例檔案