查看原文
其他

BRDF太难理解?看看这篇文章或许就懂了

lingzerg 腾讯GWB游戏无界 2022-08-30

编者按 图形学渲染技术对游戏越来越重要,甚至决定了游戏品质的好坏,本文作者从工程学角度切入,带你了解图形学中让人很难理解的BRDF。


本文内容由知乎专栏“番茄的游戏开发世界”提供,转载请征得同意。


在开始学习图形学的时候,看到兰伯特、 blinnPhong 这些小可爱,内心就会想:“也不过就是这样嘛。”


直到慢慢看到BRDF,然后我就产生了这种感觉:



但是到现在我自己手撸了一个BRDF的材质后,我觉得这并不是我的错,从工程的角度看问题,可能比物理的角度切入会更好理解。在工程上完全理解了BRDF,你再去看物理原理和数学,至少比较舒服一点?如果你和之前的我有同样的感觉,希望这篇文章可以帮到你。


BRDF到底是什么?


我们先看小可爱们,兰伯特+BlinnPhong 其实就是 漫反射+高光。


只要你能看懂上面这句话,我保证你可以看懂下面的内容了,但是你要看不懂上面这段,你需要先去学一下基本光照模型。


BRDF可以分为两种实现:直接光照和间接光照


直接光照和间接光照的实现又同时都包含两部分:漫反射和高光(镜面高光)。


这里我们先无视间接光,直接看直接光照。


我们把那一坨劝退公式拿出来:


童靴们不要被他吓到,其实他的意思是这样的:


其实大家只要明白,漫反射+高光 就等于结果就行了,当然严谨的讲,实际上是反射,不过我们先无视这个。


而兰伯特其他的部分,都是为了这两项服务的,为了让大家不晕,我在最后在解释其他细节。


而间接光和直接光的不同就在于漫反射和高光(镜面反射)的计算方式不同,本质还是漫反射+高光。


于是我做了这个表格来像大家展示BRDF的本质:



看到这里,大家应该理解了BRDF就是漫反射+高光(镜面反射)这件事吧!


BRDF为什么是漫反射+高光?


这里我们先慢慢聊直接光的实现。


我们还是小可爱镇楼, Lambert漫反射:

翻译一下:

BlinnPhong:

翻译一下:

这里讲下h,其实h就是半角向量。


普通的Phong不是这样的么:

r是反射方向,而实际上r是要算出来的, r的位置是:

公式是:

带入到Phong里:

一看就很复杂,对不对?


所以大佬们用了一个手段,引入一个V和L之间的向量,我们叫他半角向量:



h等于:

所以用h点积n 代替了r点积l,这样可以避免计算r,h就是这样来的啦。


我们接下来看他们产生的结果就是相加:

上面的公式等于下面这个:

如果上面这一坨你能看懂,下面这一坨应该也可以了。


BRDF直接光漫反射:

在这里C实际上就是albedo。

就是这么简单。


高光 (其实这里是镜面反射,我们在下一部分介绍DGF这三个东西):

最后还是加一起:


是不是顿时感觉也不过如此?


此时加上两个参数ks和kd在看这个公式:

这时我们知道,这里已经基本齐了。


不知道大家注意到没?在漫反射项和高光项前面,有两个 Kd 和 Ks,分别是什么?


我们要从菲涅尔讲起,菲涅尔的公式是:


这个F0 实际上就是菲涅尔系数,unity里用的是0.04,其他材料的F0:



这样我们就容易解释ks了,ks就是F0:

kd实际上是为了保证能量守恒的一个:

而Ks就是F,所以实际使用中我们就省略了,所以最后我们得到这东西了:

然后我们全部再乘上Li:

我们把Li写成人能看懂的东西:

最后因为kd和ks太长了,我们还是把kd、ks代入方程再看一遍这个劝退公式正经有效的部分:


注意: ks 在这里和F重了, 实际上是不需要额外乘一次F的



而外面那个积分,我给大家说,所有的积分就是加一起。


那个积分其实抽出来看长这样:

以后你但凡看见积分就当成加号,不要被他吓到了。


