计算机图形学-05:可见性、着色与纹理
像素可见性
三角形投影到屏幕后,同一个像素可能同时被几个三角形覆盖。覆盖测试只能告诉我们这些三角形都经过这里,还要从中找出相机真正能看见的表面。
画家算法 Painter’s Algorithm 先绘制远处的表面,再让近处的表面覆盖已经写入的颜色。绘制立方体时,先画背面、再画侧面、最后画正面,通常能够得到正确遮挡。但是侧面的远近关系不能只由一个代表深度决定:某一侧面在一部分位置较近,在另一部分位置却可能较远,错误顺序会留下不应露出的边缘。排序不仅需要约 的代价,有时还不存在能够满足所有遮挡关系的顺序。

图中 遮住 的一部分, 遮住 , 又遮住 。若按整个三角形排序,三个条件无法同时满足。问题出在把整个图元作为排序单位;在每个屏幕位置上,最近表面仍然可以单独判断。
因此,深度缓冲 Depth Buffer,也称 Z-Buffer,为每个采样点保存当前最小深度,并让颜色缓冲 Framebuffer 保存与该深度对应的颜色。下面先约定深度 为正,且越小越近;它不等同于相机坐标中朝向负 的带符号坐标。
1 | 将所有采样点的深度初始化为无穷大 |
例如某点先收到深度为 5 的片元,就保存它;后来收到深度 3,则替换深度和颜色;再收到深度 8,两个缓冲都保持不变。处理任意数量的图元之后,总有
片元 Fragment 是光栅化生成的候选记录,包含位置、深度以及插值属性。只有通过深度等测试的片元才可能影响最终像素,一个像素因而可以先后收到多个片元。这个最小值关系说明:Z-Buffer 无须对图元排序;若每个三角形的覆盖样本数视为常数, 个三角形的比较总量是 。一般情况下更准确的工作量是所有候选片元数的总和,较大的三角形和重复遮挡都会增加它。

