一个三角形的顶点越过近裁面,整个三角形就消失;两个物体只交换绘制顺序,前后关系就颠倒。这是两种不同的可见性错误。前者需要裁剪保留仍在视锥内的几何,后者需要在每个样本处比较深度。

第02篇只能判断一个点是否合法,第05篇只能判断一个屏幕样本是否属于三角形。本篇将两者连接起来:先在齐次空间裁剪,再投影、光栅化和比较深度。实验使用128×128图像,颜色仍按第04篇的线性RGB约定编码输出。

裁掉的是外侧部分,不是整个图元

沿用相机朝−Z、列向量、NDC深度[0,1]的约定。裁剪坐标记为(x,y,z,w)(x,y,z,w),可见体满足:

w+x0,wx0,w+y0,wy0,z0,wz0.w+x\ge0,\quad w-x\ge0,\quad w+y\ge0,\quad w-y\ge0,\quad z\ge0,\quad w-z\ge0.

六个不等式分别表示左、右、下、上、近、远平面。它们在四个分量上都是线性的,因此可以直接求边与平面的交点。这里的z已经是投影矩阵输出,不能再把相机空间的负z代入近面条件。

一个顶点不满足条件,只能证明这个顶点在外侧。三角形的另外一部分仍可能可见。甚至三个顶点分别落在不同外侧,三角形也可能横跨整个视口。因此“只画三个顶点全部合法的三角形”会造成突然消失,而“把非法顶点扔掉”也不能留下正确边界。

当前实现采用逐平面多边形裁剪。输入是三个顶点,经过一个平面后可能变成四边形;继续处理其余平面,始终保存有序顶点环。裁剪后为空,才表示这次处理没有留下可光栅化的区域。

一条边怎样产生新顶点

把某个平面条件写成f(p)0f(p)\ge0,边的起点为a、终点为b。沿边参数化:

p(t)=a+t(ba),0t1.p(t)=a+t(b-a),\qquad 0\le t\le1.

因为f是线性的,交点满足:

0=f(a)+t(f(b)f(a)),t=f(a)f(a)f(b).0=f(a)+t(f(b)-f(a)),\qquad t=\frac{f(a)}{f(a)-f(b)}.

只在一端内、一端外时计算这个比值。两端都在同一侧时不需要求交,避免平行边出现无意义的零分母。边界f=0算作内侧。

按顶点环顺序访问每条a→b边:内到内输出b;内到外只输出交点;外到内先输出交点再输出b;外到外没有输出。上一条边与下一条边共享端点,这个顺序保留多边形绕序。随后用新的顶点环继续裁下一个平面。

交点的四个分量都按同一个t插值。不能先除w,再拿齐次平面公式求交:两端w不同的时候,除法改变了参数沿边的分布;边跨过w=0时还有奇点。保留齐次坐标能在除法之前处理这些问题。

实现把交点受约束的分量重新设到本次平面上。例如近面直接令z=0,右面令x=w。这只消除当前求交舍入造成的微小越界,不使用一个大容差把真正的外侧区域收进来。输入分量要求有限且绝对值不超过101210^{12},限制教学接口的算术范围;它不是任意尺度的鲁棒几何库。

穿过近面的三角形留下什么

实验采用竖直视场90°、宽高比1、近距离1、远距离10。三个相机空间顶点为:

A=(0.6,0.6,2),B=(0.6,0.6,2),C=(0,0.4,0.5).A=(-0.6,-0.6,-2),\quad B=(0.6,-0.6,-2),\quad C=(0,0.4,-0.5).

A、B在近面之后,C位于近面与相机之间。沿A→C求相机z=−1处的交点,参数为t=2/3t=2/3,得到(0.2,1/15,1)(-0.2,1/15,-1);另一条边得到(0.2,1/15,1)(0.2,1/15,-1)。经本系列投影矩阵后,这两个交点的w=1、裁剪z=0。

检查器独立比较交点x、y、w与这些手算常数,绝对容差101210^{-12};它还确认裁剪后的四个顶点满足全部六个半空间。只检查“输出四个点”不够,因为四个错误位置也可能通过数量检查。

近面裁剪后的橙色梯形,程序输出128乘128

图中橙色梯形是程序实际输出,覆盖736个像素中心。原三角形尖端被近面切掉,剩下的底边与两个交点形成四边形。它在画面下方,是因为保留部分的相机y主要为负;视口再将Y方向翻转为图像向下。

四边形用一个固定顶点连接其余相邻顶点,形成两个三角形。第05篇的共享边规则负责这条内部对角线的归属。裁剪输出还可能包含重合点或共线边,量化后退化的三角形按既有约定不产生覆盖。

背面剔除只解决朝向

一个封闭物体的外壳通常只需绘制朝向相机的一侧。这里显式选择NDC横纵平面中逆时针绕序为正面;视口Y翻转后,它对应屏幕有符号面积为负。启用剔除时跳过屏幕面积非负的三角形。

这项选择与第05篇“正负绕序都能正确覆盖”没有冲突。覆盖函数接受两种绕序,剔除策略决定是否调用它。默认关闭剔除,需要检查闭合外壳时才显式打开;薄片、双面材料与反射变换不能无条件套用同一策略。

