WebGL第二十四课:画多边形| 8月更文挑战

943 阅读5分钟

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

本文标题:WebGL第二十四课:画多边形| 8月更文挑战

本文的最后代码,用一篇单独的文章给出,需要的小伙伴直接跳:二十四课代码

引子

上一次课,我们用三个点,画出了一个三角形。并且中间填充了黑色(其实是WebGL自行插值的)。

那么这一次课,我们扩展一下,画一个N边形,并且这个N可以通过页面上的一个滑竿来控制,即时生效。

最终的效果如下:

24-1.gif

我们通过上面的动图可以看出,当N边形的边数越来越多,我们画出的图形就越趋向于一个圆。

这正是WebGL如何模拟一些复杂的曲线、曲面的,就是不断的逼近。(要考虑效果和性能,不能把数据点搞太多)

准备坐标点

我们用四边形,也就是正方形,来作为例子。

我们先观察一下正方形:

image.png

正方形有四个顶点,我们通过划分区域,将这个正方形划分成四个小三角形。

而每一个三角形有三个顶点,也就是说,我们需要事先准备 12 个坐标点。

我们先人工的把这十二个点描述出来,如下:

第一个三角形的三个点:OAB

第二个三角形的三个点:OBC

第三个三角形的三个点:OCD

第四个三角形的三个点:ODA

注意,我上面严格遵守了,一定要逆时针这个原则。

后面写代码的时候,也是按照这个顺序来写。

编写生成N边形坐标点的函数

我们先写出函数头:

function GetPolyN(center, R, N) {
}

center :中心

R : 中心到顶点的距离

N : 多边形的边数

我们的思维模式就是:

    1. 将一个圆周 N 等分,找出这N等分的坐标点。
    1. 再将这N等分的坐标点,两两一组,与中心点组合,正好就是三个点一组。
    1. 将上面的所有坐标点,平坦化

根据上面的思路,得出下面的代码:

// GetTri 函数在上次课已经讲解过了
function GetTri(A, B, C) {
    return [A[0], A[1], B[0], B[1], C[0], C[1]];
}

// 获得N边形
function GetPolyN(center, R, N) {
    // 1. 先在圆周上,均匀获取 N 个点
    let idx = 0;
    let x = 0;
    let y = 0;
    let rad = 0;
    let pointArr = [];
    for (; idx != N; idx++) {
        rad = ((2 * Math.PI) / N) * idx;
        x = R * Math.cos(rad) + center[0]; // 考虑 中心到顶点的长度  中心
        y = R * Math.sin(rad) + center[1]; // 考虑 中心到顶点的长度  中心
        pointArr.push([x, y]);
    }
    // 2. 将这N个点,每两个一组,与中心,正好是三个点,组成N个三角形
    let res = []; // 平坦化数组
    for (idx = 0; idx != N; idx++) {
        res.push(...GetTri(pointArr[idx], pointArr[(idx + 1) % N], center)); // 注意这里是逆时针排布的
    }
    return res;
}

页面上加一个滑竿来控制N

这个属于html的知识,我们只写列出代码如下:

    <p>
        <b>N边形:</b>
        <input id="N" type="range" min="3" max="100" value="3" step="1" oninput="updatefunc()" />
        <b id="Nvalue">0</b>
    </p>

值得注意的是,我们设置了 min 是3,也就是最小画的就是三角形。最大是100,我们最后会发现100,足以模拟一个圆了。

buffer 的删除与新建

这一次课有一点不一样的过程, 那就是当N变化的时候,我们需要重新生成数据点,然后重新在WebGL里生成一个buffer,为了不造成内存泄露,我们还需要将前面的buffer删掉。

也就是说,我们需要将生成buffer,传入数据的代码,挪到updatefunc函数里。

最终的updatefunc如下:

        function updatefunc() {
            
            // 这里判断一下,如果以前有buffer,就删掉
            if (buffer_id) {
                gl.deleteBuffer(buffer_id);
            }
            // 生成N边形
            data = GetPolyN([0, 0], 0.5, NDom.value);
            dataArr = new Float32Array(data);
            pointCount = data.length / 2;
            // 重新创建WebGL的buffer,并且将多边形的点传入
            buffer_id = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer_id);
            gl.bufferData(gl.ARRAY_BUFFER, dataArr, gl.STATIC_DRAW);
            // 指定 data 的格式
            gl.vertexAttribPointer(a_PointVertex, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(a_PointVertex);

            /*
            ...
            ...
            没有变化的代码
            ...
            ...
            */
            
            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
            gl.drawArrays(gl.TRIANGLES, 0, pointCount);
        }

整点花活

上面确实可以画出多边形了,但是我们还是不确认,是不是由一个一个小三角形组成的。

那不妨这样。我们把小三角形的颜色区分开,一个一个的,用不同的颜色。那就一目了然了。

思路:

既然要不同的点,用不同的颜色。那么就必须要把颜色也传入到WebGLbuffer里去了。

这里有两种途径:

    1. 利用老的buffer,如下

[数据点1坐标][数据点1颜色][数据点2坐标][数据点2颜色][数据点3坐标][数据点3颜色]………………

    1. 新开一个buffer

buffer_point(老的buffer):

[数据点1坐标][数据点2坐标][数据点3坐标]………………

buffer_color:

[数据点1颜色][数据点2颜色][数据点3颜色]………………

上面两种办法都行啊,这里我选择第二种办法,也就是不动原有的buffer,而新建一个buffer_color。

我们知道一个数据点,就要对应一个颜色,而一个颜色由RGB三个字段组成。

所以我们在shader里接收的时候,要使用vec3。

这里对比坐标,坐标是xy,只需要vec2。

vertex_shader 的改动

vertex_shader 中要加一个 attribute 变量 a_PointColor:

    attribute vec3 a_PointColor;

为了将这个 a_PointColor 传给 fragment_shader ,我们必须再加一个 varying 变量:

    attribute vec3 a_PointColor;
    varying vec3 color;

那么最终的vertex_shader

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

        attribute vec2 a_PointVertex;

        attribute vec3 a_PointColor;

        varying vec3 color;

        void main() {
          vec3 coord = u_all * vec3(a_PointVertex, 1.0);
          gl_Position = vec4(coord.x, coord.y, 0.0, 1.0);
          color = a_PointColor;
        }
    </script>

fragment_shader 的改动

这个就很简单了,加一个 varying 同名变量 , 然后用这个变量当做颜色就行了:

    <script id="fragment_shader" type="myshader">
        // Fragment shader
        precision mediump int;
        precision mediump float;

        varying vec3 color;

        void main() {
        
          gl_FragColor = vec4(color, 1.0);
        }
        
    </script>

针对坐标点数组,生成一个颜色数组

我们只需要记住,每个坐标点,对应一个颜色,而一个颜色有三个分量RGB就行了:

function GetRandomColor(pointCount) {
    let res = [];
    let idx = 0;
    for (; idx != pointCount; idx++) {
        res.push(Math.random());//R
        res.push(Math.random());//G
        res.push(Math.random());//B
    }
    return res;
}

需要注意的是,我们上面的颜色是随机生成的,这样看起来更 花活 一点。

最终效果

这里不给出最终的代码,从文章开头的链接,可以直接拿到最终代码。

其实只要将上面所说的,稍微攒一攒,就行了。

最后的花活效果如下:

24-2.gif




  正文结束,下面是答疑

小丫丫说:我终于看见五颜六色的东西了!

  • 答:花里胡哨的东西!