左图保存可见颜色,右图把最近深度显示成灰度:靠近相机的立方体角较暗,地板向远处逐渐变亮。两图位置一一对应,深度图的明暗只是数值的可视化,不是光照结果。
实际深度缓冲通常保存投影后的规范化深度。沿用透视投影中近平面对应 、远平面对应 的约定,可令
这样仍可用“小于”判断更近。硬件可把缓冲清为 1;严格小于测试会拒绝恰好位于最远边界、深度也为 1 的片元,边界是否绘制取决于所选比较规则。概念伪代码中的无穷大避免了这个细节。不同 API 或 reversed-Z 实现可能采用不同映射和比较方向,必须整套保持一致。
不透明表面、没有深度并列且比较规则一致时,交换图元提交顺序不会改变结果。共面或近共面表面可能出现深度并列与 Z-fighting,有限精度会使它们争夺同一位置。透明表面还要与后面的颜色混合,仅留下最近一层无法表示这种累积过程;通常需结合不透明深度测试、透明排序和混合,或专门的顺序无关透明技术。使用 MSAA 时深度也按覆盖样本分别记录: 样本是 4 份深度, 是 16 份,并非一个像素只存一份就足够。
局部着色
可见性正确之后,若把立方体的所有面填成同一种颜色,画面仍只有外轮廓,转动时不容易区分哪个面朝向哪里。给不同朝向的面计算不同亮度,才会显出表面的方向和转折。着色 Shading 研究表面在给定照明和观察条件下呈现什么颜色。
先只考虑一个表面点 。计算需要该点的单位法线 、指向光源的单位向量 、指向相机的单位向量 ,以及颜色、反射系数、Shininess 等材质参数。对点光源,另需光源强度 和光源到 的距离 。向量必须归一化并放在同一坐标系里;否则点积既不再等于夹角余弦,也无法正确比较方向。若程序把光方向定义成光线传播方向,它与这里的 相反,使用时需取负号。
这是一种局部着色 Local Shading:只根据 的属性和这些方向计算响应,还没有检查 到光源之间是否被其他物体挡住。因而表面可以有明暗和高光,却不自动产生投射阴影。阴影 Shadow 需要额外的可见性判断;间接光还需要计算别的表面如何反射光。先把这三类问题分清,后面的模型才不会被赋予它没有计算的效果。
以一张被照亮的表面为例,可以区分较宽的底色变化、局部较亮的高光,以及背光侧仍然保留的亮度。Blinn–Phong 模型 用漫反射、镜面反射和环境光三个分量近似它们。下面先从较宽的底色变化开始。
漫反射
对哑光表面,移动观察者时,同一个点的亮度可以近似不变。这种理想化行为称为朗伯漫反射 Lambertian Reflection:固定入射照明下,表面沿不同观察方向的出射辐亮度相同。辐亮度描述沿某个方向看到的表面亮度,严格定义见辐射度量。此处“各方向相同”不表示单位立体角的总功率相同,后者还与投影面积有关。
要算漫反射亮度,先看表面实际接收了多少光。把一小块表面正对平行光束,原来落入它的六条等能量光线都被接收;将表面旋转 后,它在光束垂直截面上的面积只剩一半,因此同样的表面积只接收到约三条光线。这个变化由朗伯余弦定律 Lambert’s Cosine Law 表示:
这里 是光源方向与法线夹角。正对光源时投影面积最大,接近掠射时投影面积趋近零。季节中的太阳高度变化也有这一因素:斜射时同样一束光分布在更大的地面面积上;季节本身还涉及日照时长等条件。单面不透明表面的背侧不接收这束直接光,因此使用 。
再看光源距离。假定点光源向各方向均匀发光,半径 的球面截获同样总功率,而球面面积为 。若单位距离处单位正交面积接收的值为 ,则
这里解释的是接收光随距离的变化,光源自身的辐射强度并未变小。更严格地把 定义为每单位立体角的辐射强度,也得到正对光源时的辐照度 。平行光源不使用这个点光源距离衰减。
最后乘以表面颜色系数,得到经验模型中的漫反射项:
三个因子分别控制材质响应、到达的光、表面对这束光的接收量。 取 RGB 三分量时,每个颜色通道有自己的响应比例;取零会让这一项为零,增大它会让这一项更亮。这里的 可吸收归一化常数,不能直接把 当成严格的“全部入射能量被反射”。若采用物理上的反照率 Albedo ,即总反射能量占入射能量的比例,朗伯 BRDF 为 ,对应
公式不含 ,所以固定点的漫反射亮度不随观察方向改变;也不含相机距离。在没有吸收和散射的传播介质中,辐亮度沿光线保持不变,远处物体主要是占据的图像面积变小。光源到表面的距离衰减与表面到相机的距离是两个不同问题。
镜面高光
光滑表面把反射集中在镜面方向附近。观察方向越靠近这个方向,看到的高光通常越亮。Phong 反射模型 直接比较观察方向 和理想反射方向
Blinn–Phong 采用另一种方向比较。若 与 恰好满足镜面反射定律,法线正好平分两者夹角。两单位向量相加后指向角平分线,因此定义半程向量 Halfway Vector

图中 平分 与 , 是 与 的夹角。理想反射条件下它们重合,;偏离时点积下降。Blinn–Phong 用这个点积计算镜面项:
为什么还要取 次幂?仅用余弦时,夹角达到 ,数值仍有 ,亮区过宽。将它取高次幂后,除了接近 1 的数值,其余很快下降:,。指数 即 Shininess,控制高光分布宽度。

四幅图从 到 ,峰值都为 1,但显著非零的角度范围逐渐缩小。 时在约 到 的范围已经明显衰减; 到 200 会得到更窄的高光。这些角度不是模型的硬边界:半高宽可由 求出 ,可见范围还取决于亮度尺度和显示条件。
控制镜面分量的颜色和强度。许多非金属表面的镜面反射近似不改变入射光颜色,白光下高光常接近白色;金属高光可以具有明显颜色。Phong 与 Blinn–Phong 都是经验模型,指数不能直接等同于物理粗糙度。半程方向提供了方便的计算方式,实际性能仍取决于实现,不能据此断言它在所有硬件上都更快。单面模型还应要求光源和相机位于表面正侧; 时半程向量未定义。上式省略了更完整物理模型中的若干几何和能量约束,因此适合用来理解高光,不保证能量守恒。
环境光
只计算直接光照,背向光源的表面会变黑。然而真实场景中,别的表面还会把光反射到这里。暂时不计算这些反弹,可以加入环境光 Ambient Lighting 常数项:
它与光源方向、观察方向和法线都无关,相当于给同一材质各点加一个相同的亮度基底。真实间接光会随遮挡、位置和其他材质改变,常数项只是近似。

