可视化学习:使用webgl绘制圆形,实现色盘-kb88凯时官网登录

来自:
时间:2024-04-13
阅读:
免费资源网 - https://freexyz.cn/

前言

在canvas2d中实现圆形的绘制比较简单,只要调用arc指令就能在canvas画布上绘制出一个圆形,类似的,在svg中我们也只需要一个标签就能在页面上绘制一个圆形。那么在webgl中我们要怎么去绘制呢?webgl只能绘制三种形状:点、线段和三角形,它没有提供直接绘制圆形的功能,当然也无法像svg一样使用标签,所以我们是无法直接绘制圆形曲线的,这个时候我们可以借助相关的数学知识,来实现圆形的绘制。

参数方程

相信数学基础好的小伙伴一定能很快想到,我们可以使用参数方程去获取圆形曲线上的点的坐标,只要我们收集足够多的点,再通过绘制线段的方式将这些点连接起来,就能得到接近圆的图形,从视觉上看就是一个圆形了。其实圆形就是曲线中的一个特例,所以也就是说我们还可以通过参数方程绘制其他常见的曲线,比如圆、椭圆、抛物线、正余弦曲线等等。

以下是圆的参数方程:

\[\begin{cases} x = x0 r * cos(θ)\\ y = y0 r * sin(θ)\\ \end{cases} \]

在圆的参数方程中,可以使用圆心坐标、半径和夹角的正余弦值来表示横纵坐标的值。

具体实现

按照这个思路,我们就可以编写代码来绘制圆形曲线了。

在正式实现之前,在html中准备一个canvas:


在之后的代码中会用到我自己之前简单封装的一个webgl的类,只是封装了一些繁琐的创建着色器程序的步骤,封装的比较粗糙。下面就开始具体的实现。

  • 首先,定义函数获取圆形曲线的顶点集合。

    const tau_segments = 60;
    const tau = math.pi * 2;
    // 获得圆形曲线顶点集合
    function arc(x0, y0, radius, startang = 0, endang = math.pi * 2) {
      const ang = math.min(tau, endang - startang);
      const ret = ang === tau ? []: [[x0, y0]];
      const segments = math.round(tau_segments * ang / tau);
      for (let i = 0; i <= segments; i   ) {
        const x = x0   radius * math.cos(startang   ang * i / segments);
        const y = y0   radius * math.sin(startang   ang * i / segments);
        ret.push([x, y]);
      }
      return ret;
    }
    

    x0和y0是圆心坐标,radius是半径,startang和endang表示圆弧的起始角度和结束角度,对于整个圆来说,就是从0到2π,这些参数都比较好理解。

    再来看arc这个函数的内部变量,ang好理解,就是结束角度和起始角度的差值;segments表示要在圆弧上取的点的总数,如果是整个圆就取60个点。

    接着就是遍历,获取segments数量的点的坐标,并存储在ret数组中。

  • 这样,我们就可以调用arc函数来获取顶点集合了。

    const vertices = arc(0, 0, 0.8);
    

    因为在webgl中坐标系在视口的坐标范围默认是-1到1,要在视口中看到整个圆,这个圆的半径不能超过1,所以这里半径我取0.8,圆心为(0, 0),然后获取到顶点集合。

  • 创建webgl程序并绘制。

    webgl部分的代码就比较简单了,首先是两段glsl代码,和常见的实现三角形的glsl代码没什么太大区别:

    const vertex = `
      attribute vec2 position;
      void main() {
        gl_pointsize = 1.0;
        gl_position = vec4(position, 1, 1);
      }
    `;
    const fragment = `
      precision mediump float;
      void main() {
        gl_fragcolor = vec4(0, 0, 0, 1);
      }
    `;
    

    因为通过参数方程获取到的是连续的点,所以我们可以通过gl.line_loop的绘图模式,将所有的点串联起来,这样就得到了一个视觉上的圆形曲线。

    const gl = webglref.value.getcontext('webgl');
    const webgl = new webgl(gl, vertex, fragment);
    webgl.drawsimple(vertices.flat(), 2, gl.line_loop);
    

    具体在封装的drawsimple方法中我调用了gl.drawarrays来绘制图形。

    gl.drawarrays(gl.line_loop, 0, points.length / size);
    

