MEGALOVANIA MEGALOVANIA

Audaces fortuna iuvat:命运眷顾勇敢之人

目录
图形学 杂项其一:BRDF
/  

图形学 杂项其一:BRDF

姑且先说下现在的学习思路:

大概想法是复习回顾 + 学 CatlikeCoding 里的 Srp 教程 + 实践。

复习回顾主要就是以前看过的各种乱七八糟的东西,比如:

  • UnityShader 入门精要的数学部分
  • LearnOpenGL 里的某些实践
  • TinyRender 的软渲染器
  • 可能会读读 RtR4?但是似乎觉得这种字典类的大部头对我现在来说也意义不大。。
  • 其他看过的技术文章,零零散散的内容之类的

CatlikeCoding 的 Srp 本来我是不想看到的,因为年代版本太久远了。但是看到它最近的更新为了 Unity6 做了更新,所以我决定还是看它的教程好了,就不去看那些国内翻唱的版本了(有些翻唱版本还号称自己是原创,有点不要脸了)

实践部分目前还是没太想好思路,现在想法是做 3D 像素的那些内容。就我目前不健全的知识体系来说的话,我觉得似乎也没有任何的实现思路。当然同样也不排除中间同步做一些其他的小的 TA 项目,比如随机地形,水,模拟之类乱七八糟的东西之类的。

但是主要形式和内容还是输出为主,避免走到那种看着会了,做着一摊的情况。虽然人的大脑真的容易忘记,但是记录在博客里输出成自己的教学,往后回看的时候也能快速回忆起来,虽然这样比较花时间就是了。

如文章表示,这篇主要就来说一下 BRDF,基础源于很多年前看的一篇讲得非常清楚的文章。这篇文章基本算是那篇文章的自己输出翻版,供我自己输出巩固记录用,如果看官有兴趣可以直接看那篇,那篇也许写得更好。

前置知识

  • 基础的光照模型,如 blinn-phong

BRDF 是什么

冗长的定义:

BRDF 是指双向反射分布函数(Bidirectional Reflectance Distribution Function)的缩写,是用于描述材料表面反射特性的函数。它描述了入射光线与出射光线之间的关系,包括反射角度、入射角度和光线波长等因素。BRDF 常用于计算机图形学、计算机视觉、光学等领域,用于模拟真实世界中的光照效果,从而提高图像的真实感和逼真度

说定义没太大意义,我们从一个非常简单的光照模型说起吧

Blinn-Phong 模型

经典公式

L=C_{diff}N \cdot L+C_{spec}(N \cdot H)^m

非常非常简单的公式,注释如下:

  • Cdiff:漫反射颜色
  • N:法线
  • L:光照方向
  • H:半角向量,后面会解释
  • Cspec:高光颜色
  • m:控制高光范围,m 越大,高光越集中越亮

如果对这个公式有疑问或者不了解的,建议学一下图形学最基础的部分,应该就很好理解了

半角向量

按照数学和物理原理,高光的颜色应该是 V 和 R 点乘。

其中 R 代表:反射方向。

其中 V 代表:观察方向。

原理也很好理解,反射光线和视角决定了高光的内容。

那么这里高光部分写成 NH,其实就是为了简化计算

具体为什么这么做,有兴趣的自己搜,现在你只需要知道

N \cdot H \approx R \cdot V

其中

H = \frac{V+L}{|V+L|}

其中 L 代表:光源方向

简化写法

我们来简化一下 Bling-Phong 的写法

因为两个点乘项都有 N,而且本质就是在求投影,因此可以直接写成 Cos+ 向量的形式,类似这样

N \cdot L = \cos\theta_l
N \cdot H = \cos \theta_h

所以我们的公式就变成了

L=C_{diff}\cos\theta_l+C_{spec}\cos^m\theta_h

这样看着简单易懂多了,对吧

能量守恒:归一化 Bling-Phong

这个公式效果明显不够好,原因在于以下几点:

  • 高光和漫反射颜色毫无关系。
  • 完全没考虑能量守恒。

因此我们引入一些参数来弥补这一点,公式就变成这样:

