原生webgl学习(三) WebGL中的矩阵运算:平移、旋转和缩放

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37338983/article/details/83036245

本专栏所有文章示例代码均可在我的git码云上获取,读者可自行下载:https://gitee.com/babyogl/learnWebGL,本文示例代码在chapter-02的color-triangle-01,引用文件都在libs文件夹下;在上一节中,笔者介绍了如何用WebGL绘制一个三角形:原生webgl学习(二) 用WebGL绘制一个三角形,那我们会想,画一个三角形有什么了不起的?的确,画一个三角形的确很一般,所以我们要最求更加高大上的骚操作,这就涉及到接下来笔者要讲的矩阵运算;可能学过计算机图形学的同学都知道,在计算机图形学中,一切运算皆矩阵,图形的平移、旋转和缩放都可以表示为图形顶点坐标与相关矩阵的运算。我们先假设在二维空间进行上述的变换操作,假设某个顶点v的坐标为(x, y, 1);那为什么二位的坐标看起来确实三维的?那是由于我们在进行平移运算的时候,坐标必须是其次坐标,否则容易出现错误;同理,如果是三维空间的坐标,在进行平移变换时,应写为(x, y, z, 1)。三维的情况本文先不涉及,先掌握二维,自然而然地就可以很容易扩展到三维空间。

1.平移矩阵

直观的理解,假设点v平移了在x方向平移看了tx,在y方向平移了ty, 则平移后v点的坐标可以表示(x+tx, y+ty),用矩阵表示这个运算过程如下:

\LARGE \begin{pmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ tx & ty &1 \end{pmatrix}[\begin{smallmatrix} x &y &1 \end{smallmatrix}]= [\begin{matrix} x+tx&y+ty &1 \end{matrix}]

 2.旋转矩阵

如下图所示,旋转只需要用到和旋转角对应的正弦值和余弦值:

è¿éåå¾çæè¿°

由上图得到旋转后的坐标:

è¿éåå¾çæè¿°

 将其表示为矩阵形式:

 3.缩放变换

图形的缩放,无非就是顶点的缩放,假设存在系数s,对顶点v进行缩放变换后的坐标是(sx, sy),以矩阵的形式表达如下:

\LARGE \begin{pmatrix} s & 0 & 0\\ 0 & s & 0\\ 0} & 0 &1 \end{pmatrix}[\begin{smallmatrix} x &y &1 \end{smallmatrix}]=[\begin{matrix} sx&sy &1 \end{matrix}]

几种变换可以组合到一起运算,让平移矩阵、旋转矩阵和缩放矩阵相乘后再乘以顶点坐标即可。在下面笔者将带领大家实现一个带菜单栏工具的实时交互demo,界面如下图:

如果大家想更进一步了解三维空间中的平移、旋转和缩放变换可以参考: WebGL中的图形变换:旋转、平移和缩放,里面有更详细的介绍。

说了这么多理论的东西,接下来我们将它们变成代码吧,如果是刚学WebGL入门的,建议先参考:原生webgl学习(二) 用WebGL绘制一个三角形,学会构建整个页面后再学习本节的内容,会更容易上手。

我们首先构建点点着色器和片元着色器:

 <script id="vertex-shader" type="x-shader/vertex">
        attribute vec2 a_position;
        uniform mat3 u_matrix;//用于变换矩阵的传入
        varying vec4 v_color;//用于设置顶点的颜色
        void main() {
            gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy * 0.8, 0, 1);
            v_color = gl_Position * 0.5 + 0.5;//顶点的颜色随顶点位置变化
        }
  </script>
  <script id="fragment-shader" type="x-shader/x-fragment">
        precision mediump float;
        varying vec4 v_color;//用于设置顶点的颜色
        void main() {
            gl_FragColor = v_color;
        }
   </script>

我们创建一个canvas画布,并将相关的文件引用进来,同时给gui菜单开辟一块div,菜单栏工具主要使用dat.gui.min.js这个库来构建:

<canvas id="color-triangle" width="800" height="600"></canvas>
<div id="gui"></div>
<script type="text/javascript" src="../libs/webgl-utils.js"></script>
<script type="text/javascript" src="../libs/shader.js"></script>
<script type="text/javascript" src="../libs/m3.js"></script>
<script type="text/javascript" src="../libs/dat.gui.min.js"></script>

下面是主要的功能实现代码:

