首页/ 学科题库/ webgl/ WebGL 纹理创建与 UV 映射
webgl 实战教程

WebGL 纹理创建与 UV 映射

2026-09-13 约 8 分钟 juanwangdev

WebGL 纹理创建与 UV 映射

一句话结论:纹理创建负责把图片搬进显存,UV 映射负责告诉每个顶点取图片的哪一块,两者对接正确才能贴出正确图案。

纹理对象创建与图片上传

是什么

纹理对象是显存中存放图像数据的资源,创建流程为生成纹理、绑定目标、写入像素、设置采样参数。上传后由片元着色器中的采样器读取。

解决什么问题

它解决逐像素颜色数据来源问题。用顶点颜色表达细节需要海量顶点,而一张纹理用一次上传就能给整个表面提供细致图案,极大降低了顶点数量与内存占用。

为什么这样做

采样是 GPU 的固定硬件能力,纹理存储按二维局部性优化,相邻像素读取几乎无额外开销。把图像数据放进显存后,每帧只需绑定引用,不需要重复传输,因此贴图是渲染最经济的细节表达方式。

怎么用

JavaScript
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);

// 图片加载完成后再上传
const img = new Image();
img.onload = () => {
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); // 纠正 Y 轴方向
  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);
};
img.src = 'texture.png';

// 绘制前绑定纹理单元
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(gl.getUniformLocation(program, 'uSampler'), 0);

常见坑

  • 图片未加载就调用 texImage2D → 原因:异步加载未完成 → 处理:在 onload 回调中上传
  • 尺寸非 2 的幂却启用 mipmap → 原因:WebGL 1.0 限制 → 处理:改用 CLAMP_TO_EDGE 加非 mipmap 过滤,或补到 2 的幂
  • 忘记设置 MIN_FILTER → 原因:默认需要 mipmap → 处理:显式设为 LINEAR 或 NEAREST
  • 采样器 uniform 未赋值 → 原因:默认绑定 0 号单元但纹理绑在别的单元 → 处理:用 uniform1i 明确指定

纹理坐标与 UV 映射

是什么

UV 坐标是把纹理图像映射到模型表面的二维坐标,取值范围一般是 0 到 1,写在顶点属性中,经 varying 插值后由片元着色器用于采样。

解决什么问题

它解决"贴图的哪一部分对应模型的哪个位置"的问题。没有 UV,GPU 不知道该取纹理的哪一点;有了 UV,同一张贴图可以按不同方式铺在不同模型上,也能只取其中一部分做图集采样。

为什么这样做

UV 用归一化坐标表达,与图像实际分辨率解耦。更换不同分辨率的贴图时无需修改顶点数据,图集复用也只需调整 UV 区间,这让资源替换与合批变得非常廉价。

怎么用

JavaScript
// 顶点数据:位置 3 个 float + UV 2 个 float,交错存放
const vertices = new Float32Array([
  // x, y, z,   u, v
  -0.5, -0.5, 0, 0, 0,
   0.5, -0.5, 0, 1, 0,
   0.5,  0.5, 0, 1, 1,
  -0.5,  0.5, 0, 0, 1,
]);
const STRIDE = 5 * 4;
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, STRIDE, 0);
gl.vertexAttribPointer(aTexCoord, 2, gl.FLOAT, false, STRIDE, 3 * 4);
glsl
// 片元着色器采样
precision mediump float;
uniform sampler2D uSampler;
varying vec2 vTexCoord;
void main() {
  gl_FragColor = texture2D(uSampler, vTexCoord);
}

常见坑

  • 贴图上下颠倒 → 原因:图片与纹理坐标 Y 轴方向相反 → 处理:启用 UNPACK_FLIP_Y_WEBGL,或在着色器中翻转 v
  • UV 顺序与顶点不一致 → 原因:交错布局的偏移写错 → 处理:核对步长与偏移字节数
  • 模型出现拉伸 → 原因:UV 未按面积分配 → 处理:展开 UV 时保持面积比例
  • 图集采样串到相邻图标 → 原因:UV 边界与插值溢出 → 处理:内缩半像素或加边距

要点总结

  • 纹理四步:创建、绑定、texImage2D 上传、设置过滤与环绕参数
  • 图片必须在 onload 之后上传,并按需开启 UNPACK_FLIP_Y_WEBGL
  • UV 归一化到 0 到 1,与分辨率解耦,便于换图与图集合批
  • 采样器 uniform 用 uniform1i 指定纹理单元序号
  • 交错布局中 UV 的偏移是 12 字节(位置占 3 个 float)

常见问题

上传纹理需要几步?

创建纹理对象、绑定到 TEXTURE_2D、用 texImage2D 写入图像数据、设置过滤与环绕参数,然后在绘制前激活纹理单元并把采样器 uniform 指向该单元。

为什么贴图上下颠倒了?

因为图片坐标原点在左上角,而 WebGL 纹理坐标原点在左下角。启用 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) 让 WebGL 上传时翻转 Y 轴即可。

UV 坐标的取值范围是多少?

通常是 0 到 1,左下角为 (0, 0),右上角为 (1, 1)。超出 0 到 1 的部分由环绕模式决定,可重复平铺或钳制到边缘。

纹理采样器 uniform 应该怎么设置?

先激活一个纹理单元(如 TEXTURE0),把纹理绑定到该单元,再用 gl.uniform1i 把采样器 uniform 设为单元序号 0,序号与单元编号一一对应。

想在手机上练这篇的配套题目?
使用微信 卷王开发者小程序/App,首页顶部扫一扫识别下方二维码,即可解锁本讲对应真题与错题本。
了解产品 App
← 上一篇 WebGL 正交投影与法线矩阵 下一篇 → WebGL 纹理过滤与环绕模式