学数学的程序猿
数学 / 前端开发 / 算法About me
webgl绘制带宽度的线条-将线条三角化后在webgl里绘制
WebGL
2023-03-10 16:53:36

在webgl里绘制带宽度的线条,是初学者遇到的难点之一

使用过canvas的同学应该知道,在canvas里可以通过context.lineWidth的方式设置线宽,比如:

context.lineWidth = 10;

这样就可以绘制一条10px宽度的线条。

但是在webgl里,虽然有类似的方法:gl.lineWidth(),但是仅少数浏览器支持,其中谷歌chrome浏览器就不支持此方法。
webgl支持基础的三角形绘制,要想在webgl里绘制带宽度的线条,就只能通过绘制三角形的方式。
方法有很多,可以在着色器里处理,也可以在js里将三角形顶点计算出来后再绘制。本文就简单记录下第二种线条三角化的方法。

将线段根据线宽三角化


首先看一条线段的情况

如上图所示,A和B是线段的两个端点,A1、A2和B1、B2是由A、B两个端点向垂直于直线的方向平移线宽的一半得出来的。

得到A1、A2和B1、B2后,将A1、A2、B1和A2、B1、B2连接成两个三角形∆1、∆2。
然后将这个两个三角形的顶点数据在webgl里使用TRIANGLES的方式来绘制。

然后看两条线段连接的情况

两条线段连接的时候略微复杂一些,在A和C点出还是跟上面那种情况一样只需要沿着线段AB、BC的垂线方向平移线宽的一半即可。
但是在拐点B点处,点平移的方向变成了B点切线的垂线方向。

最后三角化后是这样的四个三角形。


A点和C点的平移比较简单,我们先看A的平移计算方法,C点跟A点计算方法相同


在这里我们使用向量的运算

A点平移到A1点,因为AA1和AB是垂直的,可以通过计算AB向量的单位向量,旋转90°后得到AA1方向的单位向量,再在这个单位向量上平移h个单位即可得到A1点。

用AB表示AB向量,vAB表示AB向量的单位向量,verticalVAB表示AB单位法向量,其余同理,具体算法步骤为:
向量单位化的算法为 (x / sqrt(x*x + y*y), y / sqrt(x*x + y*y)),即分别用x、y去除以向量的模(长度)。

  1. 计算AB的单位向量vAB = (x, y)
  2. 旋转90°得到AA1的单位向量vAA1 = verticalVAB = (-y, x)
  3. 乘以h得到AA1向量 AA1 = vAA1 * h = (-y * h, x * h)
  4. 假设原点为O,那么计算A1的坐标只需要 OA向量 + AA1向量即可,即 OA1 = OA + vAA1 * h即可得到A1点坐标
  5. A2点跟A1点反向,只要把h改为-h就行,即 OA2 = OA - vAA1 * h即可得到A2点坐标

拐角B点的平移稍微复杂一些

B点平移到B1点,需要计算出BB1的单位向量,乘以h,来得到BB1向量。
使用AB向量的法向量,加上BC向量的法向量可以得到BB1方向上的向量,先单位化再相加和先相加再单位化,都可以得到BB1的单位向量。

  1. 计算AB向量的单位向量 vAB = (x1, y1),计算AB的单位法向量 verticalVAB = (-y1, x1)
  2. 计算BC向量的单位向量 vBC = (x2, y2),计算BC的单位法向量 verticalVBC = (-y2, x2)
  3. 上个步骤的两个法向量相加再单位化得到BB1的单位向量 vBB1 = normalize(verticalVAB + verticalVBC)
  4. 与A点的第4步平移方法相同,OB1 = OB + vBB1 * h即可得到B1点坐标,不同的是h不是线宽的一半了
  5. 如下图所示,线宽的一半为w,AB向量和BB1向量的夹角为θ,那么h = w / sin(θ)
  6. 所以OB1 = OB + vBB1 * (w / sin(θ))
  7. B2跟B1点反向,OB2 = OB - vBB1 * (w / sin(θ))

A1、A2、B1、B2、C1、C2这六个点计算出来后,就可以拿去放到着色器里通过

gl.drawArrays(gl.TRIANGLES, 0, triangles.length * 3);

来绘制了。