计算机图形学 E02:透明叠面与会失效的历史颜色
两层半透明颜色交换绘制顺序,交叠区会变色。一个白色矩形移开后,上一帧的白色如果仍参与平均,原本露出的棋盘会留下尾迹。这两个问题都涉及多个样本的组合,但有效条件不同:透明合成依赖前后层次,时间抗锯齿依赖历史样本是否仍对应当前表面。
本篇将它们分别验证。真实 WebGPU 绘制两层半透明矩形并读回浮点附件;C++ 用带抖动的运动场景、运动向量和深度拒绝检查 TAA。解析面积覆盖率提供图像参考,亚像素速度另保留一次效果变差的实验。
alpha 需要和颜色存储一起定义
取未预乘的线性颜色 C 与 alpha α。预乘颜色 c=αC;透明红 C=(1,0,0)、α=0.5 存为 (0.5,0,0,0.5)。若存的是 (1,0,0,0.5),却把它当预乘输入,颜色会过亮。
alpha 可以在此表示像素内覆盖比例与不透明程度的综合权重,但它不自动包含厚度、折射方向或波长相关的吸收。第 23 篇的玻璃路径不是把物体画成一层低 alpha 就能替代的。
source-over 中,前层占去 α_s,后层只贡献剩余部分。预乘形式为:
这里 c_b 已预乘,不能再乘一次 α_b。若最终需要未预乘颜色,α_o>0 时再做 c_o/α_o;α_o=0 没有可恢复的颜色。本实验保留预乘结果,在黑背景显示时直接使用 c_o,避免混淆透明附件和屏幕背景。
先画半红再画半蓝,结果为 (0.25,0,0.5,0.75);先蓝后红则为 (0.5,0,0.25,0.75)。alpha 相同而 RGB 不同,合成不满足交换律。三层固定顺序可结合,但交换前后顺序改变遮挡解释。
通常可以让透明表面通过不透明深度测试,并保持关闭透明深度写入,再从远到近绘制。若近透明面先写深度,后面的透明层可能被直接拒绝。对象相交时,一个对象级排序键未必能给每个像素都提供正确层次;本篇只使用已知前后关系的两片矩形,不声称解决通用透明排序。
真实附件怎样证明排序差异
GPU 图像为 64²。红矩形覆盖 [8,44)×[8,44),蓝矩形覆盖 [24,56)×[24,56),交叠为 20×20,共 400 像素。顶点颜色已预乘,附件为 rgba32float,颜色和 alpha 都使用 src=one、dst=one-minus-src-alpha、operation=add。
页面实际请求 float32-blendable,清空附件为透明黑,绘制后复制到 MAP_READ 缓冲。程序逐 4,096 像素、四通道与独立矩形参考比较。canvas 的 alphaMode 不代替管线 blend;最终画布只是把附件回读在黑背景上做一次 sRGB 显示编码。
2026-10-01 的真实执行得到 Apple/metal-3 适配器,浏览器用户代理为 Chrome 154。正确顺序的最大通道误差为 0,中心交叠为 (0.25,0,0.5,0.75)。点击反序按钮后,全部结果仍匹配反序参考,但与正确参考差 0.25,并恰有 400 个像素改变。两种完整回读与 module/shader SHA 均保存;控制台本次没有新增 warn/error。
配套交互实验页可以重新执行两种顺序。需要本地 HTTP 服务和支持相应特性的适配器;不支持时显示失败,不替换为 CPU 后冒充 GPU。
时间采样需要当前表面的历史位置
第 08 篇在一个像素内进行空间采样。TAA 把不同帧的子像素样本也积累起来:同一几何经过变化的采样相位落到像素中,可以逐渐补足覆盖信息。仅将每帧相同的像素中心值做指数平均,不能证明实现了抗锯齿。
场景为 64×32 线性灰度图,白色矩形覆盖四像素宽的黑灰棋盘,运动 32 帧。前景值为 1,背景为 0 或 0.35。矩形初始边界 x∈[8.25,24.75)、y∈[7.375,23.625),每帧向右移动一个像素。
颜色在八点循环抖动上查询:j_x=(i+0.5)/8,j_y=((3i mod 8)+0.5)/8,i=frame mod 8。无随机种子。背景棋盘边界恰好位于像素边界,矩形的真实像素平均可由它与单位像素的重叠面积 A 精确计算:I_ref=A+(1−A)I_bg。
比较项包括中心单样本、每像素 16×16 空间采样、TAA,以及关闭深度拒绝的错误历史累积。空间采样与解析参考平均误差约 4×10⁻¹⁶,当前边界恰好与子样本格相容;这个结果不能推广成任意形状的 256 样本都精确。
定义运动向量 v=uv_current−uv_previous,则历史位置为:
前景向右移动一像素时,v_x=1/64,历史位置向左查一像素。背景静止,向量为零。方向用独立的左右像素检查固定;反号可能仍产生平滑画面,却会把另一位置的颜色带过来。
历史为什么必须拒绝
深度取简化代理:前景 0.25,背景 0.75;当重投影处当前与历史深度差大于 0.01,历史作废。初帧、尺寸变化、相机切换、UV 超过 [0,1) 也直接输出当前样本。所有分支在配套检查中分别执行,不能因为正常轨迹没有越界就省去越界检查。
有效时,当前权重固定 0.1:
深度与运动向量使用未抖动像素中心分类,颜色使用抖动样本。这是明确的简化代理,边缘像素可能包含混合覆盖,中心标签不足以表达它。history 用最近邻读取,没有双线性重建、邻域裁剪、反应性掩码或复杂透明历史。
在原来白色前景离开的地方,历史为 1、当前背景为 0。错误接受历史后,第一帧残留 0.9,下一帧 0.81,第 n 帧为 0.9ⁿ。独立单像素实验逐 12 帧核对这个公式;正确拒绝第一帧就回到 0。
深度差只能检出这类身份变化。相同深度的另一物体、变化的材质与光照,或透明前景和后景混合,都可能没有足够大的深度差。夹到当前邻域范围能限制离群颜色,却也可能压掉真实细节;不能称为所有历史错误的修复。
平均误差和显露像素分别统计
每组保留 32×64×32=65,536 行像素记录。下表是所有帧、所有像素对解析线性参考的 MAE:
| 水平速度 | 中心单样本 | 256空间样本 | TAA | 关闭深度拒绝 |
|---|---|---|---|---|
| 1像素/帧 | 0.00840912 | 3.987×10⁻¹⁶ | 0.00454444 | 0.0240159 |
| 0.75像素/帧 | 0.00825806 | 3.988×10⁻¹⁶ | 0.0171721 | 0.0236868 |
整像素运动时,TAA 降低了这个场景的平均覆盖误差。527 个从前景变成背景的中心样本中,正确拒绝的 MAE 为 0,错误累积为约 0.371865。整体 MAE 受大量静止背景稀释,单独统计显露集合才显示拖影的强度。
0.75 像素速度下,历史连续位置需要亚像素重建,最近邻选择会反复把相位不同的边界样本合并;深度中心代理也可能错配覆盖。TAA 的整体 MAE 反而超过中心单样本约两倍。391 个显露样本中,正确版本仍有约 0.137676 误差,错误版本约 0.270068。
这个反例保留了相同采样、32 帧预算和权重,没有通过改速度隐藏问题。它说明当前有限实现适用范围窄,而不是“TAA 必然比空间采样差”。若增加历史过滤与更一致的深度/覆盖信息,需要重新比较误差;本篇没有将这些尚未实现的方法计为完成。
练习与自检
练习一。预乘红层 (0.25,0,0,0.25) 覆盖预乘蓝层 (0,0,0.5,0.5),输出是什么?
答案:c=(0.25,0,0.375),α=0.25+0.75×0.5=0.625。若要求未预乘 RGB,则除以 0.625 得 (0.4,0,0.6)。不能直接把预乘 c 当成这个未预乘结果。
练习二。当前像素 UV=(0.6,0.5),运动向量为 (0.1,0),历史位置在哪里?白色移开而背景为黑时,错误累积经过十帧还剩多少?
答案:历史 UV=(0.5,0.5);错误残留为 0.9¹⁰≈0.348678。改变查询位置与拒绝历史是两项必要检查,正确运动向量不能使已遮挡的背景历史自动存在。
练习三。正常动画未出现 resize 和 camera cut,是否可以用它的画面证明两种拒绝分支正确?
解题要点:不能。应分别改变尺寸、设置切换标记,检查结果逐像素等于当前值,且记录对应拒绝原因。构建通过不证明分支被执行。
复跑与原始材料
累计目录 examples/computer-graphics/ 中执行:
1 | |
temporalE02.hpp 保存合成与历史解析,检查器输出两组完整像素和逐帧统计、拒绝分支 CSV、十二帧尾迹与实际图像。首次数据在本文素材目录:整像素统计、亚像素统计、拒绝记录。真实 GPU 完整附件回读与截图在 writing-plans/computer-graphics/evidence/E02-gpu-*,CPU/Node 命令在 E02-cpu.txt。本篇没有计时或 GPU TAA 验证。
- W3C Compositing and Blending Level 1,2024-03-21 CRD,§5.1 与 §9.1.4:预乘颜色和 source-over。
- Nehab 等 2007:Accelerating Real-Time Shading with Reverse Reprojection Caching,§3.1–3.3:重投影、深度有效性与高频信息。
- Karis 2014:High Quality Temporal Supersampling,幻灯片 15–16、29–30、37、44、47:抖动、历史权重、裁剪与透明历史边界。本文仅实现其中可独立检查的小范围算法。
系列入口 · 上一篇:网格与距离场 · 下一篇:介质中的透射。






