Microfacet模型采样下的brdf
本文前言
在学习图形学(games101 from bilibili)的时候,也遇到了像这样的问题,Cook-Torrance模型无法实现粗糙度为0时,物体微表面呈现绝对镜面的效果(呈现出一面镜子),为了搜寻解决办法,因此看到了这篇博客,因为是全英文,所以就花了一点时间翻译了一下,方便日后重新观看,红色字对原博客的补充说明
前言
最近我正在为我的渲染器开发microfacet brdf
模型,我注意到为microfacet brdf
提供一个单独的采样方法是非常必要的,而不是使用默认的方法,因为默认的方法通常用于像漫反射一样的表面,而对于像镜面一样的带有mirror的brdf,效率非常低。下面的图片是由默认采样方法生成的。
左边的猴子是纯反射brdf,这在我之前的博文中提到过,右边的猴子使用microfacet模型,粗糙度值为0(对于一个平滑的光面,光线大体上更趋向于向同一个方向反射,造成更小更锐利的反射)。我本来以为这两只猴子会有类似的结果,但事实证明这里是错误的,我们几乎看不到猴子的反射。其实没有什么问题,事实是,默认采样的粗糙度为0的microfacet
模型的收敛率非常低。只要有足够的样本,它就会达到与左图相似的外观。然而,足够的样本数可以是任意的高,这取决于你的brdf有多尖锐。
本文中提到了对microfacet brdf
进行采样的正确方法。我想在这篇博客中记录的是这些结论是如何从原始microfacet
模型中得出的。使用这些更好的采样方法,我们最终会对那两只猴子得到类似的结果。
为什么默认的抽样方法是低效的
那么,为什么这种默认的抽样方法对于像brdf模型这样的镜像来说是低效的。首先,默认的抽样方法与以下的pdf。
\]
对于像Lambert, OrenNayar这样的漫反射表面,它能达到很好的效果。事实上,它不是漫反射brdfs的取样方法,Lambert brdf的取样方法会尊重一个恒定值的pdf,它还涉及到位于渲染方程或LTE中的余弦因子。
漫反射的brdf是一个均匀分布的,而金属表面呈现出的高光项,更像是集中于一点向周围扩散,所以需要对某个方向进行重要性采样,就像是一个函数中间部分的权重,比两旁的权重占比更大
\]
由于对入射光的radiance了解极为困难,甚至不可能,我们唯一拥有的就是brdf和余弦系数。如果brdf和余弦系数的乘积是这个方程中的主导因素,那么上面提到的采样方法可能是有效的。在大多数时候,它在实践中是非常有效的。
然而,当brdf成为主导因素,且粗糙度为0时,默认的取样方法的效率就会迅速下降。对于这里的microfacet brdf
,其极端形式就像dirac-delta函数
(狄拉克δ函数),几乎不可能得到brdf为非零值的样本。而对于那些幸运的、有非零brdf值的样本,由于其概率低,可能会达到超高值,给采样结果带来高方差。换句话说,收敛率相当低,这正是我们看到上述图像的原因。它甚至可以被当作是一个bug。很明显,我们需要一个更好的方法来对这些microfacet brdfs
进行采样。
微表面模型
Microfacet Model
是这几年在实时渲染中比较热门的,它是基于物理的着色算法的基本原理。一般来说,它的基本形式是这样的。
\]
第一个分量是Fresnel
,第二个分量是几何项G项(Geometry Function,一般来说包含Geometry Obstruction和Geometry Shadowing),最后一个分量是正态分布函数(Normal Distribution Function,简称NDF)。与传统的bxdf模型相比,microfacet
模型遵守能量守恒规则,允许艺术家通过一个参数而不是两个参数来改变材料的粗糙度,即镜面颜色和镜面功率。
在这个方程式的所有这些因素中,NDF项通常是最主要的。NDF的具体形状在很大程度上受到bxdf的粗糙度的影响。为了对microfacet brdf
模型进行采样,通常是先对NDF进行采样,得到一个遵循NDF的随机microfacet
法线,然后沿法线反射出射radiance
,产生入射方向。
目前有几种NDF。本博客将介绍以下三种,所提到的都是各向同性(当旋转一定方位角时,能得到相同的BRDF)的。
- GGX
- Beckmann
- Blinn
在我们继续之前,有一条规则是所有NDF应该遵循的。
\]
我们将在下面的推导中使用这个方程。解释这个方程不在本博客的范围内,你们可以参考这里的进一步细节。公式的由来hows-the-ndf-really-defined/
NDF(补充)
NDF详细的定义可了解这篇文章(How Is The NDF Really Defined),文章中有对上述规则的说明
接下来介绍下面公式推导之前需要了解的
NDF遵循公式
\]
同时NDF的归一化条件:
\]
根据式(2)可知\(\int_{\varOmega}{D\left( m \right) \cos \left( \theta _m \right) d\omega =1}\),因此\(D(h)cos(θ_h)\),即表示当前所采取模型NDF的概率密度(pdf)
概率密度函数的转换有:\(p(θ,φ)=sinθp(ω)\)
GGX采样
下面是GGX的基本形式。
\]
因此,关于立体角的pdf是这样的。
\]
我们通常不直接对立体角进行采样,而是用球面坐标来采样。所以我们感兴趣的不是关于立体角的pdf,而是关于球坐标的pdf。
\]
下面的公式非常简单,基本上是根据特定的pdf进行抽样,反转法在本博客中适用于所有ndf。请注意,这个公式没有\(\pi\),也就是说NDF是完全各向同性的,我们可以均匀地采样\(\pi\)。详细地证明,请参考这里。这里唯一剩下的是如何对\(\theta\)进行采样。为了做到这一点,我们需要先得到\(θ\)的pdf。
\]
接下来我们来计算CDF
(Cumulative Distribution Function):
\]
\]
\]
\]
\]
\]
\]
当一个标准随机数\(\epsilon\)等于这个CDF
时,我们有以下方程:
\]
解这个方程不需要比初中数学多的知识,我想也没有必要把整个过程展示出来。上式的最终解为:
\(\theta =\mathrm{arc}\cos \sqrt{\frac{1-\epsilon}{\epsilon \left( \alpha ^2-1 \right) +1}}\) or \(\theta =\mathrm{arc}\tan \left( \alpha \sqrt{\frac{\epsilon}{1-\epsilon}} \right)\)
上述两个方程式是完全相同的东西。选择使用哪一个只是一个品味问题。
Beckmann采样
Beckmann
抽样法的推导过程与上述过程十分相似。这是Beckmann
分布:
\]
关于球面坐标的pdf是这样的:
\]
同理,\(\phi\)可以被均匀地采样。\(\theta\)的pdf应该是这样的
\]
\(\theta\)的CDF可以这样计算:
\\
=\int_0^{\theta}{\frac{-2}{\alpha ^2\cos ^3t}e^{-\frac{\tan ^2t}{\alpha ^2}}}d\left( \cos \left( t \right) \right)
\\
=\int_0^{\theta}{\frac{1}{\alpha ^2}e^{-\frac{\tan ^2t}{\alpha ^2}}}d\left( \frac{1}{\cos ^2\left( t \right)} \right)
\\
=\int_0^{\theta}{\frac{1}{\alpha ^2}e^{\frac{1}{\alpha ^2}\left( 1-\frac{1}{\cos ^2t} \right)}}d\left( \frac{1}{\cos ^2\left( t \right)} \right)
\\
=\int_0^{\theta}{\frac{1}{\alpha ^2}e^{\frac{1}{\alpha ^2}\left( 1-\frac{1}{\cos ^2t} \right)}}d\left( \frac{1}{\cos ^2\left( t \right)} \right)
\\
=\int_{\theta}^0{d\left( e^{\frac{1}{\alpha ^2}\left( 1-\frac{1}{\cos ^2t} \right)} \right)}
\\
=1-e^{\frac{1}{\alpha ^2}\left( 1-\frac{1}{\cos ^2\theta} \right)}
\]
解决\(P_h(\theta)= \epsilon\)的方程可以得到以下解决方案。
\(\theta =\mathrm{arc}\cos \sqrt{\frac{1}{1-\alpha ^2\ln \left( 1-\epsilon \right)}}\) or \(\theta =\mathrm{arc}\tan \sqrt{-\alpha ^2\ln \left( 1-\epsilon \right)}\)
Blinn采样
这里是Blinn的NDF:
\]
关于球面坐标的pdf是:
\]
分离\(\phi\)可以得到以下结果:
\]
这个比前两个简单多了,这是CDF:
\\
=\int_{\theta}^0{\left( \alpha +2 \right) \cos \left( t \right) ^{\alpha +1}d\left( \cos \left( t \right) \right)}
\\
=\int_{\theta}^0{d\left( \cos \left( t \right) ^{\alpha +2} \right)}
\\
=1-\cos \left( \theta \right) ^{\alpha +2}
\]
以下是经典随机数与\(\theta\)之间的关系
\]
由于\(\epsilon\)是一个经典的随机数,\(1-\epsilon\)也是一个。因此,我们可以用下面的方程式来简化上述方程式。
\]
关于这种抽样方法,再多说一点。我对这个解决方案不太自信,尽管我看不出这个推导有什么问题。PBRT(Physically Based Rendering: From Theory to Implemention 3rd)对Blinn给出了一个类似的解决方案,这就是:
\]
而另一个开源的光线追踪器Mitsuba也采用了这种采样方式。我不太理解书中的推导,所以我就坚持用这个,也是这里提到的那个。我试了一下pbrt的取样方式,从图片上可以看出只有微小的差别。
一个额外的步骤
还有一步没有完成。我们感兴趣的是入射方向的采样,而不是法线。我们之所以直接对法线而不是入射方向进行采样,是因为NDF通常是microfacet
模型中的主导因素。在对法线采样后生成入射方向是比入射方向本身采样更有效的方法。
然而,我们计算给定入射方向的PDF的方式与上述尊重半向量的方式不同,无论是实体角还是球面坐标。到目前为止,我们所拥有的是半向量的PDF,一个转换是必要的。
\]
结论
下面是新的取样方法之后和之前产生的图像。
64spp,GGX
被选为NDF,三只猴子的粗糙度值不同(0.0,0.5,1.0)。从图中我们可以看到,最左边的猴子得到的结果比默认的采样方法好得多。对于其他两只猴子,我们用余弦-pdf取样方法得到了类似的结果。
已经有一些研究工作改进了这个博客中提到的采样方法。一旦我有一些空闲时间,我可能需要试试他们的方法。
本文后续
原文的翻译到这里就结束了,来看看用了重要性采样后的作业7对比吧!
第一幅图的粗糙度因为接近于0,可以看到略微的镜面效果,但是不如第二张(去掉漫反射项)明显
请忽略第二幅图,球体中映射灯光为全黑,我仍在查找这个问题的solution
扩展阅读
引用
Microfacet模型采样下的brdf的更多相关文章
- NLP论文解读:无需模板且高效的语言微调模型(下)
原创作者 | 苏菲 论文题目: Prompt-free and Efficient Language Model Fine-Tuning 论文作者: Rabeeh Karimi Mahabadi 论文 ...
- Spark入门实战系列--3.Spark编程模型(下)--IDEA搭建及实战
[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 . 安装IntelliJ IDEA IDEA 全称 IntelliJ IDEA,是java语 ...
- 基于java.util.logging实现轻量级日志记录库(增加根据当前类class初始化,修复线程池模型(javaEE)下的堆栈轨迹顺序与当前调用方法不一致问题)
前言: 本章介绍自己写的基于java.util.logging的轻量级日志记录库(baseLog). 该版本的日志记录库犹如其名,baseLog,是个实现日志记录基本功能的小库,适合小型项目使用,方便 ...
- Faster_RCNN 3.模型准备(下)
总结自论文:Faster_RCNN,与Pytorch代码: 本文主要介绍代码第二部分:model/ , 首先分析一些主要理论操作,然后在代码分析里详细介绍其具体实现. 首先在参考文章的基础上进一步详细 ...
- Django - 模型层 - 下
一.多表 sql 单表 多表 多对一 多对多 一对一 =============================================== 一对多:Book id title price p ...
- 第三章 Java内存模型(下)
锁的内存语义 中所周知,锁可以让临界区互斥执行.这里将介绍锁的另一个同样重要但常常被忽视的功能:锁的内存语义 锁的释放-获取建立的happens-before关系 锁是Java并发编程中最重要的同步机 ...
- Netty源码分析--内存模型(下)(十二)
这一节我们一起看下分配过程 PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacit ...
- Knockout v3.4.0 中文版教程-3-监控-通过监控创建视图模型(下)
6. 显式订阅监控 你通常不需要手动设置订阅,所以初学者应该跳过这一节. 对于高级用户,如果你想注册自己的订阅来监控通知变化,你可以使用 subscribe函数,比如: myViewModel.per ...
- Spark 编程模型(下)
创建Pair RDD 什么是Pair RDD 创建Pair RDD Pair RDD的转化操作 Pair RDD的转化操作1 在xshell启动 reduceByKey的意思是把相同的key的valu ...
随机推荐
- Linux从头学08:Linux 是如何保护内核代码的?【从实模式到保护模式】
作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...
- gitlab-ci集成SonarQube代码质量检查
SonarQube是管理代码质量一个开放平台,可以快速的定位代码中潜在的或者明显的错误. docker安装 1.拉取 postgres:docker pull postgres:10 2.拉取sona ...
- rabbitMq镜像集群
rabbitMq延迟投递的方案 1 把消息记录到数据路,通过定时器进行刷新 2 TTL 加上死信队列 :通过路由把过期的消息同步到死信队列,通过死信队列的消费者进行消费 3
- Linux 单实例oracle安装步骤
一.查看逻辑盘大小,执行 lsblk 二.查看硬盘及分区信息 ,执行 fdisk -l 三.将物理硬盘分区初始化为物理卷,以便LVM使用 ,创建pv pvcreate /dev/sdb 四.查看物理卷 ...
- leetcode 括号
1. 括号(0809) 设计一种算法,打印n对括号的所有合法的(例如,开闭一一对应)组合. 说明:解集不能包含重复的子集. 例如,给出 n = 3,生成结果为: [ "((()))" ...
- charles 抓包修改app页面数据
1,首先给手机安装Charles证书,安装官方的来,在无线网配置项目,输入手动代理地址,后开启飞行模式刷新网络, 2,在浏览器输入chls.pro/ssl 下载并安装证书,此时电脑端charles 会 ...
- CSS导航菜单(二级菜单)
index.html <div class="nav"> <ul> <li> <a href="#">Java& ...
- python库--pandas--写入文本文件
pandas.DataFrame.to_csv 参数 说明 path_or_buf=None 文件路径或对象, 若果为None, 则结果作为字符串返回 sep=',' 分隔符 na_rep='' 缺失 ...
- shell脚本之循环语句 for-while-until
目录: 一.for循环语句 二.while循环语句 三.unti循环语句 一.for循环语句 读取不同的变量值,用来逐个执行同一组命令 举例 批量添加用户◆ 用户名存放在users.txt文件中, ...
- 计算字符串的长度.len,RuneCountInString
内置函数len(),可以返回字符串/数组/切片/map/channel的长度. unicode/utf8包 函数:RuneCountInString(输入一个字符串),返回int类型的字符串长度.由于 ...