3.2 颜色:覆盖写 vs 混合写


文档摘要

3.2 颜色:覆盖写 vs 混合写 本节摘要:片元着色器算出的颜色并不自动“变成屏幕上的颜色”。默认路径是覆盖:新颜色替换帧缓冲里的旧颜色。打开混合后,源颜色与目标颜色按 合成。半透明、粒子、UI 叠加走混合写;不透明物体应走覆盖写并让深度测试生效。只在着色器里把 alpha 写成 0.3,却不启用混合,画面仍是一块不透明的淡色,因为覆盖不读旧像素。 本节目标 阅读完本节,你应当能够: 说明覆盖写与混合写在是否读取目标颜色上的差别 读懂常用 与预乘的 解释绘制顺序对混合结果的影响,以及对覆盖写几乎无影响的原因 对照关闭混合的不透明 pass 与开启混合的透明 pass 指出清屏是一种特殊的覆盖,发生在绘制之前 着色器只提出颜色,状态机决定怎么落盘 FS 输出 vec4。

3.2 颜色:覆盖写 vs 混合写

本节摘要:片元着色器算出的颜色并不自动“变成屏幕上的颜色”。默认路径是覆盖:新颜色替换帧缓冲里的旧颜色。打开混合后,源颜色与目标颜色按 blendFunc 合成。半透明、粒子、UI 叠加走混合写;不透明物体应走覆盖写并让深度测试生效。只在着色器里把 alpha 写成 0.3,却不启用混合,画面仍是一块不透明的淡色,因为覆盖不读旧像素。

本节目标

阅读完本节,你应当能够:

  1. 说明覆盖写与混合写在是否读取目标颜色上的差别
  2. 读懂常用 SRC_ALPHA, ONE_MINUS_SRC_ALPHA 与预乘的 ONE, ONE_MINUS_SRC_ALPHA
  3. 解释绘制顺序对混合结果的影响,以及对覆盖写几乎无影响的原因
  4. 对照关闭混合的不透明 pass 与开启混合的透明 pass
  5. 指出清屏是一种特殊的覆盖,发生在绘制之前

着色器只提出颜色,状态机决定怎么落盘

FS 输出 vec4。若混合关闭,RGB 直接写入,alpha 按颜色掩码决定是否写入。旧值被扔掉。这叫覆盖,便宜,可与早期深度优化合作。若混合打开,硬件取源(FS 输出)与目标(帧缓冲旧值),按函数做乘加。这叫混合写,必须读旧值,顺序敏感。

固定管线记忆里“打开 ALPHA”就变透明,WebGL 里对应的是 enable(BLEND) 加函数,而不是某个灯光式开关自动读 alpha。可编程只负责把 alpha 算对;读不读目标,是固定阶段的事。第 1.2 节那条边界在这里落地。

覆盖: 新颜色 → 直接进附件 混合: 新颜色 + 旧颜色 + 因子 → 进附件 清屏: 用 clearColor 覆盖整附件

函数对照,预乘是合同不是审美

配置 公式直觉 适合 风险
混合关 覆盖 不透明世界 半透明物体像实心淡色
SRC_ALPHA 与 ONE_MINUS_SRC_ALPHA 普通插值 非预乘贴图 与预乘图一起用会黑边
ONE 与 ONE_MINUS_SRC_ALPHA 预乘插值 预乘 PNG、部分引擎默认 非预乘图会过亮
ONE 与 ONE 加法 火焰、光晕 饱和爆白,顺序仍有影响
关闭颜色写入只写深度 深度预 pass 复杂不透明场景 忘开回来导致后面全黑

预乘:RGB 已经乘过 alpha。CPU 上传时可以预乘,也可以让 GPU 当非预乘处理。两端合同必须一致。图集工具、canvas 2D 导出、视频帧,默认不一定相同。黑边与白边是合同裂缝的典型视觉。

gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // 透明物体:通常先画不透明,再按从远到近画透明 gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);

顺序:覆盖写下,近处后画会通过深度测试盖住远处,结果与画家算法相反但正确。混合写下,深度测试若仍丢弃更远的片元,而你又先画了近处透明,远处透明就被永远丢掉。所以透明 pass 常关深度写入、按距离排序。引擎的透明队列就是 CPU 在履行混合对顺序的合同。裸写忘了排序,玻璃会时对时错。

