内容工厂. / 演示空间 / 管线各阶段的数据处理 · 第 3 课 · 像素上贴什么 段 1 / 3
管线各阶段的数据处理 | 第 3 课 | 上一课:三角形怎么变成像素

像素上贴什么

读时 约 12 分钟 本课 3 段 每段 三件套 保真度 真机可证
课题骨架(本课题只声明一次)

GPU 干两件事:① 把顶点流处理成裁剪空间顶点;② 据此画像素。 前两课把「哪些像素」和「像素什么颜色」解决了,本课管颜色从哪取——以及取完之后往哪写。

01
顶点 → 裁剪空间顶点
02
据此画像素
本课三段的位置
3.1
该用哪一级贴图
3.2
一次采样走多远
3.3
像素怎么落进缓冲
3.1

贴图该用哪一级

真机可证 · 屏幕导数

本节点在骨架 ② 的第 3 环——像素已经有颜色了,但那颜色还没从贴图上取。

采样贴图有一个绕不开的矛盾:屏幕上的一个像素,覆盖的贴图面积可能远大于一个纹素。 这时如果还去取最细的那一层,相邻像素会取到毫不相关的纹素——整片花纹开始闪烁、起摩尔纹。 硬件对此的解法是:先在片元里算出 uv 相对屏幕的变化率,再据此选一个够粗的层。 本段只增加一样东西:屏幕导数与层选择。

件一 含代码的数据流示意图 · 硬件在采样时算的是判定式
左列是采样点的 uv,中间是层选择的判定式,右列是选中的层——层越粗,一格代表的贴图面积越大。
注意看

决定选哪一层的不是「贴图本身多大」,而是 uv 在一个像素上变了多少。同样的贴图,压得越扁,选的层越粗。

件二 可操作活例 · 纯 WebGPU · 三种模式对照
启动中…
模式
贴图被压得多密

把 uv 缩放推大:一像素覆盖的贴图面积变大。 第一种模式下会看到摩尔纹与闪烁——那就是「不选层」的代价;第二种模式下花纹会平滑地退成灰,这就是选层。

动手

先在「最细层」把 uv 缩放推到最大,看花纹怎么烂掉;再切到「按导数选层」。注意看:密的地方不再闪,而是均匀地变糊。

件三 中间量数值表 · 与件一、件二同源
① 采样点 · 画布中心
项值
② 屏幕导数 · 每像素变化率
项值
③ 层选择
项值
② 里的导数是解析值(本页的 uv 是平面的线性映射,解析值与硬件 dpdx/dpdy 相同)。 ③ 的 λ 就是由它算出来的——件二「按导数选层」用的正是这个 λ。
本节获得

知道选层的依据是uv 在一个像素上变了多少(屏幕导数),而不是贴图本身的尺寸; 也知道「越密越糊」不是画质变差,而是用分辨率换掉闪烁。

运行时自检 等待脚本…

跑不起来先看设备要求:活例由真 GPU 出画面 —— 手机需 Android 12+ 且 Chrome 121+,或 iOS / iPadOS 26+;电脑需 Chrome / Edge 113+ 或 Safari 26+。Android 上取不到适配器多是设备不支持 Vulkan 1.1+,会自动改走兼容模式(需 Chrome 146+)。不满足时画面区会写出原因与自查步骤,同一页的示意图与数值表照常可读。


  
3.2

一次采样在硬件里走多远

示意 · 采样单元等价重现

本节点在骨架 ② 的第 4 环——层已经选好了,但那一层里还只是一堆纹素。

上一段只解决了「去哪一层」。可层里是一格一格的值,而 uv 是连续的浮点: 落在格子中间时取哪一个?取 uv 超出 [0,1] 时怎么办?这两件事由采样器状态决定—— 过滤(邻近取 1 个纹素 / 双线性取 2×2 加权)与寻址(超出范围怎么折回)。 本段只增加一样东西:采样器状态。

件一 含代码的数据流示意图 · 同一个探测 uv 在三种寻址下的落点
左列是同一个探测 uv,中间是采样器规则,右列是它在三种寻址模式下的实际落点——当前模式高亮。
注意看

