WebGL 绘制圆与圆环

297 阅读4分钟

本篇基于《从画一个点入手学习 WebGL》《WebGL 之点的动态绘制》《WebGL 使用缓冲区绘制点线面》中所讲过的知识点,来说说在 webgl 中绘制圆与圆环的思路。

圆的绘制

思路分析

之前在《从画一个点入手学习 WebGL》中说过,webgl 可绘制的图元,只有点、线段和三角形,其它任何图形都是由它们组成的。当我们要绘制一个圆时,其实就是像下图这样让数量众多的三角形进行拼接:

1.png

本文中,我选择使用顶点法,即 gl.drawArrays(),图元选用三角扇 gl.TRIANGLE_FAN 进行绘制。问题的关键在于获取各个顶点的坐标,我们取下图中的一点 a 作为研究对象:

2.png

假设整个圆是由 8 个三角形拼接而成,那么图中的角 α 就可由 360°/8 得到。设圆心点坐标为 (x, y),半径为 r,则点 a 的 x 轴坐标就为 x+r*cos(α),y 轴坐标就为 y+r*sin(α)。知道了 1 个点的坐标,剩下的无非是通过一个循环就可以获得了:

// 获取各个顶点的坐标
function getPoints(x, y, r) {
  const pointsList = [x, y]
  const angle = (Math.PI * 2) / 8
  for (let i = 0; i <= 8; i++) {
    const pointAngle = i * angle
    const pointX = r * Math.cos(pointAngle) + x
    const pointY = r * Math.sin(pointAngle) + y
    pointsList.push(pointX, pointY)
  }
  return pointsList
}

需要注意下,循环的条件中,i 是小于等于 8 的,等于 8 时获取的这个点,其实就是起点,这样圆才能闭合,而不会缺一块(getPoints()中传入的参数都是在 canvas 坐标系下的,所以下图中的 1 号点位于 0 号点的顺时针方向):

3.png

由上图也可得知,绘制总共需要的顶点的个数为 三角扇的个数 + 2,所以执行绘制时,第 3 个参数应该传 10:

gl.drawArrays(gl.TRIANGLE_FAN, 0, 10)

具体代码

具体代码与效果如下:

圆环的绘制

思路分析

圆环可以看成是一个个梯形拼接而成,每个梯形又可以看成是 2 个三角形拼接而成:

4.png

我们可以选用图元中的三角带 gl.TRIANGLE_STRIP 来绘制,这样只需要传入 4 个顶点的坐标,如上图中的 v0、v1、v2 会绘制出一个三角形, v1、v2、v3 会绘制出另一个三角形,就可以组成一个梯形了。由于两个相邻的梯形之间会有共用的顶点,直接采用顶点法绘制会浪费内存空间,所以我们采用索引法绘制。

获取点

定义方法 getPoints() 获取顶点坐标,参数分别为圆环中心点坐标 xy,内圆半径 innerR,外圆半径 outerR,以及要分割的梯形数量 trapezoidNum

// 分割的梯形数量
const trapezoidNum = 50
// 获取各个顶点的坐标
function getPoints(x, y, innerR, outerR, trapezoidNum) {
  const pointsList = []
  const angle = (Math.PI * 2) / trapezoidNum
  for (let i = 0; i < trapezoidNum; i++) {
    const pointAngle = i * angle
    const innerX = innerR * Math.cos(pointAngle) + x
    const innerY = innerR * Math.sin(pointAngle) + y
    const outerX = outerR * Math.cos(pointAngle) + x
    const outerY = outerR * Math.sin(pointAngle) + y
    pointsList.push(innerX, innerY, outerX, outerY)
  }
  return pointsList
}

获取圆环的顶点和上文中获取圆的顶点差不多,只不过是在传入 pointsList 数组时,是内圆和外圆的顶点坐标交替传入的,这样方便索引数组的定义。另外循环的条件 i 不再需要等于 trapezoidNum,因为获取索引时,最后会传入让圆环闭合所需要的起始点的索引。

获取索引

因为是使用三角带绘制,所以索引数组里的元素只需要按 0、1、2、3、4 这样的顺序传入即可:

function getIndices(trapezoidNum) {
  const indices = []
  for (let i = 0; i < 2 * trapezoidNum; i++) {
    indices.push(i)
  }
  // 圆环闭合的关键,传入起始点的索引
  indices.push(0, 1)
  return indices
}

如果切割成 4 个梯形,则数组应该是 [0,1,2,3,4,5,6,7,0,1]

如果切割成 8 个梯形,则数组应该是 [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0,1]

以此类推,数组元素的最大值,也就是倒数第 3 个元素应该是 2 * trapezoidNum - 1,然后最后 2 个元素为 01 是因为圆环画到最后,闭合的那个梯形中,2 个三角形的顶点会再次用到 v0 和 v1。


最后执行绘制时,传入的第 2 个参数,即绘制需要的顶点个数,即为 2 * trapezoidNum + 2

gl.drawElements(
  gl.TRIANGLE_STRIP,
  2 * trapezoidNum + 2,
  gl.UNSIGNED_BYTE,
  0
)

感谢.gif 点赞.png