2023-02-03 webgl绘制粗线

2023-02-02  本文已影响0人  MrSwilder

一、原文

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的行的支持。听起来很棒,对吧?不是真的。这里有几个问题:

在一些演示中,比如上面的演示,gl.LINES可能是可以接受的,但在大多数情况下,它不适合生产质量的线渲染。

2.2 三角形线

https://mattdesl.github.io/webgl-lines/triangles/index.html

一种常见的替代方法是将一条线分成三角形或三角形条带,然后将其渲染为规则几何体。这样可以最大程度地控制线条,允许端点封口、特定连接、并集(用于重叠的透明区域)等。这也可以导致一些更具创意和有趣的线条渲染,如上面的演示中所示。

实现这一点的一种典型方法是沿路径获取每个点的法线,并向外扩展两边厚度的一半。有关实现,请参见polyline-normals.
。这里讨论斜接背后的数学。

更高级的网格可能需要为端盖、斜角连接、羽化等发出新的几何图形。处理这些边缘情况会变得相当复杂,正如您在Vaser C/C++源代码中看到的那样。

对于抗锯齿,您有几个选项:

注意:斜接线的一个缺点是边缘锋利。当连接两个线段的角度非常尖锐时,斜接长度会朝着无穷大的方向呈指数增长,并在渲染中造成巨大的瑕疵。在某些应用中,这可能不是问题,在其他应用中,当角度过大时,您可能希望限制斜接或回退到另一个连接(即斜面)。

上面的三角形演示使用了挤出多段线,这是一个正在进行的小模块,用于从二维多段线构建三角形网格。最终,它旨在支持圆形连接/封口和适当的斜接限制。

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中的大多数内容一样,有十几种方法可以剥猫皮。上面的演示是用相当低级的抽象实现的,因此您可以了解正在发生的事情,并自行决定下一个应用程序最合适的方法。其他一些可行的方法:

示例使用的模块

进阶阅读

上一篇 下一篇

猜你喜欢

热点阅读