PBR中BRDF的实现

PBR中BRDF的实现

白说

  • PBR, Physically Based Rendering, 基于物理的渲染,在一定程度上基于物理原理的渲染技术
  • PBR仍是对现实世界的一种近似
  • BRDF,Bidirectional Reflectance Distribution Function,双向反射分布函数,是PBR的重要组成部分
  • 想要理解BRDF,首先要理解辐射度量学中的四个概念

辐射度量学(Radiometry)

  • 主要理解radiant flux(辐射通量)、radiant intensity(辐射强度)、irradiance(辐射照度)和radiance(辐射率)这四个概念;对应的中文名字如果感觉蹩脚,可以忽略

Radiant flux

  • 一个光源单位时间所输出的能量,符号Φ, Φ = d Q d t Φ = \frac{dQ}{dt} Φ=dtdQ,单位瓦特(W)
  • 单位时间的能量其实就是功率,从他的单位是瓦特也能看出来,因此他表示的就是功率
  • 但是,后面三个概念提到的“能量”指的都是这个功率,而非真正的能量——单位焦耳(Q)。这种称呼只是是习惯上的叫法
  • 为什么用“功率”代替“能量”?不仅不严谨,而且还习惯性的不严谨
  • 因为在研究光的能量传播时,不仅与空间相关,而且与时间相关。在这里,为了降低问题的复杂度,只考虑空间;而在时间这个维度上,把能量单位化就可以不用再考虑时间了;将能量在时间上单位化,就变成了功率,所以“功率”就成了辐射度量学中的“能量”

Radiant Intensity

  • 一个光源单位立体角发出的能量, 符号I, I ( ω ) = d Φ d ω I(ω) = \frac{dΦ}{dω} I(ω)=dωdΦ,这里的“能量”指的就是上面说的Radiant flux
立体角(Solid Angle)
  • 如果不知道立体角是个啥,可以先看看圆心角
圆心角
  • 圆心角θ等于弧长除以半径, θ = l r θ = \frac{l}{r} θ=rl
  • 圆心角表示的是二维空间中的一个角度
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ncT2rvo4-1630740160314)(en-resource://database/1737:1)]
  • 类比圆心角,但把空间变换到三维,立体角ω等于球面上的一块面积除以半径的平方, ω = A r 2 ω = \frac{A}{r^2} ω=r2A(A表示单位球上截面面积)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LSTnNBjI-1630740160319)(en-resource://database/1739:1)]
  • 立体角表示的是三维空间中的角度
Irradiance
  • 表面上一点在单位面积上吸收的能量,符号E,这里的能量也是Radiant flux
  • E = d Φ d A E = \frac{dΦ}{dA} E=dAdΦ
    在这里插入图片描述
Radiance
  • 表面上一点p,p在单位立体角、单位面积上发出的能量,符号L,这里的能量还是Radiant flux
    L ( p , ω ) = d 2 Φ ( p , ω ) d ω d A c o s θ L(p,ω) = \frac{d^2Φ(p,ω)}{dωdAcosθ} L(p,ω)=dωdAcosθd2Φ(p,ω)
  • 这个式子的意思是:在一个表面p点处,沿ω方向上发出的能量等于flux除以立体角ω、再除以p点处的有效面积;这个有效面积是指光线垂直表面的部分,所以用面积A乘以光线与表面法线的夹角θ
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n5Q3asVr-1630740160327)(en-resource://database/1743:1)]
  • 他用来描述某一方向上发射出去的能量
  • 他可以用来表示一个拥有Radiant flux为Φ的光源,在单位立体角ω、单位有效面积A上的能量
对比Irradiance与Radiance
  • Radiance 比 Irradiance 多了方向性,Radiance 表示 Irradiance 在面积dA的某个方向上吸收到的能量,所以 Irradiance 再除以单位立体角就是 Radiance,即 L ( p , ω ) = d E ( p ) d ω c o s θ L(p,ω) = \frac{dE(p)}{dωcosθ} L(p,ω)=dωcosθdE(p)
  • Irradiance 表示在单位面积dA上吸收的各个方向的能量
  • Radiance 则表示在单位面积dA的某一个方向dω上吸收的能量,即Irradiance的微分(由光的可逆性,理解成“吸收”或者“发出”都可以)
  • 所以: d E ( p , ω ) = L i ( p , ω ) d ω c o s θ dE(p,ω) = L_i(p,ω)dωcosθ dE(p,ω)=Li(p,ω)dωcosθ L i L_i Li表示在 ω i ω_i ωi方向上的Radiance)
  • 两边在半球域内对ω积分: E ( p ) = ∫ H 2 L i ( p , ω ) c o s θ d ω E(p) = \int\limits_{H^2}L_i(p,ω)cosθdω E(p)=H2Li(p,ω)cosθdω(这个式子后面会用到)

