WEBGL学习【八】模型视图投影矩阵

<!--探讨WEBGL中不同图形的绘制方法:[待测试2017.11.6]-->
<!DOCTYPE HTML>
<html lang="en">
<head>
    <title>WEBGL高级编程----绘制三维场景(变换矩阵)</title>
    <meta charset="utf-8">
    <!--顶点着色器-->
    <script id="shader-vs" type="x-shader/x-vertex">
      attribute vec3 aVertexPosition;
      attribute vec4 aVertexColor;

      uniform mat4 uMVMatrix;
      uniform mat4 uPMatrix;

      varying vec4 vColor;

      void main() {
        vColor = aVertexColor;
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
      }

    </script>

    <!--片元着色器-->
    <script id="shader-fs" type="x-shader/x-fragment">
      precision mediump float;

      varying vec4 vColor;
      void main() {
        gl_FragColor = vColor;
      }

    </script>

    <!--引入我的库文件-->
    <script src="./lib/webgl-debug.js"></script>

    <script type="text/javascript">
        var gl;
        var canvas;
        var shaderProgram;

        //绘制桌子的场景模型
        //地板的顶点位置和索引
        var floorVertexPositionBuffer;
        var floorVertexIndexBuffer;

        //立方体的顶点位置和索引
        var cubeVertexPositionBuffer;
        var cubeVertexIndexBuffer;

        //我的模型 视图 投影矩阵
        var modelViewMatrix;
        var projectionMatrix;

        //模型视图的矩阵栈
        var modelViewMatrixStack;



        //创建我的上下文句柄
        function createGLContext(canvas) {
            var names = ["webgl", "experimental-webgl"];
            var context = null;
            for (var i = 0; i < names.length; i++) {
                try {
                    context = canvas.getContext(names[i]);
                } catch (e) {
                }
                if (context) {
                    break;
                }
            }
            if (context) {
                context.viewportWidth = canvas.width;
                context.viewportHeight = canvas.height;
            } else {
                alert("Failed to create WebGL context!");
            }
            return context;
        }

        //JavaScript代码中通过DOM加载着色器
        function loadShaderFromDOM(id) {
            var shaderScript = document.getElementById(id);

            // If we don't find an element with the specified id
            // we do an early exit
            if (!shaderScript) {
                return null;
            }

            // Loop through the children for the found DOM element and
            // build up the shader source code as a string
            var shaderSource = "";
            var currentChild = shaderScript.firstChild;
            while (currentChild) {
                if (currentChild.nodeType == 3) { // 3 corresponds to TEXT_NODE
                    shaderSource += currentChild.textContent;
                }
                currentChild = currentChild.nextSibling;
            }

            var shader;
            if (shaderScript.type == "x-shader/x-fragment") {
                shader = gl.createShader(gl.FRAGMENT_SHADER);
            } else if (shaderScript.type == "x-shader/x-vertex") {
                shader = gl.createShader(gl.VERTEX_SHADER);
            } else {
                return null;
            }

            gl.shaderSource(shader, shaderSource);
            gl.compileShader(shader);

            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                alert(gl.getShaderInfoLog(shader));
                return null;
            }
            return shader;
        }

        //设置我的着色器(完成着色器的部分初始化操作)
        function setupShaders() {
            var vertexShader = loadShaderFromDOM("shader-vs");
            var fragmentShader = loadShaderFromDOM("shader-fs");

            shaderProgram = gl.createProgram();
            gl.attachShader(shaderProgram, vertexShader);
            gl.attachShader(shaderProgram, fragmentShader);
            gl.linkProgram(shaderProgram);

            if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
                alert("Failed to setup shaders");
            }

            gl.useProgram(shaderProgram);

            //获取顶点的位置和颜色的存储地址
            shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
            shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");

            //回去模型视图矩阵的存储地址
            shaderProgram.uniformMVMatrix = gl.getUniformLocation(shaderProgram, "uMVMatrix");
            shaderProgram.uniformProjMatrix = gl.getUniformLocation(shaderProgram, "uPMatrix");

            gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

            //开始创建我的模型视图矩阵
            modelViewMatrix = mat4.create();
            projectionMatrix = mat4.create();
            modelViewMatrixStack = [];
        }

        //定义两个辅助函数(矩阵的入栈和出栈操作)
        function pushModelViewMatrix() {
            //把当前的模型视图矩阵存储在一个JavaScript数组中去
            var copyToPush = mat4.create(modelViewMatrix);
            modelViewMatrixStack.push(copyToPush);
        }

        function popModelViewMatrix() {
            if (modelViewMatrixStack.length == 0) {
                throw "Error popModelViewMatrix() - Stack was empty";
            }
            //把我自己保存的模型视图矩阵弹出去
            modelViewMatrix = modelViewMatrixStack.pop();
        }

        //处理物体运动的一些基本参数
        var xRot = 0;
        var xSpeed = 3;

        var yRot = 0;
        var ySpeed = -3;

        var z = -5.0;


        //可以处理多个按键同时按下的情形
        var currentlyPressedKeys = {};

        function handleKeyDown(event) {
            currentlyPressedKeys[event.keyCode] = true;
        }

        function handleKeyUp(event) {
            currentlyPressedKeys[event.keyCode] = false;
        }


        //根据不同的按键做出不同的反应
        function handleKeys() {
            if (currentlyPressedKeys[33]) {
                // Page Up
                z -= 0.05;
            }
            if (currentlyPressedKeys[34]) {
                // Page Down
                z += 0.05;
            }
            if (currentlyPressedKeys[37]) {
                // Left cursor key
                ySpeed -= 1;
            }
            if (currentlyPressedKeys[39]) {
                // Right cursor key
                ySpeed += 1;
            }
            if (currentlyPressedKeys[38]) {
                // Up cursor key
                xSpeed -= 1;
            }
            if (currentlyPressedKeys[40]) {
                // Down cursor key
                xSpeed += 1;
            }
        }

        //地板的顶点位置参数
        function setupFloorBuffers() {
            /****第一步****/
            //1.创建地板顶点缓冲区
            floorVertexPositionBuffer = gl.createBuffer();
            //2.绑定缓冲区到目标对象
            gl.bindBuffer(gl.ARRAY_BUFFER, floorVertexPositionBuffer);
            var floorVertexPosition = [
                // Plane in y=0
                5.0,   0.0,  5.0,  //v0
                5.0,   0.0, -5.0,  //v1
                -5.0,   0.0, -5.0,  //v2
                -5.0,   0.0,  5.0   //v3
            ];
            //3.向缓冲区对象中写入数据
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(floorVertexPosition), gl.STATIC_DRAW);

            floorVertexPositionBuffer.itemSize = 3;
            floorVertexPositionBuffer.numberOfItems = 4;

            /****第二步***/
            //1.创建地板顶点索引缓冲区.
            floorVertexIndexBuffer = gl.createBuffer();
            //2.绑定缓冲区到目标对象
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, floorVertexIndexBuffer);
            var floorVertexIndices = [
                0, 1, 2, 3
            ];
            //3.向缓冲区对象中写入数据(16为无符号整型数字)
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(floorVertexIndices), gl.STATIC_DRAW);

            floorVertexIndexBuffer.itemSize = 1;
            floorVertexIndexBuffer.numberOfItems = 4;

        }

        //立方体的顶点位置参数
        function setupCubeBuffers() {
            /*****立方体的顶点位置*****/
            //1.创建立方体的顶点缓冲区
            cubeVertexPositionBuffer = gl.createBuffer();
            //2.绑定缓冲区到目标对象
            gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
            var cubeVertexPosition = [
                // Front face
                1.0,  1.0,  1.0, //v0
                -1.0,  1.0,  1.0, //v1
                -1.0, -1.0,  1.0, //v2
                1.0, -1.0,  1.0, //v3

                // Back face
                1.0,  1.0, -1.0, //v4
                -1.0,  1.0, -1.0, //v5
                -1.0, -1.0, -1.0, //v6
                1.0, -1.0, -1.0, //v7

                // Left face
                -1.0,  1.0,  1.0, //v8
                -1.0,  1.0, -1.0, //v9
                -1.0, -1.0, -1.0, //v10
                -1.0, -1.0,  1.0, //v11

                // Right face
                1.0,  1.0,  1.0, //12
                1.0, -1.0,  1.0, //13
                1.0, -1.0, -1.0, //14
                1.0,  1.0, -1.0, //15

                // Top face
                1.0,  1.0,  1.0, //v16
                1.0,  1.0, -1.0, //v17
                -1.0,  1.0, -1.0, //v18
                -1.0,  1.0,  1.0, //v19

                // Bottom face
                1.0, -1.0,  1.0, //v20
                1.0, -1.0, -1.0, //v21
                -1.0, -1.0, -1.0, //v22
                -1.0, -1.0,  1.0, //v23
            ];
            //3.向缓冲区对象中写入数据
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeVertexPosition), gl.STATIC_DRAW);

            //错误提示:Uncaught TypeError: Cannot read property 'toString' of undefined            /*cubeVertexPosition.itemSize = 3;
             cubeVertexPosition.numberOfItems = 24;*/
            /*
            * 【经验话语】:如果控制台提示toString()类型的错误,多半原因是由于调用该函数的语句中的某一个变量没有正确定义,或者没有正确初始化操作
            * 【解决方案】:通常检查出错语句中的变量,是否正确赋值!
            * */
            cubeVertexPositionBuffer.itemSize = 3;
            cubeVertexPositionBuffer.numberOfItems = 24;


            /*****立方体的顶点位置索引信息****/
            //1.创建立方体顶点位置索引缓冲区
            cubeVertexIndexBuffer = gl.createBuffer();
            //2.绑定缓冲区到目标对象
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
            var cubeVertexIndices = [
                0, 1, 2,      0, 2, 3,    // Front face
                4, 6, 5,      4, 7, 6,    // Back face
                8, 9, 10,     8, 10, 11,  // Left face
                12, 13, 14,   12, 14, 15, // Right face
                16, 17, 18,   16, 18, 19, // Top face
                20, 22, 21,   20, 23, 22  // Bottom face
            ];
            //3.向缓冲区对象中写入数据
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);

            //索引之间互相独立, 共计有36个索引点的信息
            cubeVertexIndexBuffer.itemSize = 1;
            cubeVertexIndexBuffer.numberOfItems = 36;
        }


        //设置我的缓冲区
        function setupBuffers() {
            //设置缓冲区分解为两个部分
            //1.设置地板的顶点缓冲区
            setupFloorBuffers();
            //2.设置立方体的顶点缓冲区
            setupCubeBuffers();
        }

        //把我的模型视图矩阵传给顶点着色器
        function uploadModelViewMatrixToShader() {
            gl.uniformMatrix4fv(shaderProgram.uniformMVMatrix, false, modelViewMatrix);
        }
        //把我的投影矩阵传给顶点着色器
        function uploadProjectionMatrixToShader() {
            gl.uniformMatrix4fv(shaderProgram.uniformProjMatrix, false, projectionMatrix);
        }


        //绘制地板
        function drawFloor(r, g, b, a) {
            //指定一个常量颜色
            gl.disableVertexAttribArray(shaderProgram.vertexColorAttribute);
            gl.vertexAttrib4f(shaderProgram.vertexColorAttribute, r, g, b, a);

            //开始绘制(先把地板顶点位置信息传给顶点着色器)
            gl.bindBuffer(gl.ARRAY_BUFFER, floorVertexPositionBuffer);
            gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
                floorVertexPositionBuffer.itemSize,
                gl.FLOAT, false, 0, 0);
            //利用顶点索引信息开始绘图
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, floorVertexIndexBuffer);
            gl.drawElements(gl.TRIANGLE_FAN, floorVertexIndexBuffer.numberOfItems, gl.UNSIGNED_SHORT, 0);

        }

        //绘制立方体
        function drawCube(r, g, b, a) {
            //设置指定的颜色
            gl.disableVertexAttribArray(shaderProgram.vertexColorAttribute);
            gl.vertexAttrib4f(shaderProgram.vertexColorAttribute, r, g, b, a);

            //开始绘制
            gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
            gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

            //开始利用索引坐标绘制立方体
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
            gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numberOfItems,
                gl.UNSIGNED_SHORT, 0);

        }


        //绘制卓子
        function drawTable() {
            //绘制之前先保存当前的模型视图矩阵
            pushModelViewMatrix();//最初的模型视图矩阵
            //alert(modelViewMatrixStack.length); 此时这里面存放了两个模型视图矩阵
            //向上移动1个单位
            mat4.translate(modelViewMatrix, [0.0, 1.0, 0.0], modelViewMatrix);
            //开始缩放
            mat4.scale(modelViewMatrix, [2.0, 0.1, 2.0], modelViewMatrix);
            //把平移并且缩放后的矩阵(此时的模型视图矩阵)传到顶点着色器
            uploadModelViewMatrixToShader();

            //开始绘制立方体(主要是把立方体的顶点位置传给顶点着色器,然后顶点着色器就会对这个顶点位置向量进行矩阵变换)
            drawCube(0.72, 0.53, 0.04, 1.0);
            popModelViewMatrix();

            //绘制桌子腿
            for (var i=-1; i<=1; i+=2) {
                for (var j= -1; j<=1; j+=2) {
                    pushModelViewMatrix();
                    mat4.translate(modelViewMatrix, [i*1.9, -0.1, j*1.9], modelViewMatrix);
                    mat4.scale(modelViewMatrix, [0.1, 1.0, 0.1], modelViewMatrix);
                    uploadModelViewMatrixToShader();
                    //绘制立方体(会把立方体的顶点坐标,进行矩阵变换)
                    drawCube(0.72, 0.53, 0.04, 1.0); // argument sets brown color
                    popModelViewMatrix();
                }
            }
        }

        //绘图函数
        function draw() {
            //设置视口,清空深度缓存(左下角坐标, 宽度, 高度)
            gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
            gl.clear(gl.COLOR_BUFFER_BIT);

            //设置我的透视投影矩阵
            mat4.perspective(60, gl.viewportWidth/gl.viewportHeight, 0.1, 100, projectionMatrix);
            //重置模型视图矩阵
            mat4.identity(modelViewMatrix);
            mat4.lookAt([8, 5, -10], [0, 0, 0], [0, 1, 0], modelViewMatrix);

            //在这里设置转动整个场景
            mat4.translate(modelViewMatrix, [0, 0, z], modelViewMatrix);
            mat4.rotateY(modelViewMatrix, yRot, modelViewMatrix);

            //把当前的模型视图投影矩阵传递给顶点着色器(到这里顶点的坐标已经转换为了裁剪坐标系)
            uploadModelViewMatrixToShader();
            uploadProjectionMatrixToShader();

            //开始绘制地板
            drawFloor(1.0, 0.0, 0.0, 1.0);

            //绘制桌子
            //绘制之前先把我当前的WEBGL坐标系统的矩阵保存起来【相当于是桌子腿的最下面位置】
            pushModelViewMatrix();
            //此时物体的坐标系到达桌子的最上表面(注意物体自身的坐标系和WEBGL坐标系的区别)
            //这里实际上是把WEBGL坐标系移动到物体坐标系的原点位置
            mat4.translate(modelViewMatrix, [0.0, 1.1, 0.0], modelViewMatrix);
            uploadModelViewMatrixToShader();
            drawTable();
            popModelViewMatrix();

            //开始绘制桌子上面的一个物体
            pushModelViewMatrix();
            mat4.translate(modelViewMatrix, [0.0, 2.7, 0.0], modelViewMatrix);
            //把原来的立方体2*2*2变换为1*1*1的立方体,相当于把长宽高都缩减为原来的一半
            mat4.scale(modelViewMatrix, [0.5, 0.5, 0.5], modelViewMatrix);
            uploadModelViewMatrixToShader();
            drawCube(0.0, 0.0, 1.0, 1.0);
            popModelViewMatrix();

            //在绘制一个图形
            pushModelViewMatrix();
            mat4.translate(modelViewMatrix, [0.0, 3.7, 0.0], modelViewMatrix);
            mat4.scale(modelViewMatrix, [0.5, 0.5, 0.5], modelViewMatrix);
            uploadModelViewMatrixToShader();
            drawCube(0.0, 1.0, 0.0, 1.0);
            popModelViewMatrix();

            pushModelViewMatrix();
            mat4.translate(modelViewMatrix, [0.0, 4.7, 0.0], modelViewMatrix);
            mat4.scale(modelViewMatrix, [0.5, 0.5, 0.5], modelViewMatrix);
            //mat4.rotateY(modelViewMatrix, yRot, modelViewMatrix);
            uploadModelViewMatrixToShader();
            drawCube(1.0, 1.0, 0.0, 1.0);
            popModelViewMatrix();

            //开始转动场景
            /*mat4.rotate(modelViewMatrix, xRot, [1, 0, 0], modelViewMatrix);
            mat4.rotateY(modelViewMatrix, yRot, modelViewMatrix);
            uploadModelViewMatrixToShader();
            uploadProjectionMatrixToShader();*/


        }

        var lastTime = 0;
        //实时更新旋转角度
        function animate() {
            var timeNow = new Date().getTime();
            if (lastTime != 0) {
                var elapsed = timeNow - lastTime;

                xRot += (xSpeed * elapsed) / 1000.0;
                yRot += (ySpeed * elapsed) / 1000.0;
            }
            lastTime = timeNow;
        }


        //不断重绘场景
        function tick() {
            requestAnimationFrame(tick);
            handleKeys();
            draw();
            animate();
        }

        function startup() {
            canvas = document.getElementById("myGLCanvas");
            gl = WebGLDebugUtils.makeDebugContext(createGLContext(canvas));
            setupShaders();
            setupBuffers();
            gl.clearColor(1.0, 1.0, 1.0, 1.0);


            //逆时针方向是前面
            gl.frontFace(gl.CW);
            //激活背面剔除功能
            gl.enable(gl.CULL_FACE);
            //WEBGL剔除背面三角形
            gl.cullFace(gl.FRONT);

            //draw();

            document.onkeydown = handleKeyDown;
            document.onkeyup = handleKeyUp;

            tick();
        }
    </script>
    <script src="./lib/glMatrix.js"></script>

</head>

<body onload="startup();">
<canvas id="myGLCanvas" width="500" height="500" style="border: 2px solid springgreen;"></canvas>
</body>

</html>

猜你喜欢

转载自blog.csdn.net/m0_37981569/article/details/78473425