基于物理的渲染(2):渲染方程

发布时间 2023-12-23 20:14:31作者: 王小于的啦

基于物理的渲染(2):渲染方程

\[L_o(p,ω_o)=∫_Ωf_r(p,ω_i,ω_o)L_i(p,ω_i)n⋅ω_idω_i \]

​ 其中\(L_o\)为P点的出射辐射率,\(f_r\)是P点入射方向到出射方向光的反射比,也叫双向反射分布函数(BRDF),\(L_i\)是P点入射光辐射率。渲染方程说明了P点的出射辐射率,可以通过半球 \(Ω\)内所有入射方向光线辐射率乘以\(f_r\),和余弦值 \(n⋅ω_i\)

5.1 双向反射分布函数(BRDF)

BRDF表示材质性质,用于描述表面反射和次表面反射,分别用作高光反射项和漫反射项。举例来说,如果一个平面拥有完全光滑的表面(比如镜面),那么对于所有的入射光线ωi(除了一束以外)而言BRDF函数都会返回0.0 ,只有一束与出射光线ωo拥有相同(被反射)角度的光线会得到1.0这个返回值。

Cook-Torrance BRDF模型既有漫反射项又有高光反射项,在实时渲染中最为常用:

\[f_r=k_df_{lambert}+k_sf_{cook-torrance} \]

​ 其中,\(k_d\) 为入射光线中被折射部分能量的比例,\(k_s\) 为被反射的比例。Lambertian漫反射模型如下

​ $$f_{lambert}=\frac{c}{\pi}$$

​ c为表面颜色。Cook-Torrance 高光反射模型如下

​ $$f_{cook-torrance}=\frac{DFG}{4(W_on)(w_in)}$$

​ 其中包含三个函数,D表示法线分布函数(Normal Distribution Function),F表示菲涅尔方程(Fresnel Equation),和G表示几何函数(Geometry Function)。

5.2 法线分布函数xua

​ 法线分布函数用于估算微平面中法线和半程向量一致的平面数量,Trowbridge-Reitz GGX公式:

\[NDF_{GGXTR}(n,h,a)=\frac{a^2}{\pi((nh)^2(a^2-1)+1)^2} \]

​ 其中h表示半程向量,a表示表面粗糙度。如下图所示,粗糙度越大,微平面半程向量越分散,表面更加灰暗。

            

5.3 菲涅尔方程

​ 菲涅尔方程描述的是被反射的光线对比被折射的光线所占比例。当光线碰撞到一个表面时,菲涅尔方程会根据观察角度计算得到被反射的光线的比例。菲涅尔方程是个相当复杂的方程式,一般用Fresnel-Schlick近似法求得近似解:

​ $$F_{Schlick}(h,v,F_0)=F_0+(1-F_0)(1-(hv))^5$$

​ 其中 \(F_0\) 表示平面的基础反射率,它通过折射指数计算得到。当视线和表面法线夹角越接近90度,菲涅尔现象越明显,反光越强。Fresnel-Schlick近似法只对电介质有意义,可以通过金属度纹理插值近似得到:

vec3 F0 = vec3(0.04);
F0      = mix(F0, surfaceColor.rgb, metalness);

​ 然后计算得到实际反射率:

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

5.4 几何函数

​ 几何函数表示了微平面相互遮挡的比例,如下图所示分为两种情况:观察方向上的集合遮蔽和光线方向上的几何阴影。

\[![](https://img2023.cnblogs.com/blog/3034184/202312/3034184-20231223200553782-345416595.png) \]

​ 我们可以用Smith函数将两部分放到一起:

​ $$G(n,v,l,k)=G_{sub}(n,v,k)⋅G_{sub}(n,l,k)$$

​ 材料的粗糙度越高,微平面相互遮蔽的概率越高,Schlick-GGX几何函数

​ $$G_{SchlickGGX}(n,v,k)=\frac{n⋅v}{(n⋅v)(1−k)+k}$$

​ 其中k由粗糙度计算而来,直接光照和环境光照参数分别为:

​ $$k_{direct}=\frac{(α+1)^2}{28}$$

​ $$k_{IBL}=\frac{α^2}{2}$$

​ glsl代码如下:

float GeometrySchlickGGX(float NdotV, float k)
{
    float nom   = NdotV;
    float denom = NdotV * (1.0 - k) + k;
	
    return nom / denom;
}
  
float GeometrySmith(vec3 N, vec3 V, vec3 L, float k)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float ggx1 = GeometrySchlickGGX(NdotV, k); // 视线方向的几何遮挡
    float ggx2 = GeometrySchlickGGX(NdotL, k); // 光线方向的几何阴影
	
    return ggx1 * ggx2;
}

​ 这样得到最终的渲染方程:

\[L_o(p,ω_o)=∫_Ω(k_d\frac{c}{\pi}+k_s\frac{DFG}{4(w_on)(w_in)})L_i(p,ω_i)n⋅ω_idω_i \]