引入BRDF

  • 当一个光源照射一点p,点p处即会吸收能量也会反射能量,那么到底有多少能量被反射了,为了衡量这个量,从而引入了BRDF(双向反射分布函数 )

  • 当一个光源照射一点p,在某一方向上使p点吸收了一些能量,然后点p又在另一方向反射出了一些能量,衡量这个物体表面对反射的贡献程度就是BRDF的意义

  • 在表面一点p处,沿某一个入射方向ω吸收的能量,可以翻译成Irradiance(E§)的微分,即dE(p,ω),又由上面的内容可得 d E ( p , ω ) = L i ( p , ω ) d ω c o s θ dE(p,ω) = L_i(p,ω)dωcosθ dE(p,ω)=Li(p,ω)dωcosθ

  • 从某一方向 ω i ω_i ωi吸收能量后,这些能量的部分又在另一个方向 ω r ω_r ωr发射出的能量是Radiance(L(p,ω))的微分,即 d L r dL_r dLr,(注意Radiance描述的是从各个方向吸收能量,然后从发射方向发射出的能量;从某一方向吸收能量就是Radiane的微分)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ZZ9rbf4-1630740160329)(en-resource://database/1745:1)]

  • 所以BRDF就可以表示成
    f r ( ω i − > ω r ) = d L r ( ω r ) d E i ( ω i ) = d L r ( ω r ) d L i ( ω i ) c o s θ i d ω i f_r(ω_i->ω_r) = \frac{dL_r(ω_r)}{dE_i(ω_i)} = \frac{dL_r(ω_r)}{dL_i(ω_i)cosθ_idω_i} fr(ωi>ωr)=dEi(ωi)dLr(ωr)=dLi(ωi)cosθidωidLr(ωr)

  • 简单地说,BRDF描述了表面对光反射地贡献程度

  • 用一个词表达BRDF的意义就是“材质”,说了这么多,BRDF其实就相当于材质

引入反射方程

  • 由BRDF可以知道:光线从某一方向打到表面,表面吸收这些能量,然后又在另一方向将吸收的部分能量反射出去
  • 在实际情况中,我们更想知道表面在反射方向上总共发出了多少能量,这怎么计算?
  • 由上面的BRDF变形后,就很简单,在半球空间中其积分即可
    d L r ( ω r ) = f r ( ω i − > ω r ) d L i ( ω i ) c o s θ i d ω i dL_r(ω_r) = f_r(ω_i->ω_r)dL_i(ω_i)cosθ_idω_i dLr(ωr)=fr(ωi>ωr)dLi(ωi)cosθidωi
    L r ( p , ω r ) = ∫ H 2 f r ( p , ω i − > ω r ) L i ( p , ω i ) c o s θ i d ω i L_r(p,ω_r) = \int_{H^2}f_r(p,ω_i->ω_r)L_i(p,ω_i)cosθ_idω_i Lr(p,ωr)=H2fr(p,ωi>ωr)Li(p,ωi)cosθidωi
  • 上式就是反射方程(The Reflectance Equation),由他可知:表面吸收周围的能量后,通过BRDF损失掉一部分,最后得到在反射方向上发出的能量
  • 自此,打开PBR大门的钥匙就有了,因为PBR遵循的是物理原理,而反射方程是根据物理原理总结出来的

实现BRDF

  • 实现PBR首先要实现BRDF,现在已经有很好几种BRDF模型,但几乎所有实时渲染管线使用的都是Cook-Torrance BRDF模型
  • Cook-Torrance BRDF兼有漫反射和镜面反射两个部分:
    f r = k d f l a m b e r t + k s f c o o k − t o r r a n c e f_r = k_df_{lambert} + k_sf_{cook-torrance} fr=kdflambert+ksfcooktorrance
  • f l a m b e r t f_{lambert} flambert漫反射部分(当一束光线碰到物体表面时,它会被分离成折射部分和反射部分,折射部分也就是漫反射,反射部分就是我们所说的镜面)
  • k d k_d kd入射光线中被漫反射部分的能量所占的比率
  • f c o o k − t o r r a n c e f_{cook-torrance} fcooktorrance镜面反射部分
  • k s k_s ks被镜面反射部分的比率

漫反射部分

  • f l a m b e r t f_{lambert} flambert表示的是漫反射部分,被称为Lambertian漫反射
    f l a m b e r t = c π f_{lambert} = \frac{c}{π} flambert=πc
  • c表示表面颜色
  • 除以π是为了对漫反射光进行标准化,因为前面含有BRDF的积分方程是受π影响的

镜面反射部分

f c o o k − t o r r a n c e = D F G 4 ( ω o ⋅ n ) ( ω i ⋅ n ) f_{cook-torrance} = \frac{DFG}{4(ω_o⋅n)(ω_i⋅n)} fcooktorrance=4(ωon)(ωin)DFG

  • 字母D,F与G分别代表着一种类型的函数,各个函数分别用来近似的计算出表面反射特性的一个特定部分
  • 每一种函数都是用来估算相应的物理参数的,而且用来实现相应物理机制的每种函数都有不止一种形式,下面使用Epic Games公司在UE4中所使用的函数
正态分布函数(Normal Distribution Function)
  • 首先要再次引入一个新的概念,微平面模型
  • 所有的PBR技术都基于微平面理论。这项理论认为,达到微观尺度之后任何平面都可以用被称为微平面的细小镜面来进行描绘。根据平面粗糙程度的不同,这些细小镜面的法向可以相当不一致:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jhO6E91V-1630740160331)(en-resource://database/1749:1)]
  • 正态分布函数就是为了估算:在受到表面粗糙度的影响下,微平面法向与半程向量一致的数量
  • 正态分布函数是从统计学上近似的表示了与半程向量h方向一致的微平面的比率
  • 使用Trowbridge-Reitz GGX现实:
    N D F G G X T R ( n , h , α ) = α 2 π ( ( n ⋅ h ) 2 ( α 2 − 1 ) + 1 ) 2 NDF_{GGXTR}(n,h,α) = \frac{α^2}{π((n⋅h)^2(α^2-1)+1)^2} NDFGGXTR(n,h,α)=π((nh)2(α21)+1)2α2
  • α表示表面粗糙度
  • h表示用来与平面上微平面做比较用的半程向量
  • 当粗糙度很低(也就是说表面很光滑)的时候,与半程向量方向一致的微平面会高度集中在一个很小的半径范围内。由于这种集中性,NDF最终会生成一个非常明亮的斑点
float D_GGX_TR(vec3 N, vec3 H, float a)
{
    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;
}
菲涅尔方程(Fresnel Rquation)
  • 描述的是被反射的光线对比光线被折射的部分所占的比率,这个比率会随着我们观察的角度不同而不同
  • 当光线碰撞到一个表面的时候,菲涅尔方程会根据观察角度告诉我们被反射的光线所占的百分比。利用这个反射比率和能量守恒原则,我们可以直接得出光线被折射的部分以及光线剩余的能量
  • 菲涅尔方程是一个相当复杂的方程式,不过可以用Fresnel-Schlick近似法求得近似解:
    F S c h l i c k ( h , v , F 0 ) = F 0 + ( 1 − F 0 ) ( 1 − ( h ⋅ v ) ) 5 F_{Schlick}(h,v,F_0) = F_0 + (1 - F_0)(1 - (h⋅v))^5 FSchlick(h,v,F0)=F0+(1F0)(1(hv))5
  • F 0 F_0 F0表示平面的基础反射率
  • 电介质材质表面的基础反射率都不会高于0.17,金属材质表面的基础反射率起点更高一些并且(大多)在0.5和1.0之间变化
  • 对于导体或者金属表面而言基础反射率一般是带有色彩的(这种现象只能在金属表面观察到),这也是为什么 F 0 F_0 F0要用RGB三原色来表示的原因
  • 金属工作流的概念——由金属表面这些和电介质表面相比所独有的特性引出,也就是我们需要额外使用一个被称为金属度(Metalness)的参数来参与编写表面材质。金属度用来描述一个材质表面是金属还是非金属的。
vec3 F0 = vec3(0.04);
F0 = mix(F0, surfaceColor.rgb, metalness);//如果是金属表面的话就需要对基础反射率添加色彩
  • 对于菲涅尔函数总结两点:
  • 我们为大多数电介质表面定义了一个近似的基础反射率。F0取最常见的电解质表面的平均值,这又是一个近似值。不过对于大多数电介质表面而言使用0.04作为基础反射率已经足够好了,而且可以在不需要输入额外表面参数的情况下得到物理可信的结果
  • 基于金属表面特性,我们要么使用电介质的基础反射率要么就使用 F 0 F_0 F0来作为表面颜色。因为金属表面会吸收所有折射光线而没有漫反射,所以我们可以直接使用表面颜色纹理来作为它们的基础反射率
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);//cosTheta是表面法向量n与观察方向v的点乘的结果。
}
几何函数(Geometry Function)
  • 描述了微平面自成阴影的属性
  • 当一个平面相对比较粗糙的时候,平面表面上的微平面有可能挡住其他的微平面从而减少表面所反射的光线
  • 从统计学上近似的求得了微平面间相互遮蔽的比率,这种相互遮蔽会损耗光线的能量
  • 与NDF类似,几何函数采用一个材料的粗糙度参数作为输入参数,粗糙度较高的表面其微平面间相互遮蔽的概率就越高
  • 使用的几何函数是GGX与Schlick-Beckmann近似的结合体,因此又称为Schlick-GGX:
    G S c h l i c k G G X ( n , v , k ) = n ⋅ v ( n ⋅ v ) ( 1 − k ) + k G_{SchlickGGX}(n,v,k) = \frac{n⋅v}{(n⋅v)(1-k)+k} GSchlickGGX(n,v,k)=(nv)(1k)+knv
  • k是α基于直接光照或者IBL光照的重映射(Remapping) :
    k d i r e c t = ( α + 1 ) 2 8 k_{direct} = \frac{(α+1)^2}{8} kdirect=8(α+1)2
    k I B L = α 2 2 k_{IBL} = \frac{α^2}{2} kIBL=2α2
  • IBL,Image based lighting,基于图像的光照(之后会有专题报道。。。)
  • 为了有效的估算几何部分,需要将观察方向(几何遮蔽(Geometry Obstruction))和光线方向向量(几何阴影(Geometry Shadowing))都考虑进去。我们可以使用史密斯法(Smith’s method)来把两者都纳入其中:
    G ( n , v , l , k ) = G S c h l i c k G G X ( n , v , k ) G S c h l i c k G G X ( n , l , k ) G(n,v,l,k) = G_{SchlickGGX}(n,v,k)G_{SchlickGGX}(n,l,k) G(n,v,l,k)=GSchlickGGX(n,v,k)GSchlickGGX(n,l,k)
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;
}