图中环境项只有均匀底色;漫反射项随朝向形成大范围渐变;镜面项大部分位置较暗,仅在满足高光方向的位置出现亮斑。相加后,背光侧保留底色,面向光源的位置更亮,高光叠加在少数位置。完整经验公式为
若有多个直接光源,可以分别计算它们的漫反射和镜面贡献后相加,环境项不需要随每个光源重复添加。
着色频率
现在已经能够求一个点的颜色,还要决定在哪些点求解光照方程。着色频率 Shading Frequency 指评估光照的空间粒度:每个面、每个顶点,或每个片元。

先看第一行:左侧球的每个面内部颜色相同,面与面之间亮度突变;中间球通过插值变平滑,但高光较宽或可能被漏掉;右侧在表面内部重新计算光照,高光更集中。向下看,网格逐渐变密,三种方法之间的差别减小。两种变量需分别比较:横向改变着色频率,纵向改变几何采样密度。
- Flat Shading 为一个三角形选一个法线和代表点,只计算一次颜色,整个面使用同一结果。三角形法线可由两条边叉积后归一化得到,适合表达折面。
- Gouraud Shading 在顶点计算颜色,再插值到三角形内部。若亮峰位于面内部而三个顶点都不亮,颜色插值无法凭空生成这个峰,因此可能漏掉高光。
- Phong Shading 先把顶点法线插值到片元,再在片元位置归一化法线并计算光照。这里的 Phong 指评估位置,与使用 的 Phong 反射模型不同;Phong Shading 同样可以使用 Blinn–Phong 模型。
顶点法线从哪里来?若网格来自球面等解析曲面,可以使用原曲面的法线,例如球心指向球面点的方向。只有网格时,可以由相邻面法线估计:
最简单令权重相同,也可按面积或顶点夹角加权。这样相邻三角形共享顶点处的法线,插值后方向较平滑;机械硬边两侧则应保留独立法线,不能平均成圆滑过渡。插值一般会改变向量长度,参与点积前必须再次归一化。
更细的着色不改变实际轮廓和遮挡关系,粗球的边缘仍然是多边形。成本也不能只凭“逐面便宜”判断:当面片小于一个像素时,面片数量可能超过像素数量;粗网格上则常由逐片元计算得到更细的光照变化。
图形管线
变换、覆盖测试、可见性和着色各自处理不同对象。把它们连起来,就是图形管线 Graphics Pipeline。

沿图中的主干向下读,输入先是三维顶点;顶点处理改变它们的位置和属性;三角形处理根据连接关系形成图元;光栅化产生覆盖记录;片元处理计算颜色;帧缓冲操作决定这些颜色怎样写入图像。每个阶段接收的对象不同,所以同一段程序可以分别描述“一个顶点”或“一个片元”的计算。
具体实现中,应用在 CPU 上更新场景、相机和动画,并提交绘制指令。顶点着色器执行 MVP 等变换,输出齐次裁剪坐标;图元装配后在裁剪空间裁掉视锥外部分,进行透视除法与视口映射;光栅化执行覆盖测试和属性插值;片元着色器执行纹理查询及光照;逐样本操作执行深度、模板和混合,再形成输出图像。深度测试可能在满足条件时提前进行以避免无用着色,但其含义仍是保留可见样本。
着色器 Shader 是可编程阶段的程序。下面用 GLSL 展示一个片元的漫反射计算;纹理提供此点的颜色,光照项随后调节它:
1 | uniform sampler2D materialTexture; |
uniform 是一次绘制中共享的参数,in 接收从顶点属性插值得到的片元输入,texture 查询纹理,dot 求点积,clamp 限定数值范围,输出中的第 4 分量是 Alpha。这里省略了点光源距离衰减,按方向照明演示。硬件把程序应用到大量片元,不需要应用程序逐个遍历它们;着色器内部仍可包含循环、分支和函数。
GPU 为这种大量相似计算提供并行执行和固定功能单元。它既可作为独立显卡,也可集成在处理器中。以 2015 年的 Unreal Engine Kite Demo 为例,复杂场景包含数十万至数百万三角形,输出约 200 万至 400 万像素并进行抗锯齿,目标为每秒 30 至 60 帧,VR 往往要求更高刷新率。课件所举约 2–4 TFLOPS 是当时设备的计算量级,不是当前 GPU 的统一性能。几何着色器还可生成或丢弃图元,计算着色器可处理通用并行任务。Inigo Quilez 的约 800 行蜗牛 Shader 演示了由程序定义形状、外观和动画的用法。
OpenGL、DirectX 等 API 让应用配置这些阶段和数据接口;相机参数、变换矩阵和绘制状态仍由应用或引擎决定并提供,驱动不会替程序自动选择场景所需的 MVP。
纹理坐标
同一个球面既可以是一种纯色,也可以带有星形图案;同一块地板可以有木纹。它们使用同一光照方程,只需让 随位置变化。纹理映射 Texture Mapping 用一张图或其他可查询数据,为表面各点提供属性。

