一、原文
Drawing Lines is Hard (svbtle.com)
二、翻译
绘制线条听起来可能不像火箭科学那么高科技,但在OpenGL中很难做到,尤其是WebGL。在这里,我探索了一些不同的2D和3D线渲染技术,并附带了一个小画布演示。
源码和示例戳这儿:https://github.com/mattdesl/webgl-lines
2.1、线图元
https://mattdesl.github.io/webgl-lines/native/index.html
WebGL包括对具有gl.LINES、gl.LINE_STRIP和gl.LINE_LOOP的行的支持。听起来很棒,对吧?不是真的。这里有几个问题:
- 驱动程序可能会以稍微不同的方式实现渲染/过滤,并且您可能无法在设备或浏览器之间获得一致的渲染
- 最大线宽取决于驱动程序。例如,运行ANGLE的用户将获得最大值1.0,这是非常无用的。在我的新Yosemite机器上,线宽最大值约为10。
- 无法控制线连接或端帽样式
- 并非所有设备都支持MSAA,大多数浏览器也不支持它用于离屏缓冲区。最后可能会出现锯齿状线条
- 对于虚线/点线,glLineStipple已被弃用,并且在WebGL中不存在
在一些演示中,比如上面的演示,gl.LINES可能是可以接受的,但在大多数情况下,它不适合生产质量的线渲染。
2.2 三角形线
https://mattdesl.github.io/webgl-lines/triangles/index.html
一种常见的替代方法是将一条线分成三角形或三角形条带,然后将其渲染为规则几何体。这样可以最大程度地控制线条,允许端点封口、特定连接、并集(用于重叠的透明区域)等。这也可以导致一些更具创意和有趣的线条渲染,如上面的演示中所示。
实现这一点的一种典型方法是沿路径获取每个点的法线,并向外扩展两边厚度的一半。有关实现,请参见polyline-normals.
。这里讨论斜接背后的数学。
更高级的网格可能需要为端盖、斜角连接、羽化等发出新的几何图形。处理这些边缘情况会变得相当复杂,正如您在Vaser C/C++源代码中看到的那样。
对于抗锯齿,您有几个选项:
- 希望支持MSAA,并且永远不需要将行渲染到屏幕外缓冲区
- 为笔划的羽毛边添加更多三角形(如下图所示)
- 使用纹理查找来渐变alpha;很容易,但规模不大
- 在片段着色器中,根据屏幕空间中笔划的投影比例计算抗锯齿
- 将预过滤的gl.LINES渲染为第二遍,围绕笔划边缘
注意:斜接线的一个缺点是边缘锋利。当连接两个线段的角度非常尖锐时,斜接长度会朝着无穷大的方向呈指数增长,并在渲染中造成巨大的瑕疵。在某些应用中,这可能不是问题,在其他应用中,当角度过大时,您可能希望限制斜接或回退到另一个连接(即斜面)。
上面的三角形演示使用了挤出多段线,这是一个正在进行的小模块,用于从二维多段线构建三角形网格。最终,它旨在支持圆形连接/封口和适当的斜接限制。
2.3、在顶点着色器中扩展线
https://mattdesl.github.io/webgl-lines/expanded/index.html
三角剖分会给代码增加相当多的复杂性,当笔划和连接样式更改时,需要重新构建网格。如果你只想在WebGL中使用简单的粗线条,那就有点过分了。
上面的演示扩展了顶点着色器中的笔划,其中厚度是均匀的。我们为路径中的每个点提交两个顶点,并将线法线和斜接长度作为顶点属性传递。每对都有一个法线(或斜接)翻转,这样两个点就被推离中心,形成一条粗线。
attribute vec2 position;
attribute vec2 normal;
attribute float miter;
uniform mat4 projection;
void main() {
//push the point along its normal by half thickness
vec2 p = position.xy + vec2(normal * thickness/2.0 * miter);
gl_Position = projection * vec4(p, 0.0, 1.0);
}
左侧的内部笔划效果(单击画布以设置其动画)是使用与中心的带符号距离在片段着色器中创建的。我们还可以通过传递distance LongPath作为另一个顶点属性来实现虚线、渐变、光晕和其他效果。
2.4 屏幕空间投影线
前面的演示适用于2D(正交)线,但可能不适合您在3D空间中的设计需求。为了使线具有恒定的厚度而不管3D视图如何,我们需要在将线投影到屏幕空间后展开线。
https://mattdesl.github.io/webgl-lines/projected/index.html
与上一个演示一样,我们需要将每个点提交两次,并使用镜像方向,以便它们彼此远离。然而,我们在顶点着色器中进行计算,而不是计算法线和斜接长度CPU侧。为此,我们需要沿路径发送下一个和上一个位置的顶点属性。
在顶点着色器中,我们计算屏幕空间中的连接和拉伸,以确保恒定的厚度。为了在屏幕空间中工作,我们需要使用虚幻的同质组件W。也称为“透视分割”。这为我们提供了归一化设备坐标(NDC),其范围为[-1,1]。然后,我们在扩展线之前修正纵横比。我们也对路径上的上一个和下一个位置执行相同的操作:
mat4 projViewModel = projection * view * model;
//into clip space
vec4 currentProjected = projViewModel * vec4(position, 1.0);
//into NDC space [-1 .. 1]
vec2 currentScreen = currentProjected.xy / currentProjected.w;
//correct for aspect ratio (screenWidth / screenHeight)
currentScreen.x *= aspect;
对于路径中的第一个点和最后一个点,需要处理一些边缘情况,否则,一个简单的线段可能如下所示:
//normal of line (B - A)
vec2 dir = normalize(nextScreen - currentScreen);
vec2 normal = vec2(-dir.y, dir.x);
//extrude from center & correct aspect ratio
normal *= thickness/2.0;
normal.x /= aspect;
//offset by the direction of this point in the pair (-1 or 1)
vec4 offset = vec4(normal * direction, 0.0, 0.0);
gl_Position = currentProjected + offset;
请注意,这里没有尝试连接两个线段。这种方法有时比斜接更可取,因为它不处理尖锐边缘的问题。上述演示中的扭曲圆没有使用任何斜接。
另一方面,演示中的沙漏形状在没有斜接的情况下看起来会被挤压变形。为此,顶点着色器实现了基本的斜接,没有任何限制。
我们可以对数学做一些细微的修改,以实现不同的设计。例如,使用NDC的Z分量在线条深入场景时缩放线条的厚度。这将有助于提高深度感。
For a ThreeJS implementation of this approach, see THREE.MeshLine by @thespite.
其他相关的
与WebGL中的大多数内容一样,有十几种方法可以剥猫皮。上面的演示是用相当低级的抽象实现的,因此您可以了解正在发生的事情,并自行决定下一个应用程序最合适的方法。其他一些可行的方法:
-
Stencil Geometry
使用模板缓冲区创建复杂多边形而无需三角剖分的一个很酷的技巧。然而,它根本没有收到任何MSAA。[1][2][3] -
Loop-Blinn Curve Rendering
分辨率无关的三次样条曲线渲染,非常适合字体字形。 -
Rasterized strokes
光栅化笔划 -
Single Pass Wireframe Rendering
类似于投影线演示,但更适合3D线框[1] -
Geometry Shaders
这将允许投影线发出各种封口/连接。但是,WebGL不支持几何体着色器。 -
Analytic Distance Fields
在片段着色器中使用单个四边形和距离场也可以实现2D和3D中的粗抗锯齿线。这不是很实用,可能表现不佳,但也可以实现一些有趣的效果(如运动模糊)。
网友评论