清屏不是混合。clear 把颜色附件设成 clearColor,把深度设成 clearDepth。每帧通常先 clear 再覆盖画不透明。用全屏黑四边形代替 clear 是把覆盖做成一次 draw,通常更慢。canvas 的 alpha 与页面合成还有一层浏览器混合,和 GL 内部混合不是同一开关——页面能透过 canvas 看见后面的 DOM,要在上下文属性 alpha 与 CSS 上对照,不要只改 FS。

工程:分 pass,少让混合污染不透明

默认路径应是:关混合,开深度,画全部不透明。然后开混合,关深度写入,画透明。粒子若是加法混合,可再分一队。CPU 多几次状态切换,换来 GPU 少读目标、早深度。把所有物体都开混合“以免漏掉透明”,会让整帧变成最贵路径。

物体类型 混合 深度测试 深度写入 排序
墙壁、角色不透明 可前到后也可随便
玻璃 远到近
加法火焰 开加法 开或关视情况 常不严格
UI 常关 按图层

颜色掩码 colorMask 能禁止写 RGB 或 A。做特殊遮罩时有用。忘了设回 true,后续物体“画了没看见”,像着色器死了。状态机不会在下一帧自动复位,除非你或引擎每帧重置。裸写要有一份默认状态表,每 pass 进出时恢复。引擎通常每对象设置;混用裸调用后必须自己复位,第 1.3 节的合同撕裂在混合上特别常见。

⚠️ 常见坑:FS 写了 gl_FragColor.a = 0.2,混合仍关着。你看见的是 20% 亮度的实心块,不是 20% 透明。先问状态,再问公式。

💡 关键直觉:透明是合成问题,不是“颜色淡一点”。合成依赖旧像素,所以依赖顺序与是否预乘。

sRGB 与混合:若颜色附件是 sRGB,混合可能在线性空间进行,视实现与格式。WebGL2 对 sRGB 纹理更明确。工作流要全项目统一:贴图怎么解码、光照在哪层空间、最后怎么进显示。混合函数假定的 RGB 含义一错,玻璃颜色会脏。这不是调 blendFunc 能救的,是颜色空间合同。

问题:能不能在 FS 里自己采样“当前屏幕”做混合?

当前默认帧缓冲一般不能当纹理读。要读已画内容,必须上一趟画到离屏纹理,这一趟再采。那是 3.4 的 FBO,不是把混合函数用 GLSL 重写一遍就能代替。硬读屏幕属于把固定阶段的目标颜色通道,改造成可编程采样,必须换输出目标。

第一个半透明三角形:先画一个不透明背景,再 enable 混合画三角。对比关混合的同款三角。这一对照比背函数表更牢。再换预乘函数,看黑边是否出现。把实验留在最小几何上,不要在复杂场景里调混合。

透明排序的几种减法,以及 UI 与世界分开画

完全正确的透明是顺序无关或 OIT,WebGL 里贵且吵。产品常用减法:不透明先画;透明按重心距离排序;强加法粒子不排序;屏幕空间软粒子用深度纹理比较。排序在 CPU,比较在 GPU。物体很少时,每帧 sort 一次可接受;物体很多时,用桶排序或只排序可见透明。不要为两扇门写 OIT。

UI 与世界的混合合同经常冲突。世界已预乘,UI 从 canvas 2D 来的图非预乘。分开两个 pass,各配函数,比统一函数再在 FS 里纠正更干净。UI 通常关深度,按图层。世界透明开深度测试关写入。把 UI 网格丢进世界透明队列,按钮会被模型挡住或反过来。分队列是 CPU 组织问题。

颜色附件若带 alpha 且页面要透视 canvas,浏览器会再混一次。clearColor 的 alpha 与页面背景有关。要画布不透,请求上下文时 alpha 设 false,少一层意外混合。这是创建合同时的选择,运行时改 FS 救不回来。查看器嵌在白底营销页里出现脏边,优先查这一层,再查预乘。

调试混合:先关混合看不透明是否正确,再开混合只画一个透明物体,再加第二个看顺序。一次加齐所有玻璃,你分不清是函数错、预乘错还是顺序错。状态机调试靠加减 pass,不靠同时拧五个旋钮。

加法饱和、HDR、以及混合与 MSAA

加法混合在 LDR 8 位附件上很容易饱和成白斑。火焰看起来像洞。HDR 附件加色调映射是正路,贵。减法是限制粒子亮度、减少重叠层数。层数是 CPU 生成或 GPU 粒子寿命问题。混合函数救不了 200 层加法。overdraw 是 4.7 的 GPU 税,根源在 3.2 选了加法还无限层。

MSAA 默认 FBO 与混合一起时,边缘半透明可能与预期不同。后处理关掉默认 MSAA、在离屏自己 resolve,边缘更可控。查看器若只画不透明模型,默认 antialias true 最省事。一旦上透明玻璃加后处理,默认 MSAA 反而碍事。创建属性与混合路线要一起选,不要上线后才发现玻璃描边脏。

对照问答:黑边、顺序、页面透视

PNG 玻璃一圈黑边?

预乘合同裂了。图是直通 alpha,函数按预乘在乘,暗色渗边。或相反。统一资产预乘,函数用 ONE 与 ONE_MINUS_SRC_ALPHA;或统一非预乘用 SRC_ALPHA。不要同一帧两种图混一个函数。黑边优先查合同,不要加描边着色器遮丑。

两层玻璃前后对调会闪?

透明未排序或重心排序在交叉时不稳定。减法:合并网格、减少层、用 dither 近似、接受偶尔错。OIT 贵。两扇窗不值得。排序在 CPU 每帧做,物体少时稳定。交叉的叶子用双面加 alpha test,走覆盖加 discard,牺牲早深度,换可排序性。

canvas 后面的字透过来?

创建时 alpha 为 true,clear 的 alpha 不足 1,页面再混一次。要实心底,alpha 设 false 或 clear alpha 为 1。这与 GL 内部混合无关。营销页白底脏边先查创建合同。FS 输出 alpha 1 仍透,更是创建属性问题。

工程备忘:pass 表与默认恢复

把不透明、透明、加法粒子、UI 四行写成 pass 表:混合函数、深度测、深度写、排序。每行进出调用 restoreDefaults。表改一处,代码跟一处。不要在物体循环里临时 enable。临时 enable 必漏 restore。预乘检查放在贴图导入:发现直通 PNG 进预乘管线就告警。告警比上线黑边便宜。canvas 创建属性写进启动日志,和版本一起打。透明验收用两层已知顺序的格子,顺序反了立刻能看。不要用复杂场景验收混合。复杂场景变量太多。

混合验收用两张已知 PNG:一张预乘圆、一张直通圆,叠在棋盘上。边应干净。脏了就停业务开发。pass 表打印到调试层,能看见当前函数与掩码。幽灵透明常是上一 pass 没关混合。UI 与世界分相机分队列,禁止 UI 网格进世界透明排序。页面 alpha 合同写进 README 式的启动注释:本应用 canvas 不透明。后人改 getContext 时能看见。加法粒子层数上限写进生成器,超过则拒绝诞生。拒绝比饱和白洞体面。HDR 档才允许更高层数。层数是 3.2 送给 4.7 的开关,不开关就会在节日活动爆填充率。

混合问题禁止在复杂场景调。用棋盘加两圆的最小场景。最小场景过了再回业务。业务里灯、雾、后处理会一起改颜色,归因不可能。pass 表打印当前函数。函数与资产预乘不一致就告警。告警在导入时响,比上线黑边响得早。早响是工程,晚响是客服。

现场对照时只改一个变量。同时改版本、封装、通道,归因会消失。消失之后只能靠感觉,感觉正是本书要拆掉的东西。把改动写成一条,测完再写下一条。一条一条,四组对照才用得上。用不上的对照表只是装饰。装饰填不满格子,格子空着就不要标完工。混合这一格改完,用棋盘两圆验收,不要直接上玻璃大楼。

核心回顾

  • 覆盖不读旧值:便宜、可早深度;半透明不能只靠它。
  • 混合读旧值:函数与预乘必须和贴图合同一致。
  • 顺序是合同:透明通常远到近,且常关深度写入。
  • 分 pass:不透明与透明不要共用一套状态图省事。
  • 清屏是覆盖:别用全屏四边形假装 clear。
  • 页面合成另一层:canvas alpha 与 GL 混合不是同一个开关。

下一节对照深度测试与面剔除:谁在光栅之后扔片元,谁在装配之后扔三角形。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U