WebGL第二十二课:2D拉伸旋转平移实战| 8月更文挑战

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

本文标题:WebGL第二十二课:2D拉伸旋转平移实战| 8月更文挑战
复制代码

引子

前面用了很多次课,讲了矩阵在我们这里的应用。我们这次课就来搞一搞2D情况下的实战。

写一个生成模型的js文件,单独出去

所有的东西放在一个文件里写,过于看不清了。所以我们准备新建一个 models_point.js 来存放点的生成方面的代码。之所以加一个后缀point,是因为,这个文件里生成的东西,都是要用gl.POINTS这个画法来画。后面画三角形的时候,再新建一个文件 models_tri.js 来专门搞三角形的东西。

在页面同级目录下,新建一个 models_point.js 文件,填入以下代码:

function GetQuad_Point(xcenter, ycenter, length) {
    let xMin = xcenter - length / 2;
    let yMin = ycenter - length / 2;
    let step = length / 20;
    //
    let res = [];
    let xidx = 0;
    let yidx = 0;
    for (xidx = 0; xidx != 20; xidx++) {
        for (yidx = 0; yidx != 20; yidx++) {
            res.push(
                xMin + xidx * step,
                yMin + yidx * step,
            );
        }
    }
    return res;
}
复制代码

上面的代码没啥东西,就是生成一堆点而已。

先给出整体代码

<!doctype html>
<html>

<head>
    <style>
        canvas {
            border: 1px solid #000000;
        }
    </style>
    <script type="text/javascript" src="https://juejin.cn/post/models_point.js"></script>

</head>

