背景
最近在從頭開始寫一個引擎, 涉及到渲染部分, 就會寫 PBR(Physically Based Rendering) 相關(guān)的代碼. 這里特地記錄一下個人理解對相關(guān)原理的理解.
PBR 是什么
在 3D 中光照早期相關(guān)的實現(xiàn), 就是 Blinn-Phong , 半蘭伯特 這些了, 也是光線與視線的點積去影響 BaseColor , 最終呈現(xiàn)出光線直照更亮的效果. 這一段時期, 其實是用光線與視線之間的關(guān)系來模擬現(xiàn)實. 從這里可以看出來, 只需要光線與視線的點積, 計算量不大, 又可以比較好地達到現(xiàn)實效果, 所以最受大家的歡迎.
而 PBR 呢, 其實從它的名字(基于物理的渲染)就可以看出來, 它的整體邏輯, 是以現(xiàn)實的物理為基礎(chǔ), 來實現(xiàn)渲染. 這里要說一點, 這個是基于物理, 而不是完全物理(現(xiàn)在有計算也不可能完全按照現(xiàn)實來實現(xiàn)), 所以 PBR 中所有的實現(xiàn)都是以物理為基礎(chǔ), 同時又適應(yīng)當前的硬件而做的渲染實現(xiàn).
PBR 同時也是一種美術(shù)與程序之間交流的紐帶. 大家都遵守同樣的渲染邏輯, 就可以保證設(shè)計效果與最終效果能達到良好的一致性.
PBR 理論
微平面理論
在現(xiàn)實中, 我們認為所有的平面(平整或粗糙), 以顯微鏡或微小尺寸去觀察, 它都不是完全平整的. 所以 PBR 引入了 Roughness (粗糙度) 這個變量.
能量守恒
現(xiàn)實中, 當一束光線照射到平面上, 會發(fā)現(xiàn) reflect(反射) 與 refract(折射) , 反射與拆射的能量總和肯定不會超過入射光線的能量.
// kS 反射系數(shù)
// kD 折射/漫反射系數(shù)
float kD = 1 - kS;
菲涅爾現(xiàn)象
Fresnel 現(xiàn)象也是在現(xiàn)實中很常見的一種現(xiàn)象. 任何物體的反射率都不是固定的, 當光線越接近掠過平面時, 光線的反射率會急劇提高. 舉個現(xiàn)實中的現(xiàn)象, 早上看路面, 會發(fā)現(xiàn)遠處是亮的.
PBR 實現(xiàn)
微平面實現(xiàn)
基于微平面理論, 當光線照在平面上, 反射光會因為平面粗糙度的原因, 會向各種方面反射, 當然其中根據(jù)法線與入射光線對稱的反射方向肯定是占大部分. 所以反射的光線不會是 100% , 那么這里我們就用一個法線分布函數(shù)來, 在反射的能量有多少.
同時微平面中, 還存在幾何遮蔽, 因為平面本身的粗糙度, 而導(dǎo)致光線進入平面上, 不會再反射拆射, 而是被消耗掉了. 或反射光線直接被平面上的凸起擋住.
法線分布函數(shù)的實現(xiàn)
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;
}
幾何遮蔽的實現(xiàn)
// ----------------------------------------------------------------------------
float GeometrySchlickGGX(float NdotV, float roughness) {
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
菲涅爾反射
菲涅爾的本質(zhì)其實就是入射光線與法線的不同夾角會導(dǎo)致不同的反射率.
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
更詳細的理論可以參考這里