一起学 WebGL:纹理对象学习

2023-08-18 13:23:59 浏览数 (2)

大家好,我是前端西瓜哥,今天我们来了解 WebGL 的纹理对象(Texture)

纹理对象,是将像素(texels)以数组方式传给 GPU 的对象,常见场景是贴图,就是将图片的数据应用到 3D 物体上。

纹理对象创建和绑定

先创建纹理对象:

代码语言:javascript复制
const texture = gl.createTexture(); // 创建纹理对象

然后绑定到纹理单元:

代码语言:javascript复制
gl.bindTexture(gl.TEXTURE_2D, texture); // 将纹理对象绑定上去

填充方式

纹理是要贴到画布的某个区域上的,并不一定刚好设置一下填充方式。

纹理比绘制区域大,就要做缩放;纹理比绘制区域小,就要做放大;纹理没能完全填充绘制区域,就要在水平和垂直方向进行填充。

这些场景都需要对应设置不同的策略。

具体讲解看我的这篇文章: 《一起学 WebGL:绘制图片

代码语言:javascript复制
// 缩小和放大都都使用 “最近点采样”
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

纹理单元

WebGL 支持设置多个纹理单元(Texture Unit),即我们可以将多个图片放到多个单元中,然后进行切换。

就好像手里拿着不同的盖章,想印哪种图案就掏出哪个盖上去。

纹理单元是有上限的,至少要支持 8 个,主流浏览器一般支持 16 个。

具体支持几个,可通过下面代码获得。

代码语言:javascript复制
gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) // 通常是 16

默认使用 0 号纹理单元,可通过下面这一行代码来切换纹理单元:

代码语言:javascript复制
gl.activeTexture(gl.TEXTURE1); // 开启 1 号纹理单元

注意这个要 在将纹理对象绑定纹理单元之前 执行。

最后我们需要设置一下我们的纹理采样器选择使用哪个纹理单元:

代码语言:javascript复制
gl.uniform1i(u_Sampler, 0); // 开启 0 号纹理对象

不主动调用这个方法,默认会使用 0 号纹理单元。

切换纹理单元是有一定的性能代价的,不建议你在短时间内不断地切换纹理单元。简单的渲染场景可忽略不计。

纯色纹理

画个纯纯的红色纹理。

代码语言:javascript复制
// 红色
const data = new Uint8Array([
  255, 0, 0
]);

gl.texImage2D(
  gl.TEXTURE_2D, // 纹理目标,这里是二维纹理
  0, // 细节级别,0 表示最高级别
  gl.RGB, // 纹理内部格式,还支持其他的比如 gl.RGBA、LUMINANCE(流明)
  1, // 宽(宽高的单位为像素,且为 2 的 n 次幂)
  1, // 高
  0, // 是否描边。必须为 0(但 opengl 支持)
  gl.RGB, // 源图像数据格式
  gl.UNSIGNED_BYTE, // 纹素(单个像素)数据类型
  data // 数据数组,一个个像素点
);

主要注意的是,gl.texImage2D() 方法支持函数重载,有多种传入的参数的方式,注意分辨。具体看 官方文档。

这里选择使用 gl.RGB 格式,设置了一个 (255, 0, 0) 的红色颜色值。

最后我们成功画出一个纯红色块。

完整代码:

代码语言:javascript复制
/** @type {HTMLCanvasElement} */
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');

const vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
 gl_Position = a_Position;
 v_TexCoord = a_TexCoord;
}
`;

const fragmentShaderSrc = `
precision highp float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
  gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