实际操作下来能发现,其实绘制圆形曲线还比较简单,所以我们还可以尝试去实现色盘。

色盘是一个实心的圆,就不能通过线条的方式去绘制了,之前在中我们有提到过,对于多边形我们可以把它们看做是由多个三角形组合而成的图形,因此我们可以对多边形进行三角剖分,也就是使用多个三角形的组合来表示一个多边形,把这些三角形都绘制到画布上就组成了多边形,而圆形我们就可以把它看做是一种特殊的多边形。

因为三角剖分算法比较复杂,我们可以直接调用现有的库来完成这个操作,之前使用的是earcut这个库,现在我们换一个叫tess2的库,更详细的介绍可以查看它的,下面我们就调用tess2的api来完成三角剖分操作。

webgl.drawpolygontess2(vertices);
// ↓↓ 
drawpolygontess2(points, {
    color,
    rule = winding_odd/*winding_nonzero*/
} = {}) {
    const triangles = tess2triangulation(points, rule);
    triangles.foreach(t => this.drawtriangle(t, {color}));
}
// ↓↓
function tess2triangulation(points, rule = winding_odd) {
    const res = tesselate({
        contours: [points.flat()],
        windingrule: rule,
        elementtype: polygons,
        polysize: 3,
        vertexsize: 2,
        strict: false
    });
    const triangles = [];
    for (let i = 0; i < res.elements.length; i  = 3) {
        const a = res.elements[i];
        const b = res.elements[i   1];
        const c = res.elements[i   2];
        triangles.push([
            [res.vertices[a * 2], res.vertices[a * 2   1]],
            [res.vertices[b * 2], res.vertices[b * 2   1]],
            [res.vertices[c * 2], res.vertices[c * 2   1]],
        ])
    }
    return triangles;
}

这样我们就绘制了一个黑色的实心圆。

要实现色盘,我们需要使用hsv或者hsl的颜色表示形式,因为色相hue的取值范围是0到360度,所以这两种颜色表示形式可以让我们直接把色值和角度关联起来,因此我们可以通过varying变量将坐标信息传递给片元着色器,然后在片元着色器中使用坐标信息计算hsv形式的像素色值。

// vertex
attribute vec2 position;
varying vec2 vp;
void main() {
  gl_pointsize = 1.0;
  gl_position = vec4(position, 1, 1);
  vp = position;
}

但是webgl中还无法直接处理hsv的颜色表示形式,所以我们需要使用hsv2rgb函数来完成颜色向量的转换,这其中具体的转换算法我也并不是很懂,感兴趣的小伙伴可以自行研究。

// fragment
#define pi 3.1415926535897932384626433832795
precision mediump float;
varying vec2 vp;
// hsv -> rgb
// 参数的取值范围都是 (0, 1)
vec3 hsv2rgb(vec3 c) {
  vec3 rgb = clamp(abs(mod(c.x * 6.0   vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
  rgb = rgb * rgb * (3.0 - 2.0 * rgb);
  return c.z * mix(vec3(1.0), rgb, c.y);
}
void main() {
  float x0 = 0.0;
  float y0 = 0.0;
  float h = atan(vp.y - y0, vp.x - x0);
  h = h / (pi * 2.0); // 归一化处理
  vec3 hsv_color = vec3(h, 1.0, 1.0);
  vec3 rgb_color = hsv2rgb(hsv_color);
  gl_fragcolor = vec4(rgb_color, 1.0);
}

在上述代码中,我们调用atan函数计算得到以(0,0)为圆心的弧度值,再除以得到一个归一化的值,然后将这个归一化的值通过hsv2rgb函数转化rgb颜色向量。

这样我们就使用webgl实现了一个色盘。如果我们想要颜色的过渡显得更自然,还可以设置使饱和度随着半径增大而增大。

void main() {
  // ...
  float r = sqrt((vp.x - x0) * (vp.x - x0)   (vp.y - y0) * (vp.y - y0)); // 计算半径
  vec3 hsv_color = vec3(h, r * 1.2, 1.0);
  // ...
}

好啦,那看到这里的小伙伴应该都知道如何绘制圆形,如何实现色盘了吧,可以自己动手实践一下。

本文来自博客园,作者:,转载请注明原文链接:

免费资源网 - https://freexyz.cn/
返回顶部
顶部
网站地图