WebGL第二十八课:多贴图实战| 8月更文挑战

639 阅读4分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

本文标题:WebGL第二十八课:多贴图实战| 8月更文挑战

友情提示

这篇文章是WebGL课程专栏的第28篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。

本课代码直接跳转获取:二十八课代码

引子

在前面两次课,讲到了有关贴图的东西。

问题就是,如果我们有多个图,都想传到gl里,这时候怎么写呢?

我们在fragment_shader里正确获取到我们想要的图呢?

本次课就来解决这些问题。

代码分离

由于在html里直接写js代码,看起来十分的混乱,所以从这里开始,html 里只包含页面的东西和两个shader

其余的代码放到一个单独的文件里:logic.js

大概类似这样:

index.html     // 页面
logic.js       // 主逻辑
models_tri.js  // 模型代码
images/        // 放图片

准备两个图

由于我们要画多个图,所以你至少准备两张图,我准备了如下两张图:

images/eye.png
images/funny-cat.jpg

先拉取图片

因为图片的成功加载,是所有后续逻辑的依赖,所以,图片加载这里,我们要考虑到:

所有图片全部加载完成之后,再调用后面的函数。

所以,我们一开始就要记录一个图片加载的进度,然后每一个图片加载完成之后,进度就加1,当进度和图片的数量相等时,调用后面的函数。代码这样:

var imageUrlList = ["images/eye.png", "images/funny-cat.jpg",]; // 全局的变量,存储了想要加载的图片
var images_progress = 0; // 当前进度:0张
var textureList = [];    // 创建的texture数组

function CreateTextureAndLoadImage() {
    // 在 WebGL 里创建一个 texture
    if (images_progress == imageUrlList.length) { // 当前进度已经完成
        gl_draw(); // 绘制的函数
        return;
    }
    let texture = gl.createTexture();
    textureList.push(texture);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    let image = new Image();
    image.src = imageUrlList[images_progress];
    console.log(image.src);
    image.addEventListener('load', function () {
        gl.activeTexture(gl.TEXTURE0 + images_progress); // 激活当前的gl图片下标
        gl.bindTexture(gl.TEXTURE_2D, textureList[images_progress]);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
            gl.generateMipmap(gl.TEXTURE_2D);
        } else {
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        }
        images_progress++;
        CreateTextureAndLoadImage();
    });
}

fragment_shader 里体现两张图片

我们在前面的课程中知道,fragment_shader 中可以用 一个uniform变量来选取想要的图片,变量类型是 sampler2D:

   uniform sampler2D u_funny_cat; // 有趣的猫的图片

我们如法炮制:

  uniform sampler2D u_funny_cat; // 有趣的猫的图片
  uniform sampler2D u_eye; // 眼睛图片

js 里获取 u_eye的引用然后设置

var u_EyeLocation = gl.getUniformLocation(program, "u_eye");

gl.activeTexture(gl.TEXTURE0); // 我们要操作gl中的下标0
gl.bindTexture(gl.TEXTURE_2D, textureList[0]); // 我们将图片0绑定到下标0的位置
gl.uniform1i(u_EyeLocation, 0); // 我们将 u_eye,设置成下标0的图片

两张图片融合:相乘

由于在fragment_shader中,我们有了两张图片,我们可以对着两张图片,根据我们的UV信息,进行两次采样:

      vec4 sample_color_cat = texture2D(u_funny_cat, uv);
      vec4 sample_color_eye = texture2D(u_eye, uv);

问题就来了,然后最终的颜色,到底怎么搞呢?

这就非常灵活了,我们可以直接将两个图片的颜色相乘:

      vec4 sample_color_cat = texture2D(u_funny_cat, uv);
      vec4 sample_color_eye = texture2D(u_eye, uv);
      gl_FragColor = vec4(sample_color_cat.xyz * sample_color_eye.xyz, 1.0);

这种效果如下:

image.png

这个效果看上去没什么奇特的,不过有时候,相乘还是有特殊的用处的,这个暂时不说。

两张图片融合:线性组合

我们知道,

对cat图片采用的结果是一个vec3(第四维我们先不管)

对eye图片采样的结果也是一个vec3

既然都是向量,那我们用线性组合来搞一搞:

color=tcat+(1t)eyecolor = t * cat + (1-t) * eye

这是一个经典的插值,t的不同,会影响最终的颜色:

  • 如果t趋近于1,那么就是cat的颜色
  • 如果t趋近于0,那么就是eye的颜色

代码如下:

uniform float u_t;
//
gl_FragColor = vec4(sample_color_cat.xyz * u_t + (1.0 - u_t) * sample_color_eye.xyz, 1.0);

效果如下:

juejin28.gif

我们用一个滑竿来调解这个u_t的值,可以清晰的看见两张图片的融合关系。

两张图片融合:混乱

两张图片的信息,在这里了,你想怎么玩就怎么玩,甚至于可以,用一张图片的采样到的颜色信息,当做另一张图片的UV信息:

  vec4 sample_color_cat = texture2D(u_funny_cat, uv);
  vec4 sample_color_eye = texture2D(u_eye, sample_color_cat.xy);
  gl_FragColor = vec4(sample_color_cat.xyz * u_t + (1.0 - u_t) * sample_color_eye.xyz, 1.0);

我们看到,采样eye图片的时候,我们竟然用cat图片的颜色信息的xy,来当做UV。。。

这样是什么效果呢:

28-2.gif

怎么搞的像描边一样,,这完全是一个意外的效果,你选的两张图,并不一定能出现这个效果,就是玩玩而已。 如果你的效果,也和描边一样,那说明我这个东西,还能应用到描边里去。。。。。。




  正文结束,下面是答疑

小能能说:我去看看有没有别的融合办法,玩一玩。

  • 答:肯定行啊。