<script>
        "use strict";
        function main() {
            let canvas = document.getElementById('color-triangle');

            //判断浏览器是否支持webgl
            let gl = canvas.getContext('webgl', {antialias: true, depth: false});
            if (!gl) {
                console.log("您的浏览器不支持webgl!");
            }

            //获取GLSL的文本
            let vShaderSource = document.getElementById('vertex-shader').text;
            let fShaderSource = document.getElementById('fragment-shader').text;

            //创建着色器,加载着色器代码,编译着色器,链接着色器
            let program = initShader(gl, vShaderSource, fShaderSource);

            //在着色器中寻找attribute变量位置
            let positionAttributeLocation = gl.getAttribLocation(program, 'a_position');

            //在着色器中找uniform变量位置
            let matrixLocation = gl.getUniformLocation(program, 'u_matrix');

            //创建缓冲区
            let positionBuffer = gl.createBuffer();
            //将它绑定到ARRAY_BUFFER(将其视为ARRAY_BUFFER = positionBuffer)
            //绑定缓冲区
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

            let positions = [
                0, -100,
                150, 125,
                -175, 100
            ];

            setGeometry(gl, positions);

            let translation = [200, 150];
            let angleInRadians = 0;
            let scale = [1, 1];

            drawScene();

            //菜单栏
            let guiFiles = {
                "平移X": 200,
                "平移Y": 150,
                "旋转角度": 0,
                "缩放X方向": 1,
                "缩放Y方向": 1
            };

            //新建一个菜单栏
            let gui = new dat.GUI();
            gui.add(guiFiles, "平移X", 0, gl.canvas.width).onChange(function (e) {
                translation[0] = e;
                drawScene();//改变参数后重新绘制
            });
            gui.add(guiFiles, "平移Y", 0, gl.canvas.width).onChange(function (e) {
                translation[1] = e;
                drawScene();//改变参数后重新绘制
            });
            gui.add(guiFiles, "旋转角度", -360, 360).onChange(function(e) {
                angleInRadians = e * Math.PI / 180;
                drawScene();//改变参数后重新绘制
            });
            gui.add(guiFiles, "缩放X方向", -5, 5).onChange(function(e) {
                scale[0] = e;
                drawScene();//改变参数后重新绘制
            });
            gui.add(guiFiles, "缩放Y方向",-5, 5).onChange(function(e) {
                scale[1] = e;
                drawScene();//改变参数后重新绘制
            });

            //获取div元素
            let ui = document.getElementById('gui');
            //将gui的dom元素加入div中显示
            ui.appendChild(gui.domElement);

            //绘制场景
            function drawScene() {

                //使画布的像素数和显示大小匹配
                webglUtils.resizeCanvasToDisplaySize(gl.canvas);

                //设置视口
                gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

                //清除canvas
                gl.clearColor(0, 0, 0, 0);
                gl.clear(gl.COLOR_BUFFER_BIT);

                //应用着色器
                gl.useProgram(program);

                //建立着色器中attribute变量与缓冲区之间的连接
                gl.enableVertexAttribArray(positionAttributeLocation);

                gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

                const size = 2;//2维坐标:每次迭代运行提取两个单位数据
                const type = gl.FLOAT;//每个单位的数据类型是32位浮点型
                const normalize = false;//不需要归一化数据
                const stride = 0;//每次迭代前进大小* sizeof(类型)以获得下一个位置
                const offset1 = 0;//从缓冲起始位置开始读取

                //从缓冲区取出数据
                gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset1);

                //计算矩阵
                let matrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);
                matrix = m3.translate(matrix, translation[0], translation[1]);
                matrix = m3.rotate(matrix, angleInRadians);
                matrix = m3.scale(matrix, scale[0], scale[1]);

                gl.uniformMatrix3fv(matrixLocation, false, matrix);//将平移、旋转、缩放矩阵传给着色器中的u_matrix;

                let primitiveType = gl.TRIANGLES;
                const offset2 = 0;
                const count = 3;
                gl.drawArrays(primitiveType, offset2, count);//画图
            }
        }

        //通过绑定点向缓冲区绑定数据
        function setGeometry(gl, positions) {
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
        }
        main();

    </script>

 读者一定要仔细去体会JavaScript代码向着色器传送各种各样的数据的方法,这才是WebGL的核心灵魂所在,一旦搞懂了这个机制,那恭喜你,你可以随心所欲地用自己的数据去构建和渲染你想要的图形,笔者正在往这条道上前进,希望我们共同努力,共同进步!加上本节,我们已经画了两次三角形了,下从下一节开始,我将带领大家画出一个矩形。

猜你喜欢

转载自blog.csdn.net/qq_37338983/article/details/83036245