WebGL 同时使用多幅纹理

目录

前言

​编辑

示例代码

颜色矢量的分量乘法来计算两个纹素最终的片元颜色

注册事件响应函数:loadTexture(),最后一个参数是纹理单元编号。

请求浏览器加载图像:

配置纹理:loadTexture()函数。该函数的核心部分代码如下所示: 

需要注意的是


前言

WebGL可以同时处理多幅纹理,纹理单元就是为了这个目的而设计的。本例程序在矩形上重叠粘贴两幅纹理图像。下图显示了本例运行效果,两张纹理图像在矩形上的混合效果如下。

下图中的两幅图分别显示了示例程序用到的两幅纹理图像。为了说明WebGL具有处理不同纹理图像格式的能力,本例故意使用了两种不同格式的图像(左侧jpg,右侧gif)。 

示例代码

// 顶点着色器
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec2 a_TexCoord;\n' +
  'varying vec2 v_TexCoord;\n' +
  'void main() {\n' +
  '  gl_Position = a_Position;\n' +
  '  v_TexCoord = a_TexCoord;\n' +
  '}\n';

// 片元着色器
var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'uniform sampler2D u_Sampler0;\n' +
  'uniform sampler2D u_Sampler1;\n' +
  'varying vec2 v_TexCoord;\n' +
  'void main() {\n' +
  '  vec4 color0 = texture2D(u_Sampler0, v_TexCoord);\n' +
  '  vec4 color1 = texture2D(u_Sampler1, v_TexCoord);\n' +
  '  gl_FragColor = color0 * color1;\n' +
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }
  var n = initVertexBuffers(gl);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  // 配置纹理
  if (!initTextures(gl, n)) {
    console.log('Failed to intialize the texture.');
    return;
  }
}

function initVertexBuffers(gl) {
  var verticesTexCoords = new Float32Array([
    // 顶点坐标和纹理坐标
    -0.5,  0.5,   0.0, 1.0,
    -0.5, -0.5,   0.0, 0.0,
     0.5,  0.5,   1.0, 1.0,
     0.5, -0.5,   1.0, 0.0,
  ]);
  var n = 4; // 顶点数量
  var vertexTexCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
  var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
  gl.enableVertexAttribArray(a_Position);  // Enable the assignment of the buffer object
  var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
  gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
  gl.enableVertexAttribArray(a_TexCoord);  // Enable the buffer assignment
  return n;
}

function initTextures(gl, n) {
  // 创建纹理缓冲区对象
  var texture0 = gl.createTexture(); 
  var texture1 = gl.createTexture();
  // 获取u_Sampler1和u_Sampler2的存储位置
  var u_Sampler0 = gl.getUniformLocation(gl.program, 'u_Sampler0');
  var u_Sampler1 = gl.getUniformLocation(gl.program, 'u_Sampler1');
  var image0 = new Image();
  var image1 = new Image();
  // 注册事件响应函数,在图像加载完成后调用
  image0.onload = function(){ loadTexture(gl, n, texture0, u_Sampler0, image0, 0); };
  image1.onload = function(){ loadTexture(gl, n, texture1, u_Sampler1, image1, 1); };
  // 告诉浏览器开始加载图像
  image0.src = '../resources/sky.jpg';
  image1.src = '../resources/circle.gif';
  return true;
}
// 标记纹理单元是否已经就绪
var g_texUnit0 = false, g_texUnit1 = false; 
function loadTexture(gl, n, texture, u_Sampler, image, texUnit) {
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);// 沿反转图像
  // 激活纹理单元
  if (texUnit == 0) {
    gl.activeTexture(gl.TEXTURE0);
    g_texUnit0 = true;
  } else {
    gl.activeTexture(gl.TEXTURE1);
    g_texUnit1 = true;
  }
  // 绑定纹理对象到目标上(先绑定到纹理单元后指定纹理类型绑定到目标上)
  gl.bindTexture(gl.TEXTURE_2D, texture);   
  // 配置纹理参数
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  // 设置纹理图像
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  // 将纹理单元编号传递给取样器
  gl.uniform1i(u_Sampler, texUnit);   
  gl.clear(gl.COLOR_BUFFER_BIT);
  // 图像加载是异步的,我们无法预测哪一张纹理被加载完成,只有两幅都加载好,程序才开始绘制
  if (g_texUnit0 && g_texUnit1) {
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);  
  }
}

首先,让我们来看一下片元着色器,本例用到了两幅纹理,那么就需要两个定义uniform变量

然后,在片元着色器的main()函数中,我们从两个纹理中取出颜色,分别存储在变量color0和color1中

颜色矢量的分量乘法来计算两个纹素最终的片元颜色

使用两个纹素来计算最终的片元颜色(gl_FragColor)有多种可能的方法。示例程序使用的是颜色矢量的分量乘法——两个矢量中对应的分量相乘作为新矢量的分量,如下图所示。这很好理解。在GLSL ES中,只需要将两个vec4变量简单相乘一下就可以达到目的。 

第55、56行创建了两个纹理缓冲区对象,变量名的后缀(texture0中的 “0” 和 texture1中的 “1”)对应对应着纹理单元的编号(纹理单元0和纹理单元1),此外uniform变量(第68,69行)与image对象(第70,71行)也采用了类似的命名方式

注册事件响应函数:loadTexture(),最后一个参数是纹理单元编号。

请求浏览器加载图像:

配置纹理:loadTexture()函数。该函数的核心部分代码如下所示: 

需要注意的是

在loadTexture()函数中,我们无法预测哪一幅纹理图像先被加载完成,因为加载的过程是异步进行的。只有当两幅纹理图像都完成加载时,程序才会开始绘图。为此,我们定义了两个全局变量g_texUnit0和g_texUnit1来指示对应的纹理是否加载完成(第81行)。 

这些变量都被初始化为false(第81行)。当任意一幅纹理加载完成时,就触发onload事件并调用响应函数loadTexture()。该函数首先根据纹理单元编号0或1来将g_texUnit0或g_texUnit1赋值为true(第85行)。换句话说,如果触发本次onload事件的纹理的编号是0,那么0号纹理单元就被激活了,并将g_texUnit0设置为true;如果是1,那么1号纹理单元被激活了,并将g_texUnit0设置为ture

接着,纹理单元编号texUnit被赋给了uniform变量(第99行)。注意texUnit是通过gl.uniform1i()方法传入着色器的。在两幅纹理图像都完成加载后,WebGL系统内部的状态就如下图所示。

loadTexture()函数的最后通过检查g_texUnit0和g_texUnit1变量来判断两幅图像是否全部完成加载了(第103行)。如果是,就开始执行顶点着色器,在图形上重叠着绘制出两层纹理,如本文第一张图所示。 

猜你喜欢

转载自blog.csdn.net/dabaooooq/article/details/132776421