中间是世界空间中的地球表面,右侧是二维地图,左侧是投影后的屏幕图像。映射 关联表面点与地图位置,投影 关联表面点与屏幕位置。图像中的同一个点经历两套不同映射,不能把屏幕坐标直接当作纹理坐标。
三维表面局部只有两个自由度,因此可用二维参数 UV 标记位置。球面可以用经纬参数,复杂模型则常剪开成若干片再展开。这个建立表面与二维域对应关系的过程称为参数化 Parameterization。美术人员可手工展开,也可由算法尽量减少拉伸、压缩和接缝;曲面并不一定能无失真地展开成一张平面。

图中左右颜色对应相同位置,三维模型上的顶点在右侧都有 UV 坐标。渲染阶段已知三角形三个顶点的对应关系,再求内部点的 UV,而不需要重新进行整个模型的展开。查询纹理得到 RGB 后,可以把它用作这个点的 ,再计算光照;纹理颜色不替代光照结果。
纹素 Texel 是纹理数组的一个离散元素,像素是输出图像的元素,两者分辨率可以不同。常用归一化 UV 表示一张图的范围 。对宽高 的纹理,纹素 的中心可写为 ;具体上下方向约定需与图像加载保持一致。
UV 也可超过 :Repeat 重复平铺,Clamp 固定在边界,Mirror 交替镜像。要让重复纹理没有可见接缝,左右边界和上下边界需匹配,称为无缝纹理 Seamless Texture。Wang Tiles 用带边缘约束的多种小块拼接,可减轻单张图反复重复的规则感。法线、粗糙度、透明度等属性也能放进纹理,进一步用途见纹理作为数据。
重心插值
顶点有了颜色、法线和 UV,三角形内部应该怎样取值?先让两个顶点之间按位置比例过渡,再把这种想法推广到三角形。重心坐标 Barycentric Coordinates 为平面点 指定三个顶点的权重:
三个权重只有两个自由度,符合平面点用两个参数表示的要求。 自身的权重分别是 、、,三个权重都非负时点位于三角形内或边界;有负权重时则位于外部。

