WebGL切换着色器 绘制不同物体

WebGL切换着色器 绘制不同物体

1、为何切换着色器

WebGL绘制不同的物体需要使用不同的着色器,每个着色器中可能有非常负责的逻辑以实现各种不同的效果。我们可以准备多个着色器,然后根据需要来切换使用它们。

2、如何实现切换着色器

为了切换着色器,首先创建多个着色器程序对象,然后在绘制之前选择使用的程序对象(使用gl.useProgram()函数来进行切换)。
下面是本次实例程序(实例程序绘制了两个立方体:一个单色立方体,一个纹理立方体)的流程步骤:

1. 准备绘制单色立方体的着色器

2. 准备绘制纹理立方体的着色器

3. 调用createProgram()函数,利用第1步创建出的着色器,创建单色立方体着色器程序对象

4. 调用createProgram()函数,利用第2步创建出的着色器,创建纹理立方体着色器程序对象

5. 调用gl.useProgram()函数,指定使用第3步创建的绘制单色立方体的着色器程序对象。

6. 通过缓冲区对象向着色器中传入attribute变量并开启。

7. 绘制单色立方体。

8. 调用gl.useProgram()函数,指定使用第4步创建的绘制纹理立方体的着色器程序对象。

9. 通过缓冲区对象向着色器传入attribute变量并开启。

10. 绘制纹理立方体。

3、实例程序

效果图:WebGL切换着色器 绘制不同物体_第1张图片

实例代码:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>切换着色器-绘制不同立方体</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
    Please use the browser supporting "canvas"
</canvas>

