近面裁剪新增顶点时,UV 必须沿齐次边插值
三角形穿过相机近面,被切出的轮廓位置完全正确,边上的纹理坐标却发生了偏移。这类错误容易被误判成采样问题:交点确实落在近面上,三角形也没有翻转,但新顶点的 U 已经从应有的 1/3 变成了 1/2。后面的过滤器只能忠实地采错位置。
为了把裁剪与光栅化分开,本次 C# 研究工程只处理一个三角形和一个近面。输入是透视除法之前的齐次位置 (x,y,z,w),保留半空间明确规定为 z>=0,且所有输入 w>0。UV 是随原表面线性变化、需要透视校正的属性。该约定不覆盖其他深度范围或反向深度的近面定义,也不处理跨过 W=0 的三角形。

轮廓正确,为什么不能证明裁剪正确
固定三点为 A=(-0.6,-0.6,-0.5,1)、B=(0.8,-0.6,1,2)、C=(0,0.8,1,2),对应 UV 分别是 (0,0)、(1,0)、(0,1)。A 在外,B、C 在内,所以裁剪会用 CA、AB 上的交点替换 A,得到一个四边形。
沿 AB 令齐次位置 P(t)=(1-t)A+tB,近面条件就是 -0.5(1-t)+t=0,解得 t=1/3。把同一个 t 用于原始 UV,得到 U=1/3;新位置为 (-2/15,-3/5,0,4/3),透视除法后是 (-0.1,-0.45,0)。位置和属性在这里共享的是原始边参数。
如果先分别除以 W,A、B 的深度变为 -0.5 和 0.5,屏幕边参数就变成 s=1/2。用 s 插值投影后位置,仍会得到正确的 (-0.1,-0.45,0);但直接插值原始 UV,却会得到 U=1/2。透视投影保持这段直线的形状,不保持它的线性参数,这正是轮廓检查漏过问题的原因。
在已经除法的接口里,也可以保存 U/W 与 1/W,再做有理恢复:本例为 (0.5×0/1+0.5×1/2)/(0.5/1+0.5/2)=1/3。这条路线有成立条件,不能与“屏幕参数直接加权 UV”混为一谈。对于本次仍持有齐次顶点的裁剪入口,我更倾向直接保留原始属性,避免提前引入另一种表示。

