PBR中直接光照的实现

PBR中直接光照的实现

重点回顾

  • Radiant Flux:光源单位时间所输出的能量,符号Φ, ϕ = d Q d t \phi = \frac{dQ}{dt} ϕ=dtdQ,单位瓦特(W)

  • Radiant Intensity:光源单位立体角发出的能量, 符号I, I ( ω ) = d ϕ d ω I(\omega) = \frac{d\phi}{d\omega} I(ω)=dωdϕ

  • Irradiance:表面上一点在单位面积上吸收的能量,符号E, E = d ϕ d A E = \frac{d\phi}{dA} E=dAdϕ

  • Radiance:表面上一点在单位立体角、单位面积上发出的能量,符号L, L = d 2 ϕ d ω d A c o s θ L = \frac{d^2\phi}{d\omega dAcos\theta} L=dωdAcosθd2ϕ

  • 反射方程:
    L o ( p , ω o ) = ∫ Ω f r ( p , ω i , ω o ) L i ( p , ω i ) n ⋅ ω i d ω I L_o(p,\omega_o) = \int\limits_\Omega f_r(p,\omega_i,\omega_o)Li(p,\omega_i)n\cdot \omega_id\omega_I Lo(p,ωo)=Ωfr(p,ωi,ωo)Li(p,ωi)nωidωI

  • 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,\omega_o) = \int\limits_\Omega(k_d\frac{c}{π} + k_s\frac{DFG}{4(\omega_o\cdot n)(\omega_i\cdot n)})L_i(p,\omega_i)n\cdot\omega_id\omega_i Lo(p,ωo)=Ω(kdπc+ks4(ωon)(ωin)DFG)Li(p,ωi)nωidωi

