Cook-Torrance BRDF
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_{d}\cdot f_{lambert} + k_{s}\cdot f_{cook-torrance} fr=kd⋅flambert+ks⋅fcook−torrance
其中, k d k_{d} kd表示入射光线中被折射部分的能量所占的比率, k s k_{s} ks是被反射部分的比率
f l a m b e r t = c π f_{lambert}=\frac{c}{\pi} flambert=πc
(c为表面颜色,π用来标准化)
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(\omega_{o}\cdot n)(\omega_{i}\cdot n)} fcook−torrance=4(ωo⋅n)(ωi⋅n)DFG
D:法线分布函数(NDF),计算在rougness影响下取向方向与中间向量一致的微表面数
F:Fresnel方程,描述不同的角度下表面反射光线占比
G:几何函数,描述微表面自成阴影的属性
// main()
// Cook-Torrance BRDF
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);
法线分布函数(NDF)
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\cdot h)^{2}(\alpha^{2}-1)+1)^{2}} NDFGGXTR(n,h,α)=π((n⋅h)2(α2−1)+1)2α2
其中,h为中间向量,α为表面粗糙度的平方(据说这样会让光照看起来更自然)
roughness越小,与中间向量取向一致的微表面越集中在一个小范围里,成为一个很亮的高光区域
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 / max(denom, 0.0000001); // prevent divide by zero for roughness=0.0 and NdotH=1.0
}
Fresnel方程
返回在给定角度下表面光线被反射的百分比
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\cdot v))^{5} Fschlick(h,v,F0)=F0+(1−F0)(1−(h⋅v))5
F0
:0°入射角的反射,基础反射率,表示垂直观察表面时光线的反射率,与材料的性质有关。在PBR金属流中,认为大多数的绝缘体在F0
为0.04
// main()
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0);
}
几何函数
roughness越高,微表面之间相互遮蔽的概率就越高
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\cdot v}{(n\cdot v)(1-k)+k} GschlickGGX(n,v,k)=(n⋅v)(1−k)+kn⋅v
其中,k是roughness基于几何函数是针对直接光照还是IBL光照的重映射
k d i r e c t = ( α + 1 ) 2 8 k_{direct}=\frac{(\alpha+1)^{2}}{8} kdirect=8(α+1)2
k I B L = α 2 2 k_{IBL}=\frac{\alpha^{2}}{2} kIBL=2α2
考虑观察方向v
和光线方向l
对几何函数的影响:几何遮蔽&几何阴影
G ( n , v , l , k ) = G s u b ( n , v , k ) ⋅ G s u b ( n , l , k ) G(n,v,l,k)=G_{sub}(n,v,k)\cdot G_{sub}(n,l,k) G(n,v,l,k)=Gsub(n,v,k)⋅Gsub(n,l,k)
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;
}
计算Lo
:
vec3 numerator = NDF * G * F;
float denominator = 4 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 specular = numerator / max(denominator, 0.001); // prevent divide by zero for NdotV=0.0 or NdotL=0.0
// kS is equal to Fresnel
vec3 kS = F;
// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 1.0 - kS.
vec3 kD = vec3(1.0) - kS;
// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals
// have no diffuse light).
kD *= 1.0 - metallic;
// scale light by NdotL
float NdotL = max(dot(N, L), 0.0);
// add to outgoing radiance Lo
Lo += (kD * albedo / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
最后加上环境光,做色调映射和gamma矫正,得到最终的FragColor
:
// ambient lighting (note that the next IBL tutorial will replace
// this ambient lighting with environment lighting).
vec3 ambient = vec3(0.03) * albedo * ao;
vec3 color = ambient + Lo;
// HDR tonemapping
color = color / (color + vec3(1.0));
// gamma correct
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);