乐闻世界logo
搜索文章和话题

WebGL 中的纹理(Texture)如何使用?有哪些纹理参数需要配置?

3月6日 21:57

WebGL 纹理概述

纹理(Texture)是 WebGL 中用于给 3D 物体添加表面细节的图片。通过纹理映射,可以将 2D 图像应用到 3D 几何体表面,使渲染结果更加真实。

纹理使用流程

1. 创建纹理对象

javascript
const 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(可选)

javascript
gl.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);

可视化效果

shell
REPEAT: 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_LINEARmipmap 间线性 + 最近采样
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);

完整纹理加载示例

javascript
function 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; }

在着色器中使用纹理

顶点着色器

glsl
attribute 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; // 传递纹理坐标到片段着色器 }

片段着色器

glsl
precision 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 纹理坐标系原点在左下角,而大多数图像格式原点在左上角

性能优化建议

  1. 使用纹理图集(Texture Atlas):将多个小纹理合并为一个大纹理,减少绑定切换
  2. 合理选择纹理尺寸
    • 优先使用 2 的幂次尺寸(支持 mipmap)
    • 不要使用过大的纹理(内存和带宽开销)
  3. 使用压缩纹理格式:如 DXT、ETC、PVRTC
  4. 启用 mipmap:提高渲染质量和性能
  5. 复用纹理:避免重复加载相同纹理
标签:WebGL