本文首發于: https://github.com/TsinStudio/GameDev
Paragon是Epic制作的寫實風格的MOBA遊戲,它作為虛幻引擎技術的試驗田,同時也內建了不少Epic在基于實體渲染方向上的技術探索。
本文主要從角色渲染技術入手分析Paragon的美術制作、技術實作。
PBS材質模型
UE4的材質渲染全面擁抱了基于實體的着色模型。
它的優點集中在以下幾方面:
- 實時的渲染性能,和傳統的Blinn-Phong模型的性能相似
- 線性的參數,支援任意層數的材質混合
- 魯棒的參數控制(比如粗糙度和金屬度),不容易制作出不合常理的材質表現
- 直覺的參數接口,比如粗糙度、金屬度、基礎色
PBS原理
PBS渲染技術是基于Microfacets(微表面元)理論實作的,它描述了任意表面微觀上都是由微小平面構成。

表面越粗糙,反射的光線越混亂,高光的反射拖尾效果越明顯;反之,表面越平滑,光線的反射方向越一緻,高光越銳利。
光線在粗糙表面上的分布隻能用統計的方法去近似,而這個分布狀況可以用**粗糙度(Roughness)**做參數來表示。
上圖是不同粗糙度(由低向高)的材質渲染表現。
微表面元的近似同樣遵守能量守恒定律:即反射的光能不超過入射的光能(除了自發光表面)。
對于表面上P點的輻射照度可以這樣計算:
上面式子中的F就是BRDF(雙向反射分布函數),它描述了入射光和出射光在粗糙度為a,表面法向量n(實際使用半角向量h)兩邊的分布情況。
當下流行的實時渲染引擎(Frostbite、CryEngine、Unreal等)都采用了CookTorrence模型作為BRDF。
CookTorrence BRDF包含兩部分,Diffuse和Specular項。
Diffuse BRDF
Diffuse項最簡單的近似方法可以認為光線被均勻地反射到上半球:
Specular BRDF
Specular項通常模拟鏡子、金屬、玻璃一類材質反射、折射後的表現。
最終的CookTorrence渲染方程就成了:
D項:
D項是法向分布函數,微表面上的鏡面反射能量在統計上分布在半角向量h的兩邊,UE4中使用GGX近似函數代替D項,其中α代表粗糙度。
G項:
F項:
UE4使用Schlick近似的菲涅爾項來代替F函數。
UE4中預設的PBS材質參數有BaseColor(基礎色)、Metallic(金屬性)、Roughness(粗糙度)等。
制作PBS材質
在PBR的制作流程中,我們同樣需要對PBR支援比較到位的工具鍊,Substance Painter的預設工作流即支援UE4的PBR流程,在PBR素材制作中較為推薦使用。
UE4中分層材質
由于PBS模型的各參數是線性的,因而可以通過線性混合或者蒙版方法實作任意層材質的疊加或分層。
在隻含有一個UV通道包含多層的模型,我們可以對各層材質的BaseColor、Metallic、Roughness等項分别進行線性混合或者加以蒙闆進行區分,來完成材質的渲染。
這在表現一些效果(比如衣服被水打濕、路面積水蒸發退散)會友善很多。
這個方法在布料渲染、材質細節比較多的物件渲染中使用較為頻繁。
布料渲染
- 布料屬于粗糙的材質,鏡面高光通常比較低,高光的分布會比較平滑。
- 正面地觀察布料,會發現鏡面高光很少。
- 布料表面的纖維會發生散射現象。
- 一些織物會有兩種鏡面高光的顔色。
UE4的布料材質渲染模型參考了《教團:1886》的布料渲染方法
是以我們要改造下原來的Microfacet模型,來适配布料的“MicroFiber(微纖維)模型”。
如上圖,D項将Beckmann分布中的tan項倒置得到cot項,得到InverseGauss分布函數,該分布函數滿足了布料觀察得到的能量分布形式。
在UE4的Shader代碼中,ClothShading正是受該方法啟發,改造GGX得到InvGGX函數作為布料渲染中的D項。
我們來看下UE4Shaders目錄下ShadingModels.usf的ClothShading代碼:
// Cloth - Asperity Scattering - Inverse Beckmann Layer
float3 F1 = F_Schlick( FuzzColor, VoH );
float D1 = D_InvGGX( LobeRoughness[1], NoH ); // UE4 ClothShading模型中增加了布料粗糙表面散射的項
// 這個方法是通過觀察布料得到的
float V1 = Vis_Cloth( NoV, NoL );
float3 Spec1 = D1 * V1 * F1;
// Generalized microfacet specular
float3 F2 = F_Schlick( GBuffer.SpecularColor, VoH );
float D2 = D_GGX( LobeRoughness[1], NoH ) * LobeEnergy[1];
float V2 = Vis_SmithJointApprox( LobeRoughness[1], NoV, NoL );
float3 Spec2 = D2 * V2 * F2;
float3 Spec = lerp(Spec2, Spec1, Cloth);
return Diff + Spec;
布料的材質制作
在《教團1886》布料材質的制作過程中大量使用了通過計算機視覺方法來掃描重建布料的表面。
次表面散射
次表面散射效果也是UE4引擎的标配,它的計算是在螢幕空間内完成的。 UE4次表面散射的實作參考了Jorge Jimenez在GPU Pro中撰寫的螢幕空間渲染的方法。
如上圖,光線照射到皮膚時,由于皮膚是可透光媒體,部分光在皮膚内會被吸收,部分會經過皮膚内部反射出去。
實體上,不同顔色的波長/頻率不同,在皮膚傳遞的過程中,衰減的幅度也不相同。如上圖。
在實際的計算中,我們假定反射的光線能量在入射點的分布成正态分布。
皮膚渲染
在UE4中,次表面散射的計算實作和上面提到的Jorge Jimenez提出的方法類似,通過在螢幕空間 卷積次表面的深度貼圖,依據光源的位置和散射半徑計算出散射顔色。
以下是Jorge Jimenez給出的樣例代碼,詳細實作可參考Github
float4 BlurPS(PassV2P input, uniform float2 step) : SV_TARGET {
// Gaussian weights for the six samples around the current pixel:
// -3 -2 -1 +1 +2 +3
float w[6] = { 0.006, 0.061, 0.242, 0.242, 0.061, 0.006 };
float o[6] = { -1.0, -0.6667, -0.3333, 0.3333, 0.6667, 1.0 };
// Fetch color and linear depth for current pixel:
float4 colorM = colorTex.Sample(PointSampler, input.texcoord);
float depthM = depthTex.Sample(PointSampler, input.texcoord);
// Accumulate center sample, multiplying it with its gaussian weight:
float4 colorBlurred = colorM;
colorBlurred.rgb *= 0.382;
// Calculate the step that we will use to fetch the surrounding pixels,
// where "step" is:
// step = sssStrength * gaussianWidth * pixelSize * dir
// The closer the pixel, the stronger the effect needs to be, hence
// the factor 1.0 / depthM.
float2 finalStep = colorM.a * step / depthM;
// Accumulate the other samples:
[unroll]
for (int i = 0; i < 6; i++) {
// Fetch color and depth for current sample:
float2 offset = input.texcoord + o[i] * finalStep;
float3 color = colorTex.SampleLevel(LinearSampler, offset, 0).rgb;
float depth = depthTex.SampleLevel(PointSampler, offset, 0);
// If the difference in depth is huge, we lerp color back to "colorM":
float s = min(0.0125 * correction * abs(depthM - depth), 1.0);
color = lerp(color, colorM.rgb, s);
// Accumulate:
colorBlurred.rgb += w[i] * color;
}
// The result will be alpha blended with current buffer by using specific
// RGB weights. For more details, I refer you to the GPU Pro chapter :)
return colorBlurred;
}
皮膚制作
Paragon的角色使用的材質是次表面輪廓模式(Subsurface Profile),皮膚的資源包含Diffuse、Roughness(存儲在Diffuse的Alpha通道)、 Specular、Normal、Scatter 4份貼圖。
貼圖 | 類型 | 描述 |
---|---|---|
| 皮膚漫反射貼圖 | 作為PBS材質的基礎顔色 |
| 粗糙度貼圖 | 粗糙度是單通道參數,可以合并在漫反射貼圖的Alpha通道中 |
| 皮膚高光貼圖 | 表現皮膚不同部位的鏡面高光反射率 |
| 法線貼圖 | 用來展示皮膚的紋路細節 |
| 散射貼圖 | 表示皮膚各個部分的散射強度,散射的顔色由次表面輪廓顔色參數控制(SubSurface Profile Color) |
頭發光照
Paragon中的頭發光線傳播模型是基于Marschner的單條頭發路徑模組化的。
- R: 直接反射路徑,主要反射高光。
- TRT: 折射-反射-折射路徑,光線通過該路徑穿入頭發絲,在發絲内表面反射,然後折射出去,産生次要反射高光。
- TT: 折射-折射路徑,代表光線在大量頭發中進行散射的過程。
Marschner’s 模型分解
式子中的p代表光線傳播路徑(R、TT、TRT),theta(i,r)是光線入射和出射角,phi是散射夾角(見截面傳播路徑圖)。
上面這個式子的散射項又可以分解為徑向散射分布函數M和截面散射函數N的乘積。
M是機率分布函數表示發絲徑向散射,這裡可以采用高斯分布函數來近似。
N是發絲截面散射函數,這裡R,TT,TRT各個路徑需要分别計算。
Epic的着色模型
UE4頭發着色的算法實作有兩套,一份是參考Weta方法實作的, 另一份是對Weta方法的近似(邏輯曲線拟合)實作。 實作的理論基礎參考“An Energy-Conserving Hair Reflectance Model”論文。 由于Weta的頭發着色計算複雜度較高,在計算截面散射函數N的過程中, Epic采用了較多的Curve Fitting方法近似計算。
徑向散射函數M
式子中的beta代表粗糙度。
Weta基于能量守恒的計算方法過于昂貴, Epic的實作直接采用了高斯函數來近似表示徑向反射的能量分布, 如下式:
截面散射函數Nr (直接反射路徑)
截面散射函數Ntt,trt
截面散射函數Ntrt(内部反射再次折射路徑)
截面散射函數NTT(兩次折射路徑)
Weta vs Epic
頭發素材制作
美術可控參數
貼圖 | 類型 | 說明 |
---|---|---|
| 漫反射貼圖 | 做為頭發絲的底色 |
| Alpha蒙版貼圖 | 使用蒙版測試(AlphaTest)方法來渲染頭發絲,剔除透明的部分 |
| 頭發ID貼圖 | 灰階圖,控制頭發網格對應的發絲類型 |
| 發根-發梢漸變貼圖 | |
| 深度貼圖 | 用于像素深度,讓頭發更有層次感,減少大面積的高光。 |
頭發各向異性高光表現實作(Anisotropic Specularity)
切線貼圖/FlowMap
Tangent可以用于各向異性反射的渲染。
flowmap可以讓部分頭發絲看起來更彎曲,它代表頭發在切線空間移動的方向,可以影響頭發的反射。
使用像素深度偏移
通俗的說,增加像素深度會讓像素遠離相機。通過賦予每簇頭發不同的深度,進而讓頭發看起來更有層次。
參考資料
- Real Shading in Unreal Engine 4
- PBR Theory
- Arbitrarily Layered Micro-Facet Surfaces
- Moving Frostbite to Physically Based Rendering
- Importance Sampling for Physically-Based Hair Fiber Models
- Light Scattering from Human Hair Fibers
- Photorealistic Character
- Physically Based Hair Shading in Unreal
- Screen-Space Perceptual Rendering of Human Skin
- Distribution-based BRDFs
- Crafting a Next-Gen Material Pipeline for The Order: 1886
- Unreal Engine 4給Paragon角色賦予生命