L=\frac{C_{diff}}{\pi}\cos\theta_l+\frac{m+8}{\pi}C_{spec}\cos^m\theta_h\cos\theta_l

先来讲一下这几个修改是啥意思

  • 漫反射加了一个限制。嗯,简单的能量守恒,不然漫反射太亮了

  • 高光加了两个部分,前面的

    \frac{m+8}{\pi}

    一方面是能量守恒,另一方面,是为了表示**光泽度 m 越高,高光越集中,并且越亮。**嗯,很合理。

  • 高光在最后乘上了光的角度。比较类似 diffuse。其实这一项本来就应该有,高光自然也要考虑受光面倾斜的情况,只是之前为了节省计算省略了。

到这里我们得到了一个归一化****的 Bling-Phong

总结 BRDF

这时候我们把公式整理一下,得到的东西,就有 BRDF

L=(\frac{C_{diff}}{\pi}+\frac{m+8}{\pi}\cos^m\theta_h)\cos\theta_l

这里括号里的所有内容,就是 BRDF

用参考文章里的话再来总结一遍:

BRDF****不是一个公式,是一类公式,学名“双向反射分布函数,说人话就是:给一个入射角度和一个观察角度,它能给出一个决定最终射向观察角度的光的强度的系数(这样的说法并不严谨,但在本文中按这样理解没太大毛病),凡是干这个事情的都叫 BRDF 函数,有的 BRDF 函数离真实物理法则更接近,有的则假得一逼。常用的 Unity 引擎中就包含了三套 BRDF 公式,用作不同性能机型的适配

分析 Brdf

来看一下我们刚才得到的 BDRF

f_{BDRF}=\frac{C_{diff}}{\pi}+\frac{m+8}{\pi}\cos^m\theta_h

首先我们来看一下高光部分。也就是加号右侧的内容。

前面提过,这个的功能是让高光光斑随着 m 增大更亮,并且更加集中。另外我们也能看出,表面法线和半角向量的夹角越小,高光越亮。但是看一下前面的图就能明白:**理论上只有半角向量和法线完全重合的时候才有反射光线被视线接收到(h=n 时,r=v,即反射方向与观察方向完全一致)**

那么这个半角向量实际的计算就有点假了:有夹角的情况下,反射光线并不能进入观察者,为什么还是能有光线进入,并且强度还会随夹角变化呢

这时候就需要引入一个概念:微表面

微表面

这里我直接摘抄原文的话吧

我们在渲染中是逐个像素进行处理的,每个像素虽然足够小了,但在这个像素覆盖的范围内还有无数细微的起起伏伏的物理结构,有起伏,就会有变化的法线。我们渲染时用到那个像素法线只能算是所有细微面的法线的平均值,实际上这个像素点内部朝各个方向的法线都是有的,只是所占比例有所不同。

因此,物体表面的法线更像是一个近似的结果,自然我们点乘出来的光照强度也是一个近似的结果了。

法线分布函数

根据这个微表面理论,我们需要把能够反射到视线的法线比例找出来,并且用高光强度修正

因此,我们刚才光照模型的高光部分:

NDF=\frac{m+8}{\pi}\cos^m\theta_h

其实就是在做这部分工作,它通过半角向量和平均法线的夹角,来计算出这个微表面实际指向半角向量方向的法线比例,我们取名为 NDF:法线分布函数

这里简单写作 D,公式变成这样

f_{BRDF}=\frac{C_{diff}}{\pi}+DC_{spec}

可见性函数

法线分布函数其实也不太能涵盖所有情况:既然表面是坑坑洼洼的,那么必然有一部分法线是会被自身凹凸不平的结构所挡住,从而无法射出表面,如下图所示。

我们用来解决这个问题的办法还是一样:用一个函数来模拟它的比例,然后修正高光的强度,这个函数我们称之为可见性函数,简写为 V

于是公式变成了

f_{BRDF}=\frac{C_{diff}}{\pi}+VDC_{spec}
V=1

菲涅尔系数

最后一个需要修正的,是高光强度

C_{spec}