一次裁剪交付的是多边形和属性
完整关键实现位于 $LAB/NearClip.cs。TryTriangle 按输入环绕顺序遍历三条边;严格跨面时添加一个交点,当前顶点在面内或面上时保留它。Mix 同时插值位置四分量和原始 UV,随后将交点 Z 明确写为零。此处没有把 U/W 当成 U 继续混合。
public readonly record struct ClipVertex(
double X, double Y, double Z, double W, double U, double V);
public static class NearClip
{
public static bool TryTriangle(ClipVertex a, ClipVertex b,
ClipVertex c, out ClipVertex[] polygon)
{
polygon = [];
ClipVertex[] input = [a, b, c];
if (!input.All(Valid)) return false;
var output = new List<ClipVertex>(4);
var previous = c;
foreach (var current in input)
{
if ((previous.Z < 0 && current.Z > 0) ||
(previous.Z > 0 && current.Z < 0))
{
// 缩放距离后计算比例,避免有限大数相减溢出。
double scale = Math.Max(Math.Abs(previous.Z), Math.Abs(current.Z));
double from = previous.Z / scale;
double to = current.Z / scale;
double t = from / (from - to);
var crossing = Mix(previous, current, t) with { Z = 0 };
if (!Valid(crossing)) return false;
output.Add(crossing);
}
if (current.Z >= 0) output.Add(current);
previous = current;
}
polygon = output.ToArray();
return true;
}
private static ClipVertex Mix(ClipVertex a, ClipVertex b, double t)
{
double L(double x, double y) => (1 - t) * x + t * y;
return new(L(a.X, b.X), L(a.Y, b.Y), L(a.Z, b.Z),
L(a.W, b.W), L(a.U, b.U), L(a.V, b.V));
}
private static bool Valid(ClipVertex p) =>
double.IsFinite(p.X) && double.IsFinite(p.Y) &&
double.IsFinite(p.Z) && double.IsFinite(p.W) &&
double.IsFinite(p.U) && double.IsFinite(p.V) && p.W > 0;
}
严格跨面判断还有一个实际作用:顶点恰好位于 Z=0 时,直接保留该顶点,不再沿相邻边重复制造同一个交点。本实现不额外合并输入中已有的重复点,也没有把接近零的小数吸附到平面上;需要跨三角形无裂缝时,仍须约束共享边的数值计算与舍入策略。
求交比例先缩放两个深度,避免两个有限的大数直接相减而溢出。所有输入分量必须有限,W 必须为正;交点也再次检查。无效输入返回 false,并保持空输出。合法但全部位于面外则返回 true 和空数组,两者含义不同:前者应进入诊断或回退,后者只是没有可提交的覆盖。
本例输出顺序为 CA、AB、B、C,可组成 (0,1,2) 和 (0,2,3) 两个扇形三角形。数组也可能只有一个面上接触点或一条面上接触边,这些是合法的退化结果,消费端不能直接使用 Length-2 作为无条件绘制数量。即使有三个点,投影面积仍可能为零;三角化与面积筛选属于下一道接口,本次核心函数没有代替它们。
裁剪器拥有新增顶点的数据,光栅化阶段随后使用这些顶点的 W 和 UV 做透视校正。后者处理屏幕内部采样,前者建立新的边界顶点;如果裁剪已把 U 写错,后续透视校正不会自动找回原来的表面属性。
把位置巧合正确的路径留在回归中
Program.cs 中的 NdcLinearUvRegression 明确保留 U=1/2 的错误路径;NdcPositionStillMatches 同时断言该路径的位置仍然正确。两项必须一起看,才能验证开篇描述的现象,而非仅仅发现两次计算不相等。核心回归中的 ab 是裁剪结果里 AB 边的新顶点,Near 使用绝对容差 1e-10:
double wrongU = (-A.Z / A.W) / (B.Z / B.W - A.Z / A.W);
Require(Near(wrongU, .5) && !Near(wrongU, ab.U));
Require(Near((1-wrongU)*A.X/A.W+wrongU*B.X/B.W, ab.X/ab.W));
当天验证使用 .NET 9、double 与 Release 配置;$LAB 是保存 NearClipLab.csproj、NearClip.cs 和 Program.cs 的研究工程目录。在该目录运行:
cd "$LAB"
dotnet build NearClipLab.csproj -c Release
dotnet run --project NearClipLab.csproj -c Release --no-build
2026-09-14 的构建与运行退出码均为 0,构建零警告、零错误,命令耗时分别为 10.039 秒与 2.461 秒。程序同时写出完整顶点数组到 result.json,决定性输出如下;elapsedMs 是包含断言和测试输出的测试段时间,不是裁剪吞吐基准。
vertices=4 triangles=2 correctU=0.333333 wrongU=0.500000
passed=15 failed=0 skipped=0 samples=1197 elapsedMs=52.5122
KnownCrossingParameterSweep 预先指定 19 个交点比例,组合 9 个深度尺度和 7 组 W,共检查 1,197 组输入。测试用已知比例构造端点深度,再检验交点属性与位置,未把核心求交表达式复制成预期结果。其余用例覆盖全内、全外、面上接触、非法分量、极大正负深度、循环顶点顺序、反转绕序以及扇形面积相加。
这些证据验证的是 CPU 单平面裁剪与属性合同,没有运行 GPU 光栅化,也不据此声称真实像素无缝或性能改善。极小 W 的后续除法、任意尺度误差、其余裁剪面和共享边一致性仍需完整管线验收。flat 属性不应在交点处做这种数值混合,屏幕线性属性也有不同的插值约定,不能把所有顶点字段统一塞进 Mix。
对于这块穿过近面的三角形,保留正确轮廓只是验收的一半。渲染管线应让裁剪输出继续携带齐次位置和语义明确的属性,以交点值而非截图轮廓检查接口;等到后续阶段真正需要屏幕坐标时再除以 W。这样新增的边才能继续属于原来的表面,而不会让一条位置正确的切口带着错误 UV 进入纹理采样。