2023-06-26 17:53:53 来源 : 前端西瓜哥
大家好,我是前端西瓜哥,今天我们来了解 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 实例);
结尾纹理对象是很常用的一个对象,用于指定区域要填充的像素。
常见的是加载图片,把图片贴到三维的一个面上。也可以自己指定像素值。
标签: