2.6 纹理:CPU 解码 vs GPU 采样


文档摘要

2.6 纹理:CPU 解码 vs GPU 采样 本节摘要:纹理是 GPU 上按 UV 寻址的像素仓库。CPU 负责把压缩图片解码成位、选择内部格式、上传、生成 mipmap;GPU 在片元着色器里滤波采样。两端的决策互相惩罚:CPU 上传一张未预乘、无 mip 的 4K 图,GPU 会在远景闪烁并吃带宽;GPU 开了各向异性,CPU 若只给一层 lod,红利有限。纹理不是“贴张图那么简单”,而是解码合同加采样合同。

2.6 纹理:CPU 解码 vs GPU 采样

本节摘要:纹理是 GPU 上按 UV 寻址的像素仓库。CPU 负责把压缩图片解码成位、选择内部格式、上传、生成 mipmap;GPU 在片元着色器里滤波采样。两端的决策互相惩罚:CPU 上传一张未预乘、无 mip 的 4K 图,GPU 会在远景闪烁并吃带宽;GPU 开了各向异性,CPU 若只给一层 lod,红利有限。纹理不是“贴张图那么简单”,而是解码合同加采样合同。

本节导航

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

  1. 画出从 Image 对象到 sampler2D 的步骤,并标出拷贝发生在哪
  2. 对照放大过滤、缩小过滤、mipmap 各自解决什么视觉问题
  3. 说明 WRAP 模式 REPEAT 与 CLAMP 对 UV 越界的不同合同
  4. 解释非 2 的幂尺寸在 WebGL1 上的限制与 WebGL2 的放宽
  5. 给出固有色贴图、UI 图、法线贴图在格式与过滤上的不同选择

图片活在 CPU,采样活在 GPU

浏览器把 JPEG/PNG 解成位图,这步在 CPU(或解码器线程)。texImage2D 把位拷进 GPU 纹理对象。之后着色器用 texture2D / texture 按 UV 取值。CPU 改 Image 不会自动更新纹理,合同仍是拷贝。要更新,再上传。视频纹理每帧上传,是明确的 STREAM 路径,账单在解码加拷贝,不在 FS 里那一行采样。

UV 通常作为 Attribute 进 VS,插值进 FS。GPU 用 UV 乘纹理尺寸找到纹素,再按过滤模式混合邻居。CPU 从不“取某个像素给三角形”——除非你 readPixels,那是把数据拉回来,方向反了。

文件 → 解码成位图 → texImage2D → GPU 纹理 │ UV Attribute → 插值 → FS texture 采样 ┘

纹理单元是绑定槽:activeTexture(TEXTURE0) 再 bind,然后 Uniform 采样器填 0。槽位是 CPU 状态。绑错单元,FS 采到上一张图,表现为“材质串味”。引擎的贴图槽看起来像材质字段,底下仍是单元编号。单元数量有上限,PBR 一套贴图就能占多个槽;超了要合并图集或多次绘制。这是 CPU 侧的资源分配问题。

过滤、包裹、格式:三张对照表

过滤 何时用 视觉 代价
NEAREST 放大缩小 像素风、UI 需锐利 块状 GPU 最便宜
LINEAR 无 mip 近景尚可 远景闪烁混叠 缩小仍只看一层
LINEAR_MIPMAP_LINEAR 3D 场景默认 远景稳 多占三分之一显存级 mip 链
各向异性 地面、轨道视角 斜看更清晰 GPU 带宽与实现相关

没有 mip 的高分辨率贴图在远处会闪,因为相邻像素采样跳着吃纹素。生成 mipmap 是 CPU 调用 generateMipmap(或自己上传每一层),GPU 在运行时选层。只设 LINEAR 却不建 mip,等于告诉 GPU“缩小时仍在最高层里挣扎”。

WRAP UV 越界 适合 不适合
REPEAT 取小数部分 砖墙、地面平铺 图集里的单块,会把邻居渗进来
CLAMP_TO_EDGE 夹到边 UI、不重复的照片 要平铺的材质
MIRRORED_REPEAT 镜像重复 某些无缝图案 图集

WebGL1 对非 2 的幂纹理限制更严:REPEAT 与 mipmap 常常要求 POT。WebGL2 放宽。资产管线若仍按 1.0 出图,继续保证 POT 或用图集,少在运行时分支。这是版本轴在纹理上的落点。

