目錄
- 四、毛發渲染
- 4.1 毛發的構造及渲染技術
- 4.1.1 毛發的構造
- 4.1.2 Marschner毛發渲染模型
- 4.1.3 毛發的間接光照
- 4.2 毛發的底層實作
- 4.3 毛發的材質解析
- 4.3.1 頭發(M_Hair)
- 4.3.2 頭發模糊(M_HairBlur)
- 4.3.3 眉毛和睫毛(M_Lashes、M_Brows)
- 4.3.4 絨毛(M_Fuzz)
- 4.1 毛發的構造及渲染技術
- 五、其它部位
- 5.1 舌頭
- 5.2 牙齒
- 5.3 衣服
- 5.4 燈光
- 六、總結和展望
- 6.1 渲染技術總結
- 6.2 能達到實時逼真的原因
- 6.3 不足
- 本系列文章其它部分
- 特别說明
- 參考文獻
毛發渲染一直是實時圖形學的難題,因為其光照複雜,數量衆多,實體效果不好抽象等。在早期,隻能通過若幹面片代替,後來随着硬體及渲染技術的提升,慢慢發展出了經驗模型的Kajiya-Kay和基于實體的Marschner毛發渲染模型。Mike采用的是Marschner毛發渲染模型。
真實世界的毛發主要由纖維構造,也可分成多層結構,有中心的發髓(Medulla)、内部的皮質(Cortex)和表皮的角質層(Cuticle)構成。(下圖)
毛發剖面圖
其中角質層放大後,可見坑坑窪窪的微表面(下圖),它是造成高光和反射的媒體。此外,光線照射毛發表皮之後,還會發生透射和次反射。
毛發放大數千倍後的微表面
毛發微表面的坑窪具有較統一的指向性,由根部指向尾部,在圖形學可用切線及各向異性屬性來衡量這一現象。
簡化後的毛發模型
Marschner是基于實體的毛發渲染模型,是Stephen R. Marschner等人共同發表的論文《Light Scattering from Human Hair Fibers》内的方法。
該方法研究分析了真實世界的毛發構成及特性,抽象出如下圖所示的光照模型:
毛發對應的橫截面光照模型圖:
該模型将光照在毛發的作用分成3部位:
- 反射(R):表面的反射,産生主高光,受毛發切線和各向異性影響。
- 傳輸-傳輸(TT):傳輸-傳輸路線,光線照射并穿透毛囊,然後從另一邊照射出去。這是光線在一定發量中的散射過程。
- 傳輸-反射-傳輸(TRT):光線進入毛囊,從内表面邊界反射出來,然後再照射出來。産生的是次高光。
基于以上光照模型,論文又進一步根據幾何光學分析了光線在某一個光路上的行為,并把這個行為具體的分成了兩類,即縱向散射(longitudinal scattering)和方位角散射(azimuthal scattering)。
差角度計算如下:
\(\theta_d = (\theta_r - \theta_i) /2\)
\(\phi = (\phi_r - \phi_i)\)
半角度計算如下:
\(\theta_h = (\theta_r - \theta_i) /2\)
\(\phi_h = (\phi_r - \phi_i) /2\)
\(R\),\(TT\),\(TRT\)三種散射縱向散射函數\(M\)都滿足\(\theta_h\)符合高斯分布。公式如下:
\(M_R = g(\beta_R, \alpha_R, \theta_h)\)
\(M_{TT} = g(\beta_{TT}, \alpha_{TT}, \theta_h)\)
\(M_{TRT} = g(\beta_{TRT}, \alpha_{TRT}, \theta_h)\)
\(R\)和\(TRT\)散射方位角散射函數\(N\)分别簡化為$\cos^2 \phi \(,\)TT\(散射方位角散射函數\)N\(滿足\)\phi$ 符合高斯分布。公式如下:
\(N_R= \cos^2\phi\)
\(N_{TT} = g(\gamma_{TT}, 0.0, \pi - \phi)\)
\(N_{TRT} = \cos^2\phi\)
最終散射公式如下:
\(S = S_R + S_{TT} + S_{TRT}\)
\(S_P = M_P \cdot N_P, \ \ for \ P = R, TT, TRT\)
利用以上渲染技術可以渲染出Mike的直接光照部分:
不同燈光角度下的Mike毛發渲染效果
毛發除了上一小節描述的直接光照外,還需要增加非直接光照,以模拟環境光或漫反射。
出于性能的考慮,UE4預設給頭發加了一個類似于diffuse的fake scattering (非實體真實的散射)的散射的間接光照。渲染結果如下圖:
增加了非實體真實的間接光照的效果
UE4采用的是Dual Scattering(雙向散射)的多散射近似光照模型,論文出處:Dual Scattering Approximation for Fast Multiple Scattering in Hair。和離線光線跟蹤毛發間接采樣方法相比,雙向散射會節省大量時間,品質幾乎接近。
雙向散射主要用于估計毛發的多散射函數,這個函數有兩個部分組成:
- 全局散射函數。全局散射函數用于計算由于光穿過周邊的毛發對目前毛發的散射貢獻,
- 局部散射函數。局部散射用于計算由于光多次在周邊頭發折射對目前毛發的散射貢獻。
這兩種貢獻的總和稱為雙向多散射。這種計算模型不受光源數量和類型的限制。
如上圖所示,可獲得如下的抽象公式:
\[\Psi(x,\omega_d,\omega_i) = \Psi^G(x,\omega_d,\omega_i)(1 + \Psi^L(x,\omega_d,\omega_i))
\]
毛發光照(包含直接光照和間接光照)實作的僞代碼:
更具體的推導和實作過程請參看參考論文,也可參考這篇技術文章:Real-Time Hair Simulation and Rendering。
UE實作毛發的shader代碼主要在:
- \Engine\Shaders\Private\ShadingModels.ush。
Light Scattering from Human Hair Fibers論文給出了下面一組測量的标準值,後面的源碼中大量涉及這些常量或計算公式:
下面着手分析毛發的光照着色源碼:
// Approximation to HairShadingRef using concepts from the following papers:
// [Marschner et al. 2003, "Light Scattering from Human Hair Fibers"]
// [Pekelis et al. 2015, "A Data-Driven Light Scattering Model for Hair"]
float3 HairShading( FGBufferData GBuffer, float3 L, float3 V, half3 N, float Shadow, float Backlit, float Area, uint2 Random )
{
// to prevent NaN with decals
// OR-18489 HERO: IGGY: RMB on E ability causes blinding hair effect
// OR-17578 HERO: HAMMER: E causes blinding light on heroes with hair
float ClampedRoughness = clamp(GBuffer.Roughness, 1/255.0f, 1.0f);
//const float3 DiffuseN = OctahedronToUnitVector( GBuffer.CustomData.xy * 2 - 1 );
//const float Backlit = GBuffer.CustomData.z;
#if HAIR_REFERENCE
// todo: ClampedRoughness is missing for this code path
float3 S = HairShadingRef( GBuffer, L, V, N, Random );
//float3 S = HairShadingMarschner( GBuffer, L, V, N );
#else
// N is the vector parallel to hair pointing toward root
const float VoL = dot(V,L);
const float SinThetaL = dot(N,L);
const float SinThetaV = dot(N,V);
float CosThetaD = cos( 0.5 * abs( asinFast( SinThetaV ) - asinFast( SinThetaL ) ) );
//CosThetaD = abs( CosThetaD ) < 0.01 ? 0.01 : CosThetaD;
const float3 Lp = L - SinThetaL * N;
const float3 Vp = V - SinThetaV * N;
const float CosPhi = dot(Lp,Vp) * rsqrt( dot(Lp,Lp) * dot(Vp,Vp) + 1e-4 );
const float CosHalfPhi = sqrt( saturate( 0.5 + 0.5 * CosPhi ) );
//const float Phi = acosFast( CosPhi );
// 下面很多初始化的值都是基于上面給出的表格獲得
float n = 1.55; // 毛發的折射率
//float n_prime = sqrt( n*n - 1 + Pow2( CosThetaD ) ) / CosThetaD;
float n_prime = 1.19 / CosThetaD + 0.36 * CosThetaD;
// 對應R、TT、TRT的longitudinal shift
float Shift = 0.035;
float Alpha[] =
{
-Shift * 2,
Shift,
Shift * 4,
};
// 對應R、TT、TRT的longitudinal width
float B[] =
{
Area + Pow2( ClampedRoughness ),
Area + Pow2( ClampedRoughness ) / 2,
Area + Pow2( ClampedRoughness ) * 2,
};
float3 S = 0;
// 下面各分量中的Mp是縱向散射函數,Np是方位角散射函數,Fp是菲涅爾函數,Tp是吸收函數
// 反射(R)分量
if(1)
{
const float sa = sin( Alpha[0] );
const float ca = cos( Alpha[0] );
float Shift = 2*sa* ( ca * CosHalfPhi * sqrt( 1 - SinThetaV * SinThetaV ) + sa * SinThetaV );
float Mp = Hair_g( B[0] * sqrt(2.0) * CosHalfPhi, SinThetaL + SinThetaV - Shift );
float Np = 0.25 * CosHalfPhi;
float Fp = Hair_F( sqrt( saturate( 0.5 + 0.5 * VoL ) ) );
S += Mp * Np * Fp * ( GBuffer.Specular * 2 ) * lerp( 1, Backlit, saturate(-VoL) );
}
// 透射(TT)分量
if(1)
{
float Mp = Hair_g( B[1], SinThetaL + SinThetaV - Alpha[1] );
float a = 1 / n_prime;
//float h = CosHalfPhi * rsqrt( 1 + a*a - 2*a * sqrt( 0.5 - 0.5 * CosPhi ) );
//float h = CosHalfPhi * ( ( 1 - Pow2( CosHalfPhi ) ) * a + 1 );
float h = CosHalfPhi * ( 1 + a * ( 0.6 - 0.8 * CosPhi ) );
//float h = 0.4;
//float yi = asinFast(h);
//float yt = asinFast(h / n_prime);
float f = Hair_F( CosThetaD * sqrt( saturate( 1 - h*h ) ) );
float Fp = Pow2(1 - f);
//float3 Tp = pow( GBuffer.BaseColor, 0.5 * ( 1 + cos(2*yt) ) / CosThetaD );
//float3 Tp = pow( GBuffer.BaseColor, 0.5 * cos(yt) / CosThetaD );
float3 Tp = pow( GBuffer.BaseColor, 0.5 * sqrt( 1 - Pow2(h * a) ) / CosThetaD );
//float t = asin( 1 / n_prime );
//float d = ( sqrt(2) - t ) / ( 1 - t );
//float s = -0.5 * PI * (1 - 1 / n_prime) * log( 2*d - 1 - 2 * sqrt( d * (d - 1) ) );
//float s = 0.35;
//float Np = exp( (Phi - PI) / s ) / ( s * Pow2( 1 + exp( (Phi - PI) / s ) ) );
//float Np = 0.71 * exp( -1.65 * Pow2(Phi - PI) );
float Np = exp( -3.65 * CosPhi - 3.98 );
// Backlit是背光度,由材質提供。
S += Mp * Np * Fp * Tp * Backlit;
}
// 次反射(TRT)分量
if(1)
{
float Mp = Hair_g( B[2], SinThetaL + SinThetaV - Alpha[2] );
//float h = 0.75;
float f = Hair_F( CosThetaD * 0.5 );
float Fp = Pow2(1 - f) * f;
//float3 Tp = pow( GBuffer.BaseColor, 1.6 / CosThetaD );
float3 Tp = pow( GBuffer.BaseColor, 0.8 / CosThetaD );
//float s = 0.15;
//float Np = 0.75 * exp( Phi / s ) / ( s * Pow2( 1 + exp( Phi / s ) ) );
float Np = exp( 17 * CosPhi - 16.78 );
S += Mp * Np * Fp * Tp;
}
#endif
if(1)
{
// Use soft Kajiya Kay diffuse attenuation
float KajiyaDiffuse = 1 - abs( dot(N,L) );
float3 FakeNormal = normalize( V - N * dot(V,N) );
//N = normalize( DiffuseN + FakeNormal * 2 );
N = FakeNormal;
// Hack approximation for multiple scattering.
float Wrap = 1;
float NoL = saturate( ( dot(N, L) + Wrap ) / Square( 1 + Wrap ) );
float DiffuseScatter = (1 / PI) * lerp( NoL, KajiyaDiffuse, 0.33 ) * GBuffer.Metallic;
float Luma = Luminance( GBuffer.BaseColor );
float3 ScatterTint = pow( GBuffer.BaseColor / Luma, 1 - Shadow );
S += sqrt( GBuffer.BaseColor ) * DiffuseScatter * ScatterTint;
}
S = -min(-S, 0.0);
return S;
}
從上面可知,先算出R、TT、TRT的各個分量的函數系數,将它們的光照貢獻量相加,最後采用Kajiya Kay漫反射模型和多散射近似法模拟漫反射部分。
本節将剖析Mike用到的毛發材質,它們的材質有個共同點:都是用了Hair的着色模型(下圖)。
下圖是頭發(M_Hair)的總覽圖。
-
基礎色(Base Color)
首先是下圖模拟了頭發中心偏亮、邊緣漸變變暗的效果。(下圖)
模拟的頭發漸變效果如下圖。 下圖所示的Scalp Variation部分是提取靠近頭皮(即頭發根部)的UV紋理,然後去采樣噪點紋理,生成一張有随機變化的遮罩圖: Hair Albedo部分主要是模拟了發根到發偉的顔色漸變,其中發根處利用顔色遮罩
更好地将發根顔色融入頭皮。 顔色混合最後階段,将加入邊沿色和環境遮擋色,使得頭發顔色最終呈現出逼真的效果。 需要注意的是,頭發的頂點色大部分是黃色,小部分是白色(下圖)。hair_color_mask
-
散射(Scatter)
對于Hair着色模型,才有此屬性,以模拟頭發的漫反射顔色及強度。實作方法就是将頭發邊緣色乘以一個縮放因子。(下圖)
- 粗糙度(Roughness) 粗糙度的計算也不複雜,将基礎色涉及的Scalp Variation部分輸出的結果作為線性插值Alpha,在最大和最小值之間過渡,再經過一個縮放因子,即可得到最終結果。
-
切線(tangent)
利用基礎色涉及的Scalp Variation部分的結果和采樣噪點圖,生成紋理V方向上有随機變化紋路的切線資料,以模拟頭發的微平面。
-
背光度(Backlit)
背光度主要是控制頭發着色過程透射(TT)部分(參見[4.2 毛發的底層實作](#4.2 毛發的底層實作))的縮放。
由UV集合2控制的貼圖經由反向和陰影縮放,即可得到資料。
此外,還有頂點坐标偏移、AO等資料,這些将忽略其分析,有興趣的讀者可自行檢視材質。
頭發模糊材質主要是在頭發根部加入模糊效果,并且添加像素深度偏移,使得頭發更好地“植入”頭皮,過渡更自然。(下圖)
其實作的核心是采樣像素周邊16個場景顔色的點,做平均計算,模拟高斯模糊的結果。(下圖)
眉毛和睫毛的材質跟頭發的材質非常接近,可參看上一小節。
絨毛是很容易被忽略的渲染細節,隻有在鏡頭很近時才能發現。但實際上Mike的整個身體被絨毛所包圍,這可以提升人物皮膚的細節和渲染真實度:
黃色區域所示便是絨毛,可見絨毛在Mike身上遍地開花
來一張近處特寫:
它的材質采用透明混合、無光照着色模式。
顔色計算跟之前的毛發有點類似,先對周邊場景顔色進行模糊,經過明暗度調整、邊緣亮度調整,獲得最終顔色。此外,也采用了位置偏移。(下圖)
除了皮膚、眼睛、頭發等重要部位的渲染,Mike的其它部分的渲染也同樣注重細節。
舌頭也采用了次表面散射着色模型。
對于顔色,在一張漫反射和亮度反射圖中做插值,經過飽和度調整和顔色亮度調整,獲得最終顔色和自發光顔色。
對于法線,在一張基礎貼圖之上,混合了微觀細節法線。
對于牙齒,為了反映其類似玉石的散射效果(下圖),也同樣采用了次表面散射着色模型。
它的材質總覽圖如下:
對于顔色,在牙齒基礎色和模糊後的柔色之間插值混合,結果若幹次亮度、飽和度及色調(TeethTint)變換,得到中間色,再加入菲涅爾效應的邊緣色,獲得最終色。
對于高光,利用法線和視線向量求得一個與視角相關的因子,以便調整高光度,使得與反射向量越接近的像素高光越強。
對于粗糙度和次表面散射強度,利用AO遮罩圖經過數次調整後獲得。
對于法線,跟舌頭類似,在一張基礎貼圖之上,混合了微觀細節法線。
衣服啟用了
Masked
混合模式和
Cloth
着色模型,采用了多層材質,背景層是衣服本身的材質,第二層是紐扣材質(下圖)。
對于衣服本身的材質,顔色利用一張灰階圖乘以指定色,再經過一系列調整獲得,這種變色也是遊戲領域常采用的變色方案。優點是可控制材質的明暗度和顔色,缺點是隻能有單一的色相,不能有多種色相。衣服的法線也是采用兩層貼圖混合而成。此外,還設定了次表面散射顔色(SubsurfaceColor)、清漆(ClearCoat)、AO等屬性。
對于紐扣材質,非常簡單,此處忽略。
首先分析場景的布燈。人物左前方斜45度角是主燈,提供了攝影界常用的倫勃朗式的光照和陰影;角色正前方提供了一個補光燈,降低面部的陰影濃度;角色右邊有一個側燈,提供臉部和身體的側面輪廓,提高質感;角色後方有兩個背景燈,用以照亮背景和頭發,使頭發更具層次感,也能展現頭發和耳朵的次表面散射和透射效果。(下圖)
其中,主燈由藍圖動态建立而成,類似若幹個聚光燈組成的燈陣,模拟很大的柔光燈,提供角色的主要光源以及眼神光。(下圖)
上:由若幹盞聚光燈組成的燈陣;下:眼神高光回報的燈陣形狀。
此外,場景提供了體積霧,并且配以一個點光源,模拟自然過渡的背景效果。(下圖)
本系列文章緊緊圍繞着Unreal的官方數字人類《Meet Mike》的角色進行渲染技術的剖析,它們涉及的技術點如下:
- 皮膚
- 基于實體的渲染(PBR)
- 雙向反射分布函數(BRDF)
- 次表面散射(SSS)
- 高斯函數
- 偶極子(Dipole)
- 多偶極子(Multi Dipole)
- 多個高斯函數模拟皮膚次表面散射
- 雙向次散射反射模型(BSSRDF)
- 可分離的次表面散射(SSSS)
- 奇異值分解(SVD)
- 紋理空間模糊
- 螢幕空間模糊
- 預卷積核權重
- 眼睛
- 基于實體的反射
- 鏡面反射
- 折射
- 自反射(預烘焙)
- 參合多媒體渲染(participating media rendering)
- 其它細節:
- 濕潤度(法線擾動)
- 血色
- 接觸陰影
- 淚腺體
- 遮蔽模糊體
- 眼角混合物
- 基于實體的反射
- 頭發
- Marschner毛發渲染
- 反射(R)
- 透射(TT)
- 次反射(TRT)
- 雙層UV
- 高精度模型
- XGen生成
- Marschner毛發渲染
能達到如此逼真的渲染效果,總結起來,主要有以下原因:
- 基于實體的光照模型
- PBR
- BSSRDF
- SSSS
- 基于真人掃描的模型
- 超高精度模型(70w頂點,60w三角面)
- 超高分辨率貼圖(4K+)
- 功能衆多的貼圖
- 基礎色、高光、粗糙、次表面散射、清漆、法線、AO等貼圖
- 掃描直出、轉置、二次制作
- 衆多細節
- 皮膚細節:毛孔、雀斑、血絲、絨毛、雙層高光、皺紋......
- 眼球細節:反射、折射、自陰影、側面光、材質過渡、法線擾動......
- 基于實體和攝影藝術的場景燈光
- 聚光燈陣
- 補光燈
- 側燈
- 背面輪廓燈
- 背景過渡燈
- 高度定制的材質
- 皮膚材質
- 眼睛材質
- 毛發材質
- 衣服材質
就Mike而言,雖然渲染效果已經逼近真實,但也存在一些問題:
- 毛發沒有實體效果。
- 材質非所有場景的燈光都能适應。在某些場景,渲染出來的角色效果存在失真現象。
- SSSS渲染出現的皮膚條紋。
- 驅動效果不夠流暢(從釋出的視訊得出結論)。
當然,在後續的Siren項目中,以上有些問題得到解決或緩解。
相信在強大的UE官方團隊面前,虛拟數字人探索的腳步會一直向前邁進,為實時渲染領域拿下一個又一個裡程碑。
本系列文章完!
- 剖析Unreal Engine超真實人類的渲染技術Part 1 - 概述和皮膚渲染
- 剖析Unreal Engine超真實人類的渲染技術Part 2 - 眼球渲染
- 感謝參考文獻的所有作者們!
- 未經允許,禁止轉載!
- Next-Generation-Character-Rendering (ACM Transactions on Graphics, Vol. 29(5), SIGGRAPH Asia 2010)
- Separable Subsurface Scattering
- Real-Time Realistic Skin Translucency
- Skin Microstructure Deformation with Displacement Map Convolution
- 《由淺入深學習PBR的原理和實作》
- 數字人類
- 照片級角色
- 角色渲染技術——毛發及其他
- Light Scattering from Human Hair Fibers
- Dual Scattering Approximation for Fast Multiple Scattering in Hair
- A Data-Driven Light Scattering Model for Hair
- GPU Gem 2: Chapter 23. Hair Animation and Rendering in the Nalu Demo
- Real-Time Hair Simulation and Rendering
- Digital Mike頭發制作及渲染的深度揭秘
- NVIDIA官方展示HairWorks“海飛絲”(1.1 by Tarkan Sarim)
- 細緻到毛孔 ! 深度揭秘超真實皮膚的實時渲染技術(上篇)
- 細緻到毛孔 ! 深度揭秘超真實皮膚的實時渲染技術(下篇)