使用 WebGL/WebGPU 这样的更底层 API 绘制时,虽然在大量线段绘制时有明显优势(1000 条线段,使用 SVG),但在实现时却不能直接使用例如 gl.LINES 这样的原生方法。
gl.LINES 存在的问题
在一些场景下,尤其是涉及到地理信息的展示,直接使用原生的 gl.LINES 进行绘制存在一些问题:
线宽无法设置,Chrome 下试图设置 lineWidth 会得到警告,相关 ISSUE :
MDN :As of January 2017 most implementations of WebGL only support a minimum of 1 and a maximum of 1 as the technology they are based on has these same limits.
问题背景
之前在 L7 中的调研及实现方案:https://zhuanlan.zhihu.com/p/59541559
使用 Canvas 2D 或者 SVG 绘制直线时,通常会依据接头
lineJoin
和端点形状lineCap
生成线的 path,然后使用颜色来填充这个 path。除此之外,还支持配置虚线等样式。使用 WebGL/WebGPU 这样的更底层 API 绘制时,虽然在大量线段绘制时有明显优势(1000 条线段,使用 SVG),但在实现时却不能直接使用例如
gl.LINES
这样的原生方法。gl.LINES 存在的问题
在一些场景下,尤其是涉及到地理信息的展示,直接使用原生的 gl.LINES 进行绘制存在一些问题:
lineJoin
以及端点形状lineCap
因此我们得考虑将线段转换成其他几何图形进行绘制。
实现方案
常用的做法是沿线段法线方向进行拉伸后三角化。例如下图中线段两个端点分别沿红色虚线法向向两侧拉伸,形成 4 个顶点,三角化成 2 个三角形。
这里涉及到接头、端点形状、反走样等处理,问题是我们应该在 CPU 还是 GPU 中做?
在 CPU 侧进行预处理
之前在 L7 中选择在 CPU 侧做,所以你会看到考虑接头形状时增加顶点的处理。 这样的好处是,Shader 的写法简单,同时 GPU 内存占用较少(对比下面介绍的 GPU 处理方法)。
而局限性在于,由于顶点的拉伸是在 CPU 中完成,在透视投影下无法保持固定线宽。而这个特性在图场景中却是常见的。
GPU 处理
为了保持一致线宽,投影到屏幕空间后再进行拉伸即可,这需要在 Shader 中完成。由于 WebGL 不支持 Geometry Shader,因此除了当前顶点位置,还需要将前后顶点的位置一并传入顶点数据。这就引出了该方法的一个缺点:GPU 内存占用较多。
基于 Three.js 的实现就是用了这种方法。但并不支持圆角接头。
而 GeoJS 的实现是我认为目前最好的。