WebGL 纹理概述
纹理(Texture)是 WebGL 中用于给 3D 物体添加表面细节的图片。通过纹理映射,可以将 2D 图像应用到 3D 几何体表面,使渲染结果更加真实。
纹理使用流程
1. 创建纹理对象
javascriptconst texture = gl.createTexture();
2. 绑定纹理
javascript// 绑定到 2D 纹理目标 gl.bindTexture(gl.TEXTURE_2D, texture);
3. 上传纹理数据
javascript// 方法1:从 Image 对象加载 gl.texImage2D( gl.TEXTURE_2D, // 目标 0, // 细节级别(mipmap 级别) gl.RGBA, // 内部格式 gl.RGBA, // 源格式 gl.UNSIGNED_BYTE, // 数据类型 image // Image 对象 ); // 方法2:直接上传像素数据 gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, width, // 宽度 height, // 高度 0, // 边框(必须为0) gl.RGBA, gl.UNSIGNED_BYTE, pixels // Uint8Array 像素数据 );
4. 配置纹理参数
javascript// 纹理环绕方式 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);
5. 生成 Mipmap(可选)
javascriptgl.generateMipmap(gl.TEXTURE_2D);
纹理参数详解
纹理环绕方式(Texture Wrapping)
控制当纹理坐标超出 [0, 1] 范围时的行为:
| 参数值 | 说明 |
|---|---|
gl.REPEAT | 重复纹理(默认) |
gl.CLAMP_TO_EDGE | 边缘像素延伸 |
gl.MIRRORED_REPEAT | 镜像重复 |
javascript// S 轴(水平方向) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // T 轴(垂直方向) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
可视化效果:
shellREPEAT: CLAMP_TO_EDGE: MIRRORED_REPEAT: |ABCD|ABCD| |AAAA|ABCD|DDDD| |ABCD|DCBA|ABCD| |ABCD|ABCD| |AAAA|ABCD|DDDD| |ABCD|DCBA|ABCD|
纹理过滤方式(Texture Filtering)
控制纹理采样时的插值方式:
放大过滤(MAG_FILTER)
当纹理被放大时(纹理像素 < 屏幕像素):
| 参数值 | 说明 |
|---|---|
gl.NEAREST | 最近邻采样,像素化效果 |
gl.LINEAR | 双线性插值,平滑效果(推荐) |
缩小过滤(MIN_FILTER)
当纹理被缩小时(纹理像素 > 屏幕像素):
| 参数值 | 说明 |
|---|---|
gl.NEAREST | 最近邻采样 |
gl.LINEAR | 双线性插值 |
gl.NEAREST_MIPMAP_NEAREST | 最近 mipmap + 最近采样 |
gl.LINEAR_MIPMAP_NEAREST | 最近 mipmap + 线性插值 |
gl.NEAREST_MIPMAP_LINEAR | mipmap 间线性 + 最近采样 |
gl.LINEAR_MIPMAP_LINEAR | 三线性过滤(质量最高) |
javascript// 放大时使用线性过滤 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // 缩小时使用 mipmap 三线性过滤 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
完整纹理加载示例
javascriptfunction loadTexture(gl, url) { const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // 设置临时像素(在图片加载完成前使用) const level = 0; const internalFormat = gl.RGBA; const width = 1; const height = 1; const border = 0; const srcFormat = gl.RGBA; const srcType = gl.UNSIGNED_BYTE; const pixel = new Uint8Array([0, 0, 255, 255]); // 蓝色 gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border, srcFormat, srcType, pixel); const image = new Image(); image.onload = function() { gl.bindTexture(gl.TEXTURE_D, texture); gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, image); // 检查图片尺寸是否为 2 的幂次 if (isPowerOf2(image.width) && isPowerOf2(image.height)) { // 是 2 的幂次,可以使用 mipmap gl.generateMipmap(gl.TEXTURE_2D); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); } else { // 不是 2 的幂次,关闭 mipmap,设置环绕方式为 CLAMP_TO_EDGE 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); }; image.src = url; return texture; } function isPowerOf2(value) { return (value & (value - 1)) == 0; }
在着色器中使用纹理
顶点着色器
glslattribute vec3 a_position; attribute vec2 a_texCoord; uniform mat4 u_mvpMatrix; varying vec2 v_texCoord; void main() { gl_Position = u_mvpMatrix * vec4(a_position, 1.0); v_texCoord = a_texCoord; // 传递纹理坐标到片段着色器 }
片段着色器
glslprecision mediump float; varying vec2 v_texCoord; uniform sampler2D u_texture; // 纹理采样器 void main() { gl_FragColor = texture2D(u_texture, v_texCoord); }
JavaScript 代码
javascript// 激活纹理单元 gl.activeTexture(gl.TEXTURE0); // 绑定纹理 gl.bindTexture(gl.TEXTURE_2D, texture); // 设置 uniform 变量(告诉着色器使用哪个纹理单元) gl.uniform1i(textureLocation, 0); // 使用 TEXTURE0
多纹理绑定
javascript// 加载多个纹理 const texture1 = loadTexture(gl, 'texture1.jpg'); const texture2 = loadTexture(gl, 'texture2.png'); // 在绘制时绑定到不同纹理单元 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture1); gl.uniform1i(texture1Location, 0); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, texture2); gl.uniform1i(texture2Location, 1);
glsl// 片段着色器中使用多个纹理 uniform sampler2D u_texture1; uniform sampler2D u_texture2; varying vec2 v_texCoord; void main() { vec4 color1 = texture2D(u_texture1, v_texCoord); vec4 color2 = texture2D(u_texture2, v_texCoord); gl_FragColor = mix(color1, color2, 0.5); // 混合两个纹理 }
纹理坐标系统
shell(0, 1) -------- (1, 1) | | | 纹理图像 | | | (0, 0) -------- (1, 0)
注意:WebGL 纹理坐标系原点在左下角,而大多数图像格式原点在左上角。
性能优化建议
- 使用纹理图集(Texture Atlas):将多个小纹理合并为一个大纹理,减少绑定切换
- 合理选择纹理尺寸:
- 优先使用 2 的幂次尺寸(支持 mipmap)
- 不要使用过大的纹理(内存和带宽开销)
- 使用压缩纹理格式:如 DXT、ETC、PVRTC
- 启用 mipmap:提高渲染质量和性能
- 复用纹理:避免重复加载相同纹理