本文同时发布在我的个人博客上:https://dragon_boy.gitee.io
PBR,全称phsically based rendering,即基于物理的渲染,是一系列基于相同理论的渲染技术的集合,更接近于真实的物理世界。因为PBR的目标是模拟真实的光照,它比Phong和Blinn-Phong更为真实,而且使用者可以不必调整一些不必要的参数区尽力模拟效果。
PBR本身也并不是完全的真实的物理模拟,只是基于物理的规则。一个PBR光照模型,需要满足下面3个条件:
- 基于微表面模型
- 能量守恒
-
使用基于物理规则的BRDF
PBR是由迪士尼提出的,Epic进行了针对实时渲染的优化。它们的方法基于金属流程,被广泛运用于许多主流的引擎。下面是一个PBR渲染的例子:
微表面模型
所有的PBR技术都基于微表面模型的理论。该理论定义了,所有微观尺度的表面都可以被描述为微小独立的镜面,被称为微表面。由于表面的粗糙度不同,这些微小的镜面的排列会很不同:
如果表面越粗糙,微表面的排布越混乱。而表面越粗糙,在进行高光计算时,光线更倾向于杂乱无章地散射,就会造成更大范围的高光,相反,表面越光滑,光线的反射更有规律,结果的高光越小:
实际上,没有一个表面是完全光滑的,但如果表面的微表面排布的混乱度很小,我们可以近似的认为平面是完全光滑的,我们可以使用粗糙度(roughness)参数来近似模拟表面粗糙程度。基于一个表面的粗糙度我们可以粗略的计算微表面贴合某个向量
对齐到这个
能量守恒
微表面模型的近似采用一组能量守恒公式:离开表面的光的能量不能高于与表面发生作用前的光的能量,即能量会有一部分损失在物体内部。从上面的图中我们看到,粗糙度越高,高光范围越大,但高光的亮度越低。
对于能量守恒公式,我们需要区分漫反射和高光。当一束光线碰撞表面时,它就被分为折射部分和反射部分,反射部分形成高光,并不进入表面,这是广义上的高光光照。这是部分留在物体内部分吸收,这是广义上的漫反射光照。
其实和广义上的模型相比还是有一细微差别,折射光线并不会马上被吸收。在物理层面,我们可以将光线认为是一个带有能量能量体,在失去所有能量前会一直向前移动,而失去能量的方式是碰撞。每个物体都可以考虑为内部包含大量的粒子,这些粒子会作为碰撞体和光线进行碰撞,每次碰撞,这写粒子会吸收一部分光线的能量并转化为热能:
所有进入物体内部的折射光线的能量并不会被完全吸收,大多数都会向各个方向散射离开表面,而这些能量被吸收过的光线会参与表面的漫反射颜色计算。在PBR中,我们简化这一流程,我们假定所有的折射光线被吸收的能量接着在一个很小的范围内散射,忽略会离开表面一定距离的光线。有一些着色技术考虑了这些离开一定距离的光线,这一技术称为次表面散射,被应用于模拟皮肤、大理石蜡烛这些材质。
一个额外的差别是金属和非金属表面的区别(也被称为电解质和非电解质)。金属表面的反射和折射所基于的原则和上述一致,但折射光线全被吸收不考虑散射,这就意味着金属表面不考虑漫反射颜色只考虑高光。而非金属表面和上面的原则完全一致。正因为这种金属和非金属的差别,它们在PBR流程中会被区别对待。
反射和折射的区别也给予了我们另一个不同于能量守恒公式的结论:它们两个是相互排斥的。当光线反射时它永远不会被吸收能量,所以进入表面的能量就全部都是折射光线。
这样我们可以根据能量守恒公式首先计算高光部分,代表原光线能量被反射的部分,接着剩下的部分的能量直接用来计算折射,即漫反射部分|
float kS = calculateSpecularComponent(...); // reflection/specular fraction
float kD = 1.0 - kS; // refraction/diffuse fraction
通过这种方式,我们同时拥有了进入光线的反射值和折射值,同时也秉承了能量守恒原则。通过这种方式,让反射值和折射值超过1是不可能的,这确保了它们的能量值的和不会超过进入的光线的能量。
反射方程
根据上面讲述的理论,我们可以得出一个名为渲染方程的工具,这个方程时目前模拟光照的最好的方法。基于物理的渲染使用的渲染方程的版本称为反射方程,下面给出反射方程:
为了了解这个复杂的方程,我们先了解一下辐射度测定。辐射度测定是一种对电磁波的测定方法,包括可见光。有几个辐射度测定的参数我们可以用来测量测过平面的光和方向,但我们只考虑于反射方程有关的辐射率,这里标记为L。辐射率用来度量从某一单一方向射来的光的强度,我们可以将辐射度看作是一组物理量的结合:
辐射通量:辐射通量
辐射通量就是这么一个用来描述上述波长与光强函数总区域面积的量。为了使描述简单化,我们常常用RGB编码来表示光的颜色。
立体角:立体角,用
想象一下从单位圆的中心沿着区域的方向观察,落在单位圆上的区域就是立体角。
辐射强度:辐射强度是每个立体角范围上的辐射通量,比如,有一束向四周发光的全方位光源,我们在立体角的范围描述辐射强度:
辐射强度的等式如下:
通过辐射通量、辐射强度以及立体角,我们就可以得到辐射率的等式。辐射率被描述为某面积内(A)在单位圆中心所接收到的沿某一立体角接受得到的辐射强度:
辐射率用来测量某一区域的光的辐射度,通过入射角进行缩放。如果我们考虑一个立体角和一个无限小的区域A,我们可以使用辐射率来度量入射光在空间中某一点的通量,接着我们将立体角用光的方向向量替代,用一个点
实际计算辐射率时,我们考虑所有入射到某一点
我们用L表示某一点p从某一方向(某一无线小的立体角
反射方程围绕辐照率展开,是某个点所有入射光的辐射率综总和,不只是某一单一方向的入射光,而是所有在一个以
为了计算所有在半球内的辐射率的值的和,我们基于所有在半球内的光的入射方向
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;
}
我们将积分区域分为按w所在轴100等分,对每一份dW值,我们计算相应的辐射率,循环将值叠加得到辐照率的结果。
反射方程将半球内照射到
点的入射光的辐射率叠加起来,经由
缩放,并返回从观察者方向的反射光辐射率
的总和。入射光辐射率可以来自光源或环境贴图。
方法代表BRDF方程,也就是双向反射分布方程,可以基于物体的材质属性缩放入射光的辐射率权重。
BRDF
BRDF,即双向反射分布方程,将入射光方向,反射光方向,法线,表面粗糙度作为输入参数。BRDF近似描述了每个输入光对最后反射光的贡献,例如,一个类似镜面的表面,BRDF方程对几乎所有的输入光返回0值,除了与反射光方向呈反射关系的输入光返回1值。
BRDF基于微表面模型近似模拟材质的反射和折射属性。为了让BRDF的结果物理上可信,它必须遵循能量守恒方程。技术上来说,Blinn-Phong其实是使用相同的和
作为输入的BRDF,但由于Blinn-Phong并不遵守能量守恒定律,它也不是基于物理的模拟。目前存在许多不同种类的BRDF来基于物理模拟表面材质,对于实时渲染的PBR流程使用的是Cook-Torrance BRDF。
Cook-Torrance BRDF包含一个漫反射和一个高光部分:
高光部分这样描述:
Cook-Torrance高光BRDF有三个函数和分母的一个标准化因数构成,D、F、G各自代表近似模拟高光某一属性的函数,即法线分布函数,菲涅尔等式,几何函数:
- 法线分布函数:估计贴近中间向量的微表面数量,由表面的粗糙度影响。这是近似模拟微表面的最首要的方法
- 几何函数:描述微表面的自屏蔽属性。当某一平面相对粗糙时,平面的微表面可以遮蔽其它的微表面,降低反射光的强度。
- 菲涅尔等式:描述了不同表面角度的表面的反射率。
对上面三个函数有多种不同的模拟方式,这里我们选取虚幻4中的模拟方式。
法线分布函数
这里我们使用Trowbridge-Reitz GGX:
float DistributionGGX(vec3 N, vec3 H, float a)
{
float a2 = a*a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH*NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
几何函数
几何函数描述的是每个微表面之间的相互遮挡关系:
几何函数将材质粗糙度作为输入,包括法线和观察方向,这里使用 Schlick-GGX:
这里的
为了更有效的估计几何函数,我们将观察方向和入射光方向均考虑进来,我是史密斯方法进行合并:
右侧的
几何函数在GLSL中实现:
float GeometrySchlickGGX(float NdotV, float k)
{
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float k)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx1 = GeometrySchlickGGX(NdotV, k);
float ggx2 = GeometrySchlickGGX(NdotL, k);
return ggx1 * ggx2;
}
菲涅尔等式
菲涅尔等式描述了不同观察视角的反射光和折射光的比率。每个表面或材质都有一个基础反射率,对应垂直观察表面,从其它角度观察反射会更加明显。举个例子,看看周围的桌子,或木制,或金属制,从垂直方向观察的话会得到基础反射率的效果,如果近乎平行表面观察的方向观察表面,反射会非常明显。所有的表面从这种近乎平行的方向观察得到的强烈反射结果就是菲涅尔效应。
菲涅尔等式本身十分复杂,这里使用Fresnel-Schlick来近似模拟:
注意,Fresnel-Schlick是针对非电解质表面进行定义的,对于电解质表面,因为不考虑折射因素,所以无法计算
下面列举了一下不同材质的基础反射率:
可以观察到,非电解质的基础反射率不会超过0.17,大多数导体的基础反射率在0.5-1.0。同时,导体的基础反射率是着色的,也就是可以用某一RGB元组。
由于金属和非金属的区别,我们在金属流程中对描述材质使用一个额外的名为金属度的参数,用来判断一个表面是金属还是非金属。
通过预先计算
vec3 F0 = vec3(0.04);
F0 = mix(F0, surfaceColor.rgb, metalness);
Fresnel-Schlick函数实现:
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
Cook-Torrance反射方程
通过Cook-Torrance BRDF的每个组件,我们可以获得最终的反射方程:
这个反射方程完整的描述了一个PBR模型。
PBR材质
在实际工作中,往往使用各种贴图来控制PBR流程中的表面的参数,描述了平面中的某一点对光照的反应,即:这个点的金属度,粗糙度,平面如何应对不同波长的光。
下面是PBR流程中使用的几张贴图,并使用这几张贴图进行渲染输出:
- Albedo,反照率:即表面的基础颜色。
- Normal,法线:法线贴图。
- Metallic, 金属度 :用来判断每一个片段的金属度,往往使用灰度图。
- Roughness, 粗糙度: 用来判断每一个片段的粗糙度,使用灰度图。
- AO, 环境光遮蔽:环境光遮蔽贴图。
最后,给出原文地址供参考:https://learnopengl.com/PBR/Theory