BRDF理論及shader實現(下)
接上篇:
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(l⋅h)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(n⋅v)(n⋅l)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);
}
注意,這裡的NdotL
,NdotV
等都是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(n⋅h)4χ+(n,h)eα2(n⋅h)2(n⋅h)2−1
這個公式有一個很致命的缺陷,那就是當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] n⋅h∈[0,1]區間內,不是單調遞增的。表現在渲染上,就是在高光最強的中心點會產生一個暗斑。
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(n⋅h)α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=(α22−2),則有:
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)(n⋅h)(α22−2)
這個公式即接近於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,α)=π((n⋅h)2(α2−1)+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,α)=((n⋅h)2(α2−1)+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的影響如下圖所示。
以下是 γ = 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);
}
效果對比
可以看出,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}}) (n⋅l)(n⋅v)放在一起組成一項約分掉,這也是一種優化思路,因為G通常包含這兩個cosine因子。這裡我們約定本文的遮擋項 G G G是不約分 ( n ⋅ l ) ( n ⋅ v ) ({\bf{n}}\cdot{\bf{l}})({\bf{n}}\cdot{\bf{v}}) (n⋅l)(n⋅v)的 G G G。
Implicit
來源[7],有些BRDF公式會忽略遮擋項G,將其跟分母上的 ( n ⋅ l ) ( n ⋅ v ) ({\bf{n}}\cdot{\bf{l}})({\bf{n}}\cdot{\bf{v}}) (n⋅l)(n⋅v)一起忽略掉,這就有了第一個隱式 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)=(n⋅l)(n⋅v)
它的形態大概是,當且僅當視角和光源都垂直於物體表面的時候, 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} GCook−Torrance解決了 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)} GCook−Torrance(l,v,h)=min(1,v⋅h2(n⋅h)(n⋅v),v⋅h2(n⋅h)(n⋅l))
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} GCook−Torrance的效率更高:
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)=(v⋅h)2(n⋅l)(n⋅v)
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(n⋅l,n⋅v)(n⋅l)(n⋅v)
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−(n⋅v)2n⋅v=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.181c21−1.259x+0.396c2,0,if c<1.6if c≥1.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 c≥1.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−(n⋅v)2n⋅v=2−1+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)=(n⋅v)+α2+(1−α2)(n⋅v)22(n⋅v)
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=G1∗G1的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} G2−GGXJoint(l,v,m)=1+Λ(l)+Λ(v)1=(n⋅l)⋅α2+(1−α2)(n⋅v)2+(n⋅v)⋅α2+(1−α2)(n⋅l)22(n⋅v)(n⋅l)
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} G2−GGXJoint(l,v,m)=1+Λ(l)+Λ(v)1≈(n⋅l)⋅(α+(1−α)(n⋅v))+(n⋅v)⋅(α+(1−α)(n⋅l))2(n⋅v)(n⋅l)
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=απ2GSchlick(v)=(n⋅v)(1−k)+kn⋅v
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)=(n⋅v)(1−k)+kn⋅v
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}})
(n⋅v)(n⋅l)項,可以跟
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′=(n⋅v)(n⋅l)G
節省一部分計算。
這樣做不只是出於效能的考慮,也是出於精度的考慮。如果 n ⋅ v {\bf{n}}\cdot{\bf{v}} n⋅v和 n ⋅ l {\bf{n}}\cdot{\bf{l}} n⋅l的乘積接近於0,那麼specular項的分母會非常小,嚴重影響其精度,極端的情況下會在過渡區域產生一道割裂的分界線。下圖展示了 ( n ⋅ v ) ∗ ( n ⋅ l ) ∗ 10 ({\bf{n}}\cdot{\bf{v}})*({\bf{n}}\cdot{\bf{l}})*10 (n⋅v)∗(n⋅l)∗10(左)、未優化時(中)、優化後(右)的效果,可以看出左側兩張圖的分界線非常吻合。優化後則沒有顏色割裂的問題。
效果對比
取roughness = 0.9
,計算球體的遮擋項效果為:
最後一排是常用的幾種方法,差異並不大,邊緣的過渡也比較好。
菲涅爾項 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
1−RF。這裡需要強調的是,radiance定義的是“irradiance每立體角”,它的大小跟角度有關係,因此折射光線的radiance
L
t
L_t
Lt不能簡單用
1
−
R
F
1-R_F
1−RF乘上
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=(1−RF(θ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=(1−RF(θ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的關係曲線:
為了近似這個曲線,採取的策略是利用
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°)+(1−RF(0°))(1−cosθ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°))(1−cosθ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.16⋅reflectance2
其中,
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=baseColor⋅metallic
綜合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+(1−F0)(1−(v⋅h))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+(F90−F0)(1−(v⋅h))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}
ηcgFCook−Torrance(v,h)=1−F01+F0=v⋅h=η2+c2−1=21(g+cg−c)2(1+((g−c)c+1(g+c)c−1)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)=cdiff⋅fd
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+B⋅max(0,cosϕ)⋅sinα⋅tanβ)=1.0−0.5α+0.33α=0.45α+0.09α=max(l⋅n,v⋅n)=min(l⋅n,v⋅n)
其中, ϕ \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 roughness−0的時候, A = 1 , B = 0 A=1, B=0 A=1,B=0,此時Oren-Nayar模型退化為Lambert模型。
下圖[18]展示了真實照片、Lambert模型與Oren-Nayar的對比。
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資料壓縮到一張圖片裡。
如上圖所示,橫軸 θ h \theta_h θh表示half vector h {\bf{h}} h與法向量 n {\bf{n}} n之間的夾角。縱軸表示入射角與 h {\bf{h}} h的夾角。
Disney通過分析MERL BRDF Database,提出了兩個Lambert模型與事實不符的地方:
- diffuse也會有類似於specular的光斑;
- 部分材質的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+(F90−F0)(1−(n⋅l))5=0.5+2⋅roughness⋅cos2(θ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和微表面理論。還有一些與之相關或更深入,但是沒有涉及到的方向,例如
- 輻射度量學基礎;
- BSDF,BTDF等BRDF的進階模型;
- 各向異性BRDF,subsurface、clearCoat等模型;
- 環境光、全域性光照等;
篇幅問題,這些方向也無法展開。行文至此,強推圖形學屆的武林祕籍的目錄——Real-time Rendering,此書目前已經出到第四版了,文末也給出了電子書連結[14]。本文涉及的話題書中都有比較深入、全面的介紹。即使RTR不能滿足你,它還提供了多達1000+篇的參考文獻供學習,畢竟“目錄”,名副其實。
參考資料
- Filament文件,Filament是一個Google寫的用在Android上的PBR渲染器,它的文件非常完善,特別每個BRDF的理論和實現。同時也可以參考它的原始碼,對照學習。
- Specular BRDF Reference:這個部落格列出了幾大主流specular BRDF的公式,可以作為參考。
- Walter et al. 2007, Microfacet models for refraction through rough surfaces
- LearningOpenGL: PBR Theory:這也是一個不錯的學習PBR的教材,有一個PBR的OpenGL實現,以及簡單的理論介紹。
- Beckmann 1963, The scattering of electromagnetic waves from rough surfaces
- Blinn 1977, Models of light reflection for computer synthesized pictures
- Hoffman 2013, Background: Physics and Math of Shading
- Neumann et al. 1999, Compact metallic reflectance models
- Cook and Torrance 1982, A Reflectance Model for Computer Graphics
- Kelemen 2001, A microfacet based coupled specular-matte brdf model with importance sampling
- Schlick 1994, An Inexpensive BRDF Model for Physically-Based Rendering
- Karis 2013, Real Shading in Unreal Engine 4
- Smith 1967, Geometrical shadowing of a random rough surface
- Real-time Rendering, 4th edition,需要說明的一點是,此書的第四版比第三版增加了很多對BRDF公式的推導和歷史介紹,更具有參考價值。
- Brent Burley. 2012. Physically Based Shading at Disney. Physically Based Shading in Film and Game Production, ACM SIGGRAPH 2012 Courses.
- Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs
- SIGGRAPH 2013 Course, Background: Physics and Math of Shading
- Generalization of Lambert’s reflectance model
- Reflection from Layered Surfaces due to Subsurface Scattering
- SIGGRAPH 2013 Course, Physically Based Shading at Disney
- PBR Diffuse Lighting for GGX+Smith Microsurfaces
相關文章
- Unity Shader- UV動畫原理及簡易實現Unity動畫
- 正交分析法設計理論及實踐
- 理論+演算法+實戰,教你如何實現億級流量下的分散式限流演算法分散式
- Unity Shader 實現雨天的水面漣漪效果Unity
- 徹底看懂 PBR/BRDF
- 【數學】向量點乘、叉乘的理論、應用及程式碼實現(C++)點乘C++
- 樹迴歸|理論與演算法實現演算法
- UE4 Shader 編譯以及變種實現編譯
- Transformer註解及PyTorch實現(下)ORMPyTorch
- JVM效能調優與實戰基礎理論篇-下JVM
- Docker 理論簡介及安裝教程Docker
- Redis專案實戰---應用及理論(二)---Redis叢集原理Redis
- 完全開源!快速上手 AI 理論及應用實戰來了AI
- 基於snmp的反射攻擊的理論及其實現反射
- 詳解Diffusion擴散模型:理論、架構與實現模型架構
- games101-3 BRDF101GAM
- Unity Shader-後處理:Bloom全屏泛光UnityOOM
- Tensorflow-keras 理論 & 實戰Keras
- 對偶理論和對偶單純形法——Python實現Python
- 【高併發】如何實現億級流量下的分散式限流?這些理論你必須掌握!!分散式
- Scala隱式轉換理論及進階實踐-Coding技術進階實戰
- R-Drop論文復現與理論講解
- react-redux原始碼分析及實現原型(下)ReactRedux原始碼原型
- 精度與通用性不可兼得,北大華為理論證明低精度下scaling law難以實現
- RocketMQ實戰系列從理論到實戰MQ
- js正則理論與實踐JS
- DevOps 從理論到實踐指南dev
- 理解RESTful:理論與最佳實踐REST
- Flutter 自定義 Widget(理論+實踐)Flutter
- 分散式理論(二) - BASE理論分散式
- 總結下分散式主要理論知識分散式
- Apache歷理 phpStudy下載及使用ApachePHP
- 評論模組 – 後端資料庫設計及功能實現後端資料庫
- 評論模組 - 後端資料庫設計及功能實現後端資料庫
- 理論
- Shader從入門到跑路:螢幕後處理效果
- Shader從入門到跑路:自定義紋理輸入
- 理論與現實,人工智慧“活著”比你想象要困難人工智慧