根据物理原理,我们知道:

在一个反射面上,当入射角度越“倾斜”,反射的光线越多,折射的光线越少,入射角度越“直”,折射的光线越多,反射的越少。这个现象叫做菲涅尔现象

因此我们也需要一个函数来表示这个部分,因为这个现象叫菲涅尔现象,所以我们称之为菲涅尔系数,简称为 F,用来描述这种入射角度与反射光线的关系

f_{BRDF}=\frac{C_{diff}}{\pi}+VDF
F=C_{spec}

完整公式

最后,总结一下我们刚才的公式,它变成了这些部分:

f_{BRDF}=\frac{C_{diff}}{\pi}+VDF
D=\frac{m+8}{\pi}\cos^m\theta_h
V=1
F=C_{spec}

之后的所有内容,其实都是在这个简单的公式上来进行拓展

基于物理的渲染

那么,我们接下来要做的,就是找一些流行的公式来代替前面各个看起来假得不行的函数,让他稍微逼真一点点

法线分布函数

比较常用的是 Trowbridge-Reitz GGX 函数

D_{TR}=\frac{Roughness^2}{\pi(\cos^2\theta(Roughness^2-1)+1)^2}

这里的 Roughness 就是我们非常熟悉的粗糙度。从这个公式可以看出,当 Roughness=1 时,高光反射为 0.

当然还有一种很常见的表现形式叫光滑度,其实就是 1-粗糙度。

Unity 里用这种表示方法

菲涅尔系数

再重申一遍,我们想要的菲涅尔现象是:

光越垂直表面,半角向量和法线夹角越小,

\cos\theta_h

越大,反射光线越少

直接给一个炫酷的函数

F=F_0+(1-F_0)(1-\cos\theta_h)^5

那么 F0 是什么呢,F0 是限定条件下折射和反射的比例。完全由物体的分子结构决定。每种物质都不相同。

它更常见的说法是**“反射率”**

需要注意的是,我们原来模型里他是一个简单的固定颜色,所以这里的 F 以及 F0 都是一个三通道的颜色,现实物理中,也对应不同物质对应不同频率的光反射率不一样,比如下面这个真实物理实验的数据:

简单观察一下

  • 非金属的反射率非常低
  • 金属的反射率非常高

为了简单期间,我们直接把非金属定成一个比较小的固定值,如 Unity 用的(0.04,0.04,0.04)

那么对于金属来说,我们选择用金属自身的颜色作为 F0 的值,非常合理

但是我们实际的工作中经常遇到金属和非金属混一起的情况,那么这时候我们就再来一手无敌的差值,我们引入一个值叫金属度,用来描述到底是按照金属的方法反射还是非金属。

在下面的公式中,金属度为 1 则代表完全按照金属的方式处理,为 0 则完全按照非金属的方式处理,剩余的交给线性差值的鬼斧神工

F_0=Metalic*Albedo+(1-Metalic)*[0.04,0.04,0.04]

可见性函数

可见性函数是描述光线从入射到出射的过程中,有多少比例被微表面自身的凹凸不平遮挡住了。因此我们有理由认为,这个遮挡的强弱和粗糙程度正相关。

另外,我们需要计算的内容其实要稍微复杂一点,大概包含三个方面:

  • 入射光线没被遮挡的比例
  • 出射光线没被遮挡的比例
  • 入射光线被遮挡后,在微表面内部多次反射又射出的比例

因此我们需要的函数是:给定一个角度,获取微表面在这个方向上被自遮挡的面积的比例。然后分别计算上面三个部分,再通过一些神奇的数学(省去推导)组合在一起

直接上函数:

Schlick-GGX 函数:

G_{S-GGX}(\theta)=\frac{\cos\theta}{k+(1-k)\cos\theta}

k 其实是一个跟粗糙度有关的函数,它是

k=\frac{(Roughness+1)^2}{8}

然后通过组合前面提到的三大部分,又经过复杂而漫长的推导和优化,我们得到完整的可见性函数

V=\frac{G_{S-GGX}(\theta_l)G_{S-GGX}(\theta_v)}{4\cos\theta_l\cos\theta_v}