图中 是位于顶点 对面的子三角形面积,其他标记同理。对应权重正好是面积比例:
可把这一关系理解为点到对边的相对高度: 与 共用底边 ,面积比等于高度比; 越靠近 ,对边的相对高度越大, 的权重越大。重心把三角形等分成三个面积相等的部分,因此三个权重都是 。对外部点要使用有向面积,直接取绝对面积无法识别负权重。
在屏幕二维坐标中,也可直接解三元线性方程。令 ,其他顶点同理,非退化三角形有
若属性 在三角形上按仿射方式变化,就用 。它既能表示标量深度,也能按分量插值颜色、法线和 UV;法线还要归一化。 时三角形退化,不能通过上述除法取得唯一坐标。
还有一个关键限制:重心权重在仿射变换下保持,却不在透视投影下保持。远端等长线段在屏幕上更短,屏幕中点一般不是三维线段中点。直接用屏幕权重插值 UV,会使同一表面的纹理在不同三角形间呈现错误变化,必须做透视矫正插值 Perspective-Correct Interpolation。
设投影前权重为 ,齐次裁剪顶点为 ,第四分量为 。投影前的线性组合和投影后坐标满足
因此屏幕权重 。反解并用 消掉 ,得到
例如屏幕线段中点的两端权重都是 ,两端 分别为 1 和 4,投影前权重为 和 。近端在屏幕上占据更大的比例,所以同一屏幕中点的三维位置更靠近近端。实现中可分别线性插值 和 ,再做一次除法。普通属性和相机空间深度需要这种校正;已经完成除法的 NDC 深度 则在屏幕三角形内线性插值,不能再重复校正。
三角形内部的纹理查询把权重、属性和着色连接为下面的过程。w 来自齐次裁剪坐标,插值后的法线还需要归一化:
1 | 对每个通过覆盖测试的片元: |
纹理放大
查询得到的 UV 一般落在纹素中心之间。例如用 的图覆盖 4K 屏幕上的大块表面,一个纹素会对应多个像素,称为纹理放大 Texture Magnification。这时要从离散纹素重建连续颜色。
最近邻 Nearest Neighbor 选择离查询点最近的纹素,相当于把纹素中心坐标四舍五入。一个区域都返回同一个值,跨过中间边界时突然跳变,放大后便看到方块。双线性插值 Bilinear Interpolation 改用围住查询点的四个纹素中心。

图中红点是查询位置,粉色区域标出参与计算的四个纹素,黑点 保存已知颜色。 表示相对于左下中心的水平和垂直偏移,并以相邻中心距离为 1,所以都在 内。定义线性插值 Lerp
先沿水平方向算出上下两个中间值,再沿垂直方向求红点:
四个权重非负且和为 1。正中心 时就是四者平均,靠近某个纹素中心时,它的权重趋近 1;交换水平和垂直插值顺序,展开后仍得到同一公式。此处图中 是颜色值标记,不能与归一化 UV 坐标中的 混淆。

眼角和发丝附近,Nearest 显示方块边界,Bilinear 把相邻值连续连接,但边缘较柔和。双三次插值 Bicubic Interpolation 使用 的 16 个邻点,通过三次核重建,可改善平滑性,也增加查询和计算成本;具体连续性取决于核,带负权重的核还可能出现过冲。过滤能够改善重建,纹理原本没有采到的细节无法由插值唯一恢复。
纹理缩小
远处地板的情况相反:一个屏幕像素对应许多纹素,称为纹理缩小 Texture Minification。此时即使准确插值了像素中心的颜色,也只代表覆盖范围内的一个点。

右图近处的斜线出现锯齿,远处格子产生摩尔纹;左图细线随着距离自然汇合。越远处,每个像素跨过的黑白变化越多,单次查询容易只取到其中一个颜色,采样位置的微小变化就会引起较大的结果变化。这是纹理信号的欠采样,不是简单增加纹理原图分辨率就能解决的问题。

蓝点表示查询中心,灰点表示纹素中心,带色平行四边形表示一个像素投回纹理后的覆盖范围 Footprint。从左向右,范围由小于一个纹素变为覆盖多个纹素。放大时主要需要重建一个点的值;缩小时则需要整个范围经过滤波的平均。
每像素取 512 个样本再平均的演示可以明显减少这些伪影,但成本随样本数量增长,512 也不是所有纹理都会满足的固定阈值。问题于是转为:能否不在运行时逐个遍历范围内纹素,而直接查询预先算好的平均?这区分了点查询 Point Query 与范围查询 Range Query。范围查询还可能求最大值或最小值,这里特指颜色平均,需要与所求量匹配的数据结构。
Mipmap
Mipmap 预先保存原图以及一系列低分辨率版本。第 0 层是原图,第 1 层宽高各减半,每个值由前一层相邻 纹素平均而来,后续重复这个过程。第 层的一个纹素代表原图约 的方形范围。

