真實皮膚渲染簡述
1、PBR
PBR(Physically Based Rendering) 基於物理的渲染,目的就是為了渲染效果更像真實世界中物體呈現的樣子。一個例子就是金屬質地物體的渲染。
PBR 的關鍵,在於兩點:微表面模型和能量守恆。微表面模型假設模型表面是粗糙的,粗糙程度影響了光照在該表面反射的強弱。在實際的計算中我們假設一個粗糙度參數,該參數代表微表面法向量分布的方差或標准差。能量守恆的意義在於限制反射光線和折射光線的強度,使其和保持不變,否則違背真實物理定律。這兩點會在后面的計算中體現出來。
反射率方程
下面是一個物理反射率方程(The Reflectance Equation)
\begin{align}\ L_o(p,\omega_o) = \int\limits_{\Omega} f_r(p,\omega_i,\omega_o) L_i(p,\omega_i) n \cdot \omega_i d\omega_i \end{align}
我們知道在渲染方程中L代表通過某個無限小的立體角ωi在某個點上的輻射率,而立體角可以視作是入射方向向量ωi。注意我們利用光線和平面間的入射角的余弦值cosθ來計算能量,亦即從輻射率公式L轉化至反射率公式時的n⋅ωi。用ωo表示觀察方向,也就是出射方向,反射率公式計算了點p在ωo方向上被反射出來的輻射率Lo(p,ωo)的總和。或者換句話說:Lo表示了從ωo方向上觀察,光線投射到點p上反射出來的輻照度。
上面這段來自 learnopengl 中文網站,看不懂也正常。需要理解的就是 Li 代表的是在單位面積(或者說單位點 p)上面的輻射率(可以簡單理解爲所有光線的總能量或者說總強度),這里是不區分方向的,所以要算上 n 和 \(\omega_i\) 的點乘來計算真正反射的能量。
那么在光照計算時,研究者基於真實的反射率方程,提出了工程實用的渲染方程(Render Equation). 具體渲染方程長什么樣子后面才能講到,現在我們先來分析反射率方程中剩下的一項,Fr().
BRDF
雙向反射分布函數(Bidirectional Reflective Distribution Function), 與表面材質有關,作為輻射率的系數或者說權重。接收的參數為光照方向,觀察方向(出射方向),表面法線以及微表面粗糙度。
Cook-Torrance BRDF:
\begin{align}\ f_r = k_d f_{lambert} + k_s f_{cook-torrance} \end{align}
Kd 是折射光的比例,Ks 是反射光的比例。lambert 是一種漫反射的模擬,cook-torrance 是高光反射的模擬,公式如下:
\begin{align}\ f_{cook-torrance} = \frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)} \end{align}
DFG 分別代表三種函數- D: 正態分布函數,根據微表面假設,表面法線分布是一種正態分布,表示微表面法線與平均法線的偏離的概率
 - F: Fresnel 菲涅爾方程,就是計算反射光和折射光的比例
 - G: 物理函數,在皮膚渲染中可以忽略,詳情看參考資料
 
Distribution
這個函數接收參數 n, 中間向量 h 和粗糙度 a.
\begin{align}\ NDF_{GGX TR}(n, h, \alpha) = \frac{\alpha^2}{\pi((n \cdot h)^2 (\alpha^2 - 1) + 1)^2} \end{align}
用代碼表示是:
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
    float a      = roughness*roughness;
    float a2     = a*a;
    float NdotH  = max(dot(N, H), 0.0);
    float NdotH2 = NdotH*NdotH;
    float nom   = a2;
    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;
    return nom / denom;
}
 
        Fresnel
Fresnel-Schlick近似法求得近似解:
\begin{align}\ F_{Schlick}(n, v, F_0) = F_0 + (1 - F_0) ( 1 - (n \cdot v))^5 \end{align}
F0 代表這個平面的基礎反射率,不同的材質不一樣。
總結
總之,最終的反射率方程如下:
\begin{align}\ L_o(p,\omega_o) = \int\limits_{\Omega} (k_d\frac{c}{\pi} + k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)})L_i(p,\omega_i) n \cdot \omega_i d\omega_i \end{align}
又,此處的 Fresnel 項 F 已經代表了平面的反射率,所以不再需要 \(k_s\) 了,直接去掉。
2、皮膚渲染光照計算(BRDF)
首先,皮膚渲染還是屬於 PBR 的一種,所以總體的渲染思路還是一致,仍然是按照 BRDF 來計算。
Fresnel
在內部的幾個函數中,首先考慮 Fresnel 項,在皮膚渲染中 F0 取 0.028, 而且在針對皮膚這樣的粗糙材質時使用 H 向量,而不是上文函數中的 n dot v(也就是法線與觀察向量的點乘)。
   float fresnelReflectance( float3 H, float3 V, float F0 ) {
     float base = 1.0 - dot( V, H );
     float exponential = pow( base, 5.0 );
     return exponential + F0 * ( 1.0 - exponential );
   }
 
        Beckmann Distribution