Cook-Torrance反射方程

  • 将上面的内容总结一下,得到Cook-Torrance反射方程:
    L o ( p , ω o ) = ∫ Ω ( k d c π + k s D F G 4 ( ω o ⋅ n ) ( ω i ⋅ n ) ) L i ( p , ω i ) n ⋅ ω i d ω i L_o(p,ω_o) = \int\limits_Ω(k_d\frac{c}{π} + k_s\frac{DFG}{4(ω_o⋅n)(ω_i⋅n)})L_i(p,ω_i)n⋅ω_idω_i Lo(p,ωo)=Ω(kdπc+ks4(ωon)(ωin)DFG)Li(p,ωi)nωidωi
  • n ⋅ ω i n⋅ω_i nωi c o s θ i cosθ_i cosθi,入射光线的方向 ω i ω_i ωi与表面法相n的点乘
  • BRDF的实现即可得到解决???并不是

参数的输入

  • 在Cook-Torrance BRDF模型中提到了表面颜色、法线、粗糙度、反射率、金属度等一些参数,这些参数都会提前被制作成相应的纹理,以至于我们可以逐片段的控制物体表面每个点的属性
各种纹理
  • 反射率(Albedo)纹理:指定表面颜色或者反射率。他与漫反射纹理类似,不同的是漫反射纹理中常常包含一些细小的阴影或者深色的裂纹;而反射率纹理中是不会有这些东西的,只包含表面的颜色
  • AO纹理:环境光遮蔽纹理,为表面和周围潜在的几何图形指定了一个额外的阴影因子。弥补反射率纹理的不足
  • 金属度(Metallic)纹理:逐纹素的指定物体是不是金属质地
  • 粗糙度(Roughness)纹理:逐纹素指定表面粗糙度。粗糙的表面会得到更宽阔更模糊的镜面反射,而一个比较光滑的表面则会得到集中而清晰的镜面反射
