原文地址:Jordan Stevens - Technical Artist
我的小站地址:【翻譯】Write PBR Algorithms In Unity ③
組裝你的PBR Shader Part1:微表面分佈函式(高光函式)
什麼是微表面分佈函式(Normal Distribution Function)?
微表面分佈函式是構成BRDF函式的是哪個核心要素之一。NDF統計學上描述了微表面發現的分佈情況。在我們的使用當中,這個函式作為權重函式來縮放高光以及反射。將NDF看做表面的基礎幾何屬性是非常重要的。讓我們開始在我們的Shader中加一些演算法,這樣我們就可以看到NDF是怎麼對我們的效果產生影響的了。
首先我們要做的就是編寫一個演算法,為了讓這個演算法可見,我們將我們返回的浮點數重寫。
float3
SpecularDistribution
=
specColor
;
//the algorithm implementations will go here
return
float4
(
float3
(
1
,
1
,
1
)
*
SpecularDistribution
。
rgb
,
1
);
之後的一些計算環節也會按照這樣的形式出現,當你在計算環節中寫完了演算法,你會在當前位置寫上演算法。如果你要寫一個新的演算法的時候簡單將其註釋掉就完事兒了,結果只會基於沒有註釋的演算法。別擔心,我們會將這種做法改進,以在Unity中更輕鬆地切換不同的演算法,再也不用進行註釋惹。
讓我們從最簡單的blinn-phong演算法開始:
Blinn-Phong NDF
Blin近似算出了Phong高光,並將其作為Phong高光模型的最佳化版。
Blin透過半形向量與法向量點乘顯然比計算光的反射方向要快。這個演算法算出來的結果要比Phong更加柔和。
float
BlinnPhongNormalDistribution
(
float
NdotH
,
float
specularpower
,
float
speculargloss
){
float
Distribution
=
pow
(
NdotH
,
speculargloss
)
*
specularpower
;
Distribution
*=
(
2
+
specularpower
)
/
(
2
*
3。1415926535
);
return
Distribution
;
}
Blin-Phong演算法並不符合物理規律,但是依舊可以產生可信的高光,用於一些特殊的美術用途。將上面的演算法放入演算法階段,並且將下面的程式碼放入到片元階段。
SpecularDistribution
*=
BlinnPhongNormalDistribution
(
NdotH
,
_Glossiness
,
max
(
1
,
_Glossiness
*
40
));
如果你往你的shader中輸入一個光滑度你會看到物體會有一個高光來表現其法線分佈,而剩下的地方都是黑色。這樣我們可以更輕鬆地除錯我們的shader。上方程式碼中的40只是我能夠讓函式提供更大的範圍,但是對每個人來說並不是一個確定的值。
Phong NDF
float
PhongNormalDistribution
(
float
RdotV
,
float
specularpower
,
float
speculargloss
){
float
Distribution
=
pow
(
RdotV
,
speculargloss
)
*
specularpower
;
Distribution
*=
(
2
+
specularpower
)
/
(
2
*
3。1415926535
);
return
Distribution
;
}
Phong演算法是另外一種非物理演算法,但是它產生比Blin近似出更好的效果。下面是實現的例子:
SpecularDistribution *= PhongNormalDistribution(RdotV, _Glossiness, max(1,_Glossiness * 40));
為了與Blin-Phong進行對比,不要過於在意40這個值。
Beckman NDF
Beckman微表面分佈函式是一個更加高階的函式,並且將我們的粗糙度帶入計算當中。粗糙度和法線與半形向量的點乘共同對物體表面的法線分佈進行近似。
float
BeckmannNormalDistribution
(
float
roughness
,
float
NdotH
)
{
float
roughnessSqr
=
roughness
*
roughness
;
float
NdotHSqr
=
NdotH
*
NdotH
;
return
max
(
0。000001
,(
1。0
/
(
3。1415926535
*
roughnessSqr
*
NdotHSqr
*
NdotHSqr
))
*
exp
((
NdotHSqr
-
1
)
/
(
roughnessSqr
*
NdotHSqr
)));
}
該函式的實現非常簡單。
SpecularDistribution *= BeckmannNormalDistribution(roughness, NdotH);
在Beckman光照模型處理物體表面的時候需要注意的一點是。你可以從上面的影象中看出,Beckman光照模型隨著光滑度的變化在慢慢變化,直到一個高光點聚攏在了一個確定的點上。當表面的光滑度增加的時候反射高光聚攏在了一起,產生了非常不錯的從粗糙到光滑的藝術效果。這種表現效果在早期的粗糙材質中非常受歡迎,對於塑膠中光滑度的渲染也非常不錯。
Gaussian NDF
Gaussian NDF不像其他的一些計算模型那樣受歡迎,因為它傾向於渲染出更柔和的反射高光,而不是渲染光滑度更高的材質。對於藝術效果來說是可行的,但是對於它是否更符合自然物理還存在著爭論。
float
GaussianNormalDistribution
(
float
roughness
,
float
NdotH
)
{
float
roughnessSqr
=
roughness
*
roughness
;
float
thetaH
=
acos
(
NdotH
);
return
exp
(
-
thetaH
*
thetaH
/
roughnessSqr
);
}
這個演算法的實現與其他的NDF非常相似,都是依賴於粗糙度以及表面發現於半角向量的點乘。
SpecularDistribution
*=
GaussianNormalDistribution
(
roughness
,
NdotH
);
GGX NDF
GGX是最受歡迎的光照模型之一,當前大多數的BRDF函式都依賴於它的實現。GGX是由Bruce Walter和Kenneth Torrance發表出來的。在他們[論文]中的許多演算法都是目前大量被使用到的。
float
GGXNormalDistribution
(
float
roughness
,
float
NdotH
)
{
float
roughnessSqr
=
roughness
*
roughness
;
float
NdotHSqr
=
NdotH
*
NdotH
;
float
TanNdotHSqr
=
(
1
-
NdotHSqr
)
/
NdotHSqr
;
return
(
1。0
/
3。1415926535
)
*
sqr
(
roughness
/
(
NdotHSqr
*
(
roughnessSqr
+
TanNdotHSqr
)));
}
SpecularDistribution
*=
GGXNormalDistribution
(
roughness
,
NdotH
);
The specular highlight of the GGX Algorithm is very tight and hot, while still maintaining a smooth distribution across the surface of our ball。 這是一個簡單的例子來解釋為什麼GGX演算法經常將反射率變換為金屬度來表示。
Trowbridge-Reitz NDF
Trowbridge-Reitez方法在GGX相同的論文中被髮表出來。主要可見的區別就是物體邊緣的高光比GGX的方式產生的銳利邊緣要更加柔和了。
float
TrowbridgeReitzNormalDistribution
(
float
NdotH
,
float
roughness
){
float
roughnessSqr
=
roughness
*
roughness
;
float
Distribution
=
NdotH
*
NdotH
*
(
roughnessSqr
-
1。0
)
+
1。0
;
return
roughnessSqr
/
(
3。1415926535
*
Distribution
*
Distribution
);
}
類似地,Trobridge-reitz方法也依賴於粗糙度與半形向量和法線的點乘。
SpecularDistribution
*=
TrowbridgeReitzNormalDistribution
(
NdotH
,
roughness
);
Trowbridge-Reitz Anisotropic NDF
各向異性的NDF方程產生了各向異性的表面描述。它允許我們做出有各項異性效果的表面。這個函式我們需要新增一個新的屬性來確定各向異性的強度。
我們的屬性
_Anisotropic
(
“Anisotropic”
,
Range
(
-
20
,
1
))
=
0
我們的變數
float
_Anisotropic
;
float
TrowbridgeReitzAnisotropicNormalDistribution
(
float
anisotropic
,
float
NdotH
,
float
HdotX
,
float
HdotY
){
float
aspect
=
sqrt
(
1。0
h
-
anisotropic
*
0。9
h
);
float
X
=
max
(
。001
,
sqr
(
1。0
-
_Glossiness
)
/
aspect
)
*
5
;
float
Y
=
max
(
。001
,
sqr
(
1。0
-
_Glossiness
)
*
aspect
)
*
5
;
return
1。0
/
(
3。1415926535
*
X
*
Y
*
sqr
(
sqr
(
HdotX
/
X
)
+
sqr
(
HdotY
/
Y
)
+
NdotH
*
NdotH
));
}
各向異性的材質與各向同性的材質的其中一個區別是是否需要使用切線或者副法線來描述表面。上圖表現出來的是各向異性為1時的效果。
SpecularDistribution
*=
TrowbridgeReitzAnisotropicNormalDistribution
(
_Anisotropic
,
NdotH
,
dot
(
halfDirection
,
i
。
tangentDir
),
dot
(
halfDirection
,
i
。
bitangentDir
));
Ward AnisoTropic NDF
Ward方式的各向異性BRDF函式產生了與上一個方式不同的效果。其高光變得更加柔和,隨著光滑度的降低高光液消失得更快了。
float
WardAnisotropicNormalDistribution
(
float
anisotropic
,
float
NdotL
,
float
NdotV
,
float
NdotH
,
float
HdotX
,
float
HdotY
){
float
aspect
=
sqrt
(
1。0
h
-
anisotropic
*
0。9
h
);
float
X
=
max
(
。001
,
sqr
(
1。0
-
_Glossiness
)
/
aspect
)
*
5
;
float
Y
=
max
(
。001
,
sqr
(
1。0
-
_Glossiness
)
*
aspect
)
*
5
;
float
exponent
=
-
(
sqr
(
HdotX
/
X
)
+
sqr
(
HdotY
/
Y
))
/
sqr
(
NdotH
);
float
Distribution
=
1。0
/
(
4。0
*
3。14159265
*
X
*
Y
*
sqrt
(
NdotL
*
NdotV
));
Distribution
*=
exp
(
exponent
);
return
Distribution
;
}
就像Trowbridge-Reitz函式,Ward函式也需要切線與副法線(切線)資料,而且依賴於光與法線的點乘和視覺方向與法線的點乘。
SpecularDistribution
*=
WardAnisotropicNormalDistribution
(
_Anisotropic
,
NdotL
,
NdotV
,
NdotH
,
dot
(
halfDirection
,
i
。
tangentDir
),
dot
(
halfDirection
,
i
。
bitangentDir
));