製作一個逼真的數字人類是一項複雜的技術挑戰,需要有海量的數據才能實現極高的圖像水準。在製作《異教徒》時,Demo團隊開發了許多的工具來克服各種問題:面部動畫、毛髮與皮膚的粘連,及眼睛、延遲和皮膚的渲染。所有工具目前已發布在GitHub上。本文將詳細介紹製作方案中的完整技術細節。
我叫Lasse Jon Fuglsang Pedersen,是Unity Demo團隊的高級軟體工程師。在《異教徒》的製作中,我的一個主要任務是製作驅動數字人Gawain面部動畫的技術方案。
該方案已在GitHub上作為單獨資源包發布。而在本文中,我將討論資源包的部分功能,分享一些功能的開發幕後。
《異教徒》的數字人類在製作中有一個明確的目標:在保持整體寫實手法的前提下,避免面部動畫陷入「恐怖谷」。為了讓動畫儘可能匹配演員的動作表演,我們決定使用4D動作捕捉數據(一種逐幀3D掃描動畫)來製作面部網格的動畫。在捕捉面部表演時(在無遮攔的部位),4D數據至少可以保證網格的幾何形是準確的。
使用4D捕捉數據帶來了許多新的挑戰,這部分動畫導演Krasimir Nechevski已經在之前的博文中詳細介紹過了(點擊回看)。許多的精力都花在了數據的處理和調整、數據捕捉的實行,及做出滿意成果這些方面上。
我們遇到的問題中,有一個關於眼瞼幾何形的問題。由於睫毛在捕捉時遮住了一部分眼瞼,數據在這些位置上也會有缺失,形成一定的噪聲幹擾。因此,眼瞼的幾何形並不準確,會有抖動的現象,需要我們重新製作。
眼瞼附近的幾何形抖動
眼瞼幾何形的問題在製作早期就顯現出來了。於是我們在導入數據到Unity時,在區域內使用了差異性網格處理技術,希望減少噪聲幹擾、重建幾何形。具體來說,我們會抹平區域內的變化曲線來達到降噪效果,在捕捉序列的每一幀上,通過移植基本網格的曲線到帶噪區域來重構幾何形。
眼瞼幾何形的降噪和移植
雖然產出的幾何形比較穩固,但和源數據相比人為合成的感覺有點更明顯:眼瞼雖然更加穩定,但也缺失了部分原動作,少了人的生氣。很明顯,我們需要尋求一種中庸效果,可惜時間過於短促,仔細研究是不可能的。自然而然地,我們請來一位外包來解決眼瞼的重建工作。而GitHub上的資源包包括了原先用於降噪和網格移植的工具,作為學習資源可能會有一些價值。
另一個問題是表面細節。由於目標網格的解析度過低,表面細節有一定的缺失。Gawain的面部網格有約28000個頂點,但仍不足以將演員面部的皺紋用幾何形展現出來,更別說表現皮膚毛孔的伸展了。雖然原始4D數據有一些細節,為了讓網格的頂點符合預算,我們在變形、渲染網格時捨棄了這些細節。我們考慮過在每幀上烘焙一張法線貼圖,但這樣會佔用不少寶貴的磁碟空間。
為了處理表面細節,我們決定嘗試將導入序列的幾何形狀與來自Snappers Systems的基於混合形狀的面部綁定的姿態驅動特徵圖耦合。來自面部綁定的姿勢驅動的特徵圖包含了在導入序列時缺失的表面細節,如皺紋和毛孔的伸展。我們的想法是,如果可以找出最接近4D幀的Blendshape組合,就能用權重來驅動面部貼圖(忽略掉Blendshape的變形效果),製作出4D回放時的表面細節。
將Blendshape匹配到4D分為兩步。第一步是將問題部分以矩陣的方式列出,使用最小二乘法將所有的Blendshape(基礎網格的增量)填入矩陣A,數據的每一列都儲存了單個Blendshape的增量,而組合起來的增量用Ax = b表示,x表示單個BlendShapes的權重。
由於A不能逆算(這裡數值並不是方陣),x的數值經常不能被解算出來。不過如果將問題以另一種算式表示出來,就能取得一個近似解x*。我們使用了正態方程ATAx*=ATb,將最小平方算式寫作x*=(ATA)-1ATb,這時A只需有獨立的線性數列即可。而在使用Blendshape時,我們需要篩選出已包含的形狀,確保其能線性獨立,然後就能求出一個近似值:我們預先計算出Blendshape骨架的(ATA)-1AT 值,然後為每幀的4D數據插入增量b值,來計算x*(擬合後的權重)。
雖然上方的最小平方方法用來理解問題是很不錯,但實際使用就沒那麼好了。為了更接近4D幀的數值,方案中有時會含有負權重。但面部骨架僅允許Blendshape的值增加,不能減少,讓擬合後的權重能突破骨架的限制,於是方案並不能總是將數值轉化成有效的皺紋。
話句話說,我們需要使用一種無負數的方案來取得正確的皺紋。在計算無負數的方案時,我們使用了第三方庫Accord.NET的子集,其中包含了一個專門計算無負數最小平方的可迭代結算器。在拆解問題、測試最小平方方案後,我們就求得了過濾後的Blendshape矩陣A和增量b,這時直接插入解算器來取得一個每幀擬合權重的非負數集。
部分前額匹配皺紋運算的前後對比
順便一提,我們還嘗試了根據網格邊緣長度和邊緣曲線來計算擬合權重的方法。如果不能移除4D數據的頭部動作,我們本需要使用這些方法來讓擬合效果獨立於頭部動作。我們最終在Gawain身上擬合了位置增量,而另兩個方法也保存在了資源包中。
在導入4D數據到Unity之前,我們首先依賴外部工具將數據轉化成了一個網格序列(儲存為.obj文件),在每幀上匹配拓撲。同時拓撲還需要匹配目標網格(詳見Krasimir Nechevsky 的博文,點擊回看)。
接著,資源包中包含有一個自定義類型的資源,稱作SkinDeformationClip(皮膚變形片段),儲存了預處理的4D數據和可用於運行時的片段。SkinDeformationClip在創建後會提供導入工具(及可選處理項),可用於導入部分4D數據,路徑既可設為磁碟上所有的.obj文件(省去了在項目中導入整合性資源的必要)也能設為項目中已存有的網格資源。
從.obj文件中導入4D幀、製作片段
在配置好SkinDeformationClip資源後,點擊檢視器中的Import按鈕就能開始導入、處理幀數據。注意,如果資源設定中同時啟用了網格處理、幀擬合功能,導入過程會耗費不少時間。在導入完成後,片段將儲存導入後的幀間隔、擬合權重等等數據,但並沒有最終的幀數據。幀數據儲存在另一個二進位文件中,以在播放時高效地完成數據傳輸。
在導入完成後,就能將資源拖到Unity Timeline的自定義時間軸SkinDeformationTimeline上進行播放。該時間軸能取得專門的SkinDeformationRenderer組件,將其作為片段數據的時間軸輸出。下方視頻展示了在Timeline上制定序列和播放4D數據的流程。
使用Unity Timeline播放片段資源
在使用自定義軸和SkinDeformationRenderer時,你也能將多個4D片段混合起來,使用這些數據進行藝術創作。在《異教徒》的第一部分,我們只使用了4D數據的一個小片段,其中僅包含了一個測試時首次近距離拍攝的3秒表演。不過,通過重複利用(通過剪切、縮放和混合),同個片段被用在了整個第一部的面部動畫上。
由於我們選擇直接使用4D數據製作面部動畫,無法依靠骨骼權重蒙皮或Blendshape來表現睫毛、眉毛或胡茬這類稍顯次要的面部特徵動畫,需要用一種方法在面部網格上為這些面部部位添加動畫。
技術上來說,我們能將處理後的4D數據加載到一個外部工具中,為網格建立模型、添加次要部位,然後烘焙出所有的額外數據。然而,從磁碟空間的角度來說,傳輸每幀的上萬個額外頂點是無法實現的,並且產出結果也不會有動態的感覺。我們很明確4D數據在製作中需要迭代多次,因此在迭代時方案不能有冗長的烘焙步驟。
為了解決這個問題,我們為數字人類包添加了一個稱為皮膚粘連繫統的功能。系統允許將任意網格和變換粘連到一個指定的目標網格上,在運行時融合進目標網格,粘連的內容獨立於目標網格的動畫而存在。
在《異教徒》的數字人類身上我們使用了皮膚粘連繫統來驅動眉毛、睫毛、胡茬及其它皮膚相關的印記。我們也使用了系統將皮草附著到了夾克上,這一點我們的3D藝術家Plamen Tamnev已經詳細介紹過了(點擊回看)。
下方是使用系統粘連網格的步驟,比如我們將一個Game Object的變化看作Gawain的臉:
放置、粘連一個變換
當點擊Attack鍵來附著一個變換時,系統將使用變換的位置來向k-d樹查詢最接近的網格頂點。頂點隨後用於識別所有的三角形,系統會針對每個三角形根據變換當前位置來生成一個局部姿態,形成一套變換的局部姿態。
最近頂點在三角形上的投射
每個局部姿態都是一個三角形附著點的投射,並且還包含了三角形的頂點索引值、附著物到三角形的法線距離,以及投射點的質心坐標。
變形完成後,系統將分別去除投射點,平整網格
我們之所以為每個附著點生成多個局部姿態,而不是在三角形上生成姿態,是為了支持部分不屬於三角形的點。比如部分懸浮在網格上方的毛髮圖片。為了在多個局部姿態上解決粘連點的問題,我們首先需要分別刪去每個三角形的局部姿態,然後平整三角形的較高處。
在生成後,局部姿態將儲存在一個巨大的數組中,數組還包括了面部所有粘連的局部姿態。每個附著物將引用數據、檢查數據總量,防止底層數據因其他原因被修改。
粘連一塊網格和粘連變換類似,只是流程會多重複幾次。在粘連網格時,系統會為每個頂點生成一套局部姿態集,而不是單個的變換位置。
在普通網格模式下粘連眉毛
網格上同樣有一個稱為MeshRoots的次級粘連模式:在該模式下,系統首先根據相關性將網格劃分為網格區,再找出每個區相對於面部網格的「根」。最後,系統會讓單個區保持剛性狀態。舉例來說,睫毛就是以這種方法粘連的,而眉毛不是。這是因為眉毛的毛髮圖片會隨皮膚運動,需要有變形效果,而睫毛的毛髮圖片會保持自己的形狀。
附著到根網格上的獨立睫毛區
在運行時,系統會抓取附著對象的位置和頂點(網格的變換),持續更新數據來使其融合進面部網格。每一幀,系統會計算面部網格的狀態輸出,將其與現有局部姿態相組合,解算出所有皮膚相關的位置和頂點。下方圖像展示了Gawain身上密集的附著效果。
藉助面部網格解算出的點
運行時的解算過程藉助了C# Job System和Burst Compiler加速,讓處理數據量更加龐大。舉例來說,Gawain面部的解算需要在每幀上解算上萬個局部姿態,來生成面部細微特徵的動畫。
當我們開始製作數字人類的單獨資源包時,一個主要的目標是將所有渲染相關的內容轉換為原始的高清渲染管線(HDRP)內容,確保包能使用新的HDRP功能來升級、拓展。
背景:當我們開始製作《異教徒》的原型圖像時,HDRP仍缺少部分拓展性相關的功能。我們也還不能編寫自定義可升級著色器,不能在幀之間插入自定義渲染命令(如自定義渲染通道)。
因此,數字人類(以及影片中部分其它效果)的自定義著色器在建立原型時屬於HDRP材質的直接分支。管線在當時仍處於預覽階段,有很多的結構性改動。許多自定義著色器還需要修改HDRP的核心,使得升級愈發困難。我們需要搜尋更多的HDRP拓展功能,減少自定義的需要。
於是,製作數字人類資源包需要將原先必要的自定義設定轉換為現今HDRP拓展而來的功能:把自定義著色器轉換為Shader Graph,使用HDRP專用的主節點,藉助CustomPass API來執行必要的自定義渲染通道。這裡需要感謝一下Unity的首席圖形工程師Sebastien Lagarde和Unity Hackweek 2019上的一支團隊,他們為HDRP加入了Eye Master眼睛主節點。節點可兼容《異教徒》的自定義功能,在導入眼睛時發揮了巨大作用。
在下一節我將介紹皮膚、眼睛和牙齒的Shader Graph。資源包內還有一個頭髮的Shader Graph,是一個直通Hair主節點的單路徑式圖表。
在整體上,皮膚著色器大幅依賴HDRP內置的次表面散射功能。功能可讓藝術家們為各類材質製作、指定不同的散射配置,形成包括各類皮膚的仿真效果。皮膚著色器使用StackLit主節點,以形成兩個反射片(一種常用於製作皮膚的方法,不支持Lit主節點),因此皮膚著色器的渲染僅能是前向。
皮膚著色器的Shader Graph
與普通受光著色器一樣,這兩個反射片上的皮膚平滑度都由遮罩貼圖生成,而第二個平滑度則在材質檢視器中作為可調整的常量被暴露出來。美術們同樣可以用遮罩貼圖控制環境光遮蔽,和兩張細節貼圖的影響力程度。兩張圖分別為細節法線貼圖和細節平滑貼圖,平滑貼圖會同時影響主要和次級平滑度。
除了普通的遮罩貼圖,皮膚著色器同樣能接收凹陷貼圖(Cavity Map,單通道紋理,在凹陷處有較低的數值),用以控制小凹陷處,如皮膚毛孔上的反射剔除因素和/或平滑度。凹陷貼圖的效果也可在掠角上選擇性地消除,來模仿掠角中凹陷處被隱藏的效果。
使用凹陷貼圖修改毛孔上的平滑度
皮膚著色器還支持由Snapper面部骨架驅動的面部特徵(如皺紋)。在皮膚的著色器圖表中,功能被封裝在一個自定義節點中,節點的部分輸入本身並不能在圖表中查看。這些隱藏起來的輸入由SnappersHeadRenderer組件驅動,組件則需要作為SkinnedMeshRenderer儲存在使用著色器的GamObject上。
擬合後的權重在皮膚著色器上被轉換成皺紋
另一個有意思的節點是淚線相關的圖表,這部分將在眼睛部分之後解釋。基本上來說,為了讓淚線效果能修改皮膚的法線,我們需要在深度Prepass時計算、儲存法線,然後在前向通道中重新採樣(如果重新運算法線,會導致所有中間處理都被捨棄)。
《異教徒》的自定義眼睛著色器是與軟體工程師Nicholas Brancaccio合作製作的。Nicholas參與了著色器最初的製作,包括分成兩層的光照模型,以及眼瞼附近光照遮蔽的計算函數。
眼睛著色器的Shader Graph
著色器為眼睛建立了一個雙層材質的模型。在模型上,第一層用於表現角膜和表面的液體,而第二層用於表現鞏膜和虹膜。光線會分別分布在兩層上:上層主要計算鏡面反射(表現角膜和表面液體的模型層,其表面更加光滑),而下層僅計算光照散射(虹膜和鞏膜)。
在光源下眼球的無遮攔視圖
角膜上的折射會於眼球內處理完畢,效果依賴於幾何形輸入和一些可變更的參數。眼球幾何形的輸入需為僅描述眼球表面(包括角膜突起)的單個網格。
接著,在截面(粗略)描述出角膜的起點後,我們便能在渲染時識別出該區域是否為角膜的一部分。如果是,則折射光線,使光線在表示虹膜的平面上互相交叉。虹膜平面可經由角膜界面上的偏移來調整,允許藝術家調整眼球上並行圖像的數量。
旋轉眼球時出現的角膜反射
為了算出虹膜中的光照散射程度,眼球著色器帶有一種根據表面(角膜)光柵化片段,將入射光照折射向虹膜的選項。雖然功能並不能生成精確的焦散效果(僅會在反射表面上接收接收、積累單個片段投來的光),但在受光時,比如從側邊觀察時,虹膜至少不會在影子中不自然地凸顯出來。光照反射功能已成為眼睛主節點的一部分,可在Eye Cinematic模式中啟用。
反射照向虹膜的射入光線
我們使用了一種球型高斯各向異性算法來完成眼瞼附近的遮蔽。有四個標記(變換)會藉助皮膚附著系統追蹤眼瞼,完成遮蔽效果的分布。具體來說,我們使用兩個標記來追蹤眼球的邊角來形成一個封閉的軸,其餘兩個標記則追蹤上下兩片眼瞼,用於推測眼瞼的閉合角。封閉的軸與閉合角接著會生成測算球型高斯各向異性所需的必要矢量。我們使用測算結果直接作為眼球主節點上的環境光和鏡面反射遮蔽的因子,有時還用於選擇性地修改albedo來人為降低遮蔽區域的亮度。
驅動球型高斯各向異性遮蔽的四個標記物
在眼睛圖表中,包括角膜折射和眼瞼遮蔽在內的大部分功能都使用了一個自定義功能節點:EyeSetup,可向圖表輸出一系列可讀數據。類似於皮膚圖表面部骨架上的自定義功能節點,該節點使用了一個隱藏的參數,不能在材質檢視器中修改,僅能通過腳本代碼控制。這時因為參數十分複雜,需要在每幀上抓取。而在眼睛圖表中,隱藏參數由EyeRenderer組件驅動。而為了讓著色器能生成正確的結果,組件同樣需要作為渲染器放置在同一個GameObject下。
EyeRenderer組件除了會計算、傳入數值到著色器,還提供有協助建立眼睛模型的實用工具。比如,用戶可使用工具來可視化、調整角膜區域的截面,或者檢視、微調平面投射紋理的前向軸,防止眼球幾何形未能正對z軸。
在場景視圖中拖動控點來調整眼球
最後,眼睛圖標和皮膚圖表類似,也有一個整合淚線的節點:節點會在深度前通道中寫入法線和平滑度,在前向通道中再次採樣。
為了重現淚線(眼睛與皮膚間的潮溼帶),我們使用了HDRP自定義API,在幀的特定階段插入自定義渲染。
通過使用自定義通道來控制HDRP法線緩存上的內容(其中包含了法線貼圖和平滑度數據),我們在面部的特定屏幕空間上模糊了法線貼圖和平滑度(比如在眼睛與皮膚的接觸位置)。由於皮膚和眼睛為前向材質,我們還需要在圖表中插入特定的節點來在前向通道中採樣。
應用淚線模糊通道前後的法線貼圖
在法線緩存中添加平滑的過渡效果可讓兩個表面很好地相連接。如果與較高的平滑度數值相結合,就能在兩個材之間產生一種鏡面高光效果,讓淚線能有潮溼的感覺。
添加淚線前後的著色結果
為了標記出模糊區域,我們使用了一種簡單的遮罩貼花,放置在特定層上,並且不渲染任何顏色(調試時除外)。有了這些特殊層上的貼花,我們就能更輕鬆地用自定義通道篩選、渲染,只需要設定HDRP中的自定義模板字位即可。當所有遮罩貼花儲存進模板後,我們就有一個指示模糊區域的屏幕空間遮罩。該遮罩還能動態地將模糊通道縮小到遮罩的邊緣,防止區域的邊緣出現模糊。
顯示出淚線遮罩貼花的調試覆蓋視圖
在製作Gawain的淚線時,我們為每個眼睛製作了專門的遮罩貼花,在無表情時覆蓋住的眼瞼和眼球,然後使用皮膚附著系統粘連到皮膚上。為了做出眼球和眼瞼的小縫隙(4D數據中比較明顯),我們還稍微擴大了些幾何形,使其與眼球能有向內的覆蓋圖像。
牙齒著色器使用了受光主節點的許多功能,包括此表面散射和透光表層遮罩。除了使用受光節點現有的功能外,著色器還使用了一種自定義衰減效果,用以根據嘴巴當前的張開成程度,平滑地使口腔內逐漸變暗。
牙齒著色器的Shader Graph
為了測算出目前口部張開程度,我們在嘴唇上放置了6個標記,形成一個近似與嘴唇內部彎曲的多邊形。在Gawain身上,我們使用了皮膚附著系統來驅動標記,使其無視面部網格的動畫來跟隨嘴唇的變化。
在渲染時,我們首先將多邊形傳給著色器,然後將其投射到當前的不受光半球形網格上,形成一個球體。該球體能直觀地展示出從當前片段的角度,透過嘴部開口能看見多少內容。
在嘴內用一個球體可視化球型多邊形
為了使嘴內部變暗,我們使用了球型多邊形和對應的不受光半球作為非物理衰減的條件(忽視了餘弦)。具體來說,我們會在傳入受光主節點之前,衰減環境光和鏡面反射遮罩因子,表層遮罩和albedo貼圖。應用自定義衰減的前後效果
與皮膚和眼睛圖標類似,牙齒圖表同樣包含一個隱藏的自定義功能節點。隱藏的參數由TeethRenderer組件提供,需要作為渲染器添加在使用該著色器的GameObject上。
希望本文能闡述出制定Gawain面部技術方案的挑戰和我們付出的努力。
如果想要嘗試這些工具,或在此基礎上製作自己的工具,可以在GitHub上下載代碼庫、在自己的項目(包括商業項目)中使用。我們期待看到大家的創作!
《異教徒》製作揭秘系列(點擊文章標題,直達原文):
文中提及的相關連結:
[1] 《異教徒》連結:
https://unity.cn/the-heretic
[2] GitHub 連結:
https://github.com/Unity-Technologies/com.unity.demoteam.digital-human
[3] Accord.NET 的子集連結:
http://accord-framework.net/
[4] Unity Timeline 文檔:
https://docs.unity3d.com/Packages/com.unity.timeline@1.5/manual/tl_about.html
[5] C# Job System 文檔:
https://unity.com/dots/packages#c-job-system
[6] Burst Compiler 文檔:
https://unity.com/dots/packages#burst-compiler
[7] 高清渲染管線(HDRP):
https://unity.cn/srp/High-Definition-Render-Pipeline
[8] CustomPass API 文檔:
https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@7.1/manual/Custom-Pass.html
[9] Hair 主節點文檔:
https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@7.3/manual/Master-Node-Hair.html
[10] HDRP內置的次表面散射功能文檔:
https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@9.0/manual/Subsurface-Scattering.html
[11] HDRP中的自定義模板字位文檔:
https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@7.3/api/UnityEngine.Rendering.HighDefinition.UserStencilUsage.html