<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="../lib/cuon-matrix.js"></script>
<script>
    // canvas全屏
    var canvas = document.getElementById('webgl');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    // 顶点着色器 绘制单色立方体
    var SOLID_VSHADER_SOURCE =
        "attribute vec4 a_Position;\n" +
        "attribute vec4 a_Normal;\n" + // 法向量
        "uniform mat4 u_MvpMatrix;\n" +
        "uniform mat4 u_NormalMatrix;\n" +
        "varying vec4 v_Color;\n" +
        "void main(){\n" +
        "   vec3 lightDirection = vec3(0.0,0.0, 1.0);\n" +//设置灯光的坐标位置(世界坐标下)
        "   vec4 color = vec4(0.0, 1.0, 1.0, 1.0);\n" + //设置立方体的颜色
        "   gl_Position = u_MvpMatrix * a_Position;\n" +
        "   vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n" +
        "   float nDotL = max(dot(normal, lightDirection), 0.0);\n" +
        "   v_Color = vec4(color.rgb * nDotL, color.a);\n" +
        "}\n";

    // 片元着色器 绘制单色立方体
    var SOLID_FSHADER_SOURCE =
        "#ifdef GL_ES\n" +
        "precision mediump float;\n" +
        "#endif\n" +
        "varying vec4 v_Color;\n" +
        "void main(){\n" +
        "   gl_FragColor = v_Color;\n" +
        "}\n";

    // 顶点着色器 绘制纹理立方体
    var TEXTURE_VSHADER_SOURCE =
        "attribute vec4 a_Position;\n" +
        "attribute vec4 a_Normal;\n" +
        "attribute vec2 a_TexCoord;\n" +
        "uniform mat4 u_MvpMatrix;\n" +
        "uniform mat4 u_NormalMatrix;\n" +
        "varying float v_NdotL;\n" +
        "varying vec2 v_TexCoord;\n" +
        "void main(){\n" +
        "   vec3 lightDirection = vec3(0.0, 0.0, 1.0);\n" +
        "   gl_Position = u_MvpMatrix * a_Position;\n" +
        "   vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n" +
        "   v_NdotL = max(dot(normal, lightDirection), 0.0);\n" +
        "   v_TexCoord = a_TexCoord;\n" +
        "}\n";

    // 片元着色器 绘制纹理立方体
    var TEXTURE_FSHADER_SOURCE =
        "#ifdef GL_ES\n" +
        "precision mediump float;\n" +
        "#endif\n" +
        "uniform sampler2D u_Sampler;\n" +
        "varying vec2 v_TexCoord;\n" +
        "varying float v_NdotL;\n" +
        "void main(){\n" +
        "   vec4 color = texture2D(u_Sampler, v_TexCoord);\n" +
        "   gl_FragColor = vec4(color.rgb * v_NdotL, color.a);\n" +
        "}\n";

    // 主函数
    function main() {
        // 获取WebGL绘图上下文
        var gl = getWebGLContext(canvas);
        if(!gl) {
            console.log('获取WebGL上下文失败');
            return;
        }

        // 初始化两个程序着色器
        var solidProgram = createProgram(gl,SOLID_VSHADER_SOURCE, SOLID_FSHADER_SOURCE); // 纯色立方体
        var textureProgram = createProgram(gl,TEXTURE_VSHADER_SOURCE,TEXTURE_FSHADER_SOURCE); // 纹理立方体
        if(!solidProgram || !textureProgram) {
            console.log('创建程序对象失败');
            return;
        }

        // 获取绘制单色立方体着色器的变量位置
        solidProgram.a_Position = gl.getAttribLocation(solidProgram, "a_Position");
        solidProgram.a_Normal = gl.getAttribLocation(solidProgram, "a_Normal");
        solidProgram.u_MvpMatrix = gl.getUniformLocation(solidProgram, "u_MvpMatrix");
        solidProgram.u_NormalMatrix = gl.getUniformLocation(solidProgram, "u_NormalMatrix");
        if(solidProgram.a_Position < 0 || solidProgram.a_Normal < 0 || !solidProgram.u_MvpMatrix || !solidProgram.u_NormalMatrix) {
            console.log('获取单色立方体相关变量存储位置失败');
            return;
        }

        // 获取绘制纹理立方体着色器的变量位置
        textureProgram.a_Position = gl.getAttribLocation(textureProgram, "a_Position");
        textureProgram.a_Normal = gl.getAttribLocation(textureProgram, "a_Normal");
        textureProgram.a_TexCoord = gl.getAttribLocation(textureProgram, "a_TexCoord");
        textureProgram.u_MvpMatrix = gl.getUniformLocation(textureProgram, "u_MvpMatrix");
        textureProgram.u_NormalMatrix = gl.getUniformLocation(textureProgram, "u_NormalMatrix");
        textureProgram.u_Sampler = gl.getUniformLocation(textureProgram, "u_Sampler");

        if(textureProgram.a_Position < 0 || textureProgram.a_Normal < 0 || textureProgram.a_TexCoord < 0 || !textureProgram.u_MvpMatrix || !textureProgram.u_NormalMatrix || !textureProgram.u_Sampler) {
            console.log('获取纹理立方体相关变量存储位置失败');
            return;
        }

        // 设置单色立方体顶点信息 存入缓冲区
        var cube = initVertexBuffers(gl);

        // 设置纹理立方体的纹理数据 存入缓冲区
        var texture = initTextures(gl, textureProgram);
        if(!texture) {
            console.log('无法获取到纹理');
            return;
        }

        //开启隐藏面消除功能,并设置背景色
        gl.enable(gl.DEPTH_TEST);
        gl.clearColor(0.0, 0.0, 0.0, 1.0);

        // 计算视图投影矩阵
        var viewProjectMatrix = new Matrix4();
        viewProjectMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100.0);
        viewProjectMatrix.lookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

        // 开始绘制立方体
        var currentAngle = 0.0; // 当前立方体的角度

        //设置一个定时绘制的函数
        var tick = function() {   // Start drawing
            currentAngle = animate(currentAngle); // 更新角度

            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            // 绘制单色立方体
            drawSolidCube(gl, solidProgram, cube, -2.0, currentAngle, viewProjectMatrix);
            // 绘制纹理立方体
            drawTexCube(gl, textureProgram, cube, texture, 2.0, currentAngle, viewProjectMatrix);

            requestAnimationFrame(tick);
        };
        tick();
    }

    function drawTexCube(gl, program, obj, texture, x, angle, viewProjectMatrix) {
        gl.useProgram(program);

        // 分配缓存对象并开启赋值
        initAttributeVariable(gl, program.a_Position, obj.vertexBuffer); //顶点坐标
        initAttributeVariable(gl, program.a_Normal, obj.normalBuffer); //法向量
        initAttributeVariable(gl, program.a_TexCoord, obj.texCoordBuffer); //纹理坐标
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.indexBuffer);

        //设置好纹理对象,开启使用0号的纹理
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, texture);

        //绘制立方体
        drawCube(gl, program, obj, x, angle, viewProjectMatrix)
    }

    function drawSolidCube(gl, program, obj, x, angle, viewProjectMatrix) {
        gl.useProgram(program);

        //分配缓冲区对象并启用赋值
        initAttributeVariable(gl, program.a_Position, obj.vertexBuffer); //顶点坐标
        initAttributeVariable(gl, program.a_Normal, obj.normalBuffer); //法向量
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.indexBuffer); //绑定索引

        //绘制立方体
        drawCube(gl, program, obj, x, angle, viewProjectMatrix);
    }

    // 声明绘制需要变换矩阵变量
    var g_modelMatrix = new Matrix4();
    var g_mvpMatrix = new Matrix4();
    var g_normalMatrix = new Matrix4();

    function drawCube(gl, program, obj, x, angle, viewProjectMatrix) {
        // 计算模型矩阵
        g_modelMatrix.setTranslate(x, 0.0, 0.0);
        g_modelMatrix.rotate(20.0, 1.0, 0.0, 0.0);
        g_modelMatrix.rotate(angle, 0.0, 1.0, 0.0);

        // 计算出法向量的方向 并赋值
        g_normalMatrix.setInverseOf(g_modelMatrix);
        g_normalMatrix.transpose();
        gl.uniformMatrix4fv(program.u_NormalMatrix, false, g_normalMatrix.elements);

        // 计算视图模型投影矩阵
        g_mvpMatrix.set(viewProjectMatrix);
        g_mvpMatrix.multiply(g_modelMatrix);
        gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);

        gl.drawElements(gl.TRIANGLES, obj.numIndices, obj.indexBuffer.type, 0);
    }

    function initAttributeVariable(gl, a_attribute, buffer) {
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0);
        gl.enableVertexAttribArray(a_attribute);
    }

    var angle_step = 30; // 每秒旋转角度
    var last = +new Date(); // 保存上次调用animate函数的时间
    function animate(angle) {
        var now = +new Date();
        var elapsed = now - last;
        last = now;
        var newAngle = angle + (angle_step * elapsed) / 1000.0;

        return newAngle % 360.0;
    }

    function initTextures(gl,program) {
        var texture = gl.createTexture();
        if(!texture) {
            console.log('无法创建纹理缓冲区');
            return null;
        }

        var img = new Image();
        img.onload = function() {
            // 将图形数据存入纹理对象
            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
            gl.activeTexture(gl.TEXTURE0);
            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, img);

            // 将纹理存入到第一个纹理缓冲区
            gl.useProgram(program);
            gl.uniform1i(program.u_Sampler, 0);

            gl.bindTexture(gl.TEXTURE_2D, null); // 解绑当前
        }

        img.src = '../images/wallpaper.jpg';

        return texture;
    }

    function initVertexBuffers(gl) {
        var vertices = new Float32Array([
            1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0,    // v0-v1-v2-v3 front
            1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0,    // v0-v3-v4-v5 right
            1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0,    // v0-v5-v6-v1 up
            -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0,    // v1-v6-v7-v2 left
            -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,    // v7-v4-v3-v2 down
            1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0     // v4-v7-v6-v5 back
        ]);

        var normals = new Float32Array([
            0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,     // v0-v1-v2-v3 front
            1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,     // v0-v3-v4-v5 right
            0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,     // v0-v5-v6-v1 up
            -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,     // v1-v6-v7-v2 left
            0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0,     // v7-v4-v3-v2 down
            0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0      // v4-v7-v6-v5 back
        ]);

        var texCoords = new Float32Array([
            1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,    // v0-v1-v2-v3 front
            0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0,    // v0-v3-v4-v5 right
            1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0,    // v0-v5-v6-v1 up
            1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,    // v1-v6-v7-v2 left
            0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,    // v7-v4-v3-v2 down
            0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0     // v4-v7-v6-v5 back
        ]);

        var indices = new Uint8Array([
            0, 1, 2, 0, 2, 3,    // front
            4, 5, 6, 4, 6, 7,    // right
            8, 9, 10, 8, 10, 11,    // up
            12, 13, 14, 12, 14, 15,    // left
            16, 17, 18, 16, 18, 19,    // down
            20, 21, 22, 20, 22, 23     // back
        ]);

        var obj = {}; // 使用对象返回多个缓冲区对象

        // 将顶点信息写入缓冲区
        obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
        obj.normalBuffer = initArrayBufferForLaterUse(gl, normals, 3, gl.FLOAT);
        obj.texCoordBuffer = initArrayBufferForLaterUse(gl, texCoords, 2, gl.FLOAT);
        obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
        if(!obj.vertexBuffer || !obj.normalBuffer || !obj.texCoordBuffer || !obj.indexBuffer){
            return null;
        }

        obj.numIndices = indices.length;

        //取消绑定焦点的数据
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

        return obj;
    }

    function initElementArrayBufferForLaterUse(gl, data, type) {
        var buffer = gl.createBuffer();
        if(!buffer) {
            console.log('无法创建缓冲区对象');
            return;
        }

        // 将数据写入缓冲区
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);

        buffer.type = type;

        return buffer;
    }

    function initArrayBufferForLaterUse(gl, data, num, type) {
        var buffer = gl.createBuffer();
        if(!buffer) {
            console.log('无法创建缓冲区对象');
            return;
        }

        // 将数据写入缓冲区对象
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

        // 保留信息
        buffer.num = num;
        buffer.type = type;

        return buffer;
    }

</script>
</body>
</html>

你可能感兴趣的:(WebGL,切换着色器,WebGL)