基于物理的渲染 – 理论篇(一)

基于物理的渲染,英文缩写PBR (physically based rendering),是一个渲染技术的集合,包含多种或多或少的利用物理来更真实的模拟现实世界的渲染技术,基于物理的渲染的目的是利用更接近实际物理理论的光照计算,生成比以前的Phong、Blinn-Phong算法更真实的画面,这项技术的好处不只是让画面看起来更好看了,也让我们(尤其是美工)可以简单的通过改变物体的几个物理属性就能渲染出想要的效果,而不用担心光照错误,这是非PBR渲染无法比拟的优势。

之所以叫做基于物理的渲染而不是物理的渲染,是因为渲染过程基于物理理论,尽管物理理论很接近现实,但毕竟不是现实。

使用PBR光照模型要满足以下三条:
① 基于微表面模型;
② 能量守恒;
③ 使用BRDF理论。

在这个PBR教程/介绍中,我们将会将会专注于迪士尼发明,Epic Games(这个公司开发的虚幻)率先使用的实时PBR方案,他们使用的基于金属度的区分方法广泛的适用于大部分流行引擎且效果非常的好。在系列教程结束的时候我们将可以制作出一些类似于下图的东西:


需要注意的是这个教程是进阶教程,建议有良好的OpenGL跟Shader基础的同学阅读,除此之外,还可能用到的一些基础有framebuffers, cubemaps, gamma correction, HDR and normal mapping,还可能会用到一些高级数学。基础一般的同学也不用担心,我会尽力的把每个概念解释清楚。

