熱門
使用Unity為數(shù)字人添加逼真的毛發(fā)細節(jié)
數(shù)字人技術(shù)現(xiàn)在已經(jīng)是一項比較完善的技術(shù),,通過Unity引擎的烘焙與深度渲染,現(xiàn)在的數(shù)字人已經(jīng)變得相當逼真,。
在本文中來自完美世界移動項目支持部的徐行跟大家分享了他在完美世界研發(fā)的 Unity 毛發(fā)系統(tǒng),。
“其實這個作品還在很初期的階段,還有很大的提升空間,,這次主要是讓她幫忙展示一下毛發(fā),。這個是本工作的大概時間線,其實前期還不配被稱之為自研毛發(fā)系統(tǒng),,只能稱得上是一個英偉達 HairWorks SDK 的集成,。隨著后期需求越來越深入,自研的部分也越來越多?,F(xiàn)在只是思想上部分參考了 HairWorks,,代碼已經(jīng)全部重寫了?!毙煨姓f道,。
現(xiàn)在游戲中比較傳統(tǒng)的毛發(fā)解決方案有兩類,第一類就是網(wǎng)格頭發(fā),,這類頭發(fā)其實渲染效果還不錯,,但是往往物理效果不太好,而且也不太適合做短毛,。
Uncharted by NaughtyDog
另一類是 FurShell,,專門用來做短毛的。但是湊近了看,,層狀瑕疵也非常明顯,。做毛發(fā)的終極解決方案,肯定就是基于發(fā)絲的毛發(fā)系統(tǒng),。這是一些有代表性的案例,,比如《最終幻想》、《怪獸公司》,、《長發(fā)奇緣》,、《古墓麗影》和《巫師 3》。
雖然基于發(fā)絲的方案效果很棒,,但是問題也是顯而易見的,。那就是發(fā)絲數(shù)量太多,導致無論是物理模擬還是渲染還是存儲等等都有比較大的困難。
HairWorks 是如何應對這些挑戰(zhàn)的呢,?這就得從它的資源表示方式講起了,。HairWorks 的毛發(fā)資源并不是直接存儲每根發(fā)絲的信息,而是主要存儲了兩個東西,,圖中的黃線被稱之為導發(fā),圖中的網(wǎng)格被稱為生長網(wǎng)格,。而物理模擬則在導發(fā)上進行,,發(fā)絲則是導發(fā)間插值出來的,這樣就能極大的減少計算量和存儲間的需求,。
發(fā)絲具體是怎么插值的呢,?其實生長網(wǎng)格是由一些三角面構(gòu)成的。生長網(wǎng)格上面每一個頂點對應一根導發(fā),,隨機生成一定數(shù)量的重心坐標,,在渲染的時候就可以利用重心坐標作為權(quán)重,在三根導發(fā)間進行插值了,。
為了讓生成的發(fā)絲更加平滑,,在生成發(fā)絲前還可以對導發(fā)做一些平滑,使用平滑過的導發(fā)進行插值,。
物理模擬又是怎么做的呢,?HairWorks 選擇了一種非常易于理解的物理模擬方法,也就是質(zhì)點彈簧法,。圖中的紅線就是進行物理模擬的導發(fā),,紅線上的小圓圈就是質(zhì)點,發(fā)根處的質(zhì)點是完全受骨骼蒙皮控制的,,其他的質(zhì)點則會受到諸如風力,、重力等等的影響,也會跟碰撞體發(fā)生碰撞,。
為了使基于質(zhì)點的物理模擬能夠體現(xiàn)毛發(fā)的感覺,,HairWorks 為質(zhì)點間增加了一系列的約束,也就是俗稱的彈簧,。比如圖左中,,同一個頭發(fā)上相鄰質(zhì)點間有長度約束,太近了會排斥,,太遠了會吸引,,這有助于保持頭發(fā)的大致長度。
再比如黃色折線,,它表示的是完全受骨骼蒙皮控制的毛發(fā),。它跟紅色這條線,也就是物理模擬控制的毛發(fā)間也有約束,這有助于保持美術(shù)所制作的造型,。
另外,,HairWorks 還有一個比較有特色的約束,就是在相鄰的導發(fā)之間也有距離約束,,有助于保持毛發(fā)的體積感,,一定程度上避免穿插。當然還有很多其他種類的約束我們就不一一展開提了,。
為了加速物理模擬,,HairWorks 的物理模擬是在 Computer Shader 中并行進行的。一個控制點,,也就是一個質(zhì)點對應一個線程,,一個導發(fā)對應一個線程組,這就便于使用效率比較高的共享存儲,。
但是有些約束是有先后順序依賴關系的,,例如同一個質(zhì)點上的兩個長度約束,如果調(diào)換執(zhí)行順序,,那么執(zhí)行結(jié)果就不一樣了,。正確的做法只能是串行解算這些約束,但是串行顯然是不如并行快的,。為了加速物理模擬,,HairWorks 還是想辦法做到了并行。
如圖,,它把長度約束分為兩組,,一組內(nèi)每個約束都互不相臨。這樣一組的約束就可以并行解算,,一組解算完之后,,再解算另一組,交替迭代幾次就能獲得比較穩(wěn)定的結(jié)果,。
“當然后面我們還做了一些物理模擬的拓展和優(yōu)化,,例如加入了 3D 風場以及允許傳入不穩(wěn)定的物理模擬 TimeStep 等等?!毙煨姓f道,。
“接下是關于引擎與跨平臺的一些分享,這張圖是我們把 HairWorks 集成到自研引擎之后,,在《笑傲江湖》和一些 Demo 中的效果,。當時另一個端游項目看到了,覺得效果不錯,,挺想用,。他們是基于 Unity 開發(fā)的,所以接下來我就開始了向 Unity 的集成?!毙煨姓f,。
其實遠在完美世界之前,已經(jīng)有很多團隊進行了這項工作,。比如圖中是 Unity Japan 團隊的成果,,但是他們做的集成時間都比較早了,當時條件有限,,所以也存在一些問題,。比如無法使用 Unity 內(nèi)部的材質(zhì),或者光照不全,,沒有平行光的投影等等。
他們?yōu)槭裁磿龅絾栴}呢,?這就要講到他們集成的實現(xiàn)原理,。
他們選擇了原生插件這種集成方式,是由于 HairWorks 要用到諸如 Computer,、Tessellation 這類很底層的圖形功能,。所以說使用能達到底層圖形設備接口,例如 D3D Device 的原生插件機制還是比較穩(wěn)妥的,。
但是也正是因為他們是使用底層圖形設備接口,,例如 D3D Device,進行渲染,,而不是使用 Unity 提供的接口例如 DrawRenderer,,所以 Unity 內(nèi)的材質(zhì)、燈光,、環(huán)境以及渲染管線內(nèi)部的很多信息,,都很難傳過去,也就很難在原生插件里面重現(xiàn) Unity 的渲染效果,,所以原生插件渲染出來的東西往往光照不全,。
另外由于 Built-In 管線中提供的插入點有限,所以沒有辦法渲染陰影深度,。上述問題如果按原有思路做下去,,是很難解決的。但是這些問題不解決又沒有辦法實際投產(chǎn),,所以這些集成最后基本上就被擱置了,。
徐行的團隊最初也是一籌莫展的,差點因此放棄,,但是最后還是想到了一個解決方案,,那就是既然 Unity 里面的東西很難拿出來,是不是可以不把他們拿出來放到原生插件里面?而是想辦法把原生插件生成的 HairWorks 的幾何信息傳到 Unity 里面,,這樣就可以在 Unity 里做光照了,,這樣可行嗎?
徐行的辦法其實很簡單,,他使用了一種名為渲染代理的方法,。所謂的渲染代理其實就是一個在 Unity 里面創(chuàng)建的頭發(fā)的包圍體,原生插件把 HairWorks 的幾何信息渲染到了自己創(chuàng)建的 GBuffer 中,。
然后渲染代理掛上了 Unity 內(nèi)的材質(zhì),,直接參與 Unity 的渲染。與普通材質(zhì)有所不同的僅僅是在渲染的時候會讀取我的 GBuffer 中的幾何信息,,并把它偽裝成自己的進行光照,。這樣的話,就可以直接利用 Unity 自帶的渲染機制,,所以渲染出來的東西跟 Unity 是可以完美融合的,。
這個機制還有另外的幾個好處,第一是對項目的渲染管線沒有影響,,可以直接使用 Unity 材質(zhì),,也支持 Shader Graph。所以不管對美術(shù)用戶還是技術(shù)用戶都比較友好,。
另外,,Unity 很多時候需要把一個物體渲染多次,而使用渲染代理的話,,就可以避免多次提交復雜的毛發(fā)幾何體去渲染了,。因為我每次提交的,都只是一個很簡單的包圍體,。
最后一個優(yōu)勢,,大家可以看出來這個機制很類似于延遲渲染,每個像素上只需要進行一次著色,,所以是比較高效的,。渲染可以使用代理,投影也可以使用代理,,如此一來集成就變得很簡單了,,就不存在之前所說的諸多問題了。當然,,后來有了 SRP,,就可以在渲影子的時候直接調(diào)用原生插件渲染了,不再需要投影代理了,。
接下來就是跨平臺,,其實剛剛不管是 Unity Japan,,還是集成,一直是通過原生插件對接 Nvidia HairWorks SDK 做的,。在 SRP 誕生之前,,這可能是唯一能夠跑通的方法。HairWorks SDK 預留了跨平臺的設計,,但是 Nvidia 只實現(xiàn)的 DX11 和 DX12 的版本,,剩下所有的圖形接口都需要去重新實現(xiàn)一份 HairWorks SDK 的底層,這個工程量是比較大的,。而且由于很底層,,所以難度也比較高。徐行團隊花幾個月的時間才實現(xiàn)了一次 PS4 平臺,,雖然是做完了,,但是做完之后覺得不能再用這種方式繼續(xù)往下做了。
得益于近些年 Unity 的進化,,特別是 SRP 的加入,,讓直接在 Unity 內(nèi)實現(xiàn)這種復雜功能變成了可能。我利用近期 Unity 提供的一些新功能,,例如 Computer Shader、CommandBuffer,、RenderFeature,、CustomPass 等等,直接把紅框內(nèi)的整個流程包括資源加載,、約束初始化,、物理模擬、幾何體渲染,,這些全部都在 Unity 內(nèi)部重新實現(xiàn)了一遍,。
這個流程比之前的 PS4 移植要順利得多,因為現(xiàn)在 Unity 的渲染開發(fā)是很高效的,,做任何修改都不需要關編輯器,,也不需要花很長的時間來編譯,可以即時看到效果,。
另外由于有像 CustomPass 和 RenderFeature 這樣方便的機制,,對 SRP 的源碼修改其實只有幾行。
接下來講一下毛發(fā)著色,,我們可以先看一下最終的效果,。
說到毛發(fā)著色,我們首先會想到的就是 Kajiya-Kay 模型,,它用毛發(fā)的切線替代了常用的法線,,把 cos 換成了 sin,,快速實現(xiàn)了類似于頭發(fā)的效果。
但是如圖所示,,雖然它產(chǎn)生了類似于頭發(fā)的高光,,但是塑料感很強。在 2003 年 Marschner 提出了一種更接近物理真實的毛發(fā)著色模型,,中間就是 Marschner 成果,,右邊是真人照片。在不考慮造型的情況下,,可以說效果已經(jīng)很接近真實的照片了,。
它首先是對與毛發(fā)產(chǎn)生的交互光線進行分類,打在毛發(fā)表面就被反射的光線被稱為 R,。打入頭發(fā),,然后又從頭發(fā)背面射出的光線被稱為 TT。打入頭發(fā),,在頭發(fā)內(nèi)部被反射,,又從頭發(fā)正面透出來的光線被稱為 TRT,這里面 R 和 T 分別代表反射和透射,。
光線在頭發(fā)內(nèi)部行進的過程中,,他的部分能量會被頭發(fā)的內(nèi)核吸收,所以說圖中的 TT 和 TRT 都會帶有頭發(fā)的顏色,,而R就類似于一般的高光,,不受頭發(fā)顏色的影響。
另外,,由于頭發(fā)表面的鱗片與毛發(fā)切線形成了一定的角度,,這導致出射的 R 和 TRT 的角度產(chǎn)生了一定的偏差。直觀地來說,,就是他們兩個的中心是分離的,。
如中間的部分所示,白色的高光是 R,,最上面呈現(xiàn)發(fā)色的高光就是 TRT,,可以看出他們的中心是有一定偏差的。
Marschner 的另一項重要貢獻,,就是將散射光線在毛發(fā)橫截面和縱截面的能量分布分開建模,,這極大的簡化了建模的難度,減少了單個分布函數(shù)的參數(shù),。
盡管如此,,這個模型的數(shù)學計算還是十分復雜的。在 Shader 實現(xiàn)一套比較麻煩,,性能也比較低,。
為了提高計算性能,,英偉達很早就提出了一種簡便的方法,就是把橫截面和縱截面出射光的能量分布烘培到兩張紋理里面,,渲染的時候查詢即可,。這種方法又被稱之為查找表法。圖中就是英偉達用這種技術(shù)制作的美人魚 Demo,。
但是這個方法有一個的問題,,就是只支持一種頭發(fā)顏色和一種粗糙度。
受到英偉達思路的啟發(fā),,徐行寫了一個很簡單的光追小程序,,從各個方向向毛發(fā)發(fā)射光線,再在各個方向統(tǒng)計出射光線的能量分布,,最后把能量分布存入紋理中,。
但是在這個思路的基礎上,他又做了兩個擴展,。第一是徐行希望美術(shù)能夠調(diào)節(jié)粗糙度,,所以他將 32 個不同粗糙度的能量分布都烘焙了,排成了一個序列,。
第二是在能量分布圖中,,沒有包含毛發(fā)中心對光線吸收,而是用了一組新的紋理來記錄光線在毛發(fā)中行進的平均距離,。根據(jù)這個距離和美術(shù)設定的毛發(fā)顏色換算得到的吸收率,,就可以對毛發(fā)光線能量進行衰減,從而體現(xiàn)不同的毛發(fā)顏色,。
最終的效果如圖,,大家可以從圖左中觀察到 R 和 TRT,,可以在圖右中觀察到明顯的 TT,。
電話:010-50951355 傳真:010-50951352 郵箱:[email protected] ;點擊查看區(qū)域負責人電話
手機:13811546370 / 13720091697 / 13720096040 / 13811548270 /
13811981522 / 18600440988 /13810279720 /13581546145