抗锯齿
抗锯齿:保留 LearnOpenGL 3.3 Core 正文机制,以 context—资源—结果合同、GPU 轨迹和章专属单故障完成可重放验收。
学习目标
- 能解释锯齿是怎么来的(光栅化时每像素只取一个采样点、斜边落在像素方格上只能整涂或整不涂),以及抗锯齿在管线里发生在哪一步(光栅化判覆盖度这一环)
- 能比较 SSAA(整张图按更高分辨率渲染再缩小)和 MSAA(多样本覆盖与存储、片段着色通常仍每像素一次)各自怎么花费资源、为什么 MSAA 常更具性价比
- 能回答:开了 MSAA,物体的几何边缘顺滑了,可贴在它身上的纹理内部、还有着色器里
if/discard切出来的硬边(比如高光边、镂空草叶边)却还是锯齿——这是为什么?MSAA 到底抗的是哪种锯齿?
为什么屏幕像一张方格纸
把屏幕想成一张方格纸,每个格子就是一个像素,要么整格涂一种颜色、要么整格留白,没有「涂半格」这回事。现在让你在这张方格纸上画一条斜线——麻烦来了:斜线会斜斜地穿过一排格子,每个格子它只盖住了一角或一半,可你只能决定「这格涂还是不涂」。
于是这条本该笔直顺滑的斜边,画出来就成了一级一级的阶梯:盖得多的格子涂上、盖得少的留白,台阶状的边缘看着毛毛糙糙,尤其放大或斜线很缓时特别扎眼。圆和斜的物体边缘全是这种毛刺。这一章要解决的就是:怎么把这种台阶状的毛糙边缘,变回看起来顺滑的样子。
办法说穿了很朴素:与其在边缘格子上非黑即白地硬选,不如给它们涂上半深的过渡色——这格被斜线盖了一半,就涂一半深的颜色。远看,这些过渡色就把台阶「抹」顺滑了。没有它,你的每一条斜边、每一个圆、每一根细线都带着锯齿毛刺,画面廉价又刺眼。
锯齿:斜边落在方格上的阶梯
先给那个台阶边缘起个正名。它叫 ↡光栅化时,由于屏幕由一个个像素方格组成、每个像素只用一个采样点(通常是像素中心)来判断自己有没有被图元盖住,斜边/曲边落在方格上只能「整格涂」或「整格不涂」,于是平滑的边被画成一级一级的硬阶梯状毛刺。本质是「采样点太少、采样不足」造成的走样。(aliasing,也叫走样):GPU 把三角形变成像素时,对每个像素只取一个采样点(通常就是像素中心),用它判断「这个像素到底有没有被三角形盖住」——盖住了就整格涂三角形的色,没盖住就整格不涂。
问题正出在「一个像素只问一个点」上。斜边穿过一个像素时,可能盖住了它一大半面积,但只要像素中心那个采样点没落在三角形里,这格就被判成「没盖住」、整格留白;反之中心被盖住,哪怕只盖了一点点,也整格涂满。这种「非涂即不涂」的二选一,把本该平滑的边逼成了阶梯。下面这张图把「方格纸上的斜边为什么成阶梯」画出来——虚线是它本该有的样子:
它在管线里的位置也清楚了:锯齿诞生在光栅化这一步(三角形被切成像素、逐像素判覆盖的时候)。根子是采样不足——一个像素只采一个点,信息不够,自然还原不出平滑的边。抗锯齿要做的,就是在这一步给每个边缘像素多一点信息、或更精细的判断。
抗锯齿:给边缘格涂上过渡色
对症的解法就叫 ↡一类让图元边缘看起来平滑、消除锯齿阶梯的技术的统称。核心思路是不再对边缘像素「非涂即不涂」地硬选,而是根据它被图元盖住的程度,涂上介于图元色和背景色之间的半深过渡色,远看就把台阶抹顺滑了。常见做法有超采样 SSAA、多重采样 MSAA、后处理 FXAA 等。(anti-aliasing,常缩写 AA):不再对边缘像素「非涂即不涂」地硬选,而是看它被图元盖住了多少,盖一半就涂一半深的过渡色,盖三成就涂三成深——让边缘格子用介于图元色和背景色之间的中间色来填。
为什么这招管用?因为人眼在正常观看距离下会自动把这些深浅不一的过渡色「平均」掉,台阶的硬棱被柔化,远看就成了一条顺滑的斜边。同一条斜边,关抗锯齿是硬阶梯、开抗锯齿是带过渡灰的平滑边,差别一看便知。它仍然发生在光栅化附近,只是把「一像素一个点的硬判定」换成了「按覆盖程度的软填色」。马上的 §5 第一个 Demo 就让你左右拖着对比这两者。
剩下的问题只有一个:边缘像素到底被盖住了多少? 不同的抗锯齿方法,区别就在「怎么估这个比例、估得多准、花多大代价」。下面三种是最主流的思路。
超采样 SSAA:先画大再缩小,简单粗暴但贵
最直接的思路是 ↡超采样抗锯齿(Super Sample Anti-Aliasing):先按远高于屏幕的分辨率(如每边 2 倍 = 4 倍像素)渲染整个场景,每个子像素都完整跑一遍片段着色,再把这张大图缩小(下采样)到屏幕分辨率——缩小时多个子像素被平均成一个像素,边缘自然得到过渡色。质量极好,但因为每个子像素都要着色,开销巨大(4 倍像素就约 4 倍着色量),实际很少直接用。(Super Sample Anti-Aliasing):干脆按远高于屏幕的分辨率把整个场景渲染一遍(比如每边放大 2 倍,就是 4 倍的像素数),然后再把这张大图缩小回屏幕分辨率。
为什么能抗锯齿?因为缩小时,屏幕上一个像素对应着大图里好几个子像素,把这几个子像素的颜色一平均,边缘处「一半子像素是图元色、一半是背景色」就自然平均出了过渡色。质量极好——它对一切边缘都生效,因为整张图就是更精细地画了一遍。
代价也极其直白:你多画了好几倍的像素,每个子像素都得完整跑一遍片段着色,着色开销跟着翻几倍。所以 SSAA「只拥有过短暂的辉煌」,今天很少直接用——它太贵了。能不能只在真正需要的地方(边缘)多花钱?这就引出了 MSAA。
多重采样 MSAA:多样本覆盖,片段着色通常每像素一次
↡多重采样抗锯齿(Multisample Anti-Aliasing):多样本附件为每个像素保存多个颜色、深度和模板样本(如 4 个),用它们判断图元覆盖度;在通常的非 sample shading 路径中,片段着色器每像素只跑一次,结果按覆盖度写入样本。只有几何边缘产生部分覆盖和过渡色。它仍增加内存和带宽,却避免了 SSAA 的每样本片段着色,因而常适合实时渲染。 (Multisample Anti-Aliasing)是 SSAA 的「精打细算版」。它给每个像素放多个采样点(典型是 4 个)。多重采样缓冲会为颜色、深度和模板保存每样本的数据;关键省钱点在于:这些采样点用来判覆盖——数一数有几个采样点落在三角形里,从而知道这个像素被盖住了多少;而片段着色器通常仍然每个像素只跑一次(用插值到像素中心的数据算一次颜色),不像 SSAA 每个子像素都着一遍色。
于是边缘像素的最终色 = 这一次算出的图元色,按覆盖比例和背景色混合。比如 4 个采样点里有 2 个落在三角形内,覆盖度就是 2/4 = 50%,这个边缘像素就涂「图元色和背景色五五开」的过渡色。深度、模板和颜色都会有按样本的存储与带宽成本,但最贵的每样本片段着色通常没有翻倍。图元内部的像素覆盖度是满、外部是 0,只有几何边缘那一圈产生中间覆盖度和过渡色;这就是 MSAA 相比 SSAA 的主要节省来源,并不代表多样本附件只在边缘存在。这套「一个像素里数采样点定覆盖度、再混色」的过程,§5 第二个 Demo 单步走一遍就透了。
覆盖度:几个采样点落在图元内
上面反复出现的那个比例值得单独点名:↡一个像素里,落在图元(三角形)内部的采样点数量占总采样点的比例。如 4 个采样点中 2 个在三角形内,覆盖度就是 2/4 = 50%。它直接决定了 MSAA 给这个边缘像素混多少图元色:覆盖度 100% 就是纯图元色、0% 就是纯背景、50% 就是两者各半。覆盖度只有在几何边缘的像素上才介于 0 和 100% 之间,图元内部恒为满、外部恒为 0。(coverage)——一个像素里落在图元内部的采样点占总采样点的比例。它就是 MSAA 决定「这个边缘像素该混多少图元色」的依据:覆盖度 100%(采样点全在图元内)就涂纯图元色,0%(全在外)就纯背景,50% 就图元色和背景各半。
正因为覆盖度只有在几何边缘的像素上才是个中间值(图元内部恒满、外部恒为 0),MSAA 的过渡色才只出现在边缘那一圈。多样本附件本身仍为每个像素保存样本;它高效的关键是避免了 SSAA 的每样本片段着色。下面这张图把两者的成本边界并排画出来:
FXAA:拿到画面再找边缘抹一抹
还有一类思路完全不在光栅化里折腾采样点,而是等画面整个渲染完之后,把它当成一张普通图片来后处理——这就是 ↡快速近似抗锯齿(Fast Approximate Anti-Aliasing),一种后处理抗锯齿:在场景正常渲染完成后,把成品图当作普通图像,靠分析相邻像素的颜色/亮度差异找出「看起来像边缘」的地方,再沿边缘做有方向的模糊把锯齿抹掉。优点是极便宜、和几何无关、对纹理边和着色硬边也有效;缺点是基于图像猜测、会让画面整体偏糊、可能误伤细节。(Fast Approximate Anti-Aliasing,快速近似抗锯齿)。它的做法是:在成品图上分析相邻像素的颜色/亮度差异,猜出哪里像是边缘,然后沿着边缘做有方向的模糊,把锯齿抹平。
它的优缺点和 MSAA 几乎互补:因为只对最终图像操作、和几何无关,所以极其便宜,而且对纹理边、着色器切出的硬边这些 MSAA 管不了的锯齿也照样有效(§7 会讲 MSAA 为什么管不了)。代价是它基于图像猜测,会把整个画面弄得偏糊、有时还误伤本该锐利的细节。便宜但糊,是它的标签。
对比一下:关 vs 开抗锯齿
猜一猜:把下面这个滑块从左拖到右,左边是「抗锯齿关」的那条斜边、右边是「抗锯齿开」的。关着的时候斜边边缘会是一级级的台阶还是平滑的?开了以后,边缘多出来的那些深浅不一的格子是什么?拖动看看,再回想 §3——它们正是「按覆盖程度涂上的过渡色」。
左边抗锯齿关着,每个格子只能整涂或整不涂,斜边被画成扎眼的硬阶梯;右边抗锯齿开了,边缘那圈格子按被斜线盖住的多少涂上半深的过渡灰,远看台阶就被抹顺滑了。两侧是同一条斜边、同一张方格纸,只差边缘格子怎么涂。拖动中间滑块左右对比:
看出来了吧——开抗锯齿后多出来的,全是边缘格子上深浅不一的过渡色。它们一个不少地待在斜边经过的那一圈格子上,按各自被盖住的程度取了图元色和背景色之间的中间值。远看,正是这些过渡色把硬台阶柔化成了顺滑的斜边。这就是一切抗锯齿方法的共同终点;它们的分歧只在「怎么估出每格的覆盖比例」。
单步走一遍:MSAA 怎么在一个像素里判覆盖度
把 MSAA 那套「多采样点 → 覆盖度 → 混色」放大到一个边缘像素上,单步走完。
猜一猜:放大看一个被斜边压过的像素,里面放了 4 个采样点。如果其中 2 个落在三角形内、2 个落在外,这个像素最后会被涂成纯三角形色、纯背景色,还是两者之间的某个中间色?是几比几的中间色?先想一想,再单步走下面三步。
每一步左边画放大的这个像素、右边写这一步算出了什么,盯着「4 个采样点里几个落在图元内、最后怎么定出混色比例」,就懂了 MSAA 的全部精髓:
① 一条斜边压过一个像素格
放大看场景里的一个像素。一条三角形的 斜边正好压过它,把这个格子斜着切成两片:斜边 一侧是图元内(三角形内部)、另一侧是图元外 (背景)。光看这一个像素,它一半在内、一半在外 ——该涂什么色还说不准。
走完这一遍就抓住了 MSAA 省钱的命门:多样本颜色 / 深度 / 模板会增加内存和带宽,但最贵的片段着色通常还是每像素一次;只有边缘像素的覆盖度才不是 0 或满,只有那里会形成过渡色。这就是它比 SSAA「每子像素都着色」便宜得多、却仍能把几何边缘抹顺滑的原因。
代码对照:开 MSAA 与离屏解析
先看最省事的一种——给默认帧缓冲开 MSAA。桌面 OpenGL 在创建窗口前告诉 GLFW「我要 4 个采样点」,再启用多重采样开关;WebGL2 则在拿上下文时传一个 antialias: true,浏览器就自动给默认帧缓冲配好了多重采样。
// 创建窗口之前:要求默认帧缓冲每像素 4 个采样点
glfwWindowHint(GLFW_SAMPLES, 4);
// …创建窗口、初始化 GLAD 之后…
// 显式启用多重采样;不少驱动默认已开启,但明确设置更可读
glEnable(GL_MULTISAMPLE);
// 之后正常渲染,几何边缘就自动被 MSAA 抹顺滑了// 拿 WebGL2 上下文时开抗锯齿:浏览器给默认帧缓冲配好多重采样
const gl = canvas.getContext("webgl2", { antialias: true });
// WebGL2 没有 GL_MULTISAMPLE 这个开关——默认帧缓冲的 MSAA
// 完全由上面这个 antialias 标志决定,开了就生效,无需再 enable两端做的是同一件事,只是入口不同:桌面用 GLFW_SAMPLES 请求采样点数 + glEnable(GL_MULTISAMPLE) 开关;WebGL2 把请求收进了 getContext 的 antialias 一个布尔里。注意这里的关键差异:
可一旦你像「帧缓冲」那章那样把场景渲到自建的离屏帧缓冲(FBO)上做后处理,默认帧缓冲的 antialias 就管不着它了;而 GL_MULTISAMPLE 也不会把普通附件变成多样本附件。离屏管线必须自己创建多重采样 attachment,画完再把它**解析(blit)**到一张普通缓冲,才能按普通纹理采样、后处理或上屏。
// 给离屏 FBO 挂一个多重采样 renderbuffer 作颜色附件(4 个采样点)
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGB, w, h);
// …把场景渲进这个多重采样 FBO 之后,解析到普通 FBO…
glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);// 给离屏 FBO 挂一个多重采样 renderbuffer(WebGL2 同名方法)
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, w, h);
// …渲完后把多重采样 FBO 解析到普通 FBO(同样用 blitFramebuffer)…
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, msaaFBO);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, resolveFBO);
gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, gl.COLOR_BUFFER_BIT, gl.NEAREST);两端逻辑一字对应:renderbufferStorageMultisample 造一个带 4 个采样点的附件、把场景渲进去,再用 blitFramebuffer 把它解析成普通图像(多个采样点合成一个像素),之后才能把这张普通图当纹理采样、做后处理或上屏。WebGL2 把 GL 全局函数搬成了 gl. 上下文方法,名字基本同名。
两种附件与自定义逐样本读取
原教程给离屏 FBO 的多样本附件留了两条路:上面代码的 renderbuffer 适合“渲染后解析”的常规管线;若必须把多样本颜色本身作为附件,也可创建 GL_TEXTURE_2D_MULTISAMPLE 纹理。后者不能按普通 sampler2D 当成一张 2D 图直接做后处理,通常仍先 blitFramebuffer 解析到普通纹理。
另一条少见但重要的分叉是:不解析、自己读取每个样本。GLSL 要把 uniform 声明为 sampler2DMS,再用 texelFetch(screenTextureMS, pixelCoord, sampleIndex) 取指定样本,随后由你决定如何合成。这适合自定义抗锯齿或诊断,不是普通后处理的默认做法。
uniform sampler2DMS screenTextureMS;
ivec2 pixel = ivec2(gl_FragCoord.xy);
vec4 fourthSample = texelFetch(screenTextureMS, pixel, 3);容易踩的坑
小结
- 锯齿来自采样不足:光栅化时一个像素只取一个采样点,斜边落在像素方格上只能整涂或整不涂,平滑边被逼成阶梯
- 抗锯齿的共同终点是给边缘像素涂按覆盖程度的过渡色;分歧只在「怎么估覆盖比例、花多大代价」
- SSAA 整图按更高分辨率渲染再缩小,每个子像素都着色——质量好但着色翻几倍,太贵
- MSAA 在多样本附件中保存每像素多个样本并判覆盖度;片段着色通常仍每像素一次,只有几何边缘覆盖度才是中间值。它仍增加内存 / 带宽,却避免 SSAA 的每样本着色;
GLFW_SAMPLES/ WebGL2antialias请求默认帧缓冲,离屏要 multisample attachment +blitFramebuffer解析 - MSAA 只抗几何边缘锯齿,纹理内部 / 着色器硬边它管不了(要 mipmap / alpha-to-coverage / FXAA)——至此高级 OpenGL 篇收官
练习
问题 1(场景选型题:SSAA / MSAA / FXAA) 下面三个场景,各自最该选哪种抗锯齿,为什么?① 一款要在普通显卡上跑满 60fps 的实时 3D 游戏,主要烦恼是物体外轮廓锯齿;② 一个着色器里大量用 discard 做镂空草叶的场景,草叶边缘锯齿严重;③ 一张离线渲染、不在乎渲染时间、只求画质拉满的产品效果图。
问题 2(问答题) 有人说「MSAA 之所以比 SSAA 便宜,是因为 MSAA 的采样点比 SSAA 少」。这个说法准确吗?MSAA 省钱的真正关键是什么?它和 SSAA 各自在哪里花了/省了开销?
问题 3(观察 / 排错题) 回到 §5 那个「关 vs 开抗锯齿」的对比:开抗锯齿后,边缘格子被涂上了深浅不一的过渡色。如果有人把渲染改成「渲到自建离屏 FBO 再上屏」,却发现 MSAA 像没开一样、边缘又是硬阶梯,最可能漏了哪两步?
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 锯齿
也叫走样(aliasing):屏幕由一个个像素方格组成,光栅化时每个像素只用一个采样点(通常是像素中心)判断自己有没有被图元盖住,于是斜边/曲边落在方格上只能整格涂或整格不涂,平滑的边被画成一级一级的硬阶梯毛刺。本质是「采样点太少、采样不足」。详见本章「锯齿」一节。
- 抗锯齿
让图元边缘看起来平滑、消除锯齿阶梯的技术统称(缩写 AA)。核心是不再对边缘像素「非涂即不涂」地硬选,而是按它被图元盖住的程度涂上介于图元色和背景色之间的过渡色,远看就把台阶抹顺滑了。常见做法有 SSAA、MSAA、FXAA。详见本章「抗锯齿」一节。
- 超采样 SSAA
超采样抗锯齿(Super Sample Anti-Aliasing):先按远高于屏幕的分辨率渲染整个场景(每个子像素都完整着色),再把大图缩小回屏幕分辨率——缩小时多个子像素被平均成一个,边缘自然得到过渡色。质量极好,但每个子像素都要着色,开销巨大,很少直接用。详见本章「超采样 SSAA」一节。
- 多重采样 MSAA
多重采样抗锯齿(Multisample Anti-Aliasing):每个像素放多个采样点(如 4 个),但它们只判覆盖度,片段着色器仍每像素只跑一次,结果按覆盖度与背景混合(深度/模板按子采样点存)。因为着色不翻倍、又只有几何边缘才需混色,性价比远高于 SSAA,是实时渲染默认方案。详见本章「多重采样 MSAA」一节。
- 覆盖度
一个像素里落在图元(三角形)内部的采样点占总采样点的比例(如 4 个里 2 个在内 = 2/4 = 50%)。它直接决定 MSAA 给这个边缘像素混多少图元色:100% 涂纯图元色、0% 纯背景、50% 两者各半。只有几何边缘的像素覆盖度才是中间值,图元内部恒满、外部恒为 0。详见本章「覆盖度」一节。
- FXAA
快速近似抗锯齿(Fast Approximate Anti-Aliasing),一种后处理抗锯齿:场景渲染完后把成品图当普通图像,靠分析相邻像素的颜色/亮度差找出边缘,再沿边缘做有方向的模糊抹掉锯齿。优点是极便宜、和几何无关、对纹理边/着色硬边也有效;缺点是基于图像猜测,会让画面偏糊、可能误伤细节。详见本章「FXAA」一节。
版本、来源与运行边界
本章以 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 结果的验证。
正式概念与状态责任
- anti aliasing:在“抗锯齿”中由multisample framebuffer、sample storage 与 resolve 目标负责解释其输入、受控状态和可观察结果;运行时以GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素定位它的第一处变化。
- msaa:在“抗锯齿”中由multisample framebuffer、sample storage 与 resolve 目标负责解释其输入、受控状态和可观察结果;运行时以GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素定位它的第一处变化。
- multisample:在“抗锯齿”中由multisample framebuffer、sample storage 与 resolve 目标负责解释其输入、受控状态和可观察结果;运行时以GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素定位它的第一处变化。
- resolve:在“抗锯齿”中由multisample framebuffer、sample storage 与 resolve 目标负责解释其输入、受控状态和可观察结果;运行时以GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素定位它的第一处变化。
章专属 OpenGL 状态实验
先预测“以相同 sample count 渲染所有附件,再 blit/resolve 到单采样纹理或默认缓冲”发生后,multisample framebuffer、sample storage 与 resolve 目标应怎样改变sample count、color/depth attachments、coverage、resolve filter 和目标尺寸;再操作三个实验。实验不生成变化率或正确率等虚构总分,只显示真实 GL 状态、资源、命令和可观察结果。
实验一:Context—资源—结果合同
选择任一正式概念与基线/单故障场景,核对它是否进入本章状态合同。正式概念只有同时出现在解释、可视状态和交付证据中才算覆盖。
Context · resource · observable result
抗锯齿:状态合同
建立多采样 framebuffer、逐 sample 覆盖与 resolve 到单采样目标的完整路径
验证场景
官方教程正式概念
logl-28 · 基线帧
anti aliasing:固定 context、资源内容与输入事件,执行“以相同 sample count 渲染所有附件,再 blit/resolve 到单采样纹理或默认缓冲”
冻结输入:anti aliasing
multisample framebuffer、sample storage 与 resolve 目标记录sample count、color/depth attachments、coverage、resolve filter 和目标尺寸
结果:得到可重复的初始 GL 状态与资源身份
观测:GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素中的初始快照
预期:multisample framebuffer、sample storage 与 resolve 目标得到可复查结果,并持续满足“多采样附件样本数一致;resolve 的源/目标区域和格式兼容”
实验二:CPU 命令到 GPU 结果的五段轨迹
逐段执行“以相同 sample count 渲染所有附件,再 blit/resolve 到单采样纹理或默认缓冲”,在每一步记录资源身份、状态变化与第一个可观察结果,并持续核对“多采样附件样本数一致;resolve 的源/目标区域和格式兼容”。
CPU command · GL state · GPU result
抗锯齿:五段轨迹
当前观测:GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素中的初始快照
不变量:多采样附件样本数一致;resolve 的源/目标区域和格式兼容
实验三:单故障与同输入恢复
注入“color 是 4x MSAA 而 depth-stencil 是单采样,framebuffer 不完整”,保存首个分岔;撤销后沿用完全相同的 context、资源内容、uniform 和 draw 输入重放。只有GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素一起恢复才算修复。
Single fault · first divergence · replay
抗锯齿:反例与恢复
故障:color 是 4x MSAA 而 depth-stencil 是单采样,framebuffer 不完整
第 1 次沿用同一 context、资源、uniform 与 draw 输入
保持其余输入不变,仅注入“color 是 4x MSAA 而 depth-stencil 是单采样,framebuffer 不完整”
多采样附件样本数一致;resolve 的源/目标区域和格式兼容
GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素
最小可重放检查
unit: logl-28
owner: multisample framebuffer、sample storage 与 resolve 目标
state_or_resource: sample count、color/depth attachments、coverage、resolve filter 和目标尺寸
command: 以相同 sample count 渲染所有附件,再 blit/resolve 到单采样纹理或默认缓冲
pass_invariant: 多采样附件样本数一致;resolve 的源/目标区域和格式兼容
single_fault: color 是 4x MSAA 而 depth-stencil 是单采样,framebuffer 不完整
required_evidence: GL_SAMPLES、attachment sample count、完整性、blit 参数与边缘像素复核者先仅依据以上合同写出预期,再运行基线、单故障和清理后重放。若两次基线的资源身份、首个状态变化或 framebuffer 结果不同,必须保留差异,不能用最终截图相似掩盖中间状态错误。