BRDF理論及shader實現(下)

_子寬發表於2020-10-06

接上篇:
BRDF理論及shader實現(上)

Specular BRDF

對於specular分量來說, f m f_m fm是一個遵循菲涅爾反射定律的鏡面BRDF項,此時的 f m f_m fm滿足([3]和[21]有詳細的推導):

f m ( l , v , m ) = F ( v , m ) δ ω m ( h , m ) 4 ( l ⋅ h ) 2 f_m({\bf{l}},{\bf{v}},{\bf{m}}) = F({\bf{v}},{\bf{m}})\frac{\delta_{\omega_m}({\bf{h}}, {\bf{m}})}{4({\bf{l}}\cdot{\bf{h}})^2} fm(l,v,m)=F(v,m)4(lh)2δωm(h,m)

h {\bf{h}} h表示half vector,是 v {\bf{v}} v l {\bf{l}} l的平均;這裡分母上第一次出現了 4 4 4,這也是後面specular BRDF公式的分母上的 4 4 4的來源。 δ ω m ( s , o ) \delta_{\omega_m}({\bf{s}}, {\bf{o}}) δωm(s,o)記為:

∫ Ω g ( o ) δ ω o ( s , o ) d ω o = { g ( s ) , if  s ∈ Ω 0 , otherwise \int_\Omega g({\bf{o}})\delta_{\omega_o}({\bf{s}}, {\bf{o}})d\omega_o = \begin{cases} g({\bf{s}}), & \text {if $s\in\Omega$} \\ 0, & \text{otherwise} \end{cases} Ωg(o)δωo(s,o)dωo={g(s),0,if sΩotherwise

此時, f r f_r fr可以化簡為為:

f r ( l , v ) = D ( h , α ) G ( v , l , α ) F ( v , h , F 0 ) 4 ( n ⋅ v ) ( n ⋅ l ) f_r({\bf{l}},{\bf{v}}) = \frac{D({\bf{h}},\alpha)G({\bf{v}},{\bf{l}},\alpha)F({\bf{v}},{\bf{h}},F_0)}{4({\bf{n}}\cdot{\bf{v}})({\bf{n}}\cdot{\bf{l}})} fr(l,v)=4(nv)(nl)D(h,α)G(v,l,α)F(v,h,F0)

其中, α \alpha α取決於我們前面提到的粗糙度roughness,具體為

α = r o u g h n e s s 2 \alpha = roughness^2 α=roughness2

可以理解為 α \alpha α是對粗糙度roughness的一個對映, α \alpha α將多次被用到。

菲涅爾項及 F 0 F_0 F0會在後面詳細介紹,這裡暫時略過。

shader實現:

// #define saturate(x) clamp(x, 0, 1)
// N     = normal;
// V     = normalize(camPos - WorldPos);
// L     = normalize(LightPos - WorldPos));
// H     = normalize(V + L);
// NdotV = saturate(dot(N, V));
// NdotL = saturate(dot(N, L));
// NdotH = saturate(dot(N, H));
// LdotH = saturate(dot(L, H));
// VdotH = saturate(dot(V, H));
vec3 SpecularBRDF(float NdotV, float NdotL, float NdotH, float LdotH, float VdotH, vec3 F0, float roughness) {
    float r   = roughness * roughness;

    float D = Distribution(NdotH, r);
    float V = Geometry(NdotV, NdotL, r);
    vec3  F = Fresnel(VdotH, F0);

    return D * V * F / (4 * NdotV * NdotL);
}

注意,這裡的NdotLNdotV等都是clamp到0到1的。

接下來具體看 f r f_r fr中每個分量的可能形式都有哪些。

法向分佈函式 D

這一部分介紹3個法向分佈函式的公式,以及一個推廣。

Beckmann

來源[5], D B e c k m a n n D_{Beckmann} DBeckmann假設微表面的法向分佈是以 n {\bf{n}} n為均值的高斯分佈,也即 h {\bf{h}} h n {\bf{n}} n越接近,反射的光線越多, D B e c k m a n n D_{Beckmann} DBeckmann越大。再結合 D B e c k m a n n D_{Beckmann} DBeckmann的積分約束,求得:

D B e c k m a n n ( h , α ) = χ + ( n , h ) π α 2 ( n ⋅ h ) 4 e ( n ⋅ h ) 2 − 1 α 2 ( n ⋅ h ) 2 D_{Beckmann}({\bf{h}}, \alpha) = \frac{\chi^+({\bf{n}},{\bf{h}})}{\pi\alpha^2({\bf{n}}\cdot{\bf{h}})^4}e^{\frac{({\bf{n}}\cdot{\bf{h}})^2-1}{\alpha^2({\bf{n}}\cdot{\bf{h}})^2}} DBeckmann(h,α)=πα2(nh)4χ+(n,h)eα2(nh)2(nh)21

這個公式有一個很致命的缺陷,那就是當roughness接近於1的時候, D B e c k m a n n D_{Beckmann} DBeckmann n ⋅ h ∈ [ 0 , 1 ] {\bf{n}}\cdot{\bf{h}}\in[0,1] nh[0,1]區間內,不是單調遞增的。表現在渲染上,就是在高光最強的中心點會產生一個暗斑。

DistributionBeckmann_graph

shader實現:

float DistributionBeckmann(float NdotH, float r) {
    float NdotH2   = NdotH * NdotH;
    float r2       = r * r;
    float r2NdotH2 = r2 * NdotH2;
    return exp((NdotH2 - 1) / (r2NdotH2)) / (PI * r2NdotH2 * NdotH2);
}

BlinnPhong

來源[6],BlinnPhong公式純粹是基於經驗的,在恰當選取引數的情況下,它的函式曲線非常接近於Beckmann。

BlinnPhong原始的模型是:

D B l i n n ( h , α ) = χ + ( n , h ) α p + 2 2 π ( n ⋅ h ) α p D_{Blinn}({\bf{h}}, \alpha) = \chi^+({\bf{n}},{\bf{h}})\frac{\alpha_p + 2}{2\pi}({\bf{n}}\cdot{\bf{h}})^{\alpha_p} DBlinn(h,α)=χ+(n,h)2παp+2(nh)αp

其中, α p \alpha_p αp表示粗糙係數,或者準確的說,是光滑係數—— α p \alpha_p αp越大,表示物體表面越光滑。

  • α p = ∞ \alpha_p=\infty αp=的時候,表示絕對光滑的物體,此時 D B l i n n ( h , α ) D_{Blinn}({\bf{h}}, \alpha) DBlinn(h,α)只有在 h = n {\bf{h}} = {\bf{n}} h=n,即入射角等於出射角的時候為 ∞ \infty ,否則為0。
  • α p = 0 \alpha_p=0 αp=0的時候,表示絕對粗糙的物體, D B l i n n ( h , α ) = 1 π D_{Blinn}({\bf{h}}, \alpha) = \frac{1}{\pi} DBlinn(h,α)=π1,這個式子也是後面會提到的diffuse的式子。

α p = ( 2 α 2 − 2 ) \alpha_p = (\frac{2}{\alpha^2} - 2) αp=(α222),則有:

D B l i n n ( h , α ) = χ + ( n , h ) π α 2 ( n ⋅ h ) ( 2 α 2 − 2 ) D_{Blinn}({\bf{h}}, \alpha) = \frac{\chi^+({\bf{n}},{\bf{h}})}{\pi\alpha^2}({\bf{n}}\cdot{\bf{h}})^{(\frac{2}{\alpha^2} - 2)} DBlinn(h,α)=πα2χ+(n,h)(nh)(α222)

這個公式即接近於Beckmann的法向分佈公式,也是常用的BlinnPhong形式。

shader實現:

float DistributionBlinnPhong(float NdotH, float r) {
    float a = r * r;
    return pow(NdotH, 2.0 / a - 2.0) / (PI * a);
}

GGX

來源[3],GGX是根據實測資料擬合出來的一個公式:

D G G X ( h , α ) = χ + ( n , h ) ⋅ α 2 π ( ( n ⋅ h ) 2 ( α 2 − 1 ) + 1 ) 2 D_{GGX}({\bf{h}}, \alpha) = \frac{\chi^+({\bf{n}},{\bf{h}})\cdot\alpha^2}{\pi(({\bf{n}}\cdot{\bf{h}})^2(\alpha^2-1)+1)^2} DGGX(h,α)=π((nh)2(α21)+1)2χ+(n,h)α2

shader實現:

float DistributionGGX(float NdotH, float r) {
    float a2     = r * r;
    float NdotH2 = NdotH * NdotH;
    float nom    = a2;
    float denom  = (NdotH2 * (a2 - 1.0) + 1.0);
    denom        = PI * denom * denom;
    return nom / max(denom, 0.001);
}

除了這三種公式,還有更多更復雜的法向分佈函式D,具體可以參考[17]。但是其實最常用的還是GGX(及其各向異性模式),無論是遊戲還是影視行業都比較喜歡用GGX。

GTR

Burley通過對Berry(與GGX公式類似,分母上的指數為1)和GGX公式的觀察,提出了廣義的Trowbridge-Reitz(Generalized-Trowbridge-Reitz,GTR)法線分佈函式:

D G T R ( h , α ) = c ⋅ χ + ( n , h ) ( ( n ⋅ h ) 2 ( α 2 − 1 ) + 1 ) γ D_{GTR}({\bf{h}}, \alpha) = \frac{c\cdot\chi^+({\bf{n}},{\bf{h}})}{(({\bf{n}}\cdot{\bf{h}})^2(\alpha^2-1)+1)^\gamma} DGTR(h,α)=((nh)2(α21)+1)γcχ+(n,h)

其中, c c c表示縮放係數,是一個常數; γ \gamma γ用於控制尾部的形狀,當 γ = 1 \gamma=1 γ=1的時候, D G T R D_{GTR} DGTR就是Berry公式,當 γ = 2 \gamma=2 γ=2的時候, D G T R D_{GTR} DGTR就是 D G G X D_{GGX} DGGX

γ \gamma γ的取值對 D G T R D_{GTR} DGTR的影響如下圖所示。

GTR

以下是 γ = 1 \gamma=1 γ=1 γ = 2 \gamma=2 γ=2時的shader實現:

float DistributionGTR1(float NdotH, float r)
{
    if (r >= 1) return 1/PI;
    float a2 = r*r;
    float t = 1 + (a2-1)*NdotH*NdotH;
    return (a2-1) / (PI*log(a2)*t);
}

float DistributionGTR2(float NdotH, float r)
{
    float a2 = r*r;
    float t = 1 + (a2-1)*NdotH*NdotH;
    return a2 / (PI * t * t);
}

效果對比

Distribution

可以看出,BlinnPhong和Beckmann的差異不大。而GGX有著更平滑的邊緣和更小的峰值。除此之外,GGX運算壓力更小,因為它沒有指數操作。

遮擋項 G

和法向分佈函式 D D D一樣,遮擋項 G G G也是入射角、出射角和表面粗糙度的函式。

有些文章會把遮擋項G和BRDF的分母 ( n ⋅ l ) ( n ⋅ v ) ({\bf{n}}\cdot{\bf{l}})({\bf{n}}\cdot{\bf{v}}) (nl)(nv)放在一起組成一項約分掉,這也是一種優化思路,因為G通常包含這兩個cosine因子。這裡我們約定本文的遮擋項 G G G是不約分 ( n ⋅ l ) ( n ⋅ v ) ({\bf{n}}\cdot{\bf{l}})({\bf{n}}\cdot{\bf{v}}) (nl)(nv) G G G

Implicit

來源[7],有些BRDF公式會忽略遮擋項G,將其跟分母上的 ( n ⋅ l ) ( n ⋅ v ) ({\bf{n}}\cdot{\bf{l}})({\bf{n}}\cdot{\bf{v}}) (nl)(nv)一起忽略掉,這就有了第一個隱式 G G G

G I m p l i c i t ( l , v , h ) = ( n ⋅ l ) ( n ⋅ v ) G_{Implicit}({\bf{l}},{\bf{v}},{\bf{h}})=({\bf{n}}\cdot{\bf{l}})({\bf{n}}\cdot{\bf{v}}) GImplicit(l,v,h)=(nl)(nv)

它的形態大概是,當且僅當視角和光源都垂直於物體表面的時候, G I m p l i c i t = 1 G_{Implicit}=1 GImplicit=1,光源、視角與物體表面法線的夾角越大, G I m p l i c i t G_{Implicit} GImplicit越小,直到衰減為0,這也是很符合常識的。

shader實現:

float GeometryImplicit(float NdotV, float NdotL) {
    return NdotL * NdotV;
}

但是隱式遮擋項 G I m p l i c i t G_{Implicit} GImplicit最大的問題在於,它隨著視角的衰減速度太快,這會使得高光區域太窄。為了解決這個問題,我們繼續看顯式的遮擋項 G G G

Cook-Torrance

來源[9], G C o o k − T o r r a n c e G_{Cook-Torrance} GCookTorrance解決了 G I m p l i c i t G_{Implicit} GImplicit衰減速度太快的問題:

G C o o k − T o r r a n c e ( l , v , h ) = min ⁡ ( 1 , 2 ( n ⋅ h ) ( n ⋅ v ) v ⋅ h , 2 ( n ⋅ h ) ( n ⋅ l ) v ⋅ h ) G_{Cook-Torrance}({\bf{l}},{\bf{v}},{\bf{h}})=\min{\left(1, \frac{2({\bf{n}}\cdot{\bf{h}})({\bf{n}}\cdot{\bf{v}})}{{\bf{v}}\cdot{\bf{h}}}, \frac{2({\bf{n}}\cdot{\bf{h}})({\bf{n}}\cdot{\bf{l}})}{{\bf{v}}\cdot{\bf{h}}}\right)} GCookTorrance(l,v,h)=min(1,vh2(nh)(nv),vh2(nh)(nl))

shader實現:

float GeometryCookTorrance(float NdotV, float NdotL, float VdotH, float NdotH) {
    float ct1 = 2 * NdotH * NdotV / VdotH;
    float ct2 = 2 * NdotH * NdotL / VdotH;
    return min(1, min(ct1, ct2));
}

Kelemen

來源[10],也是解決 G I m p l i c i t G_{Implicit} GImplicit衰減速度太快的問題,同時 G K e l e m e n G_{Kelemen} GKelemen G C o o k − T o r r a n c e G_{Cook-Torrance} GCookTorrance的效率更高:

G K e l e m e n ( l , v , h ) = ( n ⋅ l ) ( n ⋅ v ) ( v ⋅ h ) 2 G_{Kelemen}({\bf{l}},{\bf{v}},{\bf{h}})=\frac{({\bf{n}}\cdot{\bf{l}})({\bf{n}}\cdot{\bf{v}})}{({\bf{v}}\cdot{\bf{h}})^2} GKelemen(l,v,h)=(vh)2(nl)(nv)

shader實現:

float GeometryKelemen(float NdotV, float NdotL, float VdotH) {
    return NdotV * NdotL / (VdotH * VdotH);
}

Neumann

來源[8], G N e u m a n n G_{Neumann} GNeumann用另一種方式解決了 G I m p l i c i t G_{Implicit} GImplicit衰減速度太快的問題:

G N e u m a n n ( l , v , h ) = ( n ⋅ l ) ( n ⋅ v ) max ⁡ ( n ⋅ l , n ⋅ v ) G_{Neumann}({\bf{l}},{\bf{v}},{\bf{h}})=\frac{({\bf{n}}\cdot{\bf{l}})({\bf{n}}\cdot{\bf{v}})}{\max{({\bf{n}}\cdot{\bf{l}}, {\bf{n}}\cdot{\bf{v}}})} GNeumann(l,v,h)=max(nl,nv)(nl)(nv)

shader實現:

float GeometryNeumann(float NdotV, float NdotL) {
    return (NdotL * NdotV) / max(NdotL, NdotV);
}

但是,以上三個解決方案也不夠完美。前面提到過,遮擋項G應該是入射角、出射角和表面粗糙度的函式,而以上四個G,包括隱式遮擋項都與粗糙度無關。

Smith

Smith家族[13]都是採用了前面介紹的 G 1 G_1 G1相乘的形式:

G 2 ( l , v , h ) = G 1 ( l ) G 1 ( v ) G_2({\bf{l}},{\bf{v}},{\bf{h}})=G_1({\bf{l}})G_1({\bf{v}}) G2(l,v,h)=G1(l)G1(v)

他們之間的區別就是 G 1 G_1 G1的選取不同。

Beckmann

Beckmann的 G G G是跟 D D D一起提出的,前面介紹過 G G G是可以從 D D D推匯出來的,因此Beckmann的 Λ \Lambda Λ為:

c = n ⋅ v α 1 − ( n ⋅ v ) 2 Λ ( v ) = erf ( c ) − 1 2 + 1 2 c π exp ⁡ ( − c 2 ) \begin{aligned} c & = \frac{{\bf{n}}\cdot{\bf{v}}}{\alpha\sqrt{1-({\bf{n}}\cdot{\bf{v}})^2}} \\ \Lambda({\bf{v}}) & = \frac{\text{erf}(c)-1}{2}+\frac{1}{2c\sqrt{\pi}}\exp(-c^2) \end{aligned} cΛ(v)=α1(nv)2 nv=2erf(c)1+2cπ 1exp(c2)

但是由於有 erf \text{erf} erf函式的存在,計算起來過於複雜,因此通常用如下的近似形式:

Λ ( v ) ≈ { 1 − 1.259 x + 0.396 c 2 3.535 c + 2.181 c 2 , if  c < 1.6 0 , if  c ≥ 1.6 \Lambda({\bf{v}}) \approx \begin{cases} \frac{1-1.259x+0.396c^2}{3.535c+2.181c^2}, & \text{if }c<1.6 \\ 0, & \text{if }c\geq1.6 \end{cases} Λ(v){3.535c+2.181c211.259x+0.396c2,0,if c<1.6if c1.6

因此,Beckmann的 G 1 G_1 G1

G B e c k m a n n ( v ) ≈ { 3.535 c + 2.181 c 2 1 + 2.276 c + 2.577 c 2 , if  c < 1.6 1 , if  c ≥ 1.6 G_{Beckmann}({\bf{v}}) \approx \begin{cases} \frac{3.535c+2.181c^2}{1+2.276c+2.577c^2}, & \text{if }c<1.6 \\ 1, & \text{if }c\geq1.6 \end{cases} GBeckmann(v){1+2.276c+2.577c23.535c+2.181c2,1,if c<1.6if c1.6

shader實現:

float GeometryBeckmann(float NdotV, float r) {
    float c  = NdotV / (r * sqrt(1 - NdotV * NdotV));
    float c2 = c * c;
    if (c < 1.6)
        return (3.535 * c + 2.181 * c2) / (1 + 2.276 * c + 2.577 * c2);
    else
        return 1.0;
}
float GeometrySmithBeckmann(float NdotV, float NdotL, float r) {
    float ggx2 = GeometryBeckmann(NdotV, r);
    float ggx1 = GeometryBeckmann(NdotL, r);
    return ggx1 * ggx2;
}
GGX

GGX[3]跟Beckmann類似,都是從法向分佈函式推匯出來的:

c = n ⋅ v α 1 − ( n ⋅ v ) 2 Λ ( v ) = − 1 + 1 + 1 c 2 2 \begin{aligned} c & = \frac{{\bf{n}}\cdot{\bf{v}}}{\alpha\sqrt{1-({\bf{n}}\cdot{\bf{v}})^2}} \\ \Lambda({\bf{v}}) & = \frac{-1+\sqrt{1+\frac{1}{c^2}}}{2} \end{aligned} cΛ(v)=α1(nv)2 nv=21+1+c21

對應的 G 1 G_1 G1定義為

G G G X ( v ) = 2 ( n ⋅ v ) ( n ⋅ v ) + α 2 + ( 1 − α 2 ) ( n ⋅ v ) 2 G_{GGX}({\bf{v}}) = \frac{2({\bf{n}}\cdot{\bf{v}})}{({\bf{n}}\cdot{\bf{v}})+\sqrt{\alpha^2+(1-\alpha^2)({\bf{n}}\cdot{\bf{v}})^2}} GGGX(v)=(nv)+α2+(1α2)(nv)2 2(nv)

shader實現:

float GeometryGGX(float NdotV, float r) {
    float r2 = r * r;
    return (2 * NdotV) / (NdotV + sqrt(r2 + (1 - r2) * NdotV * NdotV));
}
float GeometrySmithGGX(float NdotV, float NdotL, float r) {
    float ggx2 = GeometryGGX(NdotV, r);
    float ggx1 = GeometryGGX(NdotL, r);
    return ggx1 * ggx2;
}
GGX Joint

前面提到的GGX用的是 G 2 = G 1 ∗ G 1 G_2=G_1*G_1 G2=G1G1的separable G,如果用height-correlated G,那麼 G 2 G_2 G2變為:

G 2 − G G X J o i n t ( l , v , m ) = 1 1 + Λ ( l ) + Λ ( v ) = 2 ( n ⋅ v ) ( n ⋅ l ) ( n ⋅ l ) ⋅ α 2 + ( 1 − α 2 ) ( n ⋅ v ) 2 + ( n ⋅ v ) ⋅ α 2 + ( 1 − α 2 ) ( n ⋅ l ) 2 \begin{aligned} G_{2-GGXJoint}({\bf{l}},{\bf{v}},{\bf{m}}) & =\frac{1}{1+\Lambda({\bf{l}})+\Lambda({\bf{v}})}\\ & =\frac{2({\bf{n}}\cdot{\bf{v}})({\bf{n}}\cdot{\bf{l}})}{({\bf{n}}\cdot{\bf{l}})\cdot\sqrt{\alpha^2+(1-\alpha^2)({\bf{n}}\cdot{\bf{v}})^2} + ({\bf{n}}\cdot{\bf{v}})\cdot\sqrt{\alpha^2+(1-\alpha^2)({\bf{n}}\cdot{\bf{l}})^2}} \end{aligned} G2GGXJoint(l,v,m)=1+Λ(l)+Λ(v)1=(nl)α2+(1α2)(nv)2 +(nv)α2+(1α2)(nl)2 2(nv)(nl)

shader實現:

float GeometrySmithGGXJoint(float NdotV, float NdotL, float r) {
    float r2 = r * r;
    float Vis_SmithV = NdotL * sqrt(NdotV * (NdotV - NdotV * r2) + r2);
	float Vis_SmithL = NdotV * sqrt(NdotL * (NdotL - NdotL * r2) + r2);
	return 2 * NdotV * NdotL / (Vis_SmithV + Vis_SmithL);
}

為了提高計算效率,UE4對GGX Joint方法做了一個近似,公式為:

G 2 − G G X J o i n t ( l , v , m ) = 1 1 + Λ ( l ) + Λ ( v ) ≈ 2 ( n ⋅ v ) ( n ⋅ l ) ( n ⋅ l ) ⋅ ( α + ( 1 − α ) ( n ⋅ v ) ) + ( n ⋅ v ) ⋅ ( α + ( 1 − α ) ( n ⋅ l ) ) \begin{aligned} G_{2-GGXJoint}({\bf{l}},{\bf{v}},{\bf{m}}) & =\frac{1}{1+\Lambda({\bf{l}})+\Lambda({\bf{v}})}\\ & \approx\frac{2({\bf{n}}\cdot{\bf{v}})({\bf{n}}\cdot{\bf{l}})}{({\bf{n}}\cdot{\bf{l}})\cdot(\alpha+(1-\alpha)({\bf{n}}\cdot{\bf{v}})) + ({\bf{n}}\cdot{\bf{v}})\cdot(\alpha+(1-\alpha)({\bf{n}}\cdot{\bf{l}}))} \end{aligned} G2GGXJoint(l,v,m)=1+Λ(l)+Λ(v)1(nl)(α+(1α)(nv))+(nv)(α+(1α)(nl))2(nv)(nl)

shader實現:

float GeometryGGXJointApprox(float NdotV, float NdotL, float r) {
    return (NdotV) / (NdotL * (r + (1 - r) * NdotV));
}
float GeometrySmithGGXJointApprox(float NdotV, float NdotL, float r) {
	float Vis_SmithV = NdotL * ( NdotV * ( 1 - r ) + r );
	float Vis_SmithL = NdotV * ( NdotL * ( 1 - r ) + r );
	return 2 * NdotV * NdotL / ( Vis_SmithV + Vis_SmithL );
}
Schlick-Beckmann

Schlick[11]的 G 1 G_1 G1定義為
k = α 2 π G S c h l i c k ( v ) = n ⋅ v ( n ⋅ v ) ( 1 − k ) + k k=\alpha\sqrt{\frac{2}{\pi}} \\ G_{Schlick}({\bf{v}})=\frac{{\bf{n}}\cdot{\bf{v}}}{({\bf{n}}\cdot{\bf{v}})(1-k)+k} k=απ2 GSchlick(v)=(nv)(1k)+knv
shader實現:

float GeometrySchlickBeckmann(float NdotV, float r) {
    float k     = (r)*sqrt(2.0 / PI);
    float nom   = NdotV;
    float denom = NdotV * (1.0 - k) + k;
    return nom / denom;
}
float GeometrySmithSchlickBeckmann(float NdotV, float NdotL, float r) {
    float ggx2 = GeometrySchlickBeckmann(NdotV, r);
    float ggx1 = GeometrySchlickBeckmann(NdotL, r);
    return ggx1 * ggx2;
}
Schlick-GGX

Schlick-GGX[12]曾經是UE4所採用的的一個模型,跟Schlick有些類似, G 1 G_1 G1定義為
k = α 2 G S c h l i c k ( v ) = n ⋅ v ( n ⋅ v ) ( 1 − k ) + k k=\frac{\alpha}{2} \\ G_{Schlick}({\bf{v}})=\frac{{\bf{n}}\cdot{\bf{v}}}{({\bf{n}}\cdot{\bf{v}})(1-k)+k} k=2αGSchlick(v)=(nv)(1k)+knv
shader實現:

float GeometrySchlickGGX(float NdotV, float r) {
    float k     = r * 0.5;
    float nom   = NdotV;
    float denom = NdotV * (1.0 - k) + k;
    return nom / denom;
}
float GeometrySmithSchlickGGX(float NdotV, float NdotL, float r) {
    float ggx2 = GeometrySchlickGGX(NdotV, r);
    float ggx1 = GeometrySchlickGGX(NdotL, r);
    return ggx1 * ggx2;
}

這裡面還有一個細節,那就是迪士尼後來提出了對粗糙粗roughness做一個remapping,使得它更接近於真實:
α ′ = ( r o u g h n e s s + 1 2 ) 2 \alpha' = (\frac{roughness + 1}{2})^2 \\ α=(2roughness+1)2
其他的部分不變。這樣shader實現為:

float GeometrySmithSchlickGGX(float NdotV, float NdotL, float roughness) {
    float r = (roughness + 1.0) * 0.5; // remapping roughness
    r = r * r
    float ggx2 = GeometrySchlickGGX(NdotV, r);
    float ggx1 = GeometrySchlickGGX(NdotL, r);
    return ggx1 * ggx2;
}

注意,此時GeometrySmithSchlickGGX的輸入引數不是r,而改為了roughness

優化

考慮到幾乎所有 G G G都帶有 ( n ⋅ v ) ( n ⋅ l ) ({\bf{n}}\cdot{\bf{v}})({\bf{n}}\cdot{\bf{l}}) (nv)(nl)項,可以跟 f r ( l , v ) f_r({\bf{l}},{\bf{v}}) fr(l,v)的分母約分,因此在實現時,可以考慮定義
G ′ = G ( n ⋅ v ) ( n ⋅ l ) G'=\frac{G}{({\bf{n}}\cdot{\bf{v}})({\bf{n}}\cdot{\bf{l}})} G=(nv)(nl)G
節省一部分計算。

這樣做不只是出於效能的考慮,也是出於精度的考慮。如果 n ⋅ v {\bf{n}}\cdot{\bf{v}} nv n ⋅ l {\bf{n}}\cdot{\bf{l}} nl的乘積接近於0,那麼specular項的分母會非常小,嚴重影響其精度,極端的情況下會在過渡區域產生一道割裂的分界線。下圖展示了 ( n ⋅ v ) ∗ ( n ⋅ l ) ∗ 10 ({\bf{n}}\cdot{\bf{v}})*({\bf{n}}\cdot{\bf{l}})*10 (nv)(nl)10(左)、未優化時(中)、優化後(右)的效果,可以看出左側兩張圖的分界線非常吻合。優化後則沒有顏色割裂的問題。

Geometry-bugs

效果對比

roughness = 0.9,計算球體的遮擋項效果為:

Geometry

最後一排是常用的幾種方法,差異並不大,邊緣的過渡也比較好。

菲涅爾項 F

菲涅爾項描述的是物體表面的反射、折射現象。一般我們會採用常量 F 0 F_0 F0來計算菲涅爾項 F ( v , h , F 0 ) F({\bf{v}},{\bf{h}},F_0) F(v,h,F0)

要說明白菲涅爾項,得從光學在介質表面的折射反射現象說起。我們知道光線會在介質表面產生不連續性,具體表現為一部分光線反射——遵循光線反射定律,入射角等於反射角;另一部分光線會折射進入介質——遵循光線折射定律,折射角取決於入射角的大小以及構成交介面的兩種材質,即斯涅耳定律(Snell’s law):

n 1 sin ⁡ ( θ i ) = n 2 sin ⁡ ( θ t ) n_1\sin(\theta_i)=n_2\sin(\theta_t) n1sin(θi)=n2sin(θt)

斯涅耳定律描述的僅僅是光線的角度,但是圖形學研究的其實是光線的radiance/irradiance,所以我們要更進一步。定義Fresnel reflectance R F R_F RF為反射光線的radiance佔入射光線radiance的比例, R F R_F RF是入射角 θ i \theta_i θi的函式。那麼對於入射光線 L i L_i Li,在角度 θ i \theta_i θi時反射光線的radiance為 R F ( θ i ) L i R_F(\theta_i)L_i RF(θi)Li。再考慮折射部分,根據能量守恆,沒有反射的能量都會被折射(不考慮被吸收的能量),因此折射的flux佔入射flux的比例是 1 − R F 1-R_F 1RF。這裡需要強調的是,radiance定義的是“irradiance每立體角”,它的大小跟角度有關係,因此折射光線的radiance L t L_t Lt不能簡單用 1 − R F 1-R_F 1RF乘上 L i L_i Li,而要轉換角度:
L t = ( 1 − R F ( θ i ) ) sin ⁡ 2 θ i sin ⁡ 2 θ t L i L_t = (1-R_F(\theta_i))\frac{\sin^2\theta_i}{\sin^2\theta_t}L_i Lt=(1RF(θi))sin2θtsin2θiLi
將斯涅耳定律帶入上式,得到:
L t = ( 1 − R F ( θ i ) ) n 2 2 n 1 2 L i L_t = (1-R_F(\theta_i))\frac{n_2^2}{n_1^2}L_i Lt=(1RF(θi))n12n22Li
介紹了這麼多 R F R_F RF的相關知識,其實關鍵點還是前面說的, R F R_F RF是入射角 θ i \theta_i θi的函式。我們再回頭考慮這個 R F R_F RF與入射角 θ i \theta_i θi的關係。當 θ i = 90 ° \theta_i=90\degree θi=90°的時候,即 R F ( 90 ° ) R_F(90\degree) RF(90°),此時入射光平行於平面,垂直於法向,不存在折射光線, R F ( 90 ° ) = 1 R_F(90\degree)=1 RF(90°)=1;當 θ i = 0 ° \theta_i=0\degree θi=0°的時候,即 R F ( 0 ° ) R_F(0\degree) RF(0°),此時反射光線佔比最低,根據不同的材質這個 R F ( 0 ° ) R_F(0\degree) RF(0°)有不同的值,Real-time Rendering[14]給出了常見的材質的 R F R_F RF θ i \theta_i θi的關係曲線:

Fresnel

為了近似這個曲線,採取的策略是利用 R F ( 0 ° ) R_F(0\degree) RF(0°),也就是前面說的 F 0 F_0 F0
R F ( θ i ) ≈ R F ( 0 ° ) + ( 1 − R F ( 0 ° ) ) ( 1 − cos ⁡ θ i ) 5 R_F(\theta_i)\approx R_F(0\degree) + (1-R_F(0\degree))(1-\cos\theta_i)^5 RF(θi)RF(0°)+(1RF(0°))(1cosθi)5
這裡有一個預設的假設是, R F ( 90 ° ) = 1 R_F(90\degree)=1 RF(90°)=1,如果, R F ( 90 ° ) R_F(90\degree) RF(90°)未知, R F ( θ i ) R_F(\theta_i) RF(θi)應該寫為:
R F ( θ i ) ≈ R F ( 0 ° ) + ( R F ( 90 ° ) − R F ( 0 ° ) ) ( 1 − cos ⁡ θ i ) 5 R_F(\theta_i)\approx R_F(0\degree) + (R_F(90\degree)-R_F(0\degree))(1-\cos\theta_i)^5 RF(θi)RF(0°)+(RF(90°)RF(0°))(1cosθi)5
這個 R F ( 90 ° ) R_F(90\degree) RF(90°)也就是 F 90 F_{90} F90

最後,我們看一下 F 0 F_0 F0怎麼計算。對於dielectrics來說, F 0 F_0 F0的值取決於折射率,公式為:
F 0 = 0.16 ⋅ r e f l e c t a n c e 2 F_0=0.16\cdot reflectance^2 F0=0.16reflectance2
其中, r e f l e c t a n c e reflectance reflectance由物體表面的材質定義。

對於dielectric, F 0 F_0 F0通過金屬度metallic和basecolor來計算:
F 0 = b a s e C o l o r ⋅ m e t a l l i c F_0=baseColor\cdot metallic F0=baseColormetallic
綜合dielectrics和dielectric,得到:

    vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + baseColor.xyz * metallic;

說明白了 F 0 F_0 F0,我們接下來看看菲涅爾函式 F F F有哪些形式。

簡單形式

最簡單的情況,直接令菲涅爾函式等於 F 0 F_0 F0
F N o n e ( v , h ) = F 0 F_{None}({\bf{v}},{\bf{h}})=F_0 FNone(v,h)=F0
shader實現:

vec3 Fresnel(vec3 F0) {
    return F0;
}

Schlick

來源[11],公式:
F S c h l i c k ( v , h ) = F 0 + ( 1 − F 0 ) ( 1 − ( v ⋅ h ) ) 5 F_{Schlick}({\bf{v}},{\bf{h}})=F_0+(1-F_0)(1-({\bf{v}}\cdot{\bf{h}}))^5 FSchlick(v,h)=F0+(1F0)(1(vh))5
也就是我們前面說到的對 R F R_F RF的擬合。shader實現:

vec3 FresnelSchlick(float VdotH, vec3 F0) {
    return F0 + (1.0 - F0) * pow(1.0 - VdotH, 5.0);
}

如果引入 F 90 F_{90} F90,則變成:
F S c h l i c k ( v , h ) = F 0 + ( F 90 − F 0 ) ( 1 − ( v ⋅ h ) ) 5 F_{Schlick}({\bf{v}},{\bf{h}})=F_0+(F_{90}-F_0)(1-({\bf{v}}\cdot{\bf{h}}))^5 FSchlick(v,h)=F0+(F90F0)(1(vh))5
shader實現:

vec3 FresnelSchlick(float VdotH, vec3 F0, vec F90) {
    return F0 + (F90 - F0) * pow(1.0 - VdotH, 5.0);
}

對specular來說, F 90 F_{90} F90可以從 F 0 F_0 F0計算得來[1]:

    float F90 = saturate(dot(F0, vec3(50.0 * 0.33)));

Cook-Torrance

來源[9],公式:
η = 1 + F 0 1 − F 0 c = v ⋅ h g = η 2 + c 2 − 1 F C o o k − T o r r a n c e ( v , h ) = 1 2 ( g − c g + c ) 2 ( 1 + ( ( g + c ) c − 1 ( g − c ) c + 1 ) 2 ) \begin{aligned} \eta & =\frac{1+\sqrt{F_0}}{1-\sqrt{F_0}} \\ c & = {\bf{v}}\cdot{\bf{h}} \\ g & = \sqrt{\eta^2+c^2-1} \\ F_{Cook-Torrance}({\bf{v}},{\bf{h}}) & =\frac{1}{2}\left(\frac{g-c}{g+c}\right)^2\left(1+\left(\frac{(g+c)c-1}{(g-c)c+1}\right)^2\right) \end{aligned} ηcgFCookTorrance(v,h)=1F0 1+F0 =vh=η2+c21 =21(g+cgc)2(1+((gc)c+1(g+c)c1)2)
shader實現:

float FresnelCookTorrance(float VdotH, float F0) {
    float sqrtF = sqrt(F0);
    float Eta   = (1.0 + sqrtF) / (1.0 - sqrtF);
    float g     = sqrt(Eta * Eta + VdotH * VdotH - 1.0);
    return 0.5 * pow((g - VdotH) / (g + VdotH), 2) *
           (1 + pow(((g + VdotH) * VdotH - 1.0) / ((g - VdotH) * VdotH + 1.0), 2));
}

Diffuse BRDF

相比於繁瑣的specular部分,diffuse部分就簡單的多。diffuse部分由baseColor和diffuse係數相乘得到,即:
L d ( v ) = c d i f f ⋅ f d L_d({\bf{v}})={\bf{c}}_{diff}\cdot f_d Ld(v)=cdifffd
shader實現:

    vec3 colorDiffuse = baseColor * DiffuseBRDF(NdotV, NdotL, LdotH, roughness);

接下來看一下 f d f_d fd的可能取值。

Lambert

Lambert模型認為既然diffuse是漫反射,不如簡單地認為各個方向都是一樣的值,即出射光線的radiance與入射光線的角度無關。

f d = 1 π f_d = \frac{1}{\pi} fd=π1

這個實現相當於,採用BlinnPhong的法向分佈 D B l i n n ( h , α ) = 1 π D_{Blinn}({\bf{h}}, \alpha) = \frac{1}{\pi} DBlinn(h,α)=π1,同時令遮擋項為隱式形式,並且菲涅爾項為1。雖然簡單,但是已經足夠近似現實了,效果還不錯。

shader實現:

float DiffuseLambert() {
    return 1.0 / PI;
}

雖然Lambert模型已經足夠接近真實情況,但是它還是不夠理想。我們前面提到過,diffuse分量本質上是光線折射進入物體表面,經過多次反射再折射出來的現象,也就是它不是物理上真實存在的一個光學現象。而在討論specular菲涅爾項的時候又提到過,反射部分會隨著入射光線的角度變化,那麼折射部分相應的也會隨著入射角度變化,既然如此,來自於折射部分的diffuse分量肯定也是會隨著入射光線的角度而改變的!也就是說, f d f_d fd是入射角 l {\bf{l}} l的函式: f d ( l ) f_d({\bf{l}}) fd(l)

同時, f d f_d fd也應該是出射角 v {\bf{v}} v的函式[14]: f d ( l , v ) f_d({\bf{l}},{\bf{v}}) fd(l,v)。因為菲涅爾項考慮的是鏡面反射,入射角等於出射角,而diffuse項的入射角不一定等於出射角,因此兩個角度都會影響 f d f_d fd

再者,前面影響specular分量的引數當中, r o u g h n e s s roughness roughness也會影響 f d f_d fd。根據常識,不同粗糙程度的物體的diffuse是有明顯的不同的。即 f d ( l , v , r o u g h n e s s ) f_d({\bf{l}}, {\bf{v}}, roughness) fd(l,v,roughness)

基於這一點洞察,又有一些新的diffuse模型被提出,希望解決Lambert模型的不足。。

Oren–Nayar

Oren-Nayar模型是對Lambert模型的推廣。[18]指出,Lambert模型對於光滑物體或許還成立,但是對於粗糙物體是不正確的。粗糙的物體在光照下會顯得很平坦,而Lambert模型沒有表現出這種平坦。為了達到這個效果,Oren-Nayar加強了掠射逆反射(入射角和出射角在幾乎同一個方向,並且垂直於法向的情形)的強度。

Oren-Nayar公式如下。

f d = 1 π ⋅ ( A + B ⋅ max ⁡ ( 0 , cos ⁡ ϕ ) ⋅ sin ⁡ α ⋅ tan ⁡ β ) A = 1.0 − 0.5 α α + 0.33 B = 0.45 α α + 0.09 α = max ⁡ ( l ⋅ n , v ⋅ n ) β = min ⁡ ( l ⋅ n , v ⋅ n ) \begin{aligned} f_d & = \frac{1}{\pi}\cdot(A+B\cdot\max{(0, \cos{\phi})}\cdot\sin\alpha\cdot\tan\beta) \\ A & = 1.0-0.5\frac{\alpha}{\alpha+0.33} \\ B & = 0.45\frac{\alpha}{\alpha+0.09} \\ \alpha & = \max{({\bf{l}}\cdot{\bf{n}}, {\bf{v}}\cdot{\bf{n}})} \\ \beta & = \min{({\bf{l}}\cdot{\bf{n}}, {\bf{v}}\cdot{\bf{n}})} \end{aligned} fdABαβ=π1(A+Bmax(0,cosϕ)sinαtanβ)=1.00.5α+0.33α=0.45α+0.09α=max(ln,vn)=min(ln,vn)

其中, ϕ \phi ϕ表示 l n {\bf{l}}{\bf{n}} ln平面和 v n {\bf{v}}{\bf{n}} vn的夾角。

可以看出,當 r o u g h n e s s − 0 roughness-0 roughness0的時候, A = 1 , B = 0 A=1, B=0 A=1,B=0,此時Oren-Nayar模型退化為Lambert模型。

下圖[18]展示了真實照片、Lambert模型與Oren-Nayar的對比。

LambertvsOrenNayar

Hanrahan-Krueger

Hanrahan-Krueger模型[19]其實是源自次表面散射理論,是用於表現次表面散射現象的一個模型。它跟Oren-Nayar模型一樣,對掠射角進行了補償。但是它的補償過於平坦,沒有給出足夠強的峰值,也不太完美。

Hanrahan-Krueger模型和Oren-Nayar模型都不太常用,因此不再贅述。

Burley

Oren–Nayar模型雖然提高了粗糙物體的真實性,但是它對掠射逆反射現象的修正還是不夠真實。為了研究真實材料的物理特性,我們需要一個材質資料庫。

MERL BRDF Database就是這樣一個資料庫。它是由MERL(Mitsubishi Electric Research Laboratories)實驗室建立了的,測量並記錄了不同角度的光源、觀測視角情況下的BRDF數值,考慮到各向異性,每個材質都取樣了90(光源)*90(視角)*180(各向異性)三個維度的資料。如果只考慮各向同性材質,可以將BRDF資料壓縮到一張圖片裡。

ImageSlice

如上圖所示,橫軸 θ h \theta_h θh表示half vector h {\bf{h}} h與法向量 n {\bf{n}} n之間的夾角。縱軸表示入射角與 h {\bf{h}} h的夾角。

Disney通過分析MERL BRDF Database,提出了兩個Lambert模型與事實不符的地方:

  1. diffuse也會有類似於specular的光斑;
  2. 部分材質的diffuse會在掠射角有明顯的光環,這個現象即掠射逆反射(grazing retroreflection);

為了解決這些問題,Disney提出了一個diffuse BRDF公式[15]:

f d ( l , v ) = 1 π F S c h l i c k ( n , l , 1 , f 90 ) F S c h l i c k ( n , v , 1 , f 90 ) F S c h l i c k ( n , l , f 0 , f 90 ) = F 0 + ( F 90 − F 0 ) ( 1 − ( n ⋅ l ) ) 5 f 90 = 0.5 + 2 ⋅ r o u g h n e s s ⋅ cos ⁡ 2 ( θ d ) \begin{aligned} f_d({\bf{l}},{\bf{v}}) & = \frac{1}{\pi}F_{Schlick}({\bf{n}},{\bf{l}},1,f_{90})F_{Schlick}({\bf{n}},{\bf{v}},1,f_{90}) \\ F_{Schlick}({\bf{n}},{\bf{l}},f_0,f_{90}) & = F_0+(F_{90}-F_0)(1-({\bf{n}}\cdot{\bf{l}}))^5 \\ f_{90} & = 0.5 + 2\cdot roughness\cdot\cos^2(\theta_d) \end{aligned} fd(l,v)FSchlick(n,l,f0,f90)f90=π1FSchlick(n,l,1,f90)FSchlick(n,v,1,f90)=F0+(F90F0)(1(nl))5=0.5+2roughnesscos2(θd)

其中 θ d \theta_d θd是光線 L L L和half vector h h h的夾角。這個公式考慮到了入射角和出射角以及粗糙度,並且用類似菲涅爾項的公式(cosine項的五次方)來擬合衰減情況。

shader實現:

float FresnelSchlick(float VdotH, float F0, float F90) {
    return F0 + (F90 - F0) * pow(1.0 - VdotH, 5.0);
}
float DiffuseBurley(float NdotV, float NdotL, float LdotH, float roughness) {
    float f90          = 0.5 + 2.0 * roughness * LdotH * LdotH;
    float lightScatter = FresnelSchlick(NdotL, 1.0, f90);
    float viewScatter  = FresnelSchlick(NdotV, 1.0, f90);
    return lightScatter * viewScatter * (1.0 / PI);
}

總結

BRDF作為渲染裡邊最基礎的知識點,發展的已經相對成熟,雖然偶爾也會有一些改進,但是基本上都是在效率與效能之間做權衡。對於基本BRDF公式的選擇,UE4和Disney有著各自不同的邏輯:

Diffuse BRDF Distribution Visibility Fresnel UE4 L a m b e r t GGX G G X J o i n t ( A p p r o x ) Schlick Disney B u r l e y GGX G G X Schlick \begin{array}{c|ccccc} & \text{Diffuse BRDF} & \text{Distribution} & \text{Visibility} & \text{Fresnel} \\ \hline \text{UE4} & Lambert & \text{GGX} & GGX Joint(Approx) & \text{Schlick} \\ \text{Disney} & Burley & \text{GGX} & GGX & \text{Schlick} \end{array} UE4DisneyDiffuse BRDFLambertBurleyDistributionGGXGGXVisibilityGGXJoint(Approx)GGXFresnelSchlickSchlick

斜體表示二者不同的部分。可以看出,UE4選擇的都是高效的模型,而Disney選擇的都是複雜而準確的模型。

個人理解這些差異都是源於UE4和disney應用場景的不同,UE4希望每個模型儘可能高效,因此會拆分開來,針對性優化,比如它單獨設計了針對眼睛的Eye模型,專門渲染毛髮的Hair模型,專門渲染皮膚的subsurface模型等等。而Disney的訴求在於模型的表達力要足夠強,效率反而不那麼重要。

未涉及話題…

本文主要集中在BRDF項的各種實現,順帶介紹了BRDF和微表面理論。還有一些與之相關或更深入,但是沒有涉及到的方向,例如

  1. 輻射度量學基礎;
  2. BSDF,BTDF等BRDF的進階模型;
  3. 各向異性BRDF,subsurface、clearCoat等模型;
  4. 環境光、全域性光照等;

篇幅問題,這些方向也無法展開。行文至此,強推圖形學屆的武林祕籍的目錄——Real-time Rendering,此書目前已經出到第四版了,文末也給出了電子書連結[14]。本文涉及的話題書中都有比較深入、全面的介紹。即使RTR不能滿足你,它還提供了多達1000+篇的參考文獻供學習,畢竟“目錄”,名副其實。

參考資料

  1. Filament文件,Filament是一個Google寫的用在Android上的PBR渲染器,它的文件非常完善,特別每個BRDF的理論和實現。同時也可以參考它的原始碼,對照學習。
  2. Specular BRDF Reference:這個部落格列出了幾大主流specular BRDF的公式,可以作為參考。
  3. Walter et al. 2007, Microfacet models for refraction through rough surfaces
  4. LearningOpenGL: PBR Theory:這也是一個不錯的學習PBR的教材,有一個PBR的OpenGL實現,以及簡單的理論介紹。
  5. Beckmann 1963, The scattering of electromagnetic waves from rough surfaces
  6. Blinn 1977, Models of light reflection for computer synthesized pictures
  7. Hoffman 2013, Background: Physics and Math of Shading
  8. Neumann et al. 1999, Compact metallic reflectance models
  9. Cook and Torrance 1982, A Reflectance Model for Computer Graphics
  10. Kelemen 2001, A microfacet based coupled specular-matte brdf model with importance sampling
  11. Schlick 1994, An Inexpensive BRDF Model for Physically-Based Rendering
  12. Karis 2013, Real Shading in Unreal Engine 4
  13. Smith 1967, Geometrical shadowing of a random rough surface
  14. Real-time Rendering, 4th edition,需要說明的一點是,此書的第四版比第三版增加了很多對BRDF公式的推導和歷史介紹,更具有參考價值。
  15. Brent Burley. 2012. Physically Based Shading at Disney. Physically Based Shading in Film and Game Production, ACM SIGGRAPH 2012 Courses.
  16. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs
  17. SIGGRAPH 2013 Course, Background: Physics and Math of Shading
  18. Generalization of Lambert’s reflectance model
  19. Reflection from Layered Surfaces due to Subsurface Scattering
  20. SIGGRAPH 2013 Course, Physically Based Shading at Disney
  21. PBR Diffuse Lighting for GGX+Smith Microsurfaces

相關文章