WebGL 纹理完整教程:原理、场景 + 可直接运行 Demo一、WebGL 纹理核心概念1. 纹理是什么纹理就是一张图片,把像素数据贴到几何体表面(类似贴纸),WebGL 通过纹理单元、纹理对

WebGL 纹理完整教程:原理、场景 + 可直接运行 Demo一、WebGL 纹理核心概念1. 纹理是什么纹理就是一张图片,把像素数据贴到几何体表面(类似贴纸),WebGL 通过纹理单元、纹理对

WebGL 纹理完整教程:原理、场景 + 可直接运行 Demo

一、WebGL 纹理核心概念

1. 纹理是什么

纹理就是一张图片,把像素数据贴到几何体表面(类似贴纸),WebGL 通过纹理单元、纹理对象、采样器、UV 坐标配合使用。 核心四要素:

  1. 纹理对象 (Texture):存储图片像素缓存
  2. 纹理单元 (Texture Unit):GPU 并行纹理槽位,默认至少 16 个,gl.TEXTURE0 ~ gl.TEXTURE15
  3. UV 坐标:贴图坐标,范围[0,1],(0,0)= 图片左上角,(1,1)= 右下角
  4. 纹理参数:缩放过滤、环绕模式

2. 关键纹理参数

环绕模式(图片超出 UV 0~1 时)
  • gl.REPEAT:重复平铺(默认)
  • gl.CLAMP_TO_EDGE:拉伸边缘像素
  • gl.MIRRORED_REPEAT:镜像重复
过滤模式(缩放贴图时)
  • 放大TEXTURE_MAG_FILTER
    • gl.LINEAR:线性模糊平滑
    • gl.NEAREST:像素块马赛克
  • 缩小TEXTURE_MIN_FILTER,多配合 Mipmap:
    • gl.LINEAR_MIPMAP_LINEAR:三线性,画质最好
    • gl.NEAREST:硬像素

二、纹理常见应用场景

  1. 2D/3D 模型贴图:人物皮肤、墙体、地板、道具外观
  2. UI 界面:游戏按钮、图标、背景面板
  3. 帧缓冲 FBO 渲染纹理:离屏渲染、镜面反射、阴影贴图、后期滤镜(模糊 / 调色)
  4. 数据纹理:把数组数据存纹理,GPU 并行计算(GPGPU)
  5. 视频纹理:播放视频贴到模型(播放器、虚拟大屏)
  6. 文字纹理:字体图集贴图渲染文字
  7. 环境贴图:天空盒、反射球环境光

三、完整可运行 Demo(贴图矩形 + 本地图片纹理)

功能:绘制一个带图片纹理的全屏矩形,包含完整纹理创建、图片异步加载、着色器 UV 采样逻辑 复制全部代码保存为.html直接浏览器打开即可。

html