图中 的原图逐步减到 ,连同第 0 层共 8 层;越高层越少细节,最后一个纹素保存全图平均。它也称为图像金字塔 Image Pyramid;Mipmap 来自 multum in parvo,意为在小空间中保存大量信息。它支持快速、近似的方形范围平均,既不精确积分任意边界,也不直接处理任意形状。
宽高都减半,每层存储量是前一层的 。对较大的 2 的幂次方形纹理,总量接近
即额外约 。有限纹理的总和略小于这一极限。也可以把各层各复制三份:原图三份填满大正方形的三个象限,剩余象限放下一层三份,如此递归填满,得到 ,其中 为原图存储量。非 2 的幂次纹理也可生成 Mipmap,但每层尺寸取整和边界滤波需按实现规则处理,不要求所有实际纹理先补成 2 的幂。
运行时要回答的下一个问题是选哪一层。先把当前像素中心及其屏幕右侧、上侧的邻居映射到纹理。

左图的蓝色与橙色点分别表示两组相邻屏幕采样位置,右图显示它们在纹理空间中的对应位置。橙色点的纹理间距比蓝色点大,所以橙色点位置需要更高的层级。将 UV 距离换成原图纹素单位,记两个方向跨度为 ,取 。也可把像素四角映射后用边长估计,二者都是局部近似。用偏导数写为
这里偏导数描述屏幕位移如何转为纹理位移,组合成局部 Jacobian 雅可比矩阵。每升一层,原图覆盖边长翻倍,所以求 ,得到
时用第 2 层, 时用第 3 层; 属于放大,使用原图进行重建。实际还应把层级限制在 ,零跨度取一个正下限后再求对数。

这幅伪彩色图用颜色编码查询层级 ,颜色只表示层级数值。同一连续墙面上的大块色带表示层级取整导致的跳变;若直接把整数层级用于纹理颜色查询,跨层时可能出现接缝。三线性过滤 Trilinear Filtering 在相邻两层分别双线性查询,再沿层级做一次线性混合:令 、,则
例如 时两层权重为 和 。这里第三个维度是层级,并非纹理的第三个空间坐标。

同一墙面上的层级编码现在连续过渡。柱子与背景交界处仍有颜色跳变,是因为它们属于不同表面、覆盖尺度也不同,不能把所有跳变都归咎于过滤。一次三线性过滤包含相邻两层的双线性查询,再做一次层级混合;每层使用四邻点,合计最多八个纹素,硬件常能把这些操作作为一次过滤查询完成。它减少层间突变,未改变方形范围近似的限制。
各向异性
斜视地板时,footprint 往往细长。如果按长边 选择方形 Mipmap 范围,短边也被扩大到 ,许多本不该参与的纹素被平均,产生过度模糊 Overblur。需要在长轴上充分滤波,同时尽量保留短轴的细节,这就是各向异性过滤 Anisotropic Filtering 的动机。

图中远处的地面线条仍可辨认。改善来自过滤范围与斜视覆盖形状更接近,不是增加了原图细节。普通 Mipmap 各层宽高同步缩小;Ripmap 分别预计算横向与纵向缩小,得到可查询轴对齐矩形的二维层级表。

上半图把卫星纹理的不同横向、纵向缩放组合排成一个表格:向右宽度减小,向下高度减小;第一行保持原高度,第一列保持原宽度。从左上到右下的对角线就是普通 Mipmap 的同步缩小序列。它能适应轴对齐的长条,但与坐标轴斜交的 footprint 仍可能被不必要地扩宽。完整 Ripmap 的存储总量接近
即总量约原图 4 倍、额外约 3 倍,不能把额外存储量当成总量。
下半图椭圆的等权曲线具有不同长短轴和方向。EWA Elliptical Weighted Average 用椭圆高斯权重近似 footprint,通过多次加权纹理查询近似范围积分,并可借助 Mipmap 缩小查询工作量。靠近核中心的样本权重较大,沿椭圆长轴较远的样本仍可能参与平均。它适应旋转、拉伸的局部覆盖范围,查询次数随范围和精度需求增加,结果仍是近似值。
实际 GPU 的各向异性过滤也常沿长轴组合多个较小范围查询,而不存完整 Ripmap。设置中的 2×、4×、8×、16× 通常表示允许的最大各向异性程度,限制可支持的长短轴尺度比和相应采样预算,具体策略依硬件而异;它们不表示保存了多少层 Mipmap。提高等级可能改善斜视纹理,也会增加查询、带宽和计算开销,收益仍受原始分辨率、footprint 与实现限制。




