资讯> 正文

一起学 WebGL:纹理对象学习

2023-06-26 16:09:34 来源:前端西瓜哥

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

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


(相关资料图)

纹理对象创建和绑定

先创建纹理对象:

const texture = gl.createTexture(); // 创建纹理对象

然后绑定到纹理单元:

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);
纹理单元

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

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

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

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

gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) // 通常是 16

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

gl.activeTexture(gl.TEXTURE1); // 开启 1 号纹理单元

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

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

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

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

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

纯色纹理

画个纯纯的红色纹理。

// 红色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)的红色颜色值。

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

完整代码:

/** @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。

多个色块纹理

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

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。

图片纹理

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

// 将纹理图像分配给纹理对象gl.texImage2D(  gl.TEXTURE_2D,  0, // 细节级别  gl.RGB,  gl.RGB,  gl.UNSIGNED_BYTE,  img // Image 实例);
结尾

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

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

关键词:

上一篇:每日看点!日本核污染水排海隧道完工

下一篇:最后一页

  • 资讯
  • 业界
  • 行情