k d 、 k s k_d、k_s kdks
  • 菲涅尔方程的返回值F可以直接当作 k s k_s ks, 因为他们都是表示的意义一样,镜面反射在所有打在物体表面上的光线的贡献程度
  • 有了 k s k_s ks就可以很容易计算折射的比值 k d k_d kd,因为我们假设光线打到物体表面后除了反射就是折射,忽略能量损失
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;//这一步是因为只有非金属材质才有漫反射部分,metallic从金属度纹理中采样得到
当涉及到手写 PBR(Physically Based Rendering,基于物理的渲染)材质着色器时,以下是一个简单的示例,用于实现标准的 PBR 材质模型: ```c // 着色器属性 uniform vec3 uAlbedo; // 反射率颜色 uniform float uRoughness; // 粗糙度 uniform float uMetallic; // 金属度 // 表面法线和视图方向 in vec3 vNormal; // 顶点法线 in vec3 vViewDir; // 视图方向 // 光源属性 uniform vec3 uLightDir; // 光源方向 uniform vec3 uLightColor; // 光源颜色 void main() { // 计算使用 Cook-Torrance BRDF 模型的光照 vec3 N = normalize(vNormal); vec3 V = normalize(vViewDir); vec3 L = normalize(uLightDir); vec3 H = normalize(V + L); // 半向量 // 计算漫反射项 float NdotL = max(dot(N, L), 0.0); vec3 diffuse = uAlbedo * NdotL; // 计算镜面反射项(使用 Cook-Torrance BRDF) float NdotH = max(dot(N, H), 0.0); float roughnessSquared = uRoughness * uRoughness; float F0 = mix(0.04, 1.0, uMetallic); vec3 specular = vec3(0.0); if (NdotL > 0.0) { float NdotV = max(dot(N, V), 0.001); float NdotH2 = NdotH * NdotH; float alpha = roughnessSquared; float D = exp((NdotH2 - 1.0) / (alpha * NdotH2)); float F = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0); vec3 F_specular = vec3(F); float G = min(1.0, min(2.0 * NdotH * NdotV / NdotL, 2.0 * NdotH * NdotL / NdotV)); vec3 G_specular = vec3(G); specular = D * F_specular * G_specular / (4.0 * NdotL * NdotV); } // 最终颜色 = 漫反射 + 镜面反射 vec3 finalColor = diffuse + specular; // 输出颜色 gl_FragColor = vec4(finalColor, 1.0); } ``` 这只是一个简单的 PBR 材质着色器示例,实际可能还包含其他的贴图、光照模型或者其他参数。这段代码可以作为一个起点,你可以根据需要进行修改和扩展。请记住,PBR 材质着色器通常需要更多的计算和参数,以便更真实地模拟光照和材质的物理特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值