内容 CPU 格式注意 GPU 采样注意
固有色 sRGB 与线性工作流要对齐 与光照相乘应在线性空间
法线贴图 不要当普通照片乱压 FS 里解包并与切线空间结合
UI 预乘 alpha 与混合函数匹配 常关 mip,NEAREST 或 LINEAR
数据纹理 NEAREST,无 sRGB VS/FS 当查找表,不是“看的图”
gl.bindTexture(gl.TEXTURE_2D, tex); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.generateMipmap(gl.TEXTURE_2D);

上传参数里内部格式与源格式在 WebGL1 常写 RGBA 对 RGBA;WebGL2 有更细的内部格式,包括整数、浮点、sRGB。混用 1.0 习惯到 2.0 枚举,会出现 invalid enum。对照第 1.1 节:合同不同。

图:纹理从 CPU 到 GPU 的两段合同

图:纹理从 CPU 到 GPU 的两段合同

工程选择:图集、压缩、Y 翻转、异步

图集把多张小图打进一张,减少绑定切换(CPU draw 税)和采样器占用。代价是 WRAP 不能 REPEAT 到邻居,UV 要偏置。UI 与 2D 精灵几乎总该图集;平铺砖墙不该和图集混同一张,除非自己在边界留垫。

压缩纹理(ETC、ASTC、S3TC 等)让 CPU 少传未压缩位、GPU 少占显存。浏览器与 GPU 支持组合是一张矩阵,常要多份资产。引擎加载器做回退;裸写就要自己检测扩展。这是封装轴在纹理上的具体价值:不是场景图,是资产回退表。

Y 翻转:WebGL 纹理坐标原点在左下,浏览器图片原点常在左上。UNPACK_FLIP_Y_WEBGL 让 CPU 在上传时翻。法线贴图和数据纹理乱翻会把方向搞反。约定比开关更重要:全项目要么翻图像,要么翻 UV,不要有的材质翻有的不翻。

⚠️ 常见坑:把带透明的 PNG 当不预乘,却用预乘混合函数;边缘一圈黑边。或反过来。混合合同在第 3.2 节,但源像素是否预乘是 CPU 上传时就要定的。

💡 关键直觉:纹理优化先问这张图是给眼睛看的还是给公式查的。看的图要 sRGB、mip、合适过滤;查的表要 NEAREST、线性空间、禁止乱滤波。

异步加载:Image 的 decode 完成前不要上传。占位用 1x1 颜色纹理,避免 FS 采到空对象。引擎这么做不是炫技,是防止第一帧黑块或报错。裸写也应有“纹理未就绪则用默认”的 CPU 状态,而不是假设 GPU 会等网络。

问题:4K 贴图是不是一定更清晰?

在近距离、屏幕占比大时可能。物体只占 100 像素宽时,GPU 主要在低 mip 里采,4K 的高层几乎只浪费 CPU 上传与显存。对照应看屏幕覆盖,不是看资产文件夹里的分辨率数字。移动端先砍尺寸,再谈压缩格式。

立方体贴图与三维纹理把“一张 2D”扩成多张或一层层切片,CPU 上传次数变多,GPU 采样用三维或立方体坐标。细节放到第 4.3 节。本节只需承认:通道仍是“CPU 填仓、GPU 按坐标取”,坐标维度变了,分界没变。

上传陷阱清单:翻转、预乘、尺寸、空对象

上传前 CPU 要回答四问。Y 是否翻转?项目级约定写死,法线与数据图单独标注。是否预乘?PNG 带透明时必须与 3.2 的 blendFunc 成套。尺寸是否满足当前版本的 POT 与对齐?WebGL1 的 REPEAT+mip 对非 POT 会变黑或报错,有人当成“贴图坏了”去重压 JPEG,越压越花。对象是否已绑定到正确目标与单元?texImage2D 打在 TEXTURE_CUBE_MAP 的错误面上,要等到采样才看见接缝。四问写成上传函数的断言,比出了黑图再猜便宜。

视频与 canvas 作为纹理源,每帧上传。这是明确的 STREAM。摄像头纹理还有格式与方向问题。不要用处理 JPEG 的那套 mip 每帧 generateMipmap,除非你真要远景视频墙。UI 视频通常 LINEAR 无 mip。生成 mip 的 CPU/GPU 税每帧付一次,会比解码本身更疼。

压缩纹理加载失败时必须有未压缩回退。失败静默会导致采样到空,FS 输出黑。启动时若关键贴图未就绪,用 1x1 颜色占位,并在控制台记一次。占位色选显眼的粉,比黑更容易发现漏加载。上线再换成灰。显眼占位是 CPU 侧的可观测性,不是品味。