`;

// 创建程序对象
createProgram(gl);

// 顶点坐标,纹理坐标
const verticesTexCoords = new Float32Array([
  // 左上点。
  // 左边两个是顶点;右边两个是纹理
  -0.5, 0.5, 0.0, 1,
  // 左下
  -0.5, -0.5, 0.0, 0.0,
  // 右上
  0.5, 0.5, 1, 1,
  // 右下
  0.5, -0.5, 1, 0.0,
]);
const FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;

// 创建缓存对象
const verticesTexBuffer = gl.createBuffer();
// 绑定缓存对象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, verticesTexBuffer);
// 向缓存区写入数据
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);

// 获取 a_Position 变量地址
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
// 将缓冲区对象分配给 a_Position 变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
// 允许访问缓存区
gl.enableVertexAttribArray(a_Position);

// 传入纹理坐标位置信息
const a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord);

/***** 纹理对象 *****/
const texture = gl.createTexture(); // 创建纹理对象
const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler'); // 获取 u_Sampler 地址

// 记载图片

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻转纹路图像的 y 轴
gl.activeTexture(gl.TEXTURE0); // 开启 0 号纹理单元
gl.bindTexture(gl.TEXTURE_2D, texture); // 将我们的纹理对象绑定上去

// 配置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

// 【----关键代码---】配置纹理图像
const data = new Uint8Array([255, 0, 0, 0, 255, 255, 0, 255, 0, 0, 255, 0]);
gl.texImage2D(
  gl.TEXTURE_2D, // 纹理目标
  0, // 细节级别
  gl.RGB, // 纹理内部格式
  1,
  1,
  0,
  gl.RGB, // 源图像数据格式
  gl.UNSIGNED_BYTE, // 纹素数据类型
  data // 数据
);

gl.uniform1i(u_Sampler, 0); // 开启 0 号纹理对象

/****** 绘制 ******/
// 清空画布,并指定颜色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制矩形,这里提供了 4 个点
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

/**** 封装的方法 ****/

function createProgram(gl) {
  /**** 渲染器生成处理 ****/
  // 创建顶点渲染器
  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vertexShader, vertexShaderSrc);
  gl.compileShader(vertexShader);
  // 创建片元渲染器
  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fragmentShader, fragmentShaderSrc);
  gl.compileShader(fragmentShader);
  // 程序对象
  const program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  gl.useProgram(program);
  gl.program = program;
}

线上 demo:

https://codesandbox.io/s/1hvp4x?file=/index.js

多个色块纹理

也可以同时设置多个色块。

代码语言:javascript复制
const data = new Uint8Array([
  255, 0, 0, 255,   // 红色
  255, 255, 0, 255, // 黄色
  0, 0, 255, 255,  // 蓝色
  0, 255, 0, 255,  // 绿色
]);

gl.texImage2D(
  gl.TEXTURE_2D, // 纹理目标
  0, // 细节级别
  gl.RGBA, // 纹理内部格式
  2,
  2,
  0,
  gl.RGBA, // 源图像数据格式
  gl.UNSIGNED_BYTE, // 纹素数据类型
  data // 数据
);

创建了 2x2 4个像素大小的纹理,并制定了这个 4 个像素点的颜色,然后被放大绘制到指定区域上。

线上演示 demo:

https://codesandbox.io/s/7436cs?file=/index.js

图片纹理

图片纹理,需要加载玩图片,将图片对象绑定到纹理对象上。

代码语言:javascript复制
// 将纹理图像分配给纹理对象
gl.texImage2D(
  gl.TEXTURE_2D,
  0, // 细节级别
  gl.RGB,
  gl.RGB,
  gl.UNSIGNED_BYTE,
  img // Image 实例
);

具体看我的这篇文章:

一起学 WebGL:绘制图片

结尾

纹理对象是很常用的一个对象,用于指定区域要填充的像素。

常见的是加载图片,把图片贴到三维的一个面上。也可以自己指定像素值。

我是前端西瓜哥,欢迎关注我,学习更多 WebGL 知识。


相关阅读,

一起学 WebGL:绘制立方体

一起学 WebGL:可视空间之透视矩阵

一起学 WebGL:可视空间之正射投影

一起学 WebGL:感受三维世界之视图矩阵

一起学 WebGL:三角形加上渐变色

一起学 WebGL:复合矩阵

一起学 WebGL:绘制图片

一起学 WebGL:图元的类型

一起学 WebGL:绘制三角形

一起学 WebGL:改变点的颜色

一起学 WebGL:动态绘制点

一起学 WebGL:绘制一个点

一起学 WebGL:坐标系

0 人点赞