Alpha 混合、透明排序与预乘颜色
Alpha 混合、透明排序与预乘颜色:保留 LearnOpenGL 3.3 Core 正文机制,以 context—资源—结果合同、GPU 轨迹和章专属单故障完成可重放验收。
学习目标
- 能从 blend equation 推导 source-over 的 RGB 与输出 alpha,并配置独立颜色/alpha 因子
- 能区分直通 alpha 与预乘 alpha 纹理,选择与资源约定匹配的 blend factors
- 能实现 cutout 的
discard/alpha-to-coverage 与连续透明的 blending 两条渲染路径 - 能实现“不透明 → 稳定远近排序 → 透明 pass → 状态恢复”,且不因同距离键值丢对象
- 能判断对象中心排序何时失效,并为相交透明几何选择拆分或 OIT 等替代方案
为什么半透明不是简单地少写一点颜色
想象你在一张已经画好的画上,再盖一层半透明的颜料:颜料越浓,越能盖住底下的画;颜料越稀,底下的画就越透出来。一块磨砂玻璃、一片飘动的薄纱、水面下隐约的石头——它们都不是「完全挡住」或「完全没有」,而是让背后的东西按一定比例透出来。
可前面几章那套画法做不到这件事:每个色块要么把那个位置整个盖掉、要么因为更远被深度测试丢掉,没有「半盖半透」这个中间档。结果就是你画不出玻璃、画不出薄纱,所有东西都硬邦邦地互相遮挡,假得很。
这一章要解决的就是:怎么让一块新画上去的颜色,按它自己的「浓度」,和画布上已有的颜色混在一起。这件事发生在管线很靠后的位置——前几章那些前后遮挡的「质检工序」都过完了,正要把颜色写进画布的那一刻,混合才登场。
混合:新颜色和旧颜色按比例相加
那「浓度」从哪来?从颜色的第四个分量——↡一个颜色除了红绿蓝(RGB)三个分量,还可以带第四个分量 alpha,记作 a,范围 0~1,表示这个颜色的不透明度:1 = 完全不透明(完全盖住背后),0 = 完全透明(背后完全透出),0.6 = 盖六成、透四成。。一个颜色写成 (r, g, b, a),前三个是颜色本身,a 就是「这块颜色有多不透明」:a = 1 完全盖住背后、a = 0 完全透明、a = 0.6 盖六成、透四成。
把「按 alpha 把新颜色叠到旧颜色上」这件事,图形学里叫 ↡把正在画的「新颜色」(源色)按它的不透明度 alpha,和画布上「已有的颜色」(目标色)按比例相加,得到一个介于两者之间的颜色,写回画布。这就是半透明物体能透出背景的原理。默认是关闭的,要用 glEnable(GL_BLEND) 打开。(blending)。默认它是关着的——关着时新颜色直接覆盖旧颜色,没有「透」这回事;要用 glEnable(GL_BLEND) 把开关打开,才进入「按比例相加」的世界。
它在管线里的位置在最末端:一个片段先过深度测试、模板测试(前两章),通过了、轮到把它的颜色写进 ↡正在画的这个片段算出来的颜色(前景),在混合方程里记作 Csrc。它带着自己的 alpha,决定要盖住背景多少。(source,记 Csrc)写进画布时——若开了混合,就不直接覆盖,而是把它和画布上那个位置已有的颜色,也就是 ↡画布(颜色缓冲)上该像素当前已经存着的颜色(背景),在混合方程里记作 Cdst。混合时它会按目标因子被保留一部分,从源色背后透出来。(destination,记 Cdst),按比例相加。具体怎么个加法,就是下一节的混合方程。
混合方程:源色配源因子、目标色配目标因子
混合不是简单地「新的盖旧的」,而是给源色和目标色各配一个权重因子,相乘后相加。这条规则叫 ↡混合时算最终颜色的规则:C = Csrc·Fsrc + Cdst·Fdst,即源色乘源因子 + 目标色乘目标因子。因子是 0~1 的权重,由 glBlendFunc 指定,挑不同因子得到不同混合效果。(blend equation):
它说:最终颜色 = 源色乘源因子 + 目标色乘目标因子。Fsrc(源因子)控制「新颜色占多少」,Fdst(目标因子)控制「旧颜色透出多少」。这两个因子由你用 glBlendFunc 指定,可以是常数 0、1,也可以是源色的 alpha 等。下面这张图把「两块色块各乘一个权重再相加」画得很直白:
最常用的一种配法,是把源因子设成源色的 alpha、目标因子设成 1 减源色 alpha——这正好对应「颜料按浓度盖、背景按剩下的比例透出」,业界叫 ↡最常用的一种混合配法:源因子取源色的 alpha、目标因子取 1 减源色 alpha,于是结果 = αsrc·源色 + (1−αsrc)·目标色。前景占 αsrc、背景占剩下的,就像把半透明颜料盖到画上。用 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 设定。(«source over»)。它的展开式 §4 会细推,直觉就一句:前景占 αsrc,背景占剩下的 1−αsrc。这也是本章主 Demo 让你亲手调的那条公式。
直通 alpha 与预乘 alpha:资源和因子必须配套
上面的公式默认纹理 RGB 仍是原始颜色,alpha 单独存放,这叫直通 alpha(straight alpha)。此时固定管线需要再乘一次源 alpha,使用 SRC_ALPHA, ONE_MINUS_SRC_ALPHA。另一种常见资源在存储时已经把 RGB 乘过 alpha,叫预乘 alpha(premultiplied alpha);它的源 RGB 已含覆盖率,再用 SRC_ALPHA 会二次相乘、边缘发暗,因此应使用 ONE, ONE_MINUS_SRC_ALPHA。
如果渲染目标的 alpha 后续还要参与合成,只用两参数 glBlendFunc 还会让输出 alpha 按 RGB 因子计算成 αsrc² + αdst(1-αsrc),这不是标准 source-over。直通 alpha 更完整的设置是分别指定 RGB 与 alpha 因子:
glBlendFuncSeparate(
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, // RGB
GL_ONE, GL_ONE_MINUS_SRC_ALPHA // alpha
);这样 αout = αsrc + αdst(1-αsrc)。若最终默认 framebuffer 的 alpha 不再使用,两参数版本通常看不出差别;渲染到中间纹理、再做 UI/后处理合成时则必须把 alpha 契约讲清。颜色本身也应在线性空间混合,sRGB 目标需使用正确的 sRGB framebuffer/采样配置,不能直接把 gamma 编码值当线性光能相加。
discard:全透明的片段,干脆别画
混合擅长「半透明渐变」,但有一类东西不需要渐变——比如一片草、一张铁丝网贴图。它们的纹理里要么是完全不透明的草叶/铁丝、要么是完全透明的空隙,没有中间档。这种「非 0 即 1」的透明,与其走混合,不如直接把透明的部分扔掉。
办法是片段着色器里的 ↡片段着色器里的一条命令:一旦执行,当前片段就被立即丢弃,不再往后走、不会进入颜色缓冲。常用来做镂空——纹理里 alpha 很小(接近全透明)的片段直接 discard,于是草叶、铁丝网的空隙就被「抠掉」,露出背后,得到清晰的硬边镂空。:采样纹理拿到 alpha,若 alpha 小于一个阈值(接近全透明)就 discard,这个片段直接被丢掉、连颜色缓冲都不进,于是空隙处「抠空」露出背后。它和混合的区别很值得对照看——一个是硬边镂空、一个是柔和半透明:
什么时候用哪个?记住一句:二元透明(草、铁丝网这类非透即不透)用 discard,连续半透明(玻璃、水)用 blend。用错了会出毛病(§7 会讲),现在先记住这个分界。
半透明排序:为什么玻璃会互相穿帮
混合还藏着一个最容易翻车的坑。↡source-over 对多个透明层的累积不满足交换律,因此必须按从远到近的覆盖顺序提交;透明层通常读取不透明深度但不写深度。对象中心距离只能近似排序,相交或循环遮挡几何不存在单一正确对象顺序。首先来自公式本身:source-over 不满足交换律,先叠 A 再叠 B 和先叠 B 再叠 A 的颜色不同。深度缓冲又只记最近值、不保存多层透明颜色,因此无法像不透明几何那样自动合成所有层。
如果先画近玻璃且写入深度,之后的远玻璃会直接深度失败;即便关闭深度写入,顺序仍然错误,因为颜色已经按“近后远”的顺序累积。下面这张图把乱序与正确透叠并排画清楚:
经典画法是:先画所有不透明物建立深度;再把透明表面从远到近稳定排序,保持深度测试开启、通常用 glDepthMask(GL_FALSE) 关闭↡片段通过深度测试后更新深度缓冲的动作。透明 pass 通常关闭它,使透明片段仍被不透明深度遮挡,却不把某一透明层固化成最近遮挡面。它不能替代远到近排序,pass 结束必须恢复。。完美远到近且不相交时,透明层即便写深度通常也会依次通过,但关闭写入能让 pass 契约更稳健;它仍不能修复错误顺序或相交几何。
对象中心距离只是近似。两块透明几何若相交、包围或形成循环遮挡,不存在单一对象顺序;需要拆分三角形排序、depth peeling、weighted blended OIT 等方案。大量粒子常选择近似 OIT,接受一定颜色误差换稳定性能。
公式:从混合方程到 over
本节就一件事:把混合方程,和它最常用的 over 配法,一步步推清楚。先约定符号:
- :混合后写进画布的最终颜色
- :源色,正在画的这个片段的颜色(前景)
- :目标色,画布上该像素已有的颜色(背景)
- :源因子,乘在源色上的权重(由
glBlendFunc指定) - :目标因子,乘在目标色上的权重
- :源色的 alpha(不透明度,)
混合方程。最一般的形式是源色、目标色各配一个因子,相乘后相加:
这个式子在说:最终颜色由两份拼成——一份是「源色乘源因子」(新颜色贡献多少),一份是「目标色乘目标因子」(旧颜色保留多少),把它们加起来。因子是 的权重,由你挑;挑不同的因子,就得到不同的混合效果。
展开成 over。最常用的配法把源因子取成源色的 alpha、目标因子取成 1 减这个 alpha:
这个式子在说:新颜色占的权重就是它自己的不透明度 ,旧颜色占剩下的 ——两份权重加起来正好是 1,不会越加越亮。把这两个因子代回混合方程:
这个式子在说:前景占 、背景占剩下的 。比如 ,结果就是 0.6 份前景色 + 0.4 份背景色——前景盖六成、背景透四成,正是「半透明颜料盖到画上」的样子。 越接近 1,前景越实、越盖住背景;越接近 0,前景越淡、背景越透。下面 Demo 里那个 uSrcAlpha 滑块,调的就是这条式子里的 。
若目标 alpha 还要保留,source-over 的覆盖率组合是:
人话就是:新层覆盖的部分算源 alpha,未被新层覆盖的剩余区域继续保留目标 alpha。对预乘颜色 ,RGB 式则写成:
源 RGB 已经乘过覆盖率,所以固定管线源因子取 1;这就是预乘 alpha 使用 ONE, ONE_MINUS_SRC_ALPHA 的数学原因。
动手:把前景透明度调来调去,看重叠区怎么变
猜一猜:下面这块画布,左边是一块固定的暖黄背景色块,右边叠着一块紫色前景色块,两块在中间重叠。把前景的透明度
uSrcAlpha往大调(接近 1)和往小调(接近 0),重叠区的颜色会更像前景的紫,还是更像背景的黄?先动手拖一拖,再看下面的解释。
这块画布在片段着色器里手画了两块色块——背景(目标色 Cdst)和叠在它上面的前景(源色 Csrc),重叠区严格按 over 公式 result = Csrc·αsrc + Cdst·(1−αsrc) 算颜色。拖 uSrcAlpha 改前景不透明度、拖 uSrcColor 换前景颜色,盯着重叠区怎么变:
实时演示加载中…
把 uSrcAlpha 调到接近 1,重叠区几乎全是前景的紫——前景几乎完全盖住了背景;调到接近 0,前景「透没了」,重叠区几乎全是背景的黄;停在 0.5 附近,重叠区是前景和背景各占一半的中间色。这正是 over 公式 Csrc·αsrc + Cdst·(1−αsrc):αsrc 越大,前景权重越大、越盖住背景,与上一节那条式子一字对上。
半透明排序:单步走一遍从穿帮到正确
猜一猜:场景里有三块半透明玻璃,一前一后。如果你不管顺序随便画,先画了最近那块、再画后面两块,后面两块会正常透出来,还是会「消失」?想一想深度缓冲那点事,再单步走一遍下面的修法。
下面把「为什么穿帮、两招怎么修」拆成三步。每一步都画出场景这一刻长什么样——盯着哪块玻璃透出来、哪块被丢弃:
① 乱序画 → 穿帮
不管顺序随便画:先画了最近 那块玻璃,它把颜色混进画布、也把自己的 深度 写了进去。之后再画后面更远 的两块,深度测试一看「更远」,把它们整片 丢弃 ——可它们是半透明的,本该透出来!结果后面两块消失,穿帮。
走完这遍要分清两个原因:source-over 的颜色累积本身要求远到近;深度写入又可能让后续透明层提前失败。先画不透明物建立遮挡,再排序透明层并通常关闭其深度写入,分别解决这两层问题。
代码逐段拆解
混合的代码分三块:开混合并设因子、用 discard 做镂空、半透明排序 + 关深度写入。三块对照看,C++/OpenGL 与 WebGL2/TS 互为镜像。
第一块:开混合、设 over 因子
混合默认是关的。初始化(或画半透明物之前)开 GL_BLEND,再用 glBlendFunc 把源因子、目标因子设成 over 那一对——源因子 GL_SRC_ALPHA(= 源 alpha)、目标因子 GL_ONE_MINUS_SRC_ALPHA(= 1 − 源 alpha)。
// 开混合开关(默认关;关着时新颜色直接覆盖,没有「透」)
glEnable(GL_BLEND);
// 设 over 混合:源因子 = 源 alpha,目标因子 = 1 - 源 alpha
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// 运算符默认是「相加」(GL_FUNC_ADD),即 C = Csrc*Fsrc + Cdst*Fdst;
// 想做相减等可改:glBlendEquation(GL_FUNC_ADD); // 一般无需改// 开混合开关(默认关;关着时新颜色直接覆盖,没有「透」)
gl.enable(gl.BLEND);
// 设 over 混合:源因子 = 源 alpha,目标因子 = 1 - 源 alpha
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,
gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
// 运算符默认是「相加」(FUNC_ADD),即 C = Csrc*Fsrc + Cdst*Fdst;
// 想做相减等可改:gl.blendEquation(gl.FUNC_ADD); // 一般无需改glBlendFuncSeparate 前两个参数是 RGB 的 source-over 因子,后两个让输出 alpha 使用 αsrc + αdst(1-αsrc)。若默认 framebuffer 的 alpha 不再被消费,可退回原书的两参数 glBlendFunc;渲染到中间纹理时优先保留正确 alpha。glBlendEquation 控制运算符,默认 FUNC_ADD。
第二块:用 discard 做镂空(草 / 铁丝网)
镂空贴图(草、铁丝网)不走混合,走 discard。片段着色器采样纹理后,若 alpha 小于阈值(接近全透明)就 discard,这个片段直接被丢弃。两端 GLSL 几乎一样,只差版本声明与 precision。
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D texture1;
void main() {
vec4 texColor = texture(texture1, TexCoords);
// alpha 太小(接近全透明)→ 丢弃这个片段,连颜色缓冲都不进
if (texColor.a < 0.1)
discard;
FragColor = texColor;
}#version 300 es
precision highp float;
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D texture1;
void main() {
vec4 texColor = texture(texture1, TexCoords);
// alpha 太小(接近全透明)→ 丢弃这个片段,连颜色缓冲都不进
if (texColor.a < 0.1)
discard;
FragColor = texColor;
}被 discard 的片段不写颜色、不写深度,相当于「这里什么都没画」,于是草叶之间的空隙就露出背后——硬边镂空。discard 这条命令两端写法完全一致;唯一差别是版本声明:
第三块:半透明排序 + 关深度写入
半透明物体是混合最容易翻车处。正确流程:先画所有不透明物(深度测试照旧),再把半透明物从远到近排序、用 glDepthMask(GL_FALSE) 关掉深度写入后逐块画,画完恢复。
// 1) 先画所有不透明物体(正常深度测试、正常写深度)
drawOpaqueObjects();
// 2) 用容器稳定排序;不要用 map<float,...>,相同距离会覆盖丢对象
auto sorted = windows;
std::stable_sort(sorted.begin(), sorted.end(), [&](const auto& a, const auto& b) {
const glm::vec3 da = camera.Position - a;
const glm::vec3 db = camera.Position - b;
return glm::dot(da, da) > glm::dot(db, db); // 远 -> 近
});
// 3) 关深度写入:半透明物仍比深度(被不透明物挡住),但不写回自己的深度
glDepthMask(GL_FALSE);
for (const auto& pos : sorted)
drawWindowAt(pos);
glDepthMask(GL_TRUE); // 画完恢复,别污染下一帧// 1) 先画所有不透明物体(正常深度测试、正常写深度)
drawOpaqueObjects();
// 2) 半透明物体从远到近排序(离相机越远排越前面)
const sorted = [...windows].sort(
(a, b) => dist(camera.pos, b) - dist(camera.pos, a),
);
// 3) 关深度写入:半透明物仍比深度(被不透明物挡住),但不写回自己的深度
gl.depthMask(false);
for (const pos of sorted) drawWindowAt(pos); // 已是从远到近
gl.depthMask(true); // 画完恢复,别污染下一帧两端都复制原列表再按距离平方降序排,保留每一个对象。不要使用 std::map<float, Object>:两个对象距离相同会占用同一个 key,后插入者覆盖前一个,透明物会凭空消失。稳定排序还能让距离相等时沿用提交顺序,减少相机轻微移动造成的跳变,但仍无法解决相交几何。
容易踩的坑
小结
- source-over 的 RGB 与 alpha 有各自因子;中间纹理用
glBlendFuncSeparate保留正确输出 alpha - 直通 alpha 配
SRC_ALPHA,预乘 alpha 配ONE;资源约定和 blend factors 不匹配会产生边缘伪影 - 硬边 cutout 用
discard/alpha-to-coverage,连续透明用 blending,并在线性颜色空间累积 - 经典透明 pass 先画不透明物,再稳定地远到近排序;深度测试保持开启、深度写入通常关闭
- 中心距离排序不能解决相交或循环遮挡,需拆分几何或采用 OIT,结束后恢复 blend/depth 状态
练习
问题 1(改 Demo 代码 / 观察题) 在上面的 over 混合 Demo 里,把 uSrcAlpha 分别拖到 0、0.5、1 三个值,观察重叠区颜色。回答:① 这三个值下重叠区分别最接近什么颜色?② 如果想让 αsrc = 0.6 时重叠区更偏背景色(背景透出更多),在不改 alpha 的前提下,该把混合公式里哪个因子改大?这还算 over 混合吗?
问题 2(独立实现题) 你要做一片草的贴图(草叶不透明、叶间空隙完全透明)。不看上面的代码,写出片段着色器里做镂空的那一行(或几行),并说清:为什么草用这个而不用混合?这样做需不需要给草排序?
问题 3(排错题) 某人在场景里摆了一排半透明窗户,开了 glEnable(GL_BLEND) 和正确的 over glBlendFunc,但发现:站在某些角度看,后面的窗户透不出来、像消失了。他怀疑是 glBlendFunc 设错。请判断:真正的原因是什么?给出修法。
名词解释
本章出现的专业名词,用大白话再讲一遍。
- alpha 通道
颜色除了红绿蓝(RGB)之外的第四个分量,记作
a,范围 0~1,表示这块颜色的不透明度:1 = 完全不透明(完全盖住背后)、0 = 完全透明(背后完全透出)、0.6 = 盖六成透四成。混合就是按 alpha 把前景叠到背景上。详见本章「混合」一节。- 混合
把正在画的「新颜色」(源色)按它的不透明度 alpha,和画布上「已有的颜色」(目标色)按比例相加,得到一个介于两者之间的颜色写回画布。这就是半透明物体能透出背景的原理。默认关闭,用
glEnable(GL_BLEND)打开。详见本章「混合」一节。- 源色
正在画的这个片段算出来的颜色(前景),在混合方程里记作
Csrc。它带着自己的 alpha,决定要盖住背景多少。详见本章「混合」一节。- 目标色
画布(颜色缓冲)上该像素当前已经存着的颜色(背景),在混合方程里记作
Cdst。混合时它按目标因子被保留一部分,从源色背后透出来。详见本章「混合」一节。- 混合方程
混合时算最终颜色的规则:
C = Csrc·Fsrc + Cdst·Fdst——源色乘源因子 + 目标色乘目标因子。因子是 0~1 的权重,由glBlendFunc指定;挑不同因子得到不同混合效果。详见本章「混合方程」一节与 §4。- over 混合
最常用的混合配法:源因子取源色 alpha、目标因子取 1 减源色 alpha,于是结果 =
αsrc·Csrc + (1−αsrc)·Cdst——前景占 αsrc、背景占剩下的。直通 RGB 因子用SRC_ALPHA, ONE_MINUS_SRC_ALPHA;若输出 alpha 还要保留,应以 separate factors 令αout=αsrc+αdst(1-αsrc)。详见本章「混合方程」一节与 §4。- discard
片段着色器里的一条命令:一旦执行,当前片段就被立即丢弃,不往后走、不进颜色缓冲。常用来做镂空——纹理里 alpha 很小(接近全透明)的片段直接
discard,于是草、铁丝网的空隙被「抠掉」露出背后,得到清晰硬边。详见本章「discard」一节。- 半透明排序
source-over 对多个透明层不满足交换律,因此经典管线要先画不透明物,再把透明表面从远到近累积。对象中心距离只是近似;相交或循环遮挡几何需拆分或使用 OIT。详见本章「半透明排序」一节与 §5。
- 深度写入
片段通过深度测试后,把自己的深度值写回深度缓冲的动作(默认开启)。画半透明物时用
glDepthMask(GL_FALSE)通常把它关闭:透明物仍被不透明深度遮挡,却不把单层透明深度固化为最近面。它不能替代排序,画完必须恢复。详见本章「半透明排序」一节与 §6。
版本、来源与运行边界
本章以 Joey de Vries 的 LearnOpenGL 原章 为授权改编依据,教学运行基线是 OpenGL 3.3 Core Profile。Khronos 当前发布的规范参照是 OpenGL 4.6 Core Profile;这里用 4.6 规范核查术语和状态合同,但不把 4.6 API 偷偷倒填为原教程内容。GLFW、GLAD、Assimp 与驱动版本都属于运行环境,不能拿“编译通过”替代对 context、资源和 framebuffer 结果的验证。
正式概念与状态责任
- blending:在“Alpha 混合、透明排序与预乘颜色”中由blend state、depth state 与透明 draw list负责解释其输入、受控状态和可观察结果;运行时以源/目标颜色、blend state、距离排序表、depth mask 与重叠像素定位它的第一处变化。
- alpha:在“Alpha 混合、透明排序与预乘颜色”中由blend state、depth state 与透明 draw list负责解释其输入、受控状态和可观察结果;运行时以源/目标颜色、blend state、距离排序表、depth mask 与重叠像素定位它的第一处变化。
- blend function:在“Alpha 混合、透明排序与预乘颜色”中由blend state、depth state 与透明 draw list负责解释其输入、受控状态和可观察结果;运行时以源/目标颜色、blend state、距离排序表、depth mask 与重叠像素定位它的第一处变化。
- sorting:在“Alpha 混合、透明排序与预乘颜色”中由blend state、depth state 与透明 draw list负责解释其输入、受控状态和可观察结果;运行时以源/目标颜色、blend state、距离排序表、depth mask 与重叠像素定位它的第一处变化。
章专属 OpenGL 状态实验
先预测“先画不透明物,再按相机距离从远到近提交透明面并控制 depth write”发生后,blend state、depth state 与透明 draw list应怎样改变源/目标 RGBA、blend factors/equation、深度值、排序键和绘制顺序;再操作三个实验。实验不生成变化率或正确率等虚构总分,只显示真实 GL 状态、资源、命令和可观察结果。
实验一:Context—资源—结果合同
选择任一正式概念与基线/单故障场景,核对它是否进入本章状态合同。正式概念只有同时出现在解释、可视状态和交付证据中才算覆盖。
Context · resource · observable result
Alpha 混合、透明排序与预乘颜色:状态合同
把 alpha、blend equation/factors、深度写入和透明物排序接成可解释合成顺序
验证场景
官方教程正式概念
logl-20 · 基线帧
blending:固定 context、资源内容与输入事件,执行“先画不透明物,再按相机距离从远到近提交透明面并控制 depth write”
冻结输入:blending
blend state、depth state 与透明 draw list记录源/目标 RGBA、blend factors/equation、深度值、排序键和绘制顺序
结果:得到可重复的初始 GL 状态与资源身份
观测:源/目标颜色、blend state、距离排序表、depth mask 与重叠像素中的初始快照
预期:blend state、depth state 与透明 draw list得到可复查结果,并持续满足“混合因子匹配 straight/premultiplied alpha 约定,排序使用稳定对象身份”
实验二:CPU 命令到 GPU 结果的五段轨迹
逐段执行“先画不透明物,再按相机距离从远到近提交透明面并控制 depth write”,在每一步记录资源身份、状态变化与第一个可观察结果,并持续核对“混合因子匹配 straight/premultiplied alpha 约定,排序使用稳定对象身份”。
CPU command · GL state · GPU result
Alpha 混合、透明排序与预乘颜色:五段轨迹
当前观测:源/目标颜色、blend state、距离排序表、depth mask 与重叠像素中的初始快照
不变量:混合因子匹配 straight/premultiplied alpha 约定,排序使用稳定对象身份
实验三:单故障与同输入恢复
注入“透明窗按容器遍历顺序绘制,近处先写颜色导致远处被错误覆盖”,保存首个分岔;撤销后沿用完全相同的 context、资源内容、uniform 和 draw 输入重放。只有源/目标颜色、blend state、距离排序表、depth mask 与重叠像素一起恢复才算修复。
Single fault · first divergence · replay
Alpha 混合、透明排序与预乘颜色:反例与恢复
故障:透明窗按容器遍历顺序绘制,近处先写颜色导致远处被错误覆盖
第 1 次沿用同一 context、资源、uniform 与 draw 输入
保持其余输入不变,仅注入“透明窗按容器遍历顺序绘制,近处先写颜色导致远处被错误覆盖”
混合因子匹配 straight/premultiplied alpha 约定,排序使用稳定对象身份
源/目标颜色、blend state、距离排序表、depth mask 与重叠像素
最小可重放检查
unit: logl-20
owner: blend state、depth state 与透明 draw list
state_or_resource: 源/目标 RGBA、blend factors/equation、深度值、排序键和绘制顺序
command: 先画不透明物,再按相机距离从远到近提交透明面并控制 depth write
pass_invariant: 混合因子匹配 straight/premultiplied alpha 约定,排序使用稳定对象身份
single_fault: 透明窗按容器遍历顺序绘制,近处先写颜色导致远处被错误覆盖
required_evidence: 源/目标颜色、blend state、距离排序表、depth mask 与重叠像素复核者先仅依据以上合同写出预期,再运行基线、单故障和清理后重放。若两次基线的资源身份、首个状态变化或 framebuffer 结果不同,必须保留差异,不能用最终截图相似掩盖中间状态错误。