这可能是这里最复杂的公式了,我没有办法展示完整的推导,只是尽可能地给到一些参数的信息

能量守恒

前面的思路大体来说是:我们把光照效果分为漫反射镜面高光反射。字面意义,漫反射说的是表面凹凸不平的表面反射的光,但是我们刚才讨论了一大堆可见性以及法线分布函数的问题,其实就是在考虑微表面的变化

事实上也是,粗糙度为 1 时,效果和我们预期的漫反射就是一致的

还记得我们刚才在菲涅尔系数中讨论的物体反射率 F0 吗?记住这个 F0.

我们思考一下真实的物理世界,这里直接搬运原文:

而现在 BRDF 公式中的 Diffuse 实质上是这样的:光线射到表面上后,部分被反射,部分被折射而进入材质内部,在材质内部经过多次不规则的折射、反射后,部分光线被吸收,部分光线又从离射入点不远的地方射出来,这里的“不远”是指映射到屏幕后小于一个像素的大小。

因此,材质反射率越高,则越多的光线被直接反射,越少的光线进入材质内部,Diffuse 就越低。反之亦然。

所以,反射率也决定了直接反射的光线比率,进而决定了进入材质内部的光线比率,进而影响了 Diffsuse 的强度。

因此我们还需要两个系数来修正我们的高光强度和漫反射强度,大概这样

f_{BDRF}=k_{diff}\frac{C_{diff}}{\pi}+k_{spec}VD

能量守恒可知:

总能量=Diffuse+Specular+吸收的能量

那么我们还知道,所谓的吸收,一定是在物体内部折射的过程中被吸收的。而折射光线的多少又和反射率 F0 有关。因此我们大胆得到一个结论:

折射(吸收)越多,Diffuse 越强;折射(吸收)越少,反射(镜面反射)越多,高光越强

因此不难得出,kdiff 反比与金属度(或者说反射率),kspec 正比与金属度(或者说反射率)

上结论时间,首先是高光,这个直接正比于反射率,反射率越高,高光越强

k_{spec}=F_0

然后我们为了保持能量守恒,自然

k_{diff}=(1-k_{spec})

当然,这也是不够全面的,前面提了,折射的光线并不是百分百会通过漫反射的形式表现出来,需要减去被吸收的能量,我们用金属度来近似模拟,金属度越高,吸收越强

k_{diff}=(1-k_{spec})(1-Metallic)=(1-F_0)(1-Metallic)

(别说我前后矛盾,百度百科是这么说的)

总结

以上就是关于 BRDF 的全部内容了,简单总结一下

光照的基础:一个描述函数 + 光线和法线的夹角

L=f_{BDRF}\cos\theta_l
f_{BRDF}=k_{diff}\frac{C_{diff}}{\pi}+k_{spec}VDF

法线分布

D_{TR}=\frac{Roughness^2}{\pi(\cos^2\theta(Roughness^2-1)+1)^2}

菲涅尔项

F=F_0+(1-F_0)(1-\cos\theta_h)^5
F_0=Metalic*Albedo+(1-Metalic)*[0.04,0.04,0.04]

可见性函数

V=\frac{G_{S-GGX}(\theta_l)G_{S-GGX}(\theta_v)}{4\cos\theta_l\cos\theta_v}

其中:

G_{S-GGX}(\theta)=\frac{\cos\theta}{k+(1-k)\cos\theta}
k=\frac{(Roughness+1)^2}{8}

能量守恒参数

k_{spec}=F_0
k_{diff}=(1-F_0)(1-Metallic)

大概就是这些

如果不管推导的话,我觉得还是非常简单易懂的。难点可能在于某些特殊物质的物理现象能够迁移到具体的光照模型公式中,比如各向异性,比如次表面散射,通过物理现象去练习光照模型中的公式,再根据个人经验以及项目需求进行一些改进,学习 BDRF 也就不白学了


标题:图形学 杂项其一:BRDF
作者:matengli110
地址:https://www.sunsgo.world/articles/2025/04/09/1744140027942.html