前言
在我寫上一篇介紹ArcSystemWorks的卡通渲染的文章時,還沒有微軟解散Tango工作室的這個新聞。雖然我個人沒有玩過這個遊戲,但我也很欣賞他們能做出這款小而美的遊戲——這也是微軟去年一眾拉跨作品中少數意料之外的亮點了。
我在GDC Vault看到這篇今年3月的分享可以下載閱讀的時候,幾乎就是同一周看到了關於Tango被關閉的新聞。對於微軟這個無論經濟還是風評都吃虧的決策,網上的討論已經很多了,我除了遺憾更多的是不意外,畢竟大公司近些年的草臺程度已經越來越誇張了,而財報會議之後背鍋的往往就是靠拉報表排序來選擇。
回到這篇分享,它或許內容編排上略微有些瑕疵,但總的來說是富有熱情且乾貨滿滿的——大部分不是最先進的技術,但是確實都是很踏實的細節。一方面我能感受到分享人自豪地掏乾貨的那種熱誠,同時結合近況我也能感受非常遺憾;無論如何,希望粗讀這篇分享能帶大家更瞭解這個遊戲和這個團隊,畢竟人活著就還有希望。
![分享了主要內容的Kosuke Tanaka](https://image.gcores.com/036b4ca02e27e58ccad05e4d094cd77f-1408-789.png)
分享了主要內容的Kosuke Tanaka
![分享了人臉陰影方案一節的Takashi Komada](https://image.gcores.com/1340ee494e2762d28ad9dd2f37183951-1406-790.png)
分享了人臉陰影方案一節的Takashi Komada
*在通讀了整個分享原文後,我計劃用兩篇文章覆蓋原文的大約80%以上的內容,中間順序可能會略微調整一下(除了略去開頭的介紹部分外,主要是把面部陰影這一節提前了)。後面的內容還是以翻譯原文為主,打星號的則是我自己的備註和評論;另外就是儘管以分析渲染為主,但提到虛幻引擎中的一些細節或許沒那麼準確,如有不對歡迎指正。
1 整體的延遲渲染方案
*主機遊戲一般都是用延遲渲染作為主要渲染管線的,他們在力所能及的幾個方面做了引擎定製化開發。PPT中也大量使用了遊戲中角色來模擬發言,可見他們非常喜愛自己做的這款遊戲。
*這部分我調整了一下原PPT的順序並做了抽選和精簡,希望能提高易讀性。
![畫面和性能指標](https://image.gcores.com/64025c9c6f651bd5f92b29aac9c74144-1407-790.png)
畫面和性能指標
![在Xbox平臺上的預期與低端機策略](https://image.gcores.com/7d7a5d8a079a52857bbb1905b7a2e6eb-1407-790.png)
在Xbox平臺上的預期與低端機策略
——由於我們把性能和圖形質量視為最優先的項,開發過程中一個揮之不去的恐懼(原文是nagging fear)就是人們會對我們的技術力感到失望。最終,無論是遊戲的視覺還是技術上都收到了好評,這對我們是一個很好的結果。
——平衡性能、分辨率和渲染特性(的豐富程度)是很難的。作為圖形工程師我們往往會對新的渲染技術感到興奮,也想要開發出更好的圖形技術,不過我們遊戲最強調的還是性能與畫面質量,為此在選擇採用哪些渲染特性時我們必須謹慎考慮。
*英文原文中有graphics和image quality這兩個詞,在這個語境下前者我翻譯成了圖形技術。圖形技術有時候等於更好的畫面,有時候則不是。(原文是... want great graphics, but our game emphasized performance and image quality)
![默認延遲渲染](https://image.gcores.com/4b5d8834ed3140d0e68ac92b449c7d95-1407-790.png)
默認延遲渲染
![定製化卡通延遲渲染](https://image.gcores.com/7496169892dbfe0a8b1b8f15ab66b672-1404-791.png)
定製化卡通延遲渲染
*可以看到自定義風格化渲染主要的區別就是對陰影的處理,其次才是顏色等
![GBuffer分配的內容](https://image.gcores.com/7a3adc8268b51dfd223c6ba724e21f08-1403-792.png)
GBuffer分配的內容
![GBuffer分配的圖形預覽](https://image.gcores.com/9df25c9702115525b2b95310a1cf58be-1406-793.png)
GBuffer分配的圖形預覽
*提到延遲渲染就離不開GBuffer,如果還不清楚是啥的可以看看我之前的文章或是網上介紹延遲渲染的資料。可以簡單理解成這些幀緩衝都是後續計算光照的數據提供者,針對這部分內容光照不需要額外考慮其它空間中的幾何信息了。
*最基礎的PBR不透明渲染需要3維的法線(GBufferA中的RGB)、3維的顏色(GBufferC中的RGB)、2維的粗糙度和金屬度(或高光程度)就足夠了,上面的圖1中可以看到引擎中和他們做的一些補充與自定義部分(標了Customize的)。
![一幀中的幀緩衝內容](https://image.gcores.com/30535bae2233837c00b21a26739a8dd5-1405-791.png)
一幀中的幀緩衝內容
*這張圖列舉了一些其它的幀緩衝內容如深度、AO等。其中大部分內容後面的章節會分析到。
![為全局光照的每一個模塊做定製化的調整](https://image.gcores.com/2cd2395c4df27d19452f695c5adce967-1405-790.png)
為全局光照的每一個模塊做定製化的調整
*這些特性也可以結合前一張幀緩衝來看(大部分會被渲染到一張幀緩衝上)。
*很多特性之前我的文章裡也介紹過,例如ProbeGI對應光探針採樣和球諧光照技術,SSR是屏幕空間反射的縮寫,StaticShadowMap是烘焙的陰影貼圖,AOVolume是一種能動態算出AO的策略,SSAO是屏幕空間的AO等。後面和下一篇文章會有章節逐一分析。
*可以感受到引擎中的全局光照就像拼零件一樣,把各種高性能高近似度的技術一個一個拼接起來。
![逐體積區域後處理](https://image.gcores.com/b27a4cea39cbb0afa7f6372d33adfeec-1406-789.png)
逐體積區域後處理
——另一件我們想提升的事項是,UE4的後處理是逐攝像機添加並計算的。
——我們感覺這限制得太死了,因為我們想為同一個攝像機渲染的不同區域設置不同的顏色和風格(通過後處理區域)。
![依據GBuffer和Box體積渲染後處理的方案](https://image.gcores.com/41367bdfda03a0ddaae84b4b7564e96e-1406-789.png)
依據GBuffer和Box體積渲染後處理的方案
*這裡解釋了逐體積做後處理的思路——基於延遲渲染的GBuffer和DepthBuffer,可以判斷著色點是否在體積包圍盒的範圍內,並添加相應的後處理渲染。
![管線及修改總覽](https://image.gcores.com/9ce802f7293e369de871f1caa1ad6ab4-1408-790.png)
管線及修改總覽
*圖中從左到右大概是:深度緩衝、陰影深度、GBuffer、AO和膠囊陰影等、全局光照、動態屏幕空間陰影、靜態陰影貼圖、屏幕空間反射、Decal燈、後處理勾線。
*標黃的是經過自定義修改的部分。可以看到比人物難度更大的是環境的卡通化渲染,幾乎所有光照和陰影都需要做深度的定製化修改。
2 人物面部陰影
*上一篇文章中提到了面部陰影輪廓的問題,只依靠手調法線肯定是不靠譜的。這裡很快會就介紹一種目前比較主流的方案。
![角色陰影](https://image.gcores.com/f4d13d4a03012928982e6b5e5963eee0-1405-792.png)
角色陰影
——Hi-Fi RUSH中角色的自陰影是cel風格著色。陰影和非陰影區域有著清晰的分界線,並且陰影區域會被指定一個特定的顏色。由於有這種陰影風格,視覺質量就取決於陰影的輪廓和顏色表現。
——Hi-Fi RUSH的藝術理念是sharp, clean和colorful,所以陰影也需要始終保持清晰的輪廓。
*這部分理念如果看了上一篇關於罪惡裝備卡通渲染的文章,應該能看出其中的一致性。
![角色陰影](https://image.gcores.com/9d01224cc147c4505bef44bf347758b8-1405-791.png)
角色陰影
*除了臉部,角色其它部位的著色都是通過閾值判斷法線與光線的夾角(的點乘)來計算的。上一篇也介紹過一樣的內容,不過寫成step函數會比if有更好的性能。
*對於臉部,最初的方案是調整面部法線。下面就列舉了這一方案的一些問題:
![頂點法線問題1:難以生成順滑的曲線](https://image.gcores.com/28ec6adae27ab9e958aa6eb46fc7b9ff-1407-790.png)
頂點法線問題1:難以生成順滑的曲線
![頂點法線問題2:面部動作會破壞陰影輪廓](https://image.gcores.com/2ce6d0154e35b1b53234e5e455272b8d-1405-791.png)
頂點法線問題2:面部動作會破壞陰影輪廓
![頂點法線問題3:藝術家無法通過僅僅調整模型設置解決以上問題](https://image.gcores.com/465c728ffe90d6b8137f8b11c2c1d00b-1406-790.png)
頂點法線問題3:藝術家無法通過僅僅調整模型設置解決以上問題
*可以看出對於模型視角需要自由轉動、或者模型可變化的情況,只調法線都是不適用的了。
![通過閾值數據紋理來著色](https://image.gcores.com/9af0fdea23782dc95a3917c6354b4551-1404-789.png)
通過閾值數據紋理來著色
——為解決這一問題,我們採用了一張類似高度圖(heightmap)的紋理。在內部我們把這個紋理稱為閾值紋理(threshold map)。後續我會解釋如何製作它,不過讓我們先解釋一下如何用它來計算陰影。
——首先,計算面部朝向與光線方向的水平分量的夾角。這裡水平分量指面部骨骼座標系中相應的水平分量。最終計算結果為夾角除以Pi之後歸一化的值。(注意這裡不是用的點乘結果了)
——然後,通過採樣閾值紋理,比對上一步計算得到的值就能判斷著色點是否在陰影中。
*這也是目前卡通渲染遊戲中主要的面部陰影方案了。另外需要注意這個紋理是有左右方向的。
![通過閾值數據紋理來著色](https://image.gcores.com/538c4325dee9b48df695c6d41ee23ad0-1408-789.png)
通過閾值數據紋理來著色
*這樣得到的陰影是水平階段變化的(符合預期)。光線從左側入射時(對於圖中的紋理方向),需要水平翻轉閾值紋理。
*這一方案引用自2018年UnitySeoul大會上米哈遊賀甲的分享。(說句題外話,賀甲在原神後不久就離開米哈遊了,現在也已經不在騰訊了)
![如何製作閾值紋理——通過DCC工具做180度的烘焙](https://image.gcores.com/6946d03cecc45ea5c68bab0f2d7c96d5-1405-790.png)
如何製作閾值紋理——通過DCC工具做180度的烘焙
![如何製作閾值紋理——藝術家手動調整紋理(修改其中的邊緣部分)](https://image.gcores.com/28d4b8414cd4371c94ced23bd7ec9812-1407-790.png)
如何製作閾值紋理——藝術家手動調整紋理(修改其中的邊緣部分)
![如何製作閾值紋理——把一組調整後的烘焙通過類似距離場的方式插值合併](https://image.gcores.com/8fb7fb15d050f41bf9cab574b4f40d67-1407-790.png)
如何製作閾值紋理——把一組調整後的烘焙通過類似距離場的方式插值合併
*到這裡基本就揭秘了目前卡通渲染主流的面部陰影渲染方案。後續只要是想讓顏色看起來很“平”的,基本還是這個方案。
![最後是頂點法線方案和閾值紋理方案的對比](https://image.gcores.com/adb7e1ea7c2a4251d7e85bb5917a249f-1406-788.png)
最後是頂點法線方案和閾值紋理方案的對比
3 環境的卡通風格著色
*這部分主要的乾貨就是有向距離函數(SDF——Signed Distance Function)的應用。
![卡通風格著色](https://image.gcores.com/a4f58ecdbd23479e1f73b93df6bf0024-1342-754.png)
卡通風格著色
*光照的視覺是通過網點來表現,陰影是通過填充線來表現。熟悉漫畫的會發現這是日漫裡常用的明暗填充方式。(這裡Halftone的翻譯直接採用了漫畫繪製中的網點,原詞本身也是形容一種網格板的結構)
![半色調的點](https://image.gcores.com/d64b9aff9615ad6d1e2001456a9b129c-1343-754.png)
半色調的點
*網點主要運用在:(基於解析算法的)霧,Bloom光暈效果,探針全局光照,屏幕空間反射。
![填充線](https://image.gcores.com/fe93e656891c3f1fc9c9b556831ffb89-1341-755.png)
填充線
*填充線主要運用在:體積AO陰影,靜態陰影紋理,玩家動態陰影紋理,屏幕空間AO。
*下面進入乾貨部分,主要是SDF用於紋理渲染和把生成的紋理“貼”到場景:
1)SDF用於紋理渲染:
![有向距離函數——網點和填充線都可以用SDF函數計算](https://image.gcores.com/3727963481a297b39d1e7f490fdb689d-1346-755.png)
有向距離函數——網點和填充線都可以用SDF函數計算
![實現網點——第一步](https://image.gcores.com/38393507660e651e9a87357f0e78ac5c-1342-753.png)
實現網點——第一步
*SDF可以簡單理解為對空間中的任何一個點都可以描述它的一種或多種位置關係(例如距離邊緣的距離、和其它點的最小距離等)。在這個案例中比較簡單,就是一組平鋪的0到1紋理座標映射。
*對於其中一格,首先通過其紋理座標(左圖)、點的顏色、背景顏色、點的半徑就可以繪製出一個點。(貼出來的代碼頁比較簡單了,延中心點做了個距離比較)
![實現網點——保持長寬比](https://image.gcores.com/82e3c5055a9f32d68708ee6705176461-1343-753.png)
實現網點——保持長寬比
![實現網點——重複格子](https://image.gcores.com/a6de91af449356f02c6fda6c243da641-1342-754.png)
實現網點——重複格子
*在之前的一步基礎上增加了長寬比修正和平鋪,標藍的是增加的內容。
![實現網點——旋轉45度](https://image.gcores.com/d28b17661fe61aa3a47cd48906c065d6-1342-755.png)
實現網點——旋轉45度
*整個紋理旋轉45度即可。到這一步已經實現了預期效果。
*後面還補充了在公式中如何做抗鋸齒計算的內容:
![實現網點——抗鋸齒](https://image.gcores.com/3e92750485a6c6c6b6a06c0ab4e4946f-1343-754.png)
實現網點——抗鋸齒
![實現網點——通過smoothstep做柔和邊緣](https://image.gcores.com/eb732758952b141f71a8aa5a1ee68c1f-1344-754.png)
實現網點——通過smoothstep做柔和邊緣
*這裡就是上次提到過的smoothstep的作用,閾值在某兩個值之間時會有一個從0到1的過渡。通過這個函數可以把邊緣渲染成半透漸變的。
*這裡還提到如何計算這個寬度的問題,就不展開了,有興趣可以去看PPT中引用的2012年的文章。
![實現填充線效果——把Y方向的UV賦值為0即可](https://image.gcores.com/1db98654aa24c37f00f91b1ef9beccbd-1344-754.png)
實現填充線效果——把Y方向的UV賦值為0即可
2)把生成的紋理“貼”到場景
![SDF UV格子生成(屏幕空間)](https://image.gcores.com/6d7b83df063182fe270bcde6691ffbb3-1279-721.png)
SDF UV格子生成(屏幕空間)
——對於類似Bloom和體積霧等效果,我們使用屏幕空間的紋理座標。(默認會是物體空間轉到世界空間的紋理座標。體積霧可以參考上圖的射燈範圍)
——我們沒有做任何複雜的事,演示的shader已經很接近我們實際在遊戲中渲染的屏幕空間的網點了。
*使用屏幕空間紋理座標主要是為了Bloom和體積霧的網點始終是斜45的。
![SDF UV格子生成——世界空間](https://image.gcores.com/e91427f6bd030403dec5a04b22f0f96d-1282-718.png)
SDF UV格子生成——世界空間
——屏幕空間的紋理座標對於不需要表現深度的特性,例如Bloom和體積霧有很好的效果,但在特定的多邊形表面上,我們希望網點能看起來像貼在表面上而不是朝向攝像機。因此我們也需要世界空間的紋理座標。
![SDF UV格子生成——世界空間](https://image.gcores.com/08d13a74ae4c950c238339e29c7eeb7c-1279-722.png)
SDF UV格子生成——世界空間
——為計算世界空間的紋理座標,我們使用GBuffer中的世界空間法線去計算多表形表面的主要軸(dominant axis),以便(將網點等)投影到對應軸的平面上。
——例如,如果Z值是世界空間法線中的最大值,我們就會選擇投影到xy平面上,這意味著我們會選擇縮放後的世界空間xy座標作為投影的紋理座標。
![SDF UV格子生成——世界空間](https://image.gcores.com/0fb508313ea22ea4f577e64b935d0d56-1276-718.png)
SDF UV格子生成——世界空間
——對於有穿插的平面,我們沒有采用三平面映射(triplanar mapping,是一種在空間中的紋理映射方案)。我們嘗試過使用這個渲染技術,但混合的結果過於明顯,所以我們選擇了簡單投射到一個平面上;由於我也沒有因為網點和線在平面交叉處被切斷的效果被抱怨,所以最終我們保留了這種簡化的方案。
![遊戲內的卡通風格著色——漸變](https://image.gcores.com/646c5c1040196c34d62d657ca041697f-1281-719.png)
遊戲內的卡通風格著色——漸變
——由於我們程序化地生成了這些網點和線,因此我們可以輕鬆地基於場景光照條件來調整它們的視覺表現。我們使用場景的光照強度(作為參數)來調整網點和填充線的尺寸。
——在這一頁的圖中,全局光照的網點在靠近光源處更大,在遠離光源處更小。
——通過這種簡單的技術我們可以用(網點尺寸的)漸變表現出光線延距離衰減的視覺效果,這也使整體的場景視覺更傾向於計算機生成的樣式(look more computer generated)。
*網點和填充線後續會作為光照和陰影在各處的基本樣式,但其計算方式這裡都介紹完了。
最後對比一下卡通風格的光影未開啟和開啟時的視覺效果:
![卡通著色關閉的視覺](https://image.gcores.com/6be17cb480597c8142f0b10950bdeee4-1343-754.png)
卡通著色關閉的視覺
![卡通角色開啟的視覺](https://image.gcores.com/0abc2103ac0f7ff9d09823bdb3f1a99a-1341-755.png)
卡通角色開啟的視覺
結語
GDC和SIGGRAPH大家略有了解可能會知道,後者的分享內容都是有行業創新性的論文級別的內容,所以乾貨很多;而GDC的分享很多其實沒那麼先鋒,有些甚至沒有展示其有技術含量的部分,很多分享都做成了開發總覽或遊戲優點展示。
相比起來Tango的這篇分享篇幅很長細節很多。裡面提到的很多特性雖然不是最先進的技術方案,但確實深入細節知無不言,能感受到他們在做分享時的自信與自豪。(在知道結局後就更對其中的一些內容表示遺憾,有一種轉喜為悲的感覺)
下週預計會更Tango這篇分享的下半部分,講講卡通風格的光源、各種陰影的處理與全局光照。
最後是資料鏈接:
GDC Vault的《3D-Toon-Rendering-in-Hi》
Triplanar Mapping 的一篇介紹