JamesB 发表于 2021-7-14 14:01

我关于Unity中BRDF的理解,如果你实在看不懂可以试试这篇文章

大家如果看见任何错误请使劲喷! 我随后改正, 我们一起成长学习


在开始学习图形学的时候, 看到兰伯特, blinnPhong 这些小可爱.
内心就会想,也不过就是这样嘛.
Easy
直到慢慢看到BRDF,
然后我就产生了这种感觉:




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


BRDF到底是什么?



我们先看小可爱们
兰伯特+BlinnPhong 其实就是 漫反射+高光
只要你能看懂上面这句话,我保证你可以看懂下面的内容了,
但是你要看不懂上面这段,你需要先去学一下基本光照模型.
而BRDF可以分为两种实现: 直接光照和间接光照
直接光照和间接光照的实现又同时都包含两部分: 漫反射和高光(镜面高光)
这里我们先无视间接光,直接看直接光照
我们把那一坨劝退公式拿出来:

https://www.zhihu.com/equation?tex=L_0%28p%2Cw_0%29%3D%5Cint_%7B%5COmega%7D%5E%7B%7Dk_d+%5Cfrac%7Bc%7D%7B%CF%80%7D%2Bk_s+%5Cfrac%7BDGF%7D%7B4%28v+%5Ccdot+n%29%28l_%7Bdir%7D+%5Ccdot+n%29%7DL_i%28p%2Cw_i%29%28w_i+%5Ccdot+n%29dw_i
童靴们不要被他吓到, 其实他的意思是这样的:


其实大家只要明白, 漫反射+高光 就等于结果就行了,当然严谨的讲,实际上是反射,不过我们先无视这个
而兰伯特其他的部分 都是为了这两项服务的,为了让大家不晕,我在最后在解释其他细节
而间接光和直接光的不同就在于漫反射和高光(镜面反射)的计算方式不同,本质还是漫反射+高光
于是我做了这个表格来像大家展示BRDF的本质:




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


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



这里我们先慢慢聊直接光的实现
我们还是小可爱镇楼, Lambert漫反射:

https://www.zhihu.com/equation?tex=C_%7Bdiffuse%7D%3D%28C_%7Blight%7D+%5Ccdot+M_%7Bdiffuse%7D%29+max%280%2C+N+%5Ccdot+L_%7Bdir%7D+%29
翻译一下:

https://www.zhihu.com/equation?tex=%E6%BC%AB%E5%8F%8D%E5%B0%84%3D%28%E5%85%89%E8%89%B2+%5Ccdot+%E8%A1%A8%E9%9D%A2%E9%A2%9C%E8%89%B2%29+max%280%2C+%E6%B3%95%E7%BA%BF+%5Ccdot+%E5%85%89%E7%BA%BF%E6%96%B9%E5%90%91%29
BlinnPhong:

https://www.zhihu.com/equation?tex=C_%7Bspecular%7D%3D%28c_%7Blight%7D+%5Ccdot+m_%7Bspecular%7D%29max%280%2Cn+%5Ccdot+h%29%5E%7Bm_%7Bgloss%7D%7D
翻译一下:

https://www.zhihu.com/equation?tex=%E9%AB%98%E5%85%89%3D%28%E5%85%89%E7%85%A7%E9%A2%9C%E8%89%B2+%5Ccdot+%E9%AB%98%E5%85%89%E9%A2%9C%E8%89%B2%29max%280%2C%E6%B3%95%E7%BA%BF+%5Ccdot+h%29%5E%7B%E5%85%89%E6%B3%BD%E5%BA%A6%E5%8F%82%E6%95%B0%7D


这里讲下h, 其实h就是半角向量
普通的Phong不是这样的么:

https://www.zhihu.com/equation?tex=C_%7Bspecular%7D%3D%28C_%7Blight%7DM_%7Bspecular%7D%29Max%280%2Cv+%5Ccdot+r%29%5E%7Bm_%7Bgloss%7D%7D
r是反射方向,而实际上r是要算出来的, r的位置是:


