WebGL立方体的纹理贴图

634 阅读3分钟

立方体有六个面,六个面都需要添加纹理。同时为了显示立方体的三维效果,我们还需要旋转一下立方体。这里的难点还是在于顶点数据的构建。 代码如下所示:

1、顶点着色器代码

<script id="vertexShader" type="x-shader/x-vertex">
    //浮点数设置为中等精度
    precision mediump float;
    //从js中传递过来的顶点坐标数据
    attribute vec3 apos;
    //沿x轴旋转的4维矩阵
    uniform mat4 mx;
    //沿y轴旋转的4维矩阵
    uniform mat4 my;
    //js中传递过来的纹理坐标数据
    attribute vec2 inUV;
    //插值处理后传递到片元着色器的纹理坐标
    varying vec2 outUV;
    void main() {
        //两个旋转矩阵、顶点齐次坐标连乘
        gl_Position = mx*my*vec4(apos, 1);
        //插值处理
        outUV=inUV;
    }
</script>

2、片元着色器代码

在片元着色器中接收来自顶点着色器中的纹理坐标数据,以及js代码中传递过来的纹理数据,最终生成立方体的片元颜色。

<script id="fragmentShader" type="x-shader/x-fragment">
    // 所有float类型数据的精度是lowp
    precision mediump float;
    varying vec2 outUV;
    uniform sampler2D texture;
    void main() {
        gl_FragColor=texture2D(texture,outUV); 
    }
</script>

3、javascript代码

  • 获取WebGL上下文 在该函数中主要实现了如下功能:
    • 获取WebGL上下文
    • 初始化着色器
    • 顶点数据的构建及以着色器的交互
function init() {
    //通过getElementById()方法获取canvas画布
    const canvas = document.getElementById('webgl');
    //通过方法getContext()获取WebGL上下文
    const gl = canvas.getContext('webgl');
    //顶点着色器源码
    const vertexShaderSource = document.getElementById('vertexShader').innerText;
    //片元着色器源码
    const fragShaderSource = document.getElementById('fragmentShader').innerText;
    //初始化着色器
    const program = initShader(gl, vertexShaderSource, fragShaderSource);
    initBuffer(gl, program);
}
  • 立方体顶点数据的构建及与着色器的交互 该函数的主要功能可以分为以下几点
    • 获取着色器中的变量地址
    • 构建顶点数据和纹理坐标数据
    • 构建顶点索引数据
    • 创建顶点缓冲区
    • 创建索引缓冲区
    • 给着色器中的变量赋值
    • 开启深度测试
    • 加载纹理图片
    • WebGL渲染
 function initBuffer(gl, program) {
    //获取顶点着色器的位置变量apos
    const aposLocation = gl.getAttribLocation(program, 'apos');
    const inUV = gl.getAttribLocation(program, 'inUV');
    const texture = gl.getUniformLocation(program, 'texture');
    //允许数据传递
    gl.enableVertexAttribArray(aposLocation);
    //允许数据传递
    gl.enableVertexAttribArray(inUV);

    //创建立方体的顶点坐标数据及纹理坐标数据
    const data = new Float32Array([
        -0.5, -0.5, 0.5, 0.0, 0.0,
        0.5, -0.5, 0.5, 1.0, 0.0,
        0.5, 0.5, 0.5, 1.0, 1.0,
        -0.5, 0.5, 0.5, 0.0, 1.0,

        -0.5, 0.5, 0.5, 1.0, 1.0,
        -0.5, 0.5, -0.5, 0.0, 1.0,
        -0.5, -0.5, -0.5, 0.0, 0.0,
        -0.5, -0.5, 0.5, 1.0, 0.0,

        0.5, 0.5, 0.5, 0.0, 1.0,
        0.5, -0.5, 0.5, 0.0, 0.0,
        0.5, -0.5, -0.5, 1.0, 0.0,
        0.5, 0.5, -0.5, 1.0, 1.0,

        0.5, 0.5, -0.5, 0.0, 1.0,
        0.5, -0.5, -0.5, 0.0, 0.0,
        -0.5, -0.5, -0.5, 1.0, 0.0,
        -0.5, 0.5, -0.5, 1.0, 1.0,

        -0.5, 0.5, 0.5, 0.0, 1.0,
        0.5, 0.5, 0.5, 1.0, 1.0,
        0.5, 0.5, -0.5, 1.0, 0.0,
        -0.5, 0.5, -0.5, 0.0, 0.0,

        -0.5, -0.5, 0.5, 0.0, 0.0,
        -0.5, -0.5, -0.5, 0.0, 1.0,
        0.5, -0.5, -0.5, 1.0, 1.0,
        0.5, -0.5, 0.5, 1.0, 0.0
    ]);
    //构建顶点索引数据
    const indexdata = new Uint16Array([
        0, 1, 2, 0, 2, 3,
        4, 5, 6, 4, 6, 7,
        8, 9, 10, 8, 10, 11,
        12, 13, 14, 12, 14, 15,
        16, 17, 18, 16, 18, 19,
        20, 21, 22, 20, 22, 23
    ])
    //创建缓冲区对象
    const buffer = gl.createBuffer();
    //绑定缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    //顶点数组data数据传入缓冲区
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

    //创建索引缓冲区对象
    const indexBuffer = gl.createBuffer();
    //绑定索引缓冲区对象
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    // //顶点数组data数据传入索引缓冲区
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexdata, gl.STATIC_DRAW);

    ///缓冲区中的数据按照一定的规律传递给位置变量apos
    gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 4 * 5, 0);
    gl.vertexAttribPointer(inUV, 2, gl.FLOAT, false, 4 * 5, 4 * 3);
    // gl.vertexAttribPointer(acolor, 4, gl.FLOAT, false, 4 * 9, 4*3);
    gl.enable(gl.DEPTH_TEST);
    // gl.enable(gl.CULL_FACE);
    // render(gl, program, indexdata.length)
    initTexture(gl, '../images/1.jpg', texture, function () {
        render(gl, program, indexdata.length)
    })
}
  • 加载纹理图片 在该函数中主要是创建一个图片示例,最后将图片数据传递给GPU
    • 创建纹理图像缓冲区
    • 设置纹理图片上下反转
    • 激活0号纹理单元
    • 绑定纹理缓冲区
    • 设置纹素格式,jpg格式对应gl.RGB,将图片数据传递给 GPU
    • 设置纹理贴图填充方式
    • 全局变量赋值,纹理缓冲区单元TEXTURE0中的颜色数据传入片元着色器
function initTexture(gl, src, attribute, callback) {
    let img = new Image();
    img.onload = function () {
        let texture = gl.createTexture();//创建纹理图像缓冲区
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); //纹理图片上下反转
        gl.activeTexture(gl.TEXTURE0);//激活0号纹理单元TEXTURE0
        gl.bindTexture(gl.TEXTURE_2D, texture);//绑定纹理缓冲区
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
        gl.uniform1i(attribute, 0);
        callback && callback();
    };
    img.src = src;
}
  • WebGL渲染
function render(gl, program, count = 36) {
    //沿x,y轴旋转
    tranlate(gl, program);
    //设置清屏颜色为黑色。
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    // gl.drawArrays(gl.TRIANGLES, 0, count);
    gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, 0);
}

上面的js代码只例举了部分,其他的函数代码实现,在前面的例子中都有列举。 最终立方体的纹理贴图效果如下所示:

image.png