同一个 uv,换个寻址模式就落到完全不同的纹素上。采样器状态不是画质选项,它决定取到哪个值。

件二 可操作活例 · 纯 WebGPU · 过滤 × 寻址
启动中…
过滤
寻址

放大看过滤:本页的贴图只有 4×4 个纹素,被放大到整屏,所以格子清晰可见。 「邻近」给出硬边方格,「双线性」给出它们之间的过渡——那个过渡不是在贴图里,是采出来的。

看寻址:uv 从 0 一直到 3.2,本来就超出了 [0,1]。「重复」把它折回并平铺; 「钳制」把边缘那一列拉成条纹;「镜像」来回翻转。

动手

先在「邻近」下看清 4×4 的格子边界,再切「双线性」。注意看:格子变糊了,但没有任何一行代码在混合颜色——是采样器按权重把四个纹素加权出来的。

件三 中间量数值表 · 与件一、件二同源
① 采样点 · 画布中心
项值
② 纹素坐标 · 整数格 + 小数偏移
项值
③ 过滤权重 · 四个纹素怎么分
纹素权重颜色
③ 的四个权重恒等于 1((1−fx)(1−fy) + fx(1−fy) + (1−fx)fy + fx·fy = 1)。 切到「邻近」时权重会变成 one-hot:一个 1,其余 0——那时采样器只取一个纹素。
本节获得

知道「贴着看」和「远远看」的区别由采样器状态决定:过滤决定层内取几个纹素、怎么加权, 寻址决定 uv 超出范围时折向哪里——两者都不在贴图数据里,而在采样方式里。

3.3

像素最后怎么落进缓冲

真机可证 · 混合

本节点在骨架 ② 的最后一环——片元已经算出颜色了,但它还没写进缓冲。

上面两段都在讲「怎么取到一个颜色」。可颜色算完还有一步: 它怎么和缓冲里已有的内容合并。半透明物体要与后面已画好的东西叠加, 而叠加是与绘制顺序有关的——先画的先在缓冲里,后画的按公式叠上去; 写入时如果目标是 sRGB,硬件还会把线性值编码一次。 本段只增加一样东西:输出合并(混合与写入编码)。

件一 含代码的数据流示意图 · 一个值的三关
右列跟着同一个值走完全程:深度通过 → 混合得到新值 → 写入时编码。每一步都写着它实际用到的数。
注意看

混合不是「把颜色加一遍」——它按 alpha 在源与目标之间加权。而写入编码是值本身变了:0.5 写进去会变成 0.735。

件二 可操作活例 · 纯 WebGPU · 左看混合,右看编码
混合 · 两个半透明块 写入编码 · 同一组线性值
启动中…
混合
绘制顺序

先看顺序:把混合关掉,切换绘制顺序——谁后画谁在上,结果完全不同。 再打开混合,两者叠加,顺序仍然影响最终颜色。

右边是写入编码示意 · 等价重现 同一个线性灰阶,上半原样写入、下半按 sRGB 编码后写入。 两者的中灰差得很明显:128 对 188。真正的编码由硬件在写入时做,本页用同一条传递函数重现它。

动手

把混合关掉、来回切绘制顺序。注意看:不透明物体的遮挡关系,本质上就是谁后落进缓冲——这与它们谁离相机更近无关。

件三 中间量数值表 · 与件一、件二同源
① 混合 · 重叠区那一点
项值
② 写入编码 · 同一个线性值
项值
③ 顺序的影响
顺序结果
① 就是 src×α + dst×(1−α) 算出来的;③ 把两种顺序的结果并排放着—— 混合开着时两者不同,混合关掉时只取决于谁后画。这就是「半透明物体要排序」的全部理由。
本节获得

知道片元算完之后还有一关:与缓冲里已有内容合并。半透明的叠加与绘制顺序有关, 所以顺序不透明物体时才不需要排序;也知道写入 sRGB 目标时值会在写入那一刻被编码。