图形学面试精要
基于物理的渲染(PBR)
1. PBR的核心原理是什么?
答:
PBR是基于真实物理规律的光照模型,它遵循能量守恒和微表面理论。
关键点:
- 能量守恒:反射光总量不超过入射光
- 微表面理论:表面由微观几何细节决定反射特性
- BRDF:双向反射分布函数描述光线反射行为
基于物理的渲染仍然只是对基于物理原理的现实世界的一种近似,这也就是为什么它被称为基于物理的着色(Physically based Shading) 而非物理着色(Physical Shading)的原因。 最先由迪士尼(Disney)提出探讨并被Epic Games首先应用于实时渲染的PBR方案。他们基于金属质地工作流(Metallic Workflow)的方案有非常完备的文献记录。
微平面模型
所有的PBR技术都基于微平面理论。这项理论认为,达到微观尺度之后任何平面都可以用被称为微平面(Microfacets)的细小镜面来进行描绘。根据平面粗糙程度的不同,这些细小镜面的取向排列可以相当不一致。产生的效果就是:一个平面越是粗糙,这个平面上的微平面的排列就越混乱。这些微小镜面这样无序取向排列的影响就是,当我们特指镜面光/镜面反射时,入射光线更趋向于向完全不同的方向发散(Scatter)开来,进而产生出分布范围更广泛的镜面反射。而与之相反的是,对于一个光滑的平面,光线大体上会更趋向于向同一个方向反射,造成更小更锐利的反射。简单来说,越粗糙,反射越混乱,越无序,效果越平均。反之,越光滑,反射越精确,越有序,效果越丰富。(注意这些光线都是镜面反射的一部分,不要当成漫反射!!!) 一般在shader中用一个粗糙度参数来控制微平面的粗糙程度。我们可以基于一个平面的粗糙度来计算出众多微平面中,朝向方向沿着某个向量h,反射出来的光线的比例。这个h就是半程向量,是入射光和观察向量的中间的向量。微平面的朝向方向与半程向量的方向越是一致,镜面反射的效果就越是强烈越是锐利。通过使用一个介于0到1之间的粗糙度参数,我们就能概略地估算微平面的取向情况了。
能量守恒
能量守恒是PBR的核心原理之一。它要求反射光总量不超过入射光。这意味着,当光线照射到一个物体时,反射光总量不会超过入射光。,随着粗糙度的上升,镜面反射区域会增加,但是镜面反射的亮度却会下降。如果每个像素的镜面反射强度都一样(不管反射轮廓的大小),那么粗糙的平面就会放射出过多的能量,而这样就违背了能量守恒定律。这也就是为什么正如我们看到的一样,光滑平面的镜面反射更强烈而粗糙平面的反射更昏暗。
为了遵守能量守恒定律,我们需要对漫反射光和镜面反射光做出明确的区分。当一束光线碰撞到一个表面的时候,它就会分离成一个折射部分和一个反射部分。反射部分就是会直接反射开而不进入平面的那部分光线,也就是我们所说的镜面光照。而折射部分就是余下的会进入表面并被吸收的那部分光线,也就是我们所说的漫反射光照。
这里还有一些细节需要处理,因为当光线接触到一个表面的时候折射光是不会立即就被吸收的。通过物理学我们可以得知,光线实际上可以被认为是一束没有耗尽就不停向前运动的能量,而光束是通过碰撞的方式来消耗能量。每一种材料都是由无数微小的粒子所组成,每次碰撞都会吸收光子中的一部分能量或全部能量随后转化成热量。一般来说,并非全部能量都会被吸收,而光线也会继续沿着(基本上)随机的方向发散,然后再和其他的粒子碰撞直至能量完全耗尽或者再次离开这个表面。而光线脱离物体表面后将会协同构成该表面的(漫反射)颜色。不过在基于物理的渲染之中我们进行了简化,假设对平面上的每一点所有的折射光都会被完全吸收而不会散开。而有一些被称为次表面散射(Subsurface Scattering)技术的着色器技术将这个问题考虑了进去,它们显著地提升了一些诸如皮肤,大理石或者蜡质这样材质的视觉效果,不过伴随而来的代价是性能的下降。
金属雨非金属遵守的定律是相同的,但是金属会将折射的光纤全部吸收,而不散射出,只留下了反射光或者说镜面反射光,而没有漫反射光。亦即是说,金属表面只会显示镜面反射颜色,而不会显示出漫反射颜色。由于金属与电介质之间存在这样明显的区别,因此它们两者在PBR渲染管线中被区别处理,而我们将在文章的后面进一步详细探讨这个问题。
反射光与折射光之间的这个区别使我们得到了另一条关于能量守恒的经验结论:反射光与折射光它们二者之间是互斥的关系。无论何种光线,其被材质表面所反射的能量将无法再被材质吸收。因此,诸如折射光这样的余下的进入表面之中的能量正好就是我们计算完反射之后余下的能量。
我们按照能量守恒的关系,首先计算镜面反射部分,它的值等于入射光线被反射的能量所占的百分比。然后折射光部分就可以直接由镜面反射部分计算得出:
float kS = calculateSpecularComponent(...); // 反射/镜面 部分
float kD = 1.0 - ks; // 折射/漫反射 部分
这样我们就能在遵守能量守恒定律的前提下知道入射光线的反射部分与折射部分所占的总量了。按照这种方法折射/漫反射与反射/镜面反射所占的份额都不会超过1.0,如此就能保证它们的能量总和永远不会超过入射光线的能量。
双向反射分布函数(BRDF)
\[f_r(h, \theta_i, \theta_r) = \frac{c}{\pi} \frac{DFG}{4(\theta_i \cdot h)(\theta_r \cdot h)}\]- GGX 法线分布函数(GGX Normal Distribution Function): 与n,h,a(粗糙度)有关,用来描述微表面光泽的分布。
float D_GGX(float a, float nDotH) {
float alpha = a * a;
float a2 = alpha * alpha;
float denom = nDotH * nDotH * (a2 - 1.0) + 1.0;
return a2 / (PI * denom * denom);
}
- 几何函数 用于描述微表面模型中光线因遮挡和阴影效应导致的能量衰减。
- 微表面遮挡:模拟光线在微观凹凸表面上的自阴影现象
- 能量补偿:修正因粗糙度增加导致的高光扩散现象
// 计算单方向的几何衰减
float geometrySchlickGGX(float NdotV, float roughness) {
float k = (roughness * roughness) / 2.0; // 直接光照使用
// float k = roughness * roughness / 8.0; // IBL环境光使用
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
// 完整几何函数(入射+出射)
float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx1 = geometrySchlickGGX(NdotV, roughness);
float ggx2 = geometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
2. PBR的OpenGL实践
2. 金属与非金属材质在PBR中如何处理?
答:
主要区别在于电介质(非金属)和导体的光学特性:
| 特性 | 金属 | 非金属 |
|---|---|---|
| 反射率 | 高(0.5-1.0) | 低(0.02-0.05) |
| 折射 | 不透明 | 可能透明 |
| 颜色 | 反射光有色 | 漫反射有色 |
// PBR材质参数示例
struct PBRMaterial {
vec3 albedo; // 基础颜色
float metallic; // 金属度(0-1)
float roughness; // 粗糙度(0-1)
float ao; // 环境光遮蔽
};
高动态范围(HDR)
1. HDR解决了什么问题?
答:
HDR突破了传统8位色深的限制,能更好表现真实世界的光照范围。
关键优势:
- 保留极端亮度细节(如太阳直射和阴影细节)
- 更自然的颜色过渡
- 支持后期色调映射
技术实现:
- 使用浮点纹理(如GL_RGBA16F)存储颜色,每个通道16位浮点数,传统的使用GL_RGB的话,会丢失细节,限制在了0-1范围内。
- 渲染时保持线性空间计算
- 最后应用色调映射(Tone Mapping)
2. 色调映射的常见算法有哪些?
答:
主流算法包括:
-
Reinhard: \(L_{out} = \frac{L_{in}}{1 + L_{in}}\)
- ACES:电影行业标准,对比度处理优秀
- Uncharted2:游戏常用,视觉效果鲜艳
// Reinhard色调映射示例
vec3 reinhardToneMapping(vec3 color) {
return color / (color + vec3(1.0));
}