(原文:https://learnopengl.com/#!PBR/Theory, 翻译:coldkaweh)

微表面理论

所有的PBR技术都是基于微表面的。这个理论是说任何表面都可以用无数微小的镜面来描述,表面的粗糙程度越高这些微小的镜面的朝向区别越大。


对于一个物体表面来说更粗糙意味着微表面的朝向更无序,当我们讨论光线的高光反射时,这些镜子一样的微表面会将相同方向的入射光线朝着不同方向反射出去,形成一个更为宽广的镜面反射区域。而对于光滑表面来说,入射光线基本沿着同一方向反射出去,形成更清晰的反射画面。


从微观角度来说,没有任何表面是完全光滑的。我们这里使用粗糙度这个统计近似数值,来衡量那些比一个像素的覆盖范围还小的微表面的粗糙程度。我们根据表面的粗糙度,可以计算得到单个微表面的半角向量(halfway vector)平行于平均半角向量的概率,半角向量是指位于入射光 l 与眼睛向量正中间的单位向量。计算公式如下:


越多的微表面半角向量相互平行,高光反射则越清晰和强烈。我们使用统计近似把微表面的各种对齐情况统一到0到1之间,形成一个粗糙度参数。


我们可以看到粗糙度越高的物体高光部分就越大越模糊,而光滑表面的高光小且亮。

(原文:https://learnopengl.com/#!PBR/Theory, 翻译:coldkaweh)

能量守恒

微表面模型采用一种近似的能量守恒:反射出去的光的能量不应该超出入射光的总能量(发光体除外)。从上图中可以看出,随着粗糙度的增加高光范围的逐渐增大,亮度是在逐渐降低的,假如无论高光的范围多大亮度都一样,那么这个粗糙表面发射的能量就有些太多了,违反了能量守恒定律。这就是为什么光滑的平面高光更强粗糙表面更朦胧的原因。

为了实现能量守恒我们需要先弄清楚高光和漫反射之间的区别。当一束光打在平面上,将会分成反射和折射两部分,被反射的部分不会进入物体而是会被物体表面直接反射出去,这就是我们平常所说的高光,剩下的光会被折射穿透表面被物体吸收,这就是我们所说的漫反射。


一般来说,一次碰撞并不能将所有能量全部吸收,剩下的光会以随机的角度散射出去继续跟其他的粒子继续碰撞或者从物体表面离开,通过表面离开的这部分光则算作部分漫反射颜色。在基于物理的渲染里我们将其简单化一下,我们假设所有所有的折射光都在物体表面的某一点上被吸收而没有散射。使用次表面散射技术可以计算这部分光照,在一些需要考虑这部分光的特殊着色器里,比如渲染皮肤,大理石和蜡等材质的时候,这部分光对效果有非常显著的提高,不过耗费的性能也是很可观的。

在金属表面上反射和折射有一些微小的不同,金属表面对光的反应与非金属(电介质)表面是不一样的。他们也遵从同样的反射和折射原理,但是所有的折射光都会被直接的吸收而没有散射,只有反射或者说高光,金属表面是没有漫反射颜色的。因为在这点上金属和电介质有着明显的不同,因此在PBR渲染管线上也得区别对待,我们后面的章节会进一步讨论。

如果从能量的角度来描述反射光和折射光之间的关系,可以总结成一句话:他们是互斥的。反射光的能量就是没有被物质本身吸收的部分,因此,作为折射光进入物体表面被吸收的部分就是入射光能量减去被直接反射掉的部分。

我们保持能量守恒利用的就是这种关系,先计算高光部分占整个入射光能量的百分比,折射光所占的比例就可以利用高光因子通过下列等式计算而来。

float kS = calculateSpecularComponent(...); // reflection/specular fraction
float kD = 1.0 - ks;                        // refraction/diffuse  fraction

根据能量守恒定律我们只要知道入射光和反射光就的能量就能计算出折射光的能量。使用这个方法计算得到的折射(漫反射)和反射(高光)能量的总和是不可能超过1.0的,这保证了出射光的能量总和永远不会超过入射光的能量。这是传统的光照模型保证不了的。

(原文:https://learnopengl.com/#!PBR/Theory, 翻译:coldkaweh)

反射方程

这一节我们要说的是渲染方程式,这是天才们精心构造的,目前为止最好的光照模拟模型。基于物理的渲染依赖的就是其中一项名为“反射方程”的等式,这是一个更适合我们使用的渲染方程式。


反射方程看起来非常可怕,但是我们会一步步慢慢的剖析它,你也将会慢慢的理解它。为了更好的理解这个方程,我们需要了解一点辐射度量学(radiometric)知识,辐射度量学通常用来度量电磁辐射(包括可见光),其中有几个量可以用来测量光照射物体表面和其方向,但是这里我们只讨论与反射方程有关的一个叫做辐射度(radiance)东西,使用大些字母 L 来表示。辐射度是用来量化方向光强度或者说长度的量,对于初次接触的人来说要理解辐射度是由多个物理量组合而成是很有难度的,我们将会先着重讲一下这个:

辐射通量:辐射通量 Φ 是用瓦特为单位计量光源的辐射能量,光是所有不同波段能量的集合,而每种波长都对应一种特定的颜色,因此光源发射出来的能量可以用不同波长的函数集来表示,一般将波长在390nm~700nm之间的作为可见光谱,这些波长是人眼可以感知(看到)的。下图中你将可以看到阳光中不同波长对应的能量和颜色。


辐射通量函数就是要计算不同波长的总面积。直接将这个波长计算函数用于计算图形学是不切实际的,所以我们通常使用简化版的表达方式,辐射通量不再是关于不同波长的函数,而是将光分解成RGB三个分量(也就是我们常说的灯光颜色(light color)),这种表示方式丢失了非常多的信息,但是从视觉的角度来看影响微乎其微。

立体角:立体角 ω 用来衡量投影到单位球上的形状的尺寸或者面积。投影到这个单位球上的形状的面积就是我们所说的立体角,可以将立体角想象成下图所示:


想象自己作为一个观察者站在单位球的中心看向正方形方向,你的视线画出的实线的尺寸就是立体角。

辐射强度:辐射强度衡量每一单位立体角的辐射通量,或者说光源照射在一立体角在单位球上的投影区域的强度。给定一个均匀辐射的全方向光强度,我们就可以计算到每一立体角的能量强度。


辐射强度方程定义如下:


其中辐射强度 I 等于辐射通量 Φ 除以立体角 ω 。

有了辐射通量,辐射强度和立体角的知识我们终于可以定义辐射度等式了,它定义的是辐射强度为Φ的光通过立体角ω辐射在区域A的可被观察到的总能量。



辐射度是一个区域内光照量的辐射学度量,按照光的入射(或者来源)角与平面法线的夹角θ计算cosθ。越是斜着照射在平面上光越弱,反之越是垂直照射在表面上的光越强,类似基础光照中的漫反射颜色计算,cosθ直接等于光的方向和表面法线的点积。

float cosTheta = dot(lightDir, N);  

光照亮度方程非常的有用,里面包含了大部分我们感兴趣的物理方程式。如果我们将立体角跟区域A都看作无限小,那么我们就可以使用辐射度来分析一束光线打在空间上一个点的通量,这种关系使我们能够计算单束光线对单个(片元)点的辐射度影响,我们将立体角转化为方向向量,将区域A转化成点P,因此我们可以在shader中直接使用辐射度来计算单束光线对每个片元的贡献。

事实上,当谈到辐射,我们通常关心的是所有射入点P的光线,这些光的辐射度总和称为辐照度。有了辐射度和辐照度的知识,我们可以回到反射方程:


我们知道在渲染方程式中 L 代表某个点P的辐射度,而无限小的入射光的立体角 ωi 可以看作入射光方向向量 ωi ,我们将用来衡量入射光与平面法线夹角对能量的影响的cosθ分量移出辐射度方程,作为反射方程的单独项 n⋅ωi 。反射方程是 P 点的所有反射光 Lo(p,ωo) 沿着 ωo 方向出射的辐射度的总和,或者我们换个说法:Lo 计算的是在 ωo 方向的眼睛观察到的 p 点的总辐照度。

反射方程里面使用的辐照度,必须要包含所有以 p 点为中心的半球 Ω 内的入射光,而不单单是某一个方向的入射光,这个半球指的是围绕面法线n的那一半:


为了计算这个区域内的所有值,在我们这里是这个半球,在反射方程中我们使用了一个称作为积分的数学符号 ∫ ,来计算半球Ω内所有的入射向量 dωi 。积分计算面积的方法,有分析和渐近(analytically or numerically)两种方法,我们没有可以满足渲染计算的分析法,所以只能选择离散渐近的方式来解决这个积分问题。就具体问题来说,做法是将半球内的所有入射角归到相隔很近的离散点上,然后加总平均离散点各自的结果,这就是我们所说的黎曼和,下面是简单的伪代码表述:

int steps = 100;
float sum = 0.0f;
vec3 P    = ...;
vec3 Wo   = ...;
vec3 N    = ...;
float dW  = 1.0f / steps;
for(int i = 0; i < steps; ++i) 
{
    vec3 Wi = getNextIncomingLightDir(i);
    sum += Fr(P, Wi, Wo) * L(P, Wi) * dot(N, Wi) * dW;
}

dW的值越小结果越接近正确的积分函数的面积或者说体积,衡量离散步幅的dW可以看作反射方程中的dωi。积分计算中我们用到的dωi是线性连续的符号,跟代码中的dW并没有直接关系,但是这种方式有助于我们理解,而且这种离散渐近的计算方法总是可以得到一个很接近正确结果的值。细心的读者会注意到我们可以通过增加步骤数来提高黎曼和的准确性。

反射方程加总了所有的,以各个方向ωi射入半球Ω并打中点P的入射光,经过反射函数fr进入观察者眼睛的所有反射光Lo的辐射度之和。入射光的来源可以是我们熟知的光源,也可以是使用IBL技术利用360度的环境贴图作为来源。

现在只剩下 fr 项是我们不知道的了,这是双向反射分布函数(Bidirectional Reflectance Distribution Function,BRDF),衡量的是表面的材质属性如何影响入射辐照度。

(原文:https://learnopengl.com/#!PBR/Theory, 翻译:coldkaweh)

基于物理的渲染 – 理论篇(二)
基于物理的渲染 – 理论篇(三)

本文转自:CSDN,译者:coldkaweh,转载此文目的在于传递更多信息,版权归原作者所有。
原文链接:https://blog.csdn.net/coldkaweh/article/details/70187399

最新文章