剔除也不能替代遮挡判断。两个都朝向相机的三角形仍可能互相挡住;关闭剔除后,背面的样本也仍需要参与深度比较。检查器把相同三角形反向,确认启用剔除时正面有覆盖、背面没有覆盖。

深度为什么可以在屏幕上插值

完成透视除法后,顶点深度为di=zi/wid_i=z_i/w_i。对一个被覆盖的屏幕样本,第05篇给出重心权重λi\lambda_i,其深度为:

d=λ0d0+λ1d1+λ2d2.d=\lambda_0d_0+\lambda_1d_1+\lambda_2d_2.

这里插值的是已经除w的深度,不是相机距离。透视属性的一般公式会除以λi/wi\sum\lambda_i/w_i,但深度不能在上式之后再除一次。原因可以直接推导:若原空间权重为αi\alpha_i,屏幕权重满足λi=αiwi/αjwj\lambda_i=\alpha_iw_i/\sum\alpha_jw_j,代回上式正好得到αizi/αiwi\sum\alpha_i z_i/\sum\alpha_iw_i,已经是所需的裁剪z与w之比。

CPU实现继续采用第05篇量化后的屏幕几何计算权重。每个顶点的深度留在浮点数中;固定格决定覆盖位置,不意味着深度也量化成同一份整数。后续若比较GPU结果,需要分别讨论其子像素精度与深度格式。

深度数组初始填1,新的d满足0d<dold0\le d<d_{old}时,同时写颜色与深度。靠近相机的表面有更小深度,后提交的远表面无法覆盖它。最远面恰好d=1时不能通过严格小于测试,这是clear=1与less共同决定的边界行为。

这条边界检查在首轮实现中实际失败过。直接累加三个λi×1\lambda_i\times1,浮点舍入可能给出略小于1的值。当前实现使用等价形式:

d=d0+λ1(d1d0)+λ2(d2d0).d=d_0+\lambda_1(d_1-d_0)+\lambda_2(d_2-d_0).

三个顶点深度相等时,两项差值严格为零,常量深度得到保留。修复没有把less改成带容差比较;否则会同时改变靠得很近的两个表面的遮挡规则。

交换提交顺序后图像相同

第二个场景直接指定两组三角形裁剪坐标,w均为1,投影形状相同。横纵顶点为(−0.8,−0.8)、(0.8,−0.8)、(0,0.8)。橙色三角形顶点深度为(0.2,0.8,0.2),蓝色为(0.7,0.3,0.7)。它们相交,不能只凭整个三角形的平均深度排出正确前后顺序。

先橙后蓝,逐样本深度测试保留两个可见区域

先橙后蓝的结果如图;交换顺序生成另一份文件。检查器逐个比较16384个像素和整个深度数组,两者完全相同,其中3901个样本显示橙色,1301个显示蓝色。这比比较文件大小或抽查一个中心像素更严格。

顺序无关的结论有条件:本场景是不透明表面,比较位置不存在影响颜色的等深度平局。两个表面恰好同深度时,strict-less保留先到达的颜色,交换顺序可能改变结果。透明混合更不能用这次检查宣称顺序无关。

实验没有性能计时。当前每个三角形扫描整个128×128视口,目的是把裁剪、覆盖与深度的关系分开检查;包围盒优化不会改变这里的验收目标。

复现与自检

1
2
make -C examples/computer-graphics check
examples/computer-graphics/build/visibility_check examples/computer-graphics/build

输出包括near-clipped.ppmdepth-ab.ppmdepth-ba.ppm。累计检查还对六个平面分别执行“单顶点越界裁成四边形”和“全部位于同一外侧变为空”,并检查远面strict-less与正反面剔除。原始输出保存在writing-plans/computer-graphics/evidence/06-cpu.txt

练习一:若直接因为C在近面外而丢弃整个三角形,会漏掉多少本次应覆盖的样本?若先将C的z强制改为−1而不改变x、y,为什么也不正确?

答案:本场景漏掉736个中心。强制移动C会得到一个三角形,其尖端仍为(0,0.4,−1),而正确裁剪得到两个交点(±0.2,1/15,−1)和一个四边形;它改变了几何,不是裁剪原三角形。

练习二:深度缓冲清为1,比较函数为less。一个全在远平面的三角形是否写入颜色?两个完全重合、颜色不同的三角形是否满足提交顺序无关?

答案:远面深度等于1,不小于初始值,因此不写入。重合三角形深度相同,第二个不能覆盖第一个,颜色由先提交者决定。本次顺序不变实验不能推广到这种平局,也不能推广到透明混合。

系列导航与资料

前篇:05:哪些样本属于三角形系列入口。下一篇:07:为什么贴图会扭曲

WebGPU规范:Primitive Clipping,六个齐次裁剪平面;Olano与Greer:Triangle Scan Conversion Using 2D Homogeneous Coordinates,§3.2讨论混合w符号,§4讨论插值。资料核验日期2026-09-19,记录见evidence/research-05-08.md。本篇采用自己的−Z相机与CPU覆盖规则,不声称完成GPU光栅验证。

累计实现为examples/computer-graphics/visibility.hpp,实验为visibility_check.cpp。当前只处理常量图元颜色;裁剪交点携带UV等属性以及透视正确插值留到下一篇。