图集 UV 计算在 CPU:偏移加缩放。艺术家改图集布局,UV 必须重算或重导出。运行时不要试图“自动识别岛屿”。引擎的图集工具把这步放在构建期。裸写也把 UV 变换当资产,不当每帧数学。每帧算 UV 变换是把构建期工作搬进热路径。

各向异性、lod bias、以及采样器对象

WebGL2 有采样器对象,把过滤与包裹从纹理对象上拆下来。同一张图可以NEAREST给数据、LINEAR给预览。WebGL1 过滤写在纹理对象上,换用途就要改参数或复制。这是版本轴在纹理上的另一处红利。没迁 2.0 时,数据图与显示图分开两份纹理更安全,避免改参数改漏。

lod bias 能让 mip 选得更糊或更锐。锐了远景闪,糊了虚。先保证 mip 链存在,再谈 bias。没有链时 bias 无意义。各向异性扩展失败就保持三线性。把各向异性当必选项,地面在某手机变黑,因为扩展指令或参数越界。画质档,不是功能档。2.6 与 4.5 在此会合。

对照问答:黑图、闪烁、图集渗色

贴图全黑但 Image 已加载?

单元没绑对、采样器 Uniform 没设、内部格式失败、还在用占位却没发现。用粉占位。仍黑则输出 UV 看是否在 0 到 1。UV 全零会采到边角一像素。Y 翻转导致采到透明边也会像黑。先输出纹理颜色不管光照,确认采样通道,再乘光。

远景闪是压缩格式的锅吗?

更常见是无 mip 或 min filter 不含 mip。压缩格式若 mip 链没打进资产,也会闪。先 generateMipmap 或上传完整链。仍闪再查各向异性与 lod。压缩失败回退 PNG 时不要忘了给回退链建 mip。闪是缩小采样问题,不是“JPEG 质量 80 不够”。

图集里按钮边缘出现邻居颜色?

CLAMP 没设,或 UV 算到了像素中心之外,mip 渗到隔壁。图集岛屿要留垫,UV 缩进半像素,min filter 对 UI 可不用 mip。UI 与 3D 平铺材质不要共一张图集。渗色是 2.6 的 WRAP 与 mip 合同,不是 FS 里的 alpha 能切干净的。

工程备忘:预算与解码并发

纹理字节预算写死。超了降 mip 最高层或拒绝加载 4K。解码并发限 2 到 4。iOS 上过多 Image 同时 decode 会卡死主线程,像 GPU 烫。其实是 CPU。视频纹理不要进 PBR 槽当反照率除非产品就是这样。视频每帧上传,PBR 再乘一层,两端都贵。UI 图与 3D 图分文件夹约定过滤默认值,减少漏设。漏设 REPEAT 在非 POT 上变黑,查了半天着色器。默认值表比记忆可靠。粉占位保留到预发,正式再换灰。预发看见粉就是漏加载,比用户看见黑块体面。

纹理验收分三步:粉占位消失、UV 可视化合理、接上光照。一步并做会把加载失败当成光照失败。图集与平铺砖分仓,WRAP 默认不同,写在上传函数的参数而不是靠调用者记得。压缩回退链在 CI 用一台不支持 ASTC 的环境跑,失败必须看到 PNG。看不到就是假回退。4K 源图在构建期生成 1K 与 512,运行时按档位选。运行时缩放不是 WebGL 的工作,是资产管线的工作。把 4K 丢给 GPU 再靠 mip 救,上传税已经付了。付过的税在 4.7 里表现为 CPU 尖刺,常被误诊为着色器。先看上传次数与字节,再看 FS。

上传函数做成唯一入口:翻转、预乘、过滤、WRAP、mip 全部当参数,禁止散落 texParameteri。散落必漏。入口打点字节数,加载条按字节走,不要按文件个数走。一个 4K 比二十个图标还重。点文件数会让进度条骗人。骗人的进度条会在最后 1% 卡死,用户以为死机。死机其实是 CPU 解码。唯一入口让账单可见。

要点速记

  • 拷贝上传:解码在 CPU,采样在 GPU;改 Image 必须再上传。
  • mip 解决缩小:远景闪烁优先查 min filter 与是否真有 mip 链。
  • WRAP 与图集冲突:平铺要 REPEAT;图集要 CLAMP 加 UV 偏置。
  • 版本与 POT:WebGL1 对非 2 的幂更苛刻;资产管线按最老合同出图更省事。
  • 看的图 vs 查的表:过滤与颜色空间跟着用途走,不能一张表打天下。
  • 单元与图集:绑定槽有限,合图是减 CPU 切换,不是减 GPU 公式。

下一章进入仍留在固定阶段的输出合并,以及矩阵乘法该放 CPU 还是 GPU。


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