公式是:

https://www.zhihu.com/equation?tex=r%3D2%28n%5Ccdot+l%29n-l
带入到Phong里:

https://www.zhihu.com/equation?tex=C_%7Bspecular%7D%3D%28C_%7Blight%7DM_%7Bspecular%7D%29Max%280%2Cv+%5Ccdot+%282%28n%5Ccdot+l%29n-l%29%29%5E%7Bm_%7Bgloss%7D%7D
一看就很复杂,对不对?
所以大佬们用了一个手段,引入一个V和L之间的向量, 我们叫他半角向量:


h等于:

https://www.zhihu.com/equation?tex=h%3D%5Cfrac%7Bv%2Bl%7D%7B%7Cv%2Bl%7C%7D
所以用h点积n 代替了r点积l,这样可以避免计算r,
h就是这样来的啦,
我们接下来看他们产生的结果就是相加:


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

https://www.zhihu.com/equation?tex=C%3DC_%7Bdiffuse%7D%2BC_%7Bspecular%7D%3D%28C_%7Blight%7D+%5Ccdot+M_%7Bdiffuse%7D%29+max%280%2C+N+%5Ccdot+L_%7Bdir%7D+%29%2B%28C_%7Blight%7DM_%7Bspecular%7D%29Max%280%2Cv+%5Ccdot+%282%28n%5Ccdot+l%29n-l%29%29%5E%7Bm_%7Bgloss%7D%7D


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


BRDF直接光漫反射:

https://www.zhihu.com/equation?tex=C_%7Bdiffuse%7D+%3D+%5Cfrac%7BC%7D%7B%5Cpi%7D
在这里C实际上就是albedo

https://www.zhihu.com/equation?tex=C+%3D+albedo
就是这么简单


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

https://www.zhihu.com/equation?tex=C_%7Bspecular%7D%3D%5Cfrac%7BDFG%7D%7B4%28n%5Ccdot+l%29%28n+%5Ccdot+v%29%7D
最后还是加一起



https://www.zhihu.com/equation?tex=+%5Cfrac%7Bc%7D%7B%CF%80%7D%2B+%5Cfrac%7BDGF%7D%7B4%28v+%5Ccdot+n%29%28l_%7Bdir%7D+%5Ccdot+n%29%7D
是不是顿时感觉也不过如此?
此时加上两个参数ks和kd在看这个公式:.

https://www.zhihu.com/equation?tex=k_d+%5Cfrac%7Bc%7D%7B%CF%80%7D%2Bk_s+%5Cfrac%7BDGF%7D%7B4%28v+%5Ccdot+n%29%28l_%7Bdir%7D+%5Ccdot+n%29%7D


这时我们知道 这里已经基本齐了
不知道大家注意到没? 在漫反射项和高光项前面
有两个 Kd 和 Ks分别是什么,?
我们要从菲涅尔讲起, 菲涅尔的公式是:

https://www.zhihu.com/equation?tex=F+%3D+F_0+%2B+%281-F0%29%281-%28h+%5Ccdot+v%29%29%5E5
这个F0 实际上就是菲涅尔系数,unity里用的是0.04,
其他材料的F0:


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

https://www.zhihu.com/equation?tex=ks%3DF
kd实际上是为了保证能量守恒的一个

https://www.zhihu.com/equation?tex=k_d+%3D+%281-k_s%29%281-Metalic%29
而Ks就是F 所以实际使用中我们就省略了, 所以最后我们得到这东西了:

https://www.zhihu.com/equation?tex=+%281-k_s%29%281-Metalic%29+%5Cfrac%7Bc%7D%7B%CF%80%7D%2B%5Cfrac%7BDGF+%7D%7B4%28v+%5Ccdot+n%29%28l_%7Bdir%7D+%5Ccdot+n%29%7D

然后我们全部再乘上Li:

