【笔记】《WebGL编程指南》学习-第5章颜色与纹理(3-使用多幅纹理))

目标:
重叠粘贴两张不同的纹理图像
结果:
这里写图片描述


在本章之前说过,WebGL 可以同时处理多幅纹理,纹理单元就是为了这个目的而设计的。之前的示例程序都只用到了一幅纹理,也只用到了一个纹理单元。这一节的示例程序在矩形上重叠粘贴两幅纹理图像。

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

这里写图片描述

最关键的是,你需要对每一幅纹理分别进行前一节所述的将纹理图像映射到图形表面的操作,一次来将多张纹理图像同时贴到图形上去。


MultiTexture.js

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

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

function main() {
    //获取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //获取WebGL绘图上下文
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log("Failed to get the rendering context for WebGL");
        return;
    }

    //初始化着色器
    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
        console.log("Failed to initialize shaders.");
        return;
    }

    //设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }

    //指定清空<canvas>颜色
    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();
    if(!vertexTexCoordBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //将缓冲区对象保存到目标上
    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');
    if(a_Position < 0){
        console.log("Failed to get the storage location of a_Position");
        return -1;
    }

    //将缓冲区对象分配给a_Postion变量
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);

    //连接a_Postion变量与分配给它的缓冲区对象
    gl.enableVertexAttribArray(a_Position);

    //将纹理坐标分配给 a_TexCoord 并开启它
    var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
    if(a_TexCoord < 0){
        console.log("Failed to get the storage location of a_TexCoord");
        return -1;
    }

    gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);

    gl.enableVertexAttribArray(a_TexCoord);

    return n;
}

function initTextures(gl, n) {
    //创建纹理对象
    var texture0 = gl.createTexture();
    var texture1 = gl.createTexture();
    if (!texture0) {
        console.log('Failed to create the texture0 object');
        return false;
    }
    if (!texture1) {
        console.log('Failed to create the texture1   object');
        return false;
    }

    //获取 u_Sampler1 和 u_Sampler2的存储位置
    var u_Sampler1 = gl.getUniformLocation(gl.program, "u_Sampler1");
    if (!u_Sampler1) {
        console.log('Failed to get the storage location of u_Sampler1');
        return false;
    }

    var u_Sampler0 = gl.getUniformLocation(gl.program, "u_Sampler0");
    if (!u_Sampler0) {
        console.log('Failed to get the storage location of u_Sampler0');
        return false;
    }

    //创建一个image 对象
    var image0 = new Image();
    var image1 = new Image();
    if (!image0) {
        console.log('Failed to create the image0 object');
        return false;
    }
    if (!image1) {
        console.log('Failed to create the image1 object');
        return false;
    }
    //注册图像加载事件的响应函数
    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);  //对纹理图像进行Y轴反转
    //激活纹理
    if(texUnit == 0){
        gl.activeTexture(gl.TEXTURE0);
        g_texUnit0 = true;
    }else{
        gl.activeTexture(gl.TEXTURE1);
        g_texUnit1 = true;
    }
    //向 target 绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);

    //配置纹理参数
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    //配置纹理图像
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);

    //将0号纹理传递给着色器
    gl.uniform1i(u_Sampler, texUnit);

    gl.clear(gl.COLOR_BUFFER_BIT);

    if(g_texUnit0 && g_texUnit1){
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
    }

}

首先,让我们来看一下偏远着色器。在 TextureQuad.js 中片元着色器只用到了一个纹理,所以也就只准备了一个 uniform 变量 u_Sampler。然而,本例中的片元着色器用到了两个纹理,那么就需要定义两个 uniform 变量,如下所示:

    'uniform sampler2D u_Sampler0;'+
    'uniform sampler2D u_Sampler1;'+

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

'vec4 color0 = texture2D(u_Sampler0, v_TexCoord);'+
    'vec4 color1 = texture2D(u_Sampler1, v_TexCoord);'+
    'gl_FragColor = color0 * color1;'+

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

这里写图片描述

虽然示例程序用到了两个纹理图像,但 InitVertexBuffers()函数却没有改变,因为矩形顶点在两幅纹理图像上的纹理坐标是完全相同的。

相比之下,initTextures()函数被修改了。我们现在用到了两幅纹理,所以需要重复两次处理纹理图像的步骤。

创建两个纹理对象,变量名的后缀对应着纹理单元的编号。此处 uniform 变量 与 Image 对象也采用了类似的命名方式。

注册事件响应函数 loadTexture()的过程与 TextureQuad.js 类似,注意最后一个参数是纹理单元编号。

    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';

示例程序中为了处理两幅纹理,我们还修改了 loadTexture()函数。修改后该函数的核心部分代码如下所示:

var g_texUnit0 = false, g_texUnit1 = false;
function loadTexture(gl, n, texture, u_Sampler, image, texUnit){
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);  //对纹理图像进行Y轴反转
    //激活纹理
    if(texUnit == 0){
        gl.activeTexture(gl.TEXTURE0);
        g_texUnit0 = true;
    }else{
        gl.activeTexture(gl.TEXTURE1);
        g_texUnit1 = true;
    }
    //向 target 绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);

    //配置纹理参数
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    //配置纹理图像
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);

    //将0号纹理传递给着色器
    gl.uniform1i(u_Sampler, texUnit);

    gl.clear(gl.COLOR_BUFFER_BIT);

    if(g_texUnit0 && g_texUnit1){
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
    }

}

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

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

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

这里写图片描述

loadTexture()函数的最后通过检查 g_texUnit0 和 h_texUnit1 变量来判断两幅图像是否全部完全加载了。如果是,就开始执行顶点着色器,在图形上重叠着回执出两层纹理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值