這里是一個優化的策略,將 distribution 項預先計算出來並存儲在一個紋理內。在實際渲染的時候直接根據函數的兩個參數在紋理上采樣得到該點所對應的 D 函數的值。函數的參數即 n dot h 和代表粗糙度的系數 m. 計算方式如下:
   float PHBeckmann( float ndoth, float m )
{
  float alpha = acos( ndoth );
  float ta = tan( alpha );
  float val = 1.0/(m*m*pow(ndoth,4.0))*exp(-(ta*ta)/(m*m));
  return val;
}
 
        值得注意的是這個和上面的分布函數是有區別的,這里采用了另一種計算方式,這是 beckmann 分布。
3、散射 (Scattering)
Diffusion profile
散射是皮膚渲染當中最重要的概念,也是皮膚這個材質,與其他的物理材質區別最大的一點。皮膚是有很多層的,光線在某個點入射以后,一部分光線被反射,另一部分光線被折射進入皮膚內部,在皮膚內部(特別是內部的不同的層次之間),光會被吸收,會發生散射現象,最后呢,在入射點附近的一個 3D 點上終止(也就是能量耗盡)或者射出表面。如果需要渲染逼真的皮膚表面,必須對此現象進行仿真。研究者假設光線進入皮膚后迅速向四周散射,在很少的幾次散射后,光線就已經變成向各個方向平行地蔓延了。這樣簡化以后就提出了一個叫做 diffuse models 的模型。
再此基礎上再次簡化,研究者提出 diffusion profile 這個概念。這代表的是在一個表面上有一個光的發射點,有光在這個點向四周擴散,那么與這個點有不同距離的任意點,有多少光線到達了這個點,或者說分配到了多少的光的能量? diffusion profile 描述的就是光線傳播的關系,給定一個距離可以得出該點的光的強度。這個函數是和 RGB 顏色的光有關的,紅色的光有最強的擴散能力,能比另兩種顏色的光傳播更遠的距離。根據這個概念,我們考慮皮膚的次表面散射的計算,在入射點射入的光線,向四周傳播,其衰減的規律符合 diffusion profile. 那么在皮膚表面任意一點的話,需要考慮每一個入射點的次表面散射對該點的影響,將所有的結果累加就是最終的散射效果。因為皮膚下的散射現象發生得非常的迅速,所以我們幾乎可以不考慮入射光線的方向,那么散射的計算就只需要考慮距離這一個因素就可以了。
那么究竟這個 profile 是怎樣的呢?研究者發現可以用若干個不同系數的高斯函數來擬合這個函數。如下圖我們可以看到,四個高斯函數和的擬合效果已經很接近原始函數了。這四個高斯函數的計算是這樣的:
R(r) = 0.070G(0.036, r) + 0.18G(0.14, r) + 0.21G(0.91, r) + 0.29G(7.0, r).

Texture-Space Diffusion
根據上述原理,真正實現皮膚表面次表面散射效果模擬的方法之一是在渲染前對紋理做若干次高斯卷積並累加,得到最終的紋理,用於渲染。
- 首先得到紋理
 - 做 Skretch correction(可選)
 - 將光照渲染出來(irradiance map),存入 off-screen texture
 - 對每一個我們擬合 diffusion profile 的每一個高斯 kernel: 
          
- 單獨在 U 通道做模糊
 - 單獨在 V 通道做模糊
 
 - 渲染 3D mesh: 
          
- 使用每一個高斯處理后的紋理並將結果疊加
 - 增加高光部分
 
 

Screen-Space Diffusion
這里介紹另一種更高效的做法,在屏幕空間做高斯模糊。效率提高點在於,前面的方法,需要計算出 irradiance map, 導致需要額外的操作才能重新獲得 GPU 提供一些的便利,如 backface culling, viewport clipping. 而且計算 irradiance map 原本就需要費一些資源。另外前一種方法需要做兩次頂點的坐標變換:在生成 irradiance map 的時候一次,最終渲染的時候一次。總而言之,屏幕空間的處理會更加高效。其流程與紋理空間的大同小異,首先將模型渲染出來,並且順帶就把漫反射部分的光照計算好帶上了。在得到渲染結束的屏幕上的圖像后,對皮膚部分做若干次高斯處理,然后再累加起來,然后加上高光部分,最終渲染這張圖片就是處理后的結果了。
參考資料
Chapter 14. Advanced Techniques for Realistic Real-Time Skin Rendering
JORGE JIMENEZ, VERONICA SUNDSTEDT and DIEGO GUTIERREZ: Screen-Space Perceptual Rendering of Human Skin
