【USparkle專欄】如果你深懷絕技,愛“搞點(diǎn)研究”,樂于分享也博采眾長,我們期待你的加入,讓智慧的火花碰撞交織,讓知識(shí)的傳遞生生不息!
這是侑虎科技第1763篇文章,感謝作者楊超wantnon供稿。歡迎轉(zhuǎn)發(fā)分享,未經(jīng)作者授權(quán)請(qǐng)勿轉(zhuǎn)載。如果您有任何獨(dú)到的見解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群:793972859)
作者主頁:
https://www.zhihu.com/people/wantnon
(本文使用UE 4.27的版本)
早先看到下面用Niagara實(shí)現(xiàn)鏈條的教程:
Unreal Niagara - Tentacle effect with physics simulation (FULL TUTORIAL)
https://www.youtube.com/watch?v=vYaHl5bf4ww&t=1934s
后來又看到個(gè)進(jìn)一步的,在此基礎(chǔ)上加了蒙皮:
Rigging With Niagara - Part 1: Dangling Cable - Direct Interpolation
https://www.youtube.com/watch?v=r7c1s7Mwe9w
自己照著做了一遍,發(fā)現(xiàn)這方法有很多問題,于是又想了個(gè)改進(jìn)方案。
一、Niagara鏈條
首先根據(jù)教程(Unreal Niagara - Tentacle effect with physics simulation (FULL TUTORIAL))實(shí)現(xiàn)基礎(chǔ)的鏈條效果,其中幾個(gè)要點(diǎn):
1. 物理迭代是用P osition Based方法,就是對(duì)于鏈條上每個(gè)點(diǎn),計(jì)算其相對(duì)于各鄰接點(diǎn)的平衡位置偏離了多少,偏離多少就移回多少,如果有多個(gè)鄰接點(diǎn),則將分別計(jì)算的移回向量相加取平均,作為最終移回量,公式如下:
2. 在這個(gè)鏈條模擬中,我們把起始粒子的前驅(qū)認(rèn)為是自身,即存在linkPos=pos的情況,所以上式分母中+0.0001就十分必要,否則會(huì)因除以零而產(chǎn)生NAN。
3. 物理迭代通過添加Simulation Stage實(shí)現(xiàn)(只有在GPU Sim下才能用),Simulation Stage中可以設(shè)置迭代次數(shù),不同的約束可能需要的迭代次數(shù)不同,可以分成多個(gè)Stage去做,每個(gè)Stage單獨(dú)設(shè)置迭代次數(shù)。對(duì)于上述長度約束,我試了下需要4次才能效果較好。
4. Calculate Accurate Velocity這一步,若非教程里說了,自己可能想不到。另外Calculate Accurate Velocity可以從迭代次數(shù)為4的模擬Stage里移出來,放到后面迭代次數(shù)為1的Stage里。
至此,如果是要做鎖鏈之類的效果,只需把粒子替換成圓環(huán)模型,就可以了。但我們的目的是想做蒙皮。
二、鏈條蒙皮
教程(Rigging With Niagara - Part 1: Dangling Cable - Direct Interpolation)講得比較籠統(tǒng),但從中還是能大致了解其思路。要點(diǎn)如下:
1. 把模擬出的鏈條上各粒子坐標(biāo)寫到一個(gè)ParticleCount x 1像素的RT上,傳給材質(zhì)。并借助RT的雙線性插值實(shí)現(xiàn)平滑。
2. MeshRenderer的SourceMode要由Particles改為Emitter,這樣就不會(huì)為每個(gè)粒子掛一個(gè)Mesh,而是整個(gè)Emitter掛一個(gè)Mesh:
3. 沿著繩子建立切空間,則無論繩子怎么彎曲,蒙皮上各點(diǎn)的切空間坐標(biāo)不變,利用這點(diǎn)計(jì)算蒙皮各點(diǎn)的世界坐標(biāo),然后WPO。這一步在材質(zhì)中進(jìn)行。
其中3存在一個(gè)問題,就是“沿著繩子建立切空間”,即使在鎖定初始點(diǎn)的切空間的情況下,也有無數(shù)種結(jié)果。
教程中取的是:
T=normalize(p_nxt-p),
B=normalize(cross(T, (1,0,0) )),
N=cross(B,T)。
但這種算法并不能保證當(dāng)繩子任意彎曲時(shí),相鄰質(zhì)點(diǎn)的切空間足夠接近,或者說相鄰質(zhì)點(diǎn)的法線是漸變的。
在劇烈擺動(dòng)時(shí)會(huì)出問題,如下視頻所示:
于是針對(duì)如何對(duì)空間曲線生成法線這個(gè)問題,搜了一下,下面這個(gè)還算吻合:
opengl es - Vector math, finding co?rdinates on a planar between 2 vectors - Stack Overflow
https://stackoverflow.com/questions/4504331/vector-math-finding-co%c3%b6rdinates-on-a-planar-between-2-vectors/4505658#4505658
主要就是說明給曲線生成漸變的法線,只考慮局部就不行了,需要考慮整體,他給出的代碼是其中最簡單的一種方法,就是用上一個(gè)粒子的法線去算下一個(gè)粒子的法線。
我按這個(gè)簡單實(shí)現(xiàn)試了一下,發(fā)現(xiàn)效果仍不理想,原因可能有兩點(diǎn):
1. 我的分段太稀疏,導(dǎo)致即使考慮了上一個(gè)粒子的法線影響,仍然會(huì)出現(xiàn)劇烈轉(zhuǎn)折。
2. 此算法嚴(yán)格來講需順序執(zhí)行,而Niagara中GPU粒子的執(zhí)行順序是亂序的。
于是我暫時(shí)放棄了這種基于數(shù)學(xué)的思路,轉(zhuǎn)而考慮基于物理的思路。
就想是不是可以引入扭矩約束,從而避免相鄰粒子朝向差異過大,即避免擰麻花。
三、改進(jìn):防止扭轉(zhuǎn)、翻轉(zhuǎn)
我一開始想的是給粒子添加旋轉(zhuǎn)屬性,然后想著在模擬階段修正位置的同時(shí)對(duì)旋轉(zhuǎn)也作一個(gè)修正,但我不知道依據(jù)什么更新粒子的旋轉(zhuǎn)屬性,于是放棄這個(gè)思路。
然后想到,如果升維,把繩子看成有內(nèi)部結(jié)構(gòu),如圖:
則有可能僅 通過對(duì)距離作約束就同時(shí)起到防止扭轉(zhuǎn)的作用。
繩子截面邊數(shù)自然是越多越好,擺動(dòng)起來各方向物理性質(zhì)更均等,但粒子數(shù)也會(huì)成倍增加,所以我只試了正三角形截面和正方形截面。出于簡單,以下以正三角形截面為例。
如上圖, 如果僅像(a)那樣連接,側(cè)面是平行四邊形,不穩(wěn)定,可能出現(xiàn)壓扁、扭轉(zhuǎn)、翻轉(zhuǎn)三種情況,如圖所示:
為側(cè)面 添加支架,變成(b),就穩(wěn)定多了,壓扁和扭轉(zhuǎn)都能得到限制,但仍會(huì)出現(xiàn)上下翻轉(zhuǎn)問題,如下視頻所示:
之所以出現(xiàn)這種情況,是因?yàn)橐越孛鏋殓R面,上下對(duì)稱的兩個(gè)點(diǎn)的長度一樣,所以從距離約束的角度講,上下兩個(gè)位置都是滿足條件的解。為了避免這個(gè)問題,對(duì)隔一層粒子也添加距離約束,如圖(c)(d)所示,其中(d)在隔層之間作了全約束,而(c)只作了交叉約束,經(jīng)試驗(yàn)發(fā)現(xiàn)(c)就夠用了。
改為(c)后,上下翻轉(zhuǎn)不會(huì)發(fā)生了,但仍存在自穿插翻轉(zhuǎn),如下視頻所示:
要解決自穿插翻轉(zhuǎn),通過距離約束貌似是不行了(我沒想到),但觀察自穿插翻轉(zhuǎn)的特點(diǎn),容易看出當(dāng)發(fā)生自穿插翻轉(zhuǎn)時(shí),三條側(cè)邊一定不再平行。所以想到,如果加一個(gè)每段三棱柱三條側(cè)邊平行的約束,應(yīng)該就可以避免自穿插翻轉(zhuǎn)了。具體算法如下:
pos_new=pos_P +safeNormalize(c-c_P)*distance(pos-pos_P)
注:這里只讓當(dāng)前粒子pos所關(guān)聯(lián)的上游邊pos_P->pos與上游中軸線c_P->c平行,沒有讓pos所關(guān)聯(lián)的下游邊pos->pos_N與下游中軸線c->c_N平行。因?yàn)閷?shí)際試驗(yàn)發(fā)現(xiàn)上下游都處理效果不好,原因可能是當(dāng)扭轉(zhuǎn)發(fā)生時(shí),如果想通過移動(dòng)當(dāng)前粒子緩解,讓上游平行和讓下游平行移動(dòng)方向必定是沖突的,所以不如只考慮上游。
效果:
可見,無論怎么動(dòng),都不會(huì)出問題了。
有了魯棒的三棱柱繩子,就可以算出穩(wěn)定的法線,這里我們是用第一條棱與相應(yīng)的截面中心點(diǎn)作差作為法線,然后就可以在材質(zhì)里構(gòu)建穩(wěn)定的切空間,進(jìn)行蒙皮:
之前 是把Position存到了RT里傳入材質(zhì),現(xiàn)在還要增加一張RT,用來存法線。
蒙皮后效果:
四、正確的蒙皮法線
上面視頻中繩子的明暗是不正確的,因?yàn)闆]有考慮繩子彎曲對(duì)模型頂點(diǎn)法線的影響。
正確的蒙皮法線計(jì)算方法如下:
(1)繩子Tpos切空間基:
T0=(0,0,-1)
N0=(1,0,0)
B0=cross(N0,T0)
(2)vertexNormal在繩子Tpos切空間的坐標(biāo):
vertexNormalProjOnT0=dot(vertexNormal,T0)
vertexNormalProjOnN0=dot(vertexNormal,N0)
vertexNormalProjOnB0=dot(vertexNormal,B0)
(3)繩子Bendpos切空間基:
T=采樣posRT并差分得到
N=采樣normRT得到
B=cross(N,T)
(4)根據(jù)蒙皮法線Bend前后切空間坐標(biāo)不變,計(jì)算Bend后的頂點(diǎn)法線vertexNormalBend:
vertexNormalBendProjOnT=vertexNormalProjOnT0
vertexNormalBendProjOnN=vertexNormalProjOnN0
vertexNormalBendProjOnB=vertexNormalProjOnB0
vertexNormalBend=mul(vertexNormalBendProjOnT,T)+mul(vertexNormalBendProjOnN,N)+mul(vertexNormalBendProjOnB,B)
最后還有一個(gè)細(xì)節(jié)問題,就是在沒有Bend發(fā)生時(shí),Bendpos切空間應(yīng)該是與Tpos切空間完全重合的。未Bend時(shí)T由差分求出來是豎直向下,這和T0=(0,0,-1)吻合,沒問題,但未Bend時(shí)normRT采樣出來的N是否也與N0=(1,0,0)重合,就要看一下了。前面說過,我們是用第一條棱與相應(yīng)的截面中心點(diǎn)作差作為法線N,那也就是說我們要保證在不Bend時(shí)第一條棱與截面中心作差指向X軸,所以截面應(yīng)如下構(gòu)建:
修正蒙皮法線后效果:
五、補(bǔ)充
1. Niagara的Custom HLSL中語法注意事項(xiàng):
(1)錯(cuò)誤:int index=index%10;
正確:int index=index %10;//%前要有空格。
(2)錯(cuò)誤:NiagaraID idList[2]={PID,NID};
正確:NiagaraID idList[2]={ PID,NID };// { 后和 } 前要有空格
報(bào)錯(cuò)信息去Output Log窗口看,那里才是詳細(xì)信息。
2. DebugDraw節(jié)點(diǎn)中,不要用drawSphere,其編譯時(shí)間很長,drawLine和drawBox都編譯很快。
3. 如果希望不同鏈條約束強(qiáng)度有差異,或者方便忽略掉一些鏈條(權(quán)重給0),可以用加權(quán)版本公式:
4. Slover模塊連線過多,一堆GetVectorByID+Custom HLSL節(jié)點(diǎn):
可用如下方法簡化:
打開生成的Shader Code:
搜GetVectorByID,可以搜到:
聲明處:
使用處:
所以GetVectorByID可以寫到Custom HLSL里面:
所以眾GetVectorByID+Custom HLSL可合并成一個(gè)大的CustomHLSL:
需要注意的一點(diǎn)是,需要保留一個(gè)GetVectorByID("Position")節(jié)點(diǎn),以便生成函數(shù)聲明,否則會(huì)報(bào)錯(cuò)。
同理,Setup模塊也可做同樣簡化:
文末,再次感謝 楊超wantnon 的分享, 作者主頁: https://www.zhihu.com/people/wantnon, 如果您有任何獨(dú)到的見解或者發(fā)現(xiàn)也歡迎聯(lián)系我們,一起探討。(QQ群: 793972859 )。
近期精彩回顧
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.