前言
SIGGRAPH 的分享和GDC的分享,是玩家和普通開發者得以窺見遊戲開發技術脈絡的重要方式——本身這種分享也是有一定的開源精神的,很多分享者不僅分享了自己的做法,還分享了自己為何選擇該方案以及實際遇到的困難,這一方面是相當誠實與寶貴的。
例如這期要談的頭髮渲染,在實時渲染領域本身是一個持續發展、不斷改進調整的複雜渲染技術。在SIGGRAPH 歷程中頭髮渲染就多次被提到過,Unreal、Frostbite等引擎及頑皮狗都分享過當時他們各自採用的改進方案,整體是一個不斷迭代優化的脈絡。
鑑於個人水平以及話題規模綜合考慮,這次聊頭髮渲染我覺得最好的方式就是讀文檔,那麼這篇2016年的 《The Process of Creating Volumetric-based Materials in Uncharted 4》就有其承前啟後的意義,本身這個遊戲也是大家耳熟能詳的畫面很優秀的作品。
*注:本文的讀原文部分的翻譯全是我個人完成的,個人認為盡力做到了通順又精準。如需轉載需要詢問我。
1 頭髮渲染概述
這部分我會簡單的概括一下頭髮渲染的發展歷程和近況。
真實的頭髮可以理解成是有鱗片表面的半透明類圓柱結構。(這裡的半透明是Translucent)
在實時渲染領域,頭髮渲染始終還無法做到真實世界頭髮的大部分物理特性。這種無法做到主要是基於兩個方面的限制:一個是歸納其數學模型的難度,一個是數量上的難度。
目前認為最早的比較有效的歸納是Kajiya-Kay模型,它將頭髮模擬成不透明的類圓柱結構,並且歸納了符合視覺結論的各向異性高光的效果(常表現為沿頭部一圈的高光效果)。
Kajiya-Kay模型
後來提出的 Marschner模型開始將頭髮考慮成透光的類圓柱結構,採用了更符合光傳播的歸納方式:對於任意入射光線,其後續光路被拆分成R、TT、TRT三部分,R是指表面直接反射(直接高光)、TT指從內部折射後從離開發絲(表現為背光時的投光)、TRT指從內部折射又發射後從入射表面離開發絲(表現為第二重較弱的高光)。
Marschner模型
以上兩個歸納模型都曾直接應用於離線渲染中,以實際一根一根建模的方式來渲染頭髮。Marschner 模型在視覺上已經比較接近真實頭髮了,但是數量級上無法滿足實時渲染的需求。因此實時渲染長期都需要用各種trick來近似達到接近的效果。
目前主流的實時頭髮渲染方案還是Kajiya-Kay模型或基於預積分LUT的Marschner模型。LUT-Look Up Table是一種常見的多維預計算查找方式,引擎中預積分結果存儲成一張紋理圖,用紋理座標來查詢。
主流的頭髮建模方案基於半透明面片的(也稱為Hair Cards),如果看過之前半透明渲染的文章,會察覺此方案需要處理透明片元排序問題。
這裡介紹一種已經被提煉得比較泛用的流程,通過4個Pass——預繪製深度(Z-PrePass)、繪製不透明多邊形、繪製半透明背面多邊形、繪製半透明正面多邊形。(細節的AlphaTest參數和ZWrite參數之類這裡不列出了)
對於髮梢的柔化,一般有抗鋸齒和AlphaBlend兩種常見方式,也可以進行結合。
2 方案選擇與基礎視覺
這一章開始就基本是讀原文,再輔以我自己的歸納或者補充了(打星號的部分)。原文是網上能下載到的一篇PPT文檔,一共70頁4部分,講解頭髮渲染的是其中最長的第三部分。
其它幾個部分這裡粗略介紹下:
——第一部分主要是吐槽立項時採用了過高的技術方案,實際落到PS4上發現太超前了帶不動,需要各種減量優化。
——第二部分講解了布料的方案,包含其細節紋理的方案及一個近似的次表面散射的方案。
——最後一部分講解了項目中積累的材質庫,例如毛髮、眼睛、布料和皮膚的打溼效果等。這部分沒有拆解而只有一些舉例。
文檔中好多頁都是這個老哥痛苦的表情:
*以下部分開始翻譯一些關鍵的Page(圖片備註翻譯的是每張圖的題頭):
建立頭髮Shader庫
頭髮絲是非常細的,同時它們是半透明的。
每一個頭髮絲有著不同的法線,並且頭髮之間會投下自陰影。要實現這種有體積感的頭髮外觀,我們需要一個一個地解決遇到的問題。
頭髮面片 VS 有獨立幾何體的頭髮
PlayStation4儘管在可容納的多邊形數量上有重大提升,但它仍然很難支持實時渲染數以百萬計的頭髮絲。
由於我們並沒有太多時間供深入研究曲面細分著色器(由於項目週期),我們最終選擇了使用頭髮面片的方式。
我們以性能開銷較大的方式實現了很好的效果
1 高精度的透明紋理
2 使用了AlphaBlend和多重抗鋸齒
3 高精度的陰影分辨率
以上幾點帶來了很棒的渲染效果
(But.. 這裡省略一張圖 直接帶入前面老哥的痛苦表情即可)
我們的頭髮不能那麼多使用AlphaBlend
這是一張Debug屏幕截圖——紋理Overdraw(太多了)
大部分頭髮渲染我們採用了Dithered Alpha和延時渲染
*Dithered Alpha就相當於之前文章介紹過的Screen-Door Transparency,是一種用不透明渲染近似達到視覺半透明的方式
對於大部分頭髮,我們關閉了alpha-blending轉而使用dithered alpha,並結合TAA(分幀抗鋸齒)。這個方案的劣勢是角色移動快或者距離鏡頭遠的時候會帶來“鬼影”(ghost effect)效果。
項目中的頭髮絲不能太細
為了儘量規避這個問題(鬼影),我們必須調整透明像素裁剪的閾值。
*以下是個人的一點備註:
原文沒有提到什麼場合使用了AlphaBlend的模式,我的理解是alpha值大於某個閾值視為不透明,小於某個閾值還是需要使用AlphaBlend;或者僅過場動畫使用AlphaBlend模式。調高閾值是為了儘量多的片元以不透明的方式來渲染,但是以不透明方式渲染的頭髮在TAA之後會產生鬼影。
實際玩過這個遊戲的能感覺出當時這種以不透明渲染為主的頭髮方案帶來的效果還不錯。不過這才剛開始,後面他們還要解決一系列問題。
3 法線方案
但是這裡有一個問題——它們看起來還是“面片”而不是頭髮
尋找原因
1 我們使用的是頭髮片的頂點法線,而不是單個頭髮絲的法線
2 日光帶來的鋒利陰影使頭髮面片看起來像不透明的物體,但它們預期應該呈現半透明的效果
頭髮的法線
角色美術手動調整了角色的頭髮面片,使用了一些trick來讓它們儘量相互穿插,以體現體積感。
頭髮的法線
為了使頭髮面片之間的過渡顯得平滑自然,我們需要統一頂點的法線。
*這裡應該是指在空間變換時對頭髮的法線做處理,使其在世界空間中的方向基準有一致性。
頭髮的法線
大部分時候,我們不能使用高精度的法線紋理貼圖(圖中展示了它們採用的頭髮絲繪製編輯工具和Ramp紋理結構)。
4 陰影方案
為頭髮帶來有體積感的陰影
*前2句翻過了,略過。Deep Shadow Maps是一個性能開銷較大的多層陰影方案。
解決方案:
減少陰影?這會使頭髮看起來很平(削弱體積質感)。
我們在自陰影方面的探索
*原PPT中這裡是一段視頻,展示了不同燈光亮度下的預渲染結果
這裡展示了一個基於預計算的自陰影渲染結果,我們使用了57盞燈光,存儲了各種不同情況的陰影信息。
*這是上一個方案的配圖
為了實現更好的預計算自陰影,我們的嘗試
我們需要找到一個好的折衷方案,來使預計算的陰影在大部分光照場合表現得真實。
為了實現更好的預計算自陰影,我們的嘗試
我們把燈光劃分成了5個組,這會減少我們把不同方向的燈光烘焙成陰影時遇到的問題,同時會會使烘焙出的陰影看起來更軟(一次烘焙燈光更多的原因)。
(這裡又插入了老哥的痛苦表情,由於工期的原因他們也放棄了這個方案)
最終我們拿出的自陰影方案
我們最終決定組合所有燈光並把陰影烘焙到一張紋理上,放棄了方向性的陰影烘焙信息。
這(仍然)幫助我們使頭髮看起來呈現了立體感
在有限的紋理內存中存儲頭髮的細節
左圖:1K細節紋理,很多頭髮面片共用其中一部分紋理
右圖:512尺寸紋理的預計算陰影紋理,存儲在uvSet2
我們如何在遊戲中實現它
我們曾考慮用烘焙陰影完全替代頭髮的實時陰影。
雖然結果在編輯工具中看起來很好,但我們也要考慮運行時其它物體投影到頭髮的情況,那會帶來“暗中發亮”的視覺錯誤。
我們需要修改實時和烘焙陰影的計算
減少頭髮上的實時硬陰影:
如果簡單的模糊實時硬陰影會浪費太多GPU性能。
我們之前提到使頭髮更厚一些以減少overdraw和鬼影效果,但我們不需要對實時陰影也同樣處理。重置透明裁剪閾值是最簡單和高性能的減少實時陰影的方案。
*閾值等於1指只要不是完全不透明,都裁剪掉
減少頭髮的實時陰影
右側是減少後的效果
調整頭髮的烘焙陰影
為了使最終的陰影pass更有真實感,我們對烘焙陰影進行了一些調整:
1 為烘焙陰影添加LightWrap(LightWrap 是一種相對簡化的次表面散射光照方案)
2 使用光照探針的強度值及頭髮顏色值對烘焙陰影進行調整(光照探針是一種基於球諧光照的布光和渲染元件)
*圖中的這些公式為頭髮陰影這個問題提供了高性價比的效果,這些trick的組合可以認為是他們在頭髮這件事上的“卡馬克時刻”。
*這裡再解釋一下為什麼頭髮可以使用烘焙陰影,因為頭髮間的相對自遮擋關係在髮型確定時可以相對確定,一定程度上確實不會穿幫。當然,如果是還考慮了各方向光照的烘焙陰影會有更正確的效果,只是他們做了trade off放棄了。
5 散射方案
頭髮的散射效果
頭髮的散射
我們決定採用一種GPU性能開銷很低的方案來時玩家有一種頭髮有光散射的視覺印象(trick)。
這一方案有2個關鍵要素:
1 頭髮絲間的散射
2 背光的散射
髮絲間的散射
近似於布料的“Cheap SSS”,我們發現我們遊戲中的大部分頭髮顏色是棕色、褐色、黑色甚至白色,添加微紅的顏色可以使頭髮看起來更柔順和有真實感。
*本文中沒介紹他們布料的散射方案。SSS就是次表面散射,那是另一個trick。
背光散射
在這個課題上,我們把頭髮視為球體。我們總結出背光散射主要基於光方向、攝像機角度和表面法線。
散射的量會基於頭髮顏色、形狀、長度和光強度不同而變化。
背光散射
*代碼部分掠過,翻譯一下變量說明
通過調整scatterPower可以使scatterFresnel(菲涅爾 折射相關)的形狀和範圍看起來更可信。
在我們的遊戲中,scatterPower 是11時代表短髮,9代表長髮或蓬鬆的頭髮。
lightScale代表光散射的範圍。對於大部分棕色頭髮,我們選擇了一個更高的數值。
為背光散射添加變化
正如之前提到的,我們不得不讓頭髮絲儘量粗一些,以減少排序問題和overdraw。與此同時,為散射光添加一些變化是一個低開銷的提高發絲質量的方案。
頭髮的光反射模型
1 Kajiya-Kay ——在這個模型的基礎上我們沒有進行改進
2 高光的變化——我們在所有頭髮材質上共享了同一個變化遮罩層。原因:
1)高光變化僅僅用來給玩家提供一種更像頭髮絲的感覺
2)視覺上不像其它變化遮罩那麼容易穿幫
3)我們的頭髮面片的UV佈局是統一化的
*這個效果我個人理解主要是在Kajiya-Kay高光的基礎上增加了一些擾動感
為NPC和次要角色減少紋理內存
左側是主要角色Nadine的質量,右側是多玩家模式下的質量。
*可以認為是採用了LOD形式或不同預設的畫質分級。
結語
原文檔的很多部分還是略微有一些缺少上下文的感覺,如果能有講解的原視頻會更容易理解,可惜我沒有找到相關的視頻。
如果有點遊戲開發經驗,可能會感覺他們當時考慮的一部分問題到現在變成特別初級的事情了;一方面是技術也在工業化領域不斷發展,另一方面是硬件性能在之後也有了質變(儘管還不到10年)。但考慮到年代感,能讀到一點他們當時真實的想法,並稍微揭開頑皮狗這樣的公司的神秘感,我覺得也是很好的視角。
在最新的實時渲染領域,已經可以模擬髮絲(Strand-based 基於線的)進行頭髮渲染了,但是數量上仍然有一定限制,因為除了渲染開銷外,物理模擬上的性能消耗也是不可忽視的。(玩過《死亡擱淺》的想必對strand這個詞不會陌生)
更科學的歸納模型還會考慮頭髮的髓質及其影響,這在人頭髮的佔比不大,但是在動物毛髮中的佔比很大,因此動物毛髮不能僅僅採用Marschner模型。這一點Games101的閆老師提出了他的模型並被正式採用到了工業領域,有興趣的可以去看Games101的相關內容(這一技術被用到了動畫影片《新獅子王》的渲染中,效果很好)。
無論是容納更多數量,還是歸納出更精進的模型,在頑皮狗完成《神秘海域4》製作並進行技術分享的2016年之後,這個領域又不斷有了長足的進步;當初用一堆trick堆出來的特別“真實”的渲染質感,又在不斷被後人挑戰。
我覺得遊戲行業從業人員本身是有其反功利樂於分享的一面的,而研究圖形技術及其應用的分享又是各類分享中的重頭戲。不同的公司在其不斷提升畫面的路上,都或多或少的各自迎來了屬於自己的“卡馬克時刻”,他們將其分享出來,帶來行業共同的發展,還能反哺到電影、動畫、教育等很多其它領域。
只要這份分享精神還在,這個行業就始終充滿希望。
最後是一些資料鏈接:
ATI早期的頭髮渲染文檔
知乎上騰訊P13大神寫的介紹文章
SIG2016的本文下載地址
SIG2019由Frostbite提出的髮絲方案
斯坦福Deep Shadow Maps的文檔