直接光照

  • 条件1:一个点光源( 点光源:在所有方向上具有相同的亮度,且可以忽略点光源本身的大小)

  • 条件2:对于表面上一点p,在其半球领域Ω的所有可能入射方向上

  • 结论:只有一个入射方向 ω i \omega_i ωi直接来自于该点光源,即直接光照,其他入射方向吸收的能量都不是直接来自该点光源,即间接光照
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-amGMnTRj-1631346614582)(en-resource://database/1760:1)]

  • 注意:这里的点光源很重要,不能换成其他的光源。假如是体积比较大的光源,那么直接照射到点p的入射方向不只一条;假如是方向光,则只有一个入射方向,且没有衰减因子;假如是聚光灯,他的radiant intensity会根据光源的方向来缩放

  • 对于点光源这个模型,在求直接光照时不需要积分,因为一个光源只有一条光线直接照到点p;对于多个点光源的情况,在计算时只用根据光源的数目,计算每个光源的 L o L_o Lo后,再求和即可

实现直接光照

  • 由上一篇的内容可知,计算光照就是计算radiance( L o L_o Lo),反射方程给我们提供了计算方法,Cook-Torrance反射方程又通过统计学的近似为反射方程在计算机中的实现提供了可行的解决方案
  • 因此我们要做的就是依据Cook-Torrance反射方程计算radiance( L o L_o Lo
  • 表面上任一点的radiance都是来自直接光照和间接光照的共同作用,不管是直接光照还是间接光照都遵循反射方程,这一篇我们只说直接光照
  • 根据上面的分析,计算直接光照时,首先我们可以去掉积分符号,然后可以将反射方程分成前后两部分,前面是BRDF部分,后面是Irradiance部分,其中的Irradiance又被转换成了光线入射方向上的radiance( L i L_i Li),即 L i ( p , ω i ) n ⋅ ω i L_i(p,\omega_i)n\cdot\omega_i Li(p,ωi)nωi
    L o ( p , ω o ) = f r ( p , ω i , ω o ) ⋅ L i ( p , ω i ) n ⋅ ω i L_o(p,\omega_o) = f_r(p,\omega_i,\omega_o)\cdot Li(p,\omega_i)n\cdot \omega_i Lo(p,ωo)=fr(p,ωi,ωo)Li(p,ωi)nωi

Irradiance部分

  • 由上式可知,这一步主要是求radiance( L i L_i Li
  • 对于点光源的直接光照,表面上一点p的radiance就是:光源的radiant intensity在光源到点p的距离上衰减,再按照 n ⋅ ω i n\cdot\omega_i nωi缩放后的值
vec3 intensity = vec3(x.x, x.x, x.x);//光源的rgb值
vec3 wi = normalize(lightPos - fragPos);
float cosTheta = max(dot(N, Wi), 0.0);
 float distance = length(llightPos - fragPos);
 float attenuation = 1.0 / (distance * distance);
float radiance = intensity * attenuation * cosTheta;
radiant intensity为什么用颜色值表示?
  • 在计算机图形学中,radiant flux用RBG表示,即(x.x, x.x, x.x);
  • radiant intensity等于光源在所有出射光线方向上发出的radiant flux
  • 点光源总是具有相同的intensity,因此我们可以将点光源的intensity建模为他的radiant flux,即(x.x, x.x, x.x)

BRDF部分

  • Cook-Torrance 模型的BRDF
    f r ( p , ω i , ω o ) = k d c π + k s D F G 4 ( ω o ⋅ n ) ( ω i ⋅ n ) f_r(p,\omega_i,\omega_o)= k_d\frac{c}{π} + k_s\frac{DFG}{4(\omega_o⋅n)(\omega_i⋅n)} fr(p,ωi,ωo)=kdπc+ks4(ωon)(ωin)DFG
k_d、k_s
  • k d k_d kd入射光线中被漫反射部分的能量所占的比率(光的折射部分)
  • k s k_s ks被镜面反射部分的比率(光的反射部分)
  • 菲涅尔方程描述的是被反射的光线对比光线被折射的部分所占的比率
  • 因此菲涅尔方程的返回值F就可以做为 k s k_s ks,而 k d k_d kd就是1 - k s k_s ks
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;//metallic表示金属度,从金属度纹理中采样得到
  • k d k_d kd要再乘以1.0 - metallic是因为金属不会折射光线,所以没有漫反射;当物体表面是金属时, k d k_d kd应当是0
D
  • 正态分布函数估算:在受到表面粗糙度的影响下,微平面法向与半程向量一致的数量

  • 使用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,\alpha) = \frac{\alpha^2}{\pi((n⋅h)^2(\alpha^2-1)+1)^2} NDFGGXTR(n,h,α)=π((nh)2(α21)+1)2α2

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;
}
  • 上面之所以用粗糙度的平方代替粗糙度,是根据迪士尼公司给出的观察以及后来被Epic Games公司采用的光照模型,光照在正太分布函数中采用粗糙度的平方会让光照看起来更加自然
F
  • 菲涅尔函数用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
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{ 
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); //costheta是视线与半程向量的点积
}
  • F 0 F_0 F0表示如果直接(垂直)观察表面的时候有多少光线会被反射
  • F 0 F_0 F0会因为材质的不同而不同,而且会因为材质是金属而发生变色
  • 在PBR的实现中我们简单地认为绝缘体的 F 0 F_0 F0为0.04,因为这在大多数时候看起来视觉上是正确的,同时我们会特别指定 F 0 F_0 F0遇到金属表面时的值
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);//albedo从反射率纹理中采样得到,其实就是物体的表面颜色
G
  • 几何函数从统计学上近似的求得微平面间相互遮蔽的比率
  • 使用的几何函数是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 d i r e c t = ( α + 1 ) 2 8 k_{direct} = \frac{(\alpha+1)^2}{8} kdirect=8(α+1)2
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;
}
  • 为了有效的估算几何部分G,需要将观察方向(几何遮蔽(Geometry Obstruction))和光线方向向量(几何阴影(Geometry Shadowing))都分别套用几何函数。我们可以使用史密斯法(Smith’s method)来把两者都纳入其中:
    G S m i t h ( 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_{Smith}(n,v,l,k) = G_{SchlickGGX}(n,v,k)\cdot G_{SchlickGGX}(n,l,k) GSmith(n,v,l,k)=GSchlickGGX(n,v,k)GSchlickGGX(n,l,k)
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; 
}
BRDF剩余的项
  • 先看一下BRDF完整的表达式
    k d c π + k s D F G 4 ( ω o ⋅ n ) ( ω i ⋅ n ) k_d\frac{c}{π} + k_s\frac{DFG}{4(\omega_o⋅n)(\omega_i⋅n)} kdπc+ks4(ωon)(ωin)DFG
  • 上式中的c表示颜色,由上一篇的内容可知,他是从反射率纹理(Albedo texture)中采样得到,也可以理解为是从漫反射纹理/颜色纹理中采样得到,最后把采样结果记为albedo
  • ω i \omega_i ωi是光线的入射方向
  • ω o \omega_o ωo表示的是光线出射方向,其实就是观察方向或视线方向
  • 在计算机图形学中计算光照时,不管是光的入射方向还是出射方向,我们都把他们的方向认为是朝外的,即 ω i \omega_i ωi = L = lightPos - fragPos, ω o \omega_o ωo = V = cameraPos - fragPos(注意:由于lightPos,cameraPos都是世界坐标系下的位置,因此也需要把fragPos转换到世界坐标系下)
  • n表面法线

