我們團隊在過去一年多主要關注卡通渲染效果,本次介紹我們對崩壞3這款遊戲角色渲染的研究和實踐。
先看看我們的效果(截圖造成精度有所下降)。
這次是針對崩壞3角色效果的逆向復現,實現基本的角色效果,不討論角色的特殊效果,比如消失效果這類的特效,主要是還原基本的效果,包括面部表情變化的實現,描邊效果的實現,還有卡通顏色色塊渲染的實現。下面分成三個部分來說明。
我們先看看崩壞3的一張貼圖,下面這張圖:
再看看下面這張圖,注意左邊圖的網格線:
是不是看出了什麼?其實很簡單,就是通過再面部創建出左眼和右眼還有嘴部三個模型,再將眼睛和嘴型的貼圖貼上去,在一張貼圖上繪製出不同的眼睛和嘴型,通過uv縮放和偏移來對應,就能做出不同的表情。
這個shader的實現相對簡單,只要在渲染順序,深度比較,和顏色混合上稍加注意就行,這個可以有多種方案。我選擇是:
1.在渲染面部之後去渲染眼睛和嘴巴模型。
2.加一個偏移Offset -2, 2進行深度比較。
3.混合使用Blend SrcAlpha OneMinusSrcAlpha,就是默認的就行了。
崩壞使用和罪惡裝備同樣的方式, 結合了shell method和z-bias method,並引入了物體的頂點色來控制描邊細節,同時也是為了保證描邊粗細不會隨著攝像機視距發生變化。shell method是在模型的Shader中添加一個額外的pass繪製backface, 在計算頂點的投影變換過程時,將頂點的坐標進行一個 Z方向的修正,讓這個部分超出模型一部分以達到繪製邊緣的效果,同時使用頂點的顏色來控制這個Z方向的位移的強度,這樣就完成了一個基本描邊效果。
這樣處理會遇到幾個問題,第一,是在什麼空間做的頂點偏移。第二,頂點沿著什麼方向偏移。第三,當角色和攝像機的距離出現遠近變化,描邊粗細變化怎麼控制。
崩壞3選擇在攝像機空間中讓頂點沿著切線方向偏移,為啥不是法線方向呢?肯定是效果不一樣,這不是廢話嘛。效果的差異就在於,沿著法線方向擠出,產生的描邊粗細比較一致,基本不會變化,看起來比較死板。沿著切線方向擠出,產生的描邊會有粗細變化,隨著視角變化,有的地方會消失,有的地方會變粗。再加上切線不像法線那麼穩定,切線和UV的展開有關係。這樣的效果就會有更多的變化,所以在製作模型時,uv的展開也有一定的要求。
再來說說角色和攝像機的距離關係,當距離遠時,頂點擠出0.1,當距離近時,頂點也擠出0.1,這樣在畫面看起來角色的描邊就比離的近時粗了很多。崩壞對這個擠出距離進行了修正,通過unity_CameraProjection這個矩陣的第二行第二個元素,對攝像機空間中頂點坐標的z值,和這矩陣的商,來控制頂點的擠出距離。
公式為:Offset= viewPos.z / unity_CameraProjection[1].y;
注意下圖,角色和攝像機角度變化,描邊的變化。
現在到了我們的重點,蹦壞3的風格化渲染。下面的遊戲截圖,可以看到著色是以單色塊為主,有明顯的交界線,交界線有鋸齒,不是平滑曲線。高光隨觀察角度變化而變化,高亮色塊和燈光方向相關。我們再來看看蹦壞3用到的資源,兩張貼圖,頂點顏色。
我們先看看用到的資源的特點,MainTex這是一張基本顏色貼圖,主要使用了RGB三個通道,沒有用到alpha通道。記錄的是角色的基本顏色信息。
LightMapTex這是一張比較複雜貼圖,主要使用了RGB三個通道,沒有用到alpha通道。記錄了豐富的信息,每個顏色通道有不同的用處。下面我們分別看看每個通道的特點和作用。
LightMapTex的G通道,基本可以理解為是一個AO效果。基本的結構陰影關係。
在G通道還對面部做了特殊的繪製,把頭髮的陰影繪製上去了,如下圖。還有臉和鼻子的高亮區域。
LightMapTex的B通道,基本可以理解為控制高光範圍,高光出現的地方。頭髮上的高光,胸部,盔甲,鞋子的部分都是有高光的。這些地方可以繪製的亮一些,沒有高光的地方繪製的很暗。高光可以繪製出形狀和過度,例如胸部和頭髮的高光。在這個通道面部是黑的。
LightMapTex的R通道,基本可以理解為控制高光的強度。不同材質高光強度不同。這個通道可以用來微調材質效果,也可以做些過度。比較身體上相鄰區域,不同材質的地方,高光的強度有較大的差異。體現細節的地方。在這個通道面部是黑的,也就意味著面部不會出現高光。
頂點顏色R通道,基本可以理解成LightMapTex的G通道的補充。下圖左邊是頂點顏色R通道,右邊是LightMap的G通道,基本上頂點的R通道是強化大的暗面。但是臉部保持白色。
頂點顏色的G和B通道用處暫時不明。
頂點顏色A通道,基本可以理解成控制描邊的。下圖的頭髮發梢,給的基本是黑色,可以看到左邊是使用了A通道的,發梢描邊會更細,更自然,中間沒有用A通道控制,發梢描邊沒什麼粗細變化。右圖就是A通道描邊的展示,描黑的地方都是描邊不擠出的地方。
通過上面對資源的介紹和分析,我們再來看看角色shader的計算。
第一步是對角色的顏色分塊,分成幾個部分三個顏色部分,基礎顏色和兩層陰影顏色。
這三個部分的顏色:
基礎顏色是MainTex貼圖的顏色;
第一層陰影顏色是MainTex* _FirstShadowMultColor
第二層陰影顏色是MainTex* _SecondShadowMultColor
分塊的算法:
dotNL =dot(worldNormal,worldLightDir) * 0.5 + 0.5;tmpvar =vertexColor.x * LightMapTex*.y;tmpvar = ( tmpvar+ dotNL) * 0.5;if(tmpvar < _SecondShadow){ Color = MainTex * _SecondShadowMultColor;}else{ If(tmpvar < _LightArea) { Color = MainTex * _FirstShadowMultColor; }esle { Color = MainTex; }}可以看出顏色分塊和燈光方向相關,燈光方向的dot的值(-1,1)重映射到了(0,1),頂點顏色的R通道和LightMap的G通道相乘,最後兩者相加後乘以0.5。去和_SecondShadow值進行比較,小於_SecondShadow的是第二層陰影,大於等於_SecondShadow的再和_LightArea進行比較,小於_LightArea的是第一層陰影。大於等於_LightArea的是亮面,也就是MainTex基礎顏色。這種比較方式肯定會帶來鋸齒,很硬的過度。從遊戲畫面也能看到鋸齒線條。
第二步計算高光顏色。
先算出視角向量viewDir,注意是在世界空間中的視角向量:
viewDir = normalize(_WorldSpaceCameraPos - worldPos);
再算出:
halfDir = normalize(viewDir + _WorldSpaceLightPos0.xyz);
然後:
specularThreshold = pow(max(dot(worldNormal,halfDir),0.0),_Shininess);if(specularThreshold >= (1.0 -lightMap.z)){ specularColor= _LightSpecColor * _SpecMulti * lightMap.r;}else{ specularColor= float3(0.0,0.0,0.0);}在這高光的計算中,用到了燈光方向,攝像機方向,求出了折半方向。這是指向視圖和光線向量中間的方向。再計算specularThreshold高光的範圍,用normal和折半方向的dot值,映射到(0,1),和_Shininess乘方。最後specularThreshold和1減去Lightmap的B通道進行比較。大於等於的地方有高光,其餘地方就為零。高光顏色用_LightSpecColor高光顏色和_SpecMulti高光強度相乘得到。高光顏色再乘以LightMap的R通道,高光的最終顏色就得到了。因此LightMap的B通道可以控制高光範圍,值越暗高光範圍越小,值越大高光範圍約大。LightMap的R通道可以分區域控制高光的強度,值越大高光越亮。
第三步顏色合成。
outColor = Color + specularColor;
這步很簡單,此處不做過多說明。
下面的都是用來調節的一些屬性變量:
_SecondShadow 調節第二暗部的範圍
_LightArea調節第一暗部的範圍
_SecondShadowMultColor第二暗部顏色調整
_FirstShadowMultColor 第一暗部顏色調整
_SpecMulti 高光強度
_Shininess 高光範圍
_LightSpecColor 高光顏色
最後我們使用完成的shader進行的嘗試
我們製作了一個角色,也做了相應的資源,測試效果。
今天的分享就先到這兒咯,我們下期分享再見~