<body>
    <p>
        <b>scale value:</b>
        <input id="scalex" type="range" min="-2" max="2" value="1" step="0.1" oninput="updatefunc()" />
        <b id="scalevaluex">0</b>
        <input id="scaley" type="range" min="-2" max="2" value="1" step="0.1" oninput="updatefunc()" />
        <b id="scalevaluey">0</b>
    </p>
    <p>
        <b>offset value:</b>
        <input id="offsetx" type="range" min="-1" max="1" value="0" step="0.1" oninput="updatefunc()" />
        <b id="offsetvaluex">0</b>
        <input id="offsety" type="range" min="-1" max="1" value="0" step="0.1" oninput="updatefunc()" />
        <b id="offsetvaluey">0</b>
    </p>

    <p>
        <b>rotate value:</b>
        <input id="rotate" type="range" min="0" max="6.28" value="0" step="0.01" oninput="updatefunc()" />
        <b id="rotatevalue">0</b>
    </p>

    <canvas id="point" style="width:300px; height:300px">
    </canvas>
    <script id="vertex_shader" type="myshader">
        // Vertex Shader
        precision mediump int;
        precision mediump float;
        
        uniform float u_x_scale;
        uniform float u_x_offset;
        uniform float u_y_scale;
        uniform float u_y_offset;
        uniform float u_rotate;

        attribute vec2 a_PointVertex;

        void main() {
          gl_Position = vec4(a_PointVertex, 0.0, 1.0);

          gl_Position.x *= u_x_scale;
          gl_Position.y *= u_y_scale;
          
          float rx = gl_Position.x * cos(u_rotate) - gl_Position.y * sin(u_rotate);
          float ry = gl_Position.x * sin(u_rotate) + gl_Position.y * cos(u_rotate);

          gl_Position.x = rx + u_x_offset;
          gl_Position.y = ry + u_y_offset;
          gl_PointSize = 3.0;
        }
    </script>
    <script id="fragment_shader" type="myshader">
        // Fragment shader
        precision mediump int;
        precision mediump float;

        void main() {
          gl_FragColor = vec4(0, 0, 0, 1.0);
        }
        
    </script>
    <script type="text/javascript">
        var pointCanvas = document.getElementById('point'); // 我们的纸
        var gl = pointCanvas.getContext('webgl', { preserveDrawingBuffer: true }); // 我们的笔
        var pointData = GetQuad_Point(0, 0, 1);
        var pointCount = pointData.length / 2;
        var pointArray = new Float32Array(pointData);
        var buffer_id;
        buffer_id = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer_id);
        gl.bufferData(gl.ARRAY_BUFFER, pointArray, gl.STATIC_DRAW);
        //
        var vertex_shader_code = document.getElementById('vertex_shader').textContent;
        console.log(vertex_shader_code);
        var vertex_shader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertex_shader, vertex_shader_code);
        gl.compileShader(vertex_shader);
        //
        var fragment_shader_code = document.getElementById('fragment_shader').textContent;
        var fragment_shader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragment_shader, fragment_shader_code);
        gl.compileShader(fragment_shader);
        //
        var program = gl.createProgram();
        gl.attachShader(program, vertex_shader);
        gl.attachShader(program, fragment_shader);
        gl.linkProgram(program);
        gl.useProgram(program);
        //
        var a_PointVertex = gl.getAttribLocation(program, 'a_PointVertex');
        gl.vertexAttribPointer(a_PointVertex, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(a_PointVertex);
        //

    </script>
    <script>
        var u_x_offset_loc = gl.getUniformLocation(program, "u_x_offset");
        var u_x_scale_loc = gl.getUniformLocation(program, "u_x_scale");
        var u_y_offset_loc = gl.getUniformLocation(program, "u_y_offset");
        var u_y_scale_loc = gl.getUniformLocation(program, "u_y_scale");
        var u_rotate_loc = gl.getUniformLocation(program, "u_rotate");

        var scaleDomX = document.getElementById("scalex");
        var scaleValueDomX = document.getElementById("scalevaluex");
        var scaleDomY = document.getElementById("scaley");
        var scaleValueDomY = document.getElementById("scalevaluey");
        var offsetDomX = document.getElementById("offsetx");
        var offsetValueDomX = document.getElementById("offsetvaluex");
        var offsetDomY = document.getElementById("offsety");
        var offsetValueDomY = document.getElementById("offsetvaluey");

        var rotateDom = document.getElementById("rotate");
        var rotateValueDom = document.getElementById("rotatevalue");
        function updatefunc() {
            gl.uniform1f(u_x_scale_loc, parseFloat(scaleDomX.value));
            gl.uniform1f(u_y_scale_loc, parseFloat(scaleDomY.value));

            gl.uniform1f(u_x_offset_loc, parseFloat(offsetDomX.value));
            gl.uniform1f(u_y_offset_loc, parseFloat(offsetDomY.value));

            gl.uniform1f(u_rotate_loc, parseFloat(rotateDom.value));

            scaleValueDomX.innerText = scaleDomX.value;
            offsetValueDomX.innerText = offsetDomX.value;
            scaleValueDomY.innerText = scaleDomY.value;
            offsetValueDomY.innerText = offsetDomY.value;
            rotateValueDom.innerText = rotateDom.value;

            gl.enable(gl.CULL_FACE);
            gl.enable(gl.DEPTH_TEST);

            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
            gl.drawArrays(gl.POINTS, 0, pointCount);
        }
        updatefunc();
    </script>
    </script>
</body>

</html>
复制代码

注意上面的这一句:

<script type="text/javascript" src="https://juejin.cn/post/models_point.js"></script>

要引用一下我们刚才写的models_point.js文件。

现在的运行效果就是很正常的,可以拉伸,旋转, 平移。

对 vertex_shader 进行矩阵化改造

我们知道,拉伸,旋转,平移,这三个操作,可以揉到一个矩阵里去。

这样的话, 我们就不用搞那么多 uniform 变量了。

只需要一个 uniform 变量,变量类型是矩阵类型,就足矣。

        uniform float u_x_scale;
        uniform float u_x_offset;
        uniform float u_y_scale;
        uniform float u_y_offset;
        uniform float u_rotate;
复制代码

上面这些变量用一个矩阵变量代替,就可以搞定

例如下面这样:

        uniform mat3 u_all;
复制代码

我们上面的uniform变量u_all,类型是 mat3,就是 matrix 3, 就是 3 * 3 的矩阵。

为什么需要 3 * 3 的矩阵,前面在提及位移的时候,已经解释过了,不明白的小伙伴直接到前面去找一下。

我们先顺一下思路:

  1. 将我们传入的点的(x,y) 二维变成三维,第三维写死1。–>(x,y,1)
  2. 将 u_all * (x,y,1)
  3. 得到的结果,只拿前两个

好,根据上面的思路,vertex_shader代码如下:

    <script id="vertex_shader" type="myshader">
        // Vertex Shader
        precision mediump int;
        precision mediump float;
        
        uniform mat3 u_all;

        attribute vec2 a_PointVertex;

        void main() {
          vec3 coord = u_all * vec3(a_PointVertex, 1.0);
          gl_Position = vec4(coord.x, coord.y, 0.0, 1.0);
          gl_PointSize = 3.0;
        }
    </script
复制代码

看一下,是不是舒服太多了,一下少了一堆东西。

对 js 进行矩阵化改造

传入的uniform变成了一个mat3,自然的,外部js代码,也要进行相应的改造。

假设 拉伸系数是a b, 旋转弧度是 α, 位移 是 A B。

那么最终的矩阵:

[acos(α)bsin(α)Aasin(α)bcos(α)B001]\begin{bmatrix} a*cos(α) & -b*sin(α) & A \\ a*sin(α) & b*cos(α) & B \\ 0 & 0 & 1 \end{bmatrix}

根据这个矩阵,定义一个函数:

function genMat3ForGL(a, b, alpha, A, B) {
    let mat3 = [
        a * Math.cos(alpha), a * Math.sin(alpha), 0,
        -b * Math.sin(alpha), b * Math.cos(alpha), 0,
        A, B, 1,
    ];
    return new Float32Array(mat3);
}
复制代码

注意一下, 上面构造这个矩阵的时候,我们是一列一列的来填入的,这又是将矩阵看做向量数组的一个佐证。

我们先前的传入uniform变量如下:

    gl.uniform1f(u_x_scale_loc, parseFloat(scaleDomX.value));
    gl.uniform1f(u_y_scale_loc, parseFloat(scaleDomY.value));

    gl.uniform1f(u_x_offset_loc, parseFloat(offsetDomX.value));
    gl.uniform1f(u_y_offset_loc, parseFloat(offsetDomY.value));

    gl.uniform1f(u_rotate_loc, parseFloat(rotateDom.value));

复制代码

需要传五个变量进去,那么矩阵化之后:

    gl.uniformMatrix3fv(u_all_loc, false, genMat3ForGL(scaleDomX.value,
        scaleDomY.value, rotateDom.value, offsetDomX.value, offsetDomY.value
    ));
复制代码

只需要使用上面新定义的函数,将这些变量揉到一个矩阵里去,就大功告成了。

同理,在js代码里,还要预先获取,这个mat3 uniform 变量的位置:

    var u_all_loc = gl.getUniformLocation(program, "u_all");
复制代码

改造之后的整体代码,不给出了,诸位请自行对代码进行修修补补:最终运行的效果如下:

22-1.gif

总结

本次课主要实战了一下,如何将拉伸旋转平移这三个操作,用矩阵代替,并且真正实战到代码里。

我们可以看出,用矩阵代替之后,代码量少了,简洁了很多。




  正文结束,下面是答疑
复制代码

小丫丫说:文中有一个没有推导的东西,那就是,拉伸->旋转->平移 的整体矩阵。

  • 答:上次课讲了 拉伸,旋转 的综合矩阵:

[acos(α)bsin(α)asin(α)bcos(α)]\begin{bmatrix} a*cos(α) & -b*sin(α) \\ a*sin(α) & b*cos(α) \end{bmatrix}

我们一样,先把这个矩阵升一个维度:

[acos(α)bsin(α)0asin(α)bcos(α)0001]\begin{bmatrix} a*cos(α) & -b*sin(α) & 0 \\ a*sin(α) & b*cos(α) & 0 \\ 0 & 0 & 1 \end{bmatrix}

为什么升到这样呢:

因为

[acos(α)bsin(α)0asin(α)bcos(α)0001]\begin{bmatrix} a*cos(α) & -b*sin(α) & 0 \\ a*sin(α) & b*cos(α) & 0 \\ 0 & 0 & 1 \end{bmatrix} * (xy1)\left(\begin{array}{cc} x\\ y\\ 1\end{array}\right) 的结果,就是我们想要的。

然后 将这个矩阵左乘 位移矩阵:

[10A01B001]\begin{bmatrix} 1 & 0 & A \\ 0 & 1 & B \\ 0 & 0 & 1\end{bmatrix} * [acos(α)bsin(α)0asin(α)bcos(α)0001]\begin{bmatrix} a*cos(α) & -b*sin(α) & 0 \\ a*sin(α) & b*cos(α) & 0 \\ 0 & 0 & 1 \end{bmatrix}

=

[acos(α)bsin(α)Aasin(α)bcos(α)B001]\begin{bmatrix} a*cos(α) & -b*sin(α) & A \\ a*sin(α) & b*cos(α) & B \\ 0 & 0 & 1 \end{bmatrix}

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享