我们以Forward为例,例如在基础光照中, 就要算一个平行光的+间接光,之后Forward Add 就一次pass添加一个光源,等于是累加一次。


所以大家会发现这里实际上就是求和,所以根本不要被积分符号吓到,不就是加一起吗!


来和我一起念: 积分就是加一起!


BRDF直接光照部分的DGF


但是你经过上面一长串的洗礼,估计已经对BRDF有了一个初步的理解。


此时我们就要聊另外一个问题,就是 镜面反射


BRDF公式中有 DGF三个字母,分别代表三种属性:


D - 正太分布函数(也有人喜欢法线分布函数),说人话就是-高光反射。

G - 几何函数,其实就是粗糙度,来确定表面散射量。

F - 菲尼尔,菲涅尔就是菲涅尔反射了,请大家自行百度,这个概念太清晰了。


这三个的公式我就不列了,只要大家可以认为这三个加一起就是求高光反射就好了。


在实际使用中,直接光是没办法实现镜面反射的。


所以如果我们只考虑直接光,这三个东西得到的结果,还真就是高光而已。


D决定你高光的强度和大小;

G决定表面的粗糙度,会影响漫反射的效果和高光的效果,这个值等于1的时候,就等于极其粗糙了;

F就是叠加一个菲涅尔反射。


直接光照中的BRDF就是为了体现物体自身被光照之后的表现,积分是为了在多光照(复杂光照就是环境光了)情况下保持一个正确的物理叠加。


BRDF间接光照部分的DGF


实际上直接光照的BRDF非常简单,易懂,但是其实BRDF真正的意义在于间接光。如果只看直接光其实用不用BRDF都还差不多,间接光其实就是如何正确的镜面透射周围的环境。而间接光照是BRDF中最核心也是最复杂的部分,因为太复杂,我就不放公式了,我主要讲下间接光到底是怎么回事。也有好多文章讲各种公式证明什么的,大家感兴趣就自行搜索吧。


而间接光照中,分为:

球谐函数 - 其实就是漫反射

IBL - 镜面反射的核心


球谐:

简单的说这就是为了算在环境光下的漫反射,所为环境光实际上就是环境贴图。球谐是根据这个环境贴图,生成一个对应的漫反射,这一步是烘焙的时候预计算的。


你可以在下面看IBL的解释:


unity里给我们提供了一个方法来计算球谐: ShadeSH9(float4(i.normal, 1));


IBL实现高光反射:


我们之前说过的镜面反射,就是利用IBL技术实现的。菲涅尔还是我们熟悉的菲涅尔,不同的是,D和G都不一样了,而间接光的DG计算都需要预积分和积分查找表(LUT)参与。


我们先介绍下什么是IBL (image-based lighting),镜面反射说白了,就是和镜子一样反射周围的环境。众所周知,实时渲染里根本不可能真的像光线追踪那样让光线弹来弹去,那这里很自然的,我们把周围的环境预先烘焙到一个贴图上。类似这样:



为什么要这么多张呢...因为G嘛,粗糙度不一样,而金属度影响的是菲涅尔,不同的光滑程度,就直接插值出当前结果了。镜面反射就是这样实现的,大家可以看看效果:


右下角还写错了...右下角是两者都为0


嗯,图上右下角还写错了...右下角是两者都为0。


横轴是光滑度下降的球体,纵轴是金属度下降的球体。左上角和右上角的环境采样纹理肯定不一样。但是左上角和左下角确实一样的,所以这个只和粗糙度有关系。金属度决定的是反射量。所以简单的理解的话,就是把不同的照片映射到物体身上产生镜面效果。反射球照探针基本都是这个原理。


这里最大的坑在于,我们如何生成这个图片,以及因为每片元收到环境光影响的贡献度是不同的所以如何积分如何采样就成了关键。


但是这两块是在太复杂,可以大致看下代码理解下:


const float MAX_REFLECTION_LOD = 4.0;
//根据粗糙度和反射向量生成lod级别对贴图进行采样vec3 R = reflect(-V, N); vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb;
//从Lut中获取预计算vec2 envBRDF = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y);

最后,谢谢你看到这里,如果有问题请留言提出,我也在不断学习。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存