图集边缘留了一像素,为什么远处图标仍会串色

物品图集沿 Mip 链缩小时出现边缘串色的渲染场景

一张物品图标在原尺寸下边缘干净,缩到远处或进入小尺寸 UI 后却冒出蓝边。基础图集已经给红色图块复制了一圈红色像素,UV 也没有越界,于是问题很容易被归因于 Shader 精度或纹理压缩。真正越界的往往不是 UV 中心,而是采样器在较低 Mip 上覆盖的 footprint。

边缘扩张不能被定义成“复制一圈像素”的布尔选项。它是图集生成器与运行时采样状态之间的容量合同:运行时允许访问多深的 Mip,生成器就必须为那个过滤范围预留足够的同色区域。

UV 没越界,过滤窗口仍然可以跨过图块边界

研究探针位于 .tmp/daily-labs/2026-08-27-2000/Program.csAtlas 生成两个相邻的 8 像素图块,左侧全红、右侧全蓝,并把每个图块的边缘颜色向外扩张。MipSampler.Downsample 每次对两个像素做 Box 平均,SampleLinear 再按归一化 UV 对当前级做线性采样。

public static Color[] Downsample(Color[] source, int levels)
{
    var current = source;
    for (var level = 0; level < levels; level++)
    {
        var next = new Color[current.Length / 2];
        for (var i = 0; i < next.Length; i++)
            next[i] = (current[i * 2] + current[i * 2 + 1]) / 2;
        current = next;
    }
    return current;
}

LOD 0 的一个纹素对应基础级的 1 像素,LOD 2 的一个纹素已经汇总 4 像素。线性采样还会在两个 Mip 纹素之间插值。即使采样中心仍在红图块最后一个有效像素上,只留 1 像素扩张时,参与插值的右侧纹素已经含有蓝图块;探针测得蓝通道为 0.3125。UV 中心合法与整个过滤窗口合法是两件事。

一像素与四像素扩张在 LOD 2 下的采样 footprint 对比

这也解释了为何关闭 Mip 后症状会消失,却不是可接受的修复。关闭 Mip 只是把 footprint 压回基础级,同时放弃远距离纹理的带宽、缓存和抗闪烁收益。把图标 UV 向内缩半个纹素同样只修正某个固定级别;当导数选择更深 Mip 时,那半个基础级纹素并不会随 footprint 一起增长。

扩张宽度必须来自最大采样级别

在本文固定的 Box Mip 与线性采样模型下,最大采样级别为 L 时,图块边缘至少要覆盖半个基础级 footprint:

padding >= 2^(L - 1), L > 0

因此 LOD 2 至少需要 2 像素;探针使用 4 像素扩张时,红图块右缘样本的蓝通道为 0.0000。若运行时允许访问 LOD 3,合同下限增加到 4 像素。RequiredPadding 把这个约束变成图集构建参数,而不是让美术导入面板保留一个没有单位的“防串色”开关:

public static int RequiredPadding(int maxLod)
{
    if (maxLod < 0 || maxLod > 20)
        throw new ArgumentOutOfRangeException(nameof(maxLod));
    return maxLod == 0 ? 0 : 1 << (maxLod - 1);
}

这个下限不能脱离采样状态直接推广。各向异性过滤会沿高导数方向拉长 footprint;块压缩可能要求图块和扩张区按压缩块对齐;非二次幂尺寸与紧密打包会改变 Mip 边界落点。实际资源管线应把 maxLod、过滤模式、压缩块尺寸和图块对齐共同输入打包器,再取满足全部约束的扩张宽度。若无法支付这个空间成本,应改用纹理数组或独立纹理,而不是在运行时继续猜安全 UV。

扩张还必须发生在生成 Mip 之前。先让整张紧密图集生成完整 Mip,再去修改基础级边缘,低级 Mip 中的相邻颜色已经混合,补基础级不会重建它们。可靠顺序是先生成带扩张区的每个图块,再构建整张图集的 Mip 链;或逐图块生成 Mip 后,把每一级连同对应边界一起打包。两种方式都要求扩张区属于图块资源合同,而不是发布前的像素修补。

固定样本把正常与失败路径分开了

Tests.Run 覆盖基础级、LOD 1、LOD 2 和 LOD 3。失败样本要求 1 像素扩张在 LOD 2 检出蓝色污染;正常样本要求 4 像素扩张在同一级保持纯红。边界测试还确认 2 像素扩张在 LOD 3 已不足,8 像素扩张仍安全,并拒绝负 LOD 输入。

cd "$WORK_DIR/.tmp/daily-labs/2026-08-27-2000"
dotnet restore AtlasMipBleedLab.csproj --ignore-failed-sources
dotnet build AtlasMipBleedLab.csproj -c Release --no-restore --nologo
dotnet run --project AtlasMipBleedLab.csproj -c Release --no-build

# 决定性输出
Build succeeded. 0 Warning(s). 0 Error(s).
tests pass=8 fail=0 skip=0
sample padding=1 lod=2 blue=0.3125
sample padding=4 lod=2 blue=0.0000
contract max_lod=3 required_padding=4

恢复、构建与运行退出码均为 0,墙钟耗时分别为 2.11s、6.35s 与 1.70s;程序内测试计时为 36.10ms。这些时间只证明本次验证确实执行,不用于比较方案性能。

探针没有模拟各向异性硬件实现、纹理压缩误差或引擎导数计算,也没有声称四像素适合所有图集。它验证的是更窄但能进入架构评审的结论:扩张宽度的所有者应是同时知道图块布局和运行时最大采样级别的资源管线;材质若提高过滤范围,必须让资产合同失效并重新构建,而不能只改采样器状态。

开头那条蓝边并不是 UV 坐标偶然偏了一点,而是资源生成阶段只保护了 LOD 0,运行时却允许采样更大的 footprint。把最大 Mip 与扩张宽度绑定后,串色会在打包检查阶段成为可拒绝的合同错误;当空间开销超过收益时,系统也能明确切换到纹理数组,而不是用关闭 Mip 掩盖边界不足。