几何(网格处理)、阴影图
前两章讨论了几何如何表示,以及曲线曲面如何由连续描述离散成网格。真正进入渲染系统时,我们经常面对的是三角网格:它可能来自建模软件、扫描重建、程序生成、曲面离散化,也可能是从高模烘焙后得到的低模。
这一章先看网格处理的基本问题:法线如何计算,网格为什么要简化、细分、重网格化,拓扑问题为什么会影响后续算法。随后,我们引入一个非常重要的可见性技术:阴影图 (Shadow Map)。它把几何从光源视角渲染成深度图,再在相机视角中用深度比较判断一个点是否被光照到。
网格处理关注什么
Section titled “网格处理关注什么”三角网格可以写成:
其中 是顶点集合, 是三角形面集合。实际项目中,网格还会携带法线、UV、切线、材质 ID、骨骼权重等属性。
网格处理要解决的不是“能不能画出三角形”,而是:
- 网格是否足够干净,能被后续算法稳定处理。
- 网格密度是否合适,既不过重,也不丢失重要形状。
- 法线、UV、切线等属性是否与几何一致。
- 拓扑是否适合变形、细分、布尔、碰撞或制造。
同一个模型在不同阶段可能需要不同版本:建模时需要高细节,运行时需要低成本,碰撞时需要简单形状,阴影渲染时又可能需要单独的 shadow caster 版本。
面法线与顶点法线
Section titled “面法线与顶点法线”三角形的面法线由两条边的叉乘得到:
面法线垂直于三角形平面,适合 Flat Shading。如果每个三角形都直接使用自己的面法线,模型会呈现明显的折面感。
为了让表面看起来更平滑,渲染时常使用顶点法线 (Vertex Normal)。一个常见做法是把共享同一顶点的相邻面法线加权平均:
权重 可以按面积、夹角或其它规则设置。顶点法线再经过光栅化插值,就能让低多边形模型呈现更连续的光照变化。
硬边、平滑组与法线拆分
Section titled “硬边、平滑组与法线拆分”不是所有边都应该平滑。立方体的边缘如果把相邻面法线平均,就会看起来像被磨圆;而真实立方体应该保留硬边。
解决方法是法线拆分:同一个空间位置可以在网格数据中对应多个顶点副本,每个副本拥有不同法线。这样,面之间虽然位置重合,但着色时不会共享平滑法线。
建模软件中的平滑组、硬边、Auto Smooth,本质上都在控制哪些面共享顶点法线,哪些面需要拆开。
网格简化 (Mesh Simplification) 的目标是在尽量保留形状的前提下降低顶点和三角形数量。它常用于:
- 生成 LOD 模型。
- 降低移动端或实时场景负担。
- 清理扫描模型中过密的三角形。
- 为碰撞或阴影生成低成本代理网格。
一种常见思想是不断折叠边:把一条边的两个端点合并成一个点,同时删除相关三角形。每次折叠都会引入误差,因此算法需要估计“折叠哪条边最不影响形状”。
网格简化不只是减少数量。它还要尽量保留轮廓、锐边、UV 边界、材质边界和动画变形关键区域。否则,模型虽然轻了,视觉和功能却可能坏掉。
细分与重网格化
Section titled “细分与重网格化”简化是减少几何,细分 (Subdivision) 则是增加几何。细分可以让模型更平滑,也可以为置换贴图、雕刻或模拟提供更高分辨率。
细分曲面会根据邻域规则插入新顶点并调整位置,使粗控制网格逐渐变成平滑表面。它适合角色、有机形状和动画资产。
重网格化 (Remeshing) 则关注重新组织三角形分布。一个扫描模型可能三角形大小不均、方向混乱、拓扑不佳;重网格化可以让面片更均匀,或者让边流更适合动画变形。
| 操作 | 目标 |
|---|---|
| 简化 | 减少三角形,降低成本 |
| 细分 | 增加几何,提升平滑度或细节承载能力 |
| 重网格化 | 改善三角形分布、边流和拓扑质量 |
网格拓扑描述顶点、边、面之间如何连接。很多算法要求网格接近“正常表面”,也就是局部看起来像一张二维薄片。
常见问题包括:
- 非流形边:一条边被超过两个三角形共享。
- 孤立点或孤立面:与主体没有连接的小碎片。
- 翻转面:三角形顶点顺序错误,导致法线朝内。
- 自交:表面穿过自己。
- 洞和裂缝:本应闭合的边界没有连接。
这些问题可能不会立刻阻止渲染,但会影响法线计算、阴影、布尔运算、物理碰撞、路径追踪求交和 3D 打印。
阴影也是可见性问题
Section titled “阴影也是可见性问题”有了光照模型后,一个表面是否亮,并不只取决于法线是否朝向光源。还要问:光源和这个表面点之间有没有被其它物体挡住?
这就是阴影问题。局部光照模型本身不会自动产生阴影,因为它只计算当前点、光源和观察方向的关系,不知道中间是否有遮挡物。
阴影可以理解为从光源出发的可见性测试:
最终光照可以写成:
环境光通常不乘这个直接光可见性,因为它近似的是间接照明;直接漫反射和高光则需要被阴影遮挡。
Shadow Mapping 的核心思想
Section titled “Shadow Mapping 的核心思想”Shadow Mapping 是实时渲染中最常见的阴影技术之一。它的思想非常直接:
- 从光源视角渲染场景:把相机放到光源位置或沿光源方向观察场景。
- 只记录深度:生成一张深度纹理,称为 shadow map。
- 从相机视角正常渲染:对每个片元,计算它在光源视角下的位置和深度。
- 比较深度:如果片元到光源的深度大于 shadow map 中记录的最近深度,说明它被挡住。
- 调制光照:被挡住的片元不接受该光源的直接漫反射和高光。
用一句话概括:先问光源看到了什么,再问当前片元是不是光源看到的最近表面。
Shadow Map 可以理解成“光源视角下的可见性缓存”。第一次渲染只关心深度,第二次渲染才从相机视角生成颜色,并用光源空间深度比较决定直接光是否被遮挡。
上面的组件把二维场景简化成一条光源深度图。调低阴影图分辨率会看到阴影边缘更粗糙;调节深度偏移可以观察 bias 对错误自阴影和阴影漂移的影响;开启柔化则模拟 PCF 对阴影边界做邻域平均。
光源空间与深度比较
Section titled “光源空间与深度比较”Shadow Mapping 的关键是把当前片元投影到光源空间。假设片元世界坐标为 ,光源也有自己的视图投影矩阵:
然后把裁剪空间坐标转换到 shadow map 的纹理坐标,并得到当前片元在光源视角下的深度:
vec4 lightClip = lightProjection * lightView * vec4(worldPosition, 1.0);vec3 lightNdc = lightClip.xyz / lightClip.w;vec2 shadowUV = lightNdc.xy * 0.5 + 0.5;float currentDepth = lightNdc.z * 0.5 + 0.5;再从 shadow map 里读取光源曾经记录的最近深度:
float closestDepth = texture(shadowMap, shadowUV).r;float shadow = currentDepth > closestDepth ? 1.0 : 0.0;实际代码通常会加入 bias、边界判断、PCF 和不同光源类型的投影处理。
Shadow Acne 与 Bias
Section titled “Shadow Acne 与 Bias”Shadow Mapping 最经典的问题是 Shadow Acne,也就是表面出现密密麻麻的错误自阴影。
它的原因是当前片元深度和 shadow map 中存储的深度来自两次不同采样。由于分辨率、浮点精度、斜面投影和光栅化误差,理论上同一个表面点在比较时可能被判断为“比最近深度更远”,于是错误地阴影了自己。
常见解决方法是加入深度偏移:
float bias = 0.005;float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;但 bias 不能无限增大。太小会有自阴影,太大则会让阴影脱离物体,看起来像悬浮在地面上,这叫 Peter Panning。
阴影图分辨率与锯齿
Section titled “阴影图分辨率与锯齿”shadow map 是一张纹理,因此它有有限分辨率。当阴影投射到屏幕上时,一个 shadow map 纹素可能覆盖很多屏幕像素,阴影边缘就会呈现块状或锯齿。
影响阴影清晰度的因素包括:
- shadow map 分辨率。
- 光源视锥覆盖范围。
- 物体与光源、相机之间的关系。
- 阴影接收面和光线夹角。
如果光源视锥覆盖了巨大场景,但 shadow map 分辨率有限,每个区域得到的深度精度就会变低。方向光的级联阴影 (Cascaded Shadow Maps) 正是为了解决大场景中近处阴影需要更高分辨率、远处阴影可以更粗的问题。
PCF 与软阴影近似
Section titled “PCF 与软阴影近似”最简单的 shadow map 比较只返回 0 或 1,阴影边缘会非常硬。PCF (Percentage-Closer Filtering) 会在 shadowUV 附近采样多个深度值,分别做比较,然后平均结果:
它不是物理真实的软阴影,但能让边缘更柔和,也能隐藏一部分分辨率锯齿。真实软阴影还和光源面积、遮挡物距离、接收面距离有关,后续更高级技术会继续处理这些问题。
网格质量如何影响阴影
Section titled “网格质量如何影响阴影”阴影图看似是渲染技术,但它和几何质量关系很大。
如果模型法线错误,光照会怪;如果模型面朝向错误,shadow caster 可能被剔除;如果网格有裂缝,阴影可能漏光;如果 shadow caster 的低模代理和可见模型差距太大,阴影轮廓会不匹配。
常见排查方向包括:
| 现象 | 可能原因 |
|---|---|
| 阴影断裂 | 网格裂缝、深度偏移过大、shadow map 分辨率不足 |
| 表面脏斑 | bias 太小、法线偏移不足、斜面深度精度差 |
| 阴影漂浮 | bias 太大、caster 代理模型偏离可见模型 |
| 背面不投影 | 面剔除设置或法线/绕序错误 |
| 远处阴影闪烁 | shadow map texel 与相机运动不稳定 |
这说明网格处理和阴影并不是两个完全割裂的话题。高质量几何数据会让可见性、光照和后续渲染技术更稳定。
本章从网格处理过渡到阴影图,重点建立两个观念:网格质量会影响后续渲染,阴影本质上是从光源视角做可见性判断。
- 面法线来自三角形边的叉乘,顶点法线通常由相邻面法线加权平均,用于获得更平滑的光照。
- 硬边和平滑边需要通过法线共享或法线拆分控制;错误法线会直接造成异常明暗。
- 网格简化、细分和重网格化分别服务于降低成本、提升平滑度和改善拓扑质量。
- Shadow Mapping 先从光源视角记录最近深度,再从相机视角把片元投影回光源空间做深度比较。
- shadow acne、peter panning、阴影锯齿和漏光都与深度精度、bias、分辨率、采样和几何质量有关。
下一章会进入光线追踪的基本原理:不再从三角形覆盖屏幕出发,而是从相机发射光线,直接求解光线与场景几何的交点。