首页/ 学科题库/ webgl/ WebGL 纹理加载时序与格式选型
webgl 实战教程

WebGL 纹理加载时序与格式选型

2026-09-13 约 8 分钟 juanwangdev

WebGL 纹理加载时序与格式选型

一句话结论:纹理必须在图像真正可读之后上传,跨域图片要 CORS 与 crossOrigin 同时配置,格式按通道需求选择才能兼顾画质与显存。

图像跨域与纹理加载时序

是什么

图像跨域指图片来自与页面不同的源,浏览器出于安全策略限制其像素读取;加载时序指纹理上传必须发生在图像解码完成、且像素可读之后,否则 texImage2D 会失败或得到空白纹理。

解决什么问题

它解决贴图不显示与控制台报安全错误的问题。跨域图片若没有正确的 CORS 头,即使图像能显示,调用 texImage2D 也会被拒绝;上传过早则拿到未解码的空图像,两种情况都表现为画面异常却不易定位。

为什么这样做

WebGL 能访问纹理像素,若允许任意跨域图像上传,就等于绕过了同源策略读取其他站点内容。因此浏览器要求服务端显式授权(CORS 头),客户端也必须在请求阶段声明跨域模式,让响应带出授权信息后像素才可读。

怎么用

JavaScript
function loadTexture(gl, url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';   // 请求阶段声明跨域
    img.onload = () => {
      const tex = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, tex);
      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      resolve(tex);
    };
    img.onerror = reject;
    img.src = url;
  });
}

// 所有纹理就绪后再启动渲染循环
Promise.all([loadTexture(gl, a), loadTexture(gl, b)]).then(() => requestAnimationFrame(render));

常见坑

  • 只设 crossOrigin 而服务端无 CORS 头 → 原因:授权需双方配合 → 处理:让图片服务返回 Access-Control-Allow-Origin
  • 在 onload 之前上传 → 原因:图像尚未解码 → 处理:把上传逻辑放进 onload 或使用 decode() 等待
  • 缓存导致 crossOrigin 失效 → 原因:同一 URL 先被无跨域请求缓存 → 处理:统一跨域设置或加查询参数区分
  • 首帧直接渲染未就绪物体 → 原因:采样空纹理 → 处理:用占位纹理或等全部资源就绪再绘制

纹理格式与颜色表示

是什么

纹理格式描述像素在显存中的通道构成与每像素字节数。常用内部格式有 RGBA、RGB、LUMINANCE、ALPHA 与 LUMINANCE_ALPHA,WebGL 2.0 还提供更丰富的 R8、RG8、RGB16F 等 sized 格式。

解决什么问题

它解决显存与带宽的取舍问题。一张 1024×1024 的 RGBA 纹理占 4MB,若只需要灰度遮罩,改用 LUMINANCE 可降到 1MB;格式选错会白白浪费显存与采样带宽,移动端尤其明显。

为什么这样做

显存容量与带宽是移动端最紧张的资源。纹理是单帧中体积最大的资源类型,按实际需要的通道数选择格式,能在几乎不损失画质的前提下成倍降低占用,也让纹理缓存命中率更高。

怎么用

JavaScript
// 彩色贴图
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);

// 灰度遮罩:只需要亮度通道,节省 3/4 显存
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0,
              gl.LUMINANCE, gl.UNSIGNED_BYTE, grayData);

// 采样时按格式取通道
格式每像素适用
RGBA4 字节彩色贴图、带透明
RGB3 字节无透明彩色贴图
LUMINANCE1 字节灰度、高度、遮罩
ALPHA1 字节仅透明度遮罩
LUMINANCE_ALPHA2 字节灰度加透明,如字体

常见坑

  • 用 RGBA 存单通道数据 → 原因:习惯性默认 → 处理:按通道需求选 LUMINANCE 或 ALPHA
  • 半透明边缘发黑 → 原因:预乘 Alpha 不一致 → 处理:统一 premultipliedAlpha 设置或手动预乘
  • 灰度图采样后颜色不对 → 原因:只写了 r 通道 → 处理:着色器中把 r 赋给三个通道
  • 忽略颜色空间 → 原因:贴图为 sRGB 却按线性计算 → 处理:统一做 sRGB 到线性的转换

要点总结

  • 跨域纹理需要 crossOrigin 与服务端 CORS 头同时配置
  • 上传必须发生在图像 onload 或 decode 完成之后
  • 首帧渲染要等纹理就绪,或先用占位纹理
  • 按通道需求选格式:彩色 RGBA、灰度 LUMINANCE、仅透明 ALPHA
  • 预乘 Alpha 设置要与纹理数据一致,避免半透明边缘发黑

常见问题

跨域图片为什么无法作为纹理上传?

浏览器默认禁止读取跨域图像的像素数据。需要在 img 上设置 crossOrigin 为 anonymous 或 use-credentials,同时服务端返回 Access-Control-Allow-Origin 响应头,两者缺一不可。

纹理还没加载完就开始渲染会怎样?

采样器读到的是空纹理,画面表现为全黑或纯色块,且不报错。正确做法是等纹理就绪后再把该物体加入渲染,或用占位纹理先顶上。

RGBA 和 LUMINANCE 纹理怎么选?

RGBA 每像素 4 字节,适合彩色贴图;LUMINANCE 每像素 1 字节,只保留亮度,适合灰度遮罩与高度图。按通道需求选择可显著节省显存与带宽。

透明贴图边缘发黑怎么处理?

因为插值时颜色被预乘或未按预乘处理。可启用 premultipliedAlpha 一致的混合模式,或在着色器中手动处理预乘,避免半透明像素与普通像素混合出错。

想在手机上练这篇的配套题目?
使用微信 卷王开发者小程序/App,首页顶部扫一扫识别下方二维码,即可解锁本讲对应真题与错题本。
了解产品 App
← 上一篇 WebGL 纹理过滤与环绕模式 下一篇 → WebGL 绘制类型与参数化顶点生成