合并

  • 开始我们把反射方程分成了前后两部分,现在把他们合并起来就达到我们的目标了
    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,\omega_o) = (k_d\frac{c}{π} + k_s\frac{DFG}{4(\omega_o⋅n)(\omega_i⋅n)})\cdot Li(p,\omega_i)n\cdot \omega_id\omega_i Lo(p,ωo)=(kdπc+ks4(ωon)(ωin)DFG)Li(p,ωi)nωidωi
float D = DistributionGGX(N, H, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
float G = GeometrySmith(N, V, L, roughness);
vec3 nominator = D * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001;//加0.001是为了避免做分母时值为零
vec3 specular = nominator / denominator;

const float PI = 3.14;
float NdotL = max(dot(N, L), 0.0);
Lo = (kD * albedo / PI + specular) * radiance * NdotL;
  • 需要注意的是:我们并没有把 k s k_s ks乘进反射方程中,这是因为我们已经在specualr中乘了菲涅尔系数F了, k s k_s ks等于F,因此我们不需要再乘一次
  • L o L_o Lo求出来了,但工作并没有结束

不能忽视的地方

加上环境光

vec3 ambient = vec3(xx) * albedo * ao;//ao从环境光遮蔽纹理中采样得到
vec3 color = ambient + Lo;

HDR渲染

  • 我们希望所有光照的输入都尽可能的接近他们在物理上的取值,这样他们的反射率或者说颜色值就会在色谱上有比较大的变化空间
  • 因此 L o L_o Lo作为结果可能会超过1.0,而默认的LDR会将超过1.0的值截断,所以我们采用色调映射使 L o L_o Lo从LDR的值映射为HDR的值
color = color / (color + vec3(1.0));
  • 这里我们采用的色调映射方法为Reinhard 操作,使得我们在伽马矫正后可以保留尽可能多的irradiance变化

Gamma校正

  • PBR要求所有的颜色输入都是在线性空间中,否则就会得到不正常的光照
  • 在线性颜色空间中进行光照计算,那么着色器的最后就要进行Gamma矫正
  • 需要注意的是:一般来说,反射率(albedo)纹理在美术人员创建的时候就已经在sRGB空间了,因此我们需要在光照计算之前先把他们转换到线性空间;环境光遮蔽贴图(ambient occlusion maps)也需要我们转换到线性空间;而金属性(Metallic)和粗糙度(Roughness)贴图大多数都会保证在线性空间中
color = pow(color, vec3(1.0/2.2));
  • HDR和Gamma校正在PBR中非常重要,如果没有这些操作,几乎不可能正确地捕获到光照强度变化的细节,这最终会导致计算不正确,在视觉上非常不自然

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3et0TfAx-1631346614587)(en-resource://database/1765:1)]

  • 上图从上到下球体的金属性从1.0变到0.0, 从左到右球体的粗糙度从0.0变到1.0
  • 金属表面会在场景中看起来有点黑,因为他们没有漫反射
  • 金属表面的镜面高光会带有物体表面的颜色,而非金属的镜面高光只有白色
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值