预览

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>WebGL Texture Demo</title> <style> canvas { border: 1px solid #000; } </style> </head> <body> <canvas id="webglCanvas" width="600" height="400"></canvas> <script> // 1. 获取Canvas与WebGL上下文 const canvas = document.getElementById('webglCanvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (!gl) alert("浏览器不支持WebGL"); // ===================== 着色器代码 ===================== // 顶点着色器:传递顶点坐标 + UV贴图坐标 const vertexShaderSource = ` attribute vec2 a_Position; attribute vec2 a_Uv; varying vec2 v_Uv; void main() { gl_Position = vec4(a_Position, 0.0, 1.0); v_Uv = a_Uv; } `; // 片元着色器:纹理采样,sampler2D读取纹理 const fragmentShaderSource = ` precision mediump float; uniform sampler2D u_Texture; varying vec2 v_Uv; void main() { // 根据UV从纹理采样像素颜色 gl_FragColor = texture2D(u_Texture, v_Uv); } `; // ===================== 工具函数:编译着色器、链接程序 ===================== function createShader(gl, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error("着色器编译失败:", gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } function createProgram(gl, vsSource, fsSource) { const vs = createShader(gl, gl.VERTEX_SHADER, vsSource); const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSource); const program = gl.createProgram(); gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error("程序链接失败:", gl.getProgramInfoLog(program)); return null; } gl.useProgram(program); return program; } // 初始化着色器程序 const program = createProgram(gl, vertexShaderSource, fragmentShaderSource); // ===================== 顶点数据:矩形 + UV坐标 ===================== // 格式:[x,y, u,v] 四个顶点,两个三角组成矩形 const vertices = new Float32Array([ -1.0, 1.0, 0.0, 0.0, // 左上 -1.0, -1.0, 0.0, 1.0, // 左下 1.0, 1.0, 1.0, 0.0, // 右上 1.0, -1.0, 1.0, 1.0 // 右下 ]); const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // 绑定顶点属性 const a_Position = gl.getAttribLocation(program, "a_Position"); const a_Uv = gl.getAttribLocation(program, "a_Uv"); const stride = 4 * Float32Array.BYTES_PER_ELEMENT; // 一组4个浮点数 // 位置:每组前2个 gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, stride, 0); gl.enableVertexAttribArray(a_Position); // UV:每组后2个,偏移2个浮点数 gl.vertexAttribPointer(a_Uv, 2, gl.FLOAT, false, stride, 2 * Float32Array.BYTES_PER_ELEMENT); gl.enableVertexAttribArray(a_Uv); // ===================== 创建纹理对象核心逻辑 ===================== function createTexture(gl) { const texture = gl.createTexture(); // 绑定到当前激活纹理单元 TEXTURE0 gl.bindTexture(gl.TEXTURE_2D, texture); // 设置纹理环绕模式 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); // 设置过滤,平滑缩放 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // 临时填充1x1白色像素,避免图片未加载时黑屏报错 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255,255,255,255])); return texture; } // 加载图片到纹理 function loadTexture(gl, texture, imgUrl) { const img = new Image(); img.crossOrigin = "anonymous"; // 解决跨域图片报错 img.onload = () => { gl.bindTexture(gl.TEXTURE_2D, texture); // 上传图片像素到GPU纹理 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); render(); // 图片加载完成重绘 }; img.src = imgUrl; } // ===================== 渲染函数 ===================== function render() { gl.clearColor(0.2, 0.2, 0.2, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 激活0号纹理单元 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture); // 把采样器u_Texture绑定到0号纹理单元 gl.uniform1i(gl.getUniformLocation(program, "u_Texture"), 0); // 绘制两个三角形(共4顶点) gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } // 启动流程 const texture = createTexture(gl); // 替换成你自己的图片链接,本地图片直接放同目录 loadTexture(gl, texture, "https://picsum.photos/id/237/400/300"); render(); </script> </body> </html>

四、关键 API 分步解析

1. 创建纹理

js

运行

const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // 绑定2D纹理目标

2. 设置纹理参数(必写,否则缩放会黑块)

js

运行

// S=水平方向,T=垂直方向环绕 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); // 缩放过滤 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

3. 上传图片数据到 GPU

js

运行

gl.texImage2D( gl.TEXTURE_2D, 0, // mip层级 gl.RGBA, // GPU内部存储格式 gl.RGBA, // 图片像素格式 gl.UNSIGNED_BYTE, // 像素数据类型 img // Image/Video/Canvas数据源 );

4. 激活纹理单元 + 传入着色器

js

运行

// 激活第0个纹理槽 gl.activeTexture(gl.TEXTURE0); // 绑定纹理对象到当前激活单元 gl.bindTexture(gl.TEXTURE_2D, texture); // 片元着色器sampler2D变量 = 纹理单元编号0 gl.uniform1i(u_TextureLoc, 0);

5. 着色器采样

glsl

uniform sampler2D u_Texture; // 纹理采样器 varying vec2 v_Uv; // 插值UV坐标 // 采样获取像素颜色 vec4 color = texture2D(u_Texture, v_Uv);

五、拓展常用纹理拓展用法

1. Mipmap 优化远距离贴图

图片尺寸是 2 的幂(512/1024/2048)时开启:

js

运行

gl.generateMipmap(gl.TEXTURE_2D); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);

2. 多纹理叠加(双纹理单元)

js

运行

// 纹理1绑定TEXTURE0 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, tex1); gl.uniform1i(texLoc1, 0); // 纹理2绑定TEXTURE1 gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, tex2); gl.uniform1i(texLoc2, 1);

片元着色器混合两张贴图:

glsl

vec4 col1 = texture2D(u_Tex1, v_Uv); vec4 col2 = texture2D(u_Tex2, v_Uv); gl_FragColor = mix(col1, col2, 0.5);

3. Video 视频纹理

直接传入<video>标签到texImage2D,渲染循环每帧更新纹理。

4. FBO 渲染纹理(离屏渲染)

创建纹理绑定帧缓冲,把 3D 场景渲染到纹理,再贴到模型实现镜面、特效。

六、常见踩坑点

  1. 图片跨域黑屏:设置img.crossOrigin = "anonymous",图片服务器允许跨域
  2. UV 超出范围拉伸异常:修改TEXTURE_WRAP_S/TCLAMP_TO_EDGE
  3. 缩小贴图出现黑块:没设置TEXTURE_MIN_FILTER过滤参数
  4. Mipmap 报错:图片宽高必须是 2 的整数次幂
  5. 图片加载前黑屏:先填充 1x1 临时像素纹理