【LearnOpenGL 学习笔记】PBR

本文深入解析Cook-Torrance BRDF模型,涵盖法线分布函数、Fresnel方程和几何函数,探讨如何计算光线反射,以及粗糙度对表面效果的影响。从NDF的Trowbridge-Reitz GGX公式到Fresnel Schlick方程,以及Smith几何函数的应用,带你走进真实材质的光照表现。
摘要由CSDN通过智能技术生成

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=kdflambert+ksfcooktorrance

其中, 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)} fcooktorrance=4(ωon)(ωin)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,α)=π((nh)2(α21)+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+(1F0)(1(hv))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)=(nv)(1k)+knv

其中,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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值