https://www.zhihu.com/equation?tex=%28+%281-k_s%29%281-Metalic%29+%5Cfrac%7Bc%7D%7B%CF%80%7D%2B%5Cfrac%7BDGF%7D%7B4%28v+%5Ccdot+n%29%28l_%7Bdir%7D+%5Ccdot+n%29%7D%29+L_i%28p%2Cw_i%29%28w_i+%5Ccdot+n%29
我们把Li写成人能看懂的东西:

https://www.zhihu.com/equation?tex=L_i+%3D+light_%7Bcolor%7D%28v+%5Ccdot+n%29
最后因为kd和ks太长了 我们还是把kd,ks代入方程再看一遍这个劝退公式正经有效的部分:
注意: ks 在这里和F重了, 实际上是不需要额外乘一次F的

https://www.zhihu.com/equation?tex=%28k_d+%5Cfrac%7Bc%7D%7B%CF%80%7D%2Bk_s+%5Cfrac%7BDGF%7D%7B4%28v+%5Ccdot+n%29%28l_%7Bdir%7D+%5Ccdot+n%29%7D%29Light_%7Bcolor%7D%28l_%7Bdir%7D%5Ccdot+n%29


而外面那个积分,我给大家说,所有的积分就是加一起
那个积分其实抽出来看长这样:

https://www.zhihu.com/equation?tex=%5Cint_%7B%5COmega%7D%7B%7D+......dw_i
后你但凡看见积分就当成加好,不要被他吓到了
我们以Forward为例
例如在基础光照中, 就要算一个平行光的+间接光
之后Forward Add 就一次pass添加一个光源 等于是累加一次
所以大家会发现这里实际上就是求和 ,所以根本不要被积分符号吓到,不就是加一起吗!
来和我一起念: 积分就是加一起!
至于更多的细节大家可以参考这篇文章:


Unity中BRDF直接光照部分的DGF



但是你经过上面一长串的洗礼,估计已经对BRDF有了一个初步的理解
此时我们就要聊另外一个问题,就是 镜面反射
BRDF公式中有 DGF三个字母
分别代表三种属性:
D - 正太分布函数(也有人喜欢法线分布函数),说人话就是-高光反射
G - 几何函数, 其实就是粗糙度,来确定表面散射量
F - 菲尼尔, 菲涅尔就是菲涅尔反射了,请大家自行百度,这个概念太清晰了


这三个的公式我就不列了,只要大家可以认为这三个加一起就是求高光反射就好了,
在实际使用中, 直接光是没办法实现镜面反射的
所以如果我们只考虑直接光, 这三个东西得到的结果,还真就是高光而已
D决定你高光的强度和大小
G决定表面的粗糙度,会影响漫反射的效果和高光的效果,这个值等于1的时候,就等于极其粗糙了
F就是叠加一个菲涅尔反射
直接光照中的BRDF就是为了体现物体自身被光照之后的表现
积分是为了在多光照(复杂光照就是环境光了)情况下保持一个正确的物理叠加


Unity - 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);

最后

谢谢你看到这里, 如果有问题请使劲喷,我也在不断学习
如果可以的话请友情三连: 点赞收藏关注

FeastSC 发表于 2021-7-14 14:03

用简单的方法表述出来就是吃透了。

c0d3n4m 发表于 2021-7-14 14:07

[害羞][害羞][害羞]

RecursiveFrog 发表于 2021-7-14 14:07

(了解)光滑度=反射(清晰与模糊)与高光(光斑大小)。 金属镀=漫反射与反射(配比)

acecase 发表于 2021-7-14 14:09

挺好的,漫反射那里可以写明C是albedo就更好了~

Arzie100 发表于 2021-7-14 14:17

可以,挺好理解的

KaaPexei 发表于 2021-7-14 14:22

ks 是F,不是F0

RhinoFreak 发表于 2021-7-14 14:26

哦哦,对,我改过来

JamesB 发表于 2021-7-14 14:33

哦哦,谢谢 我加个备注

RedZero9 发表于 2021-7-14 14:40

写的好棒啊!浅显易懂
页: [1] 2 3
查看完整版本: 我关于Unity中BRDF的理解,如果你实在看不懂可以试试这篇文章