如何创建带有宽度和厚度的Three.js 3D线系列?

14

有没有一种方法可以创建带有宽度和厚度的Three.js 3D线系列?

尽管Three.js线条对象支持linewidth属性,但在WebGL上并不是所有浏览器平台都支持该属性。

以下是您在Three.js中设置linewidth的位置:

    var material = new THREE.LineBasicMaterial({
        color: 0xff0000,
        linewidth: 5
    });

最近,Three.js中带有宽度的缎带对象已被删除。

Three.js的管道对象生成3D挤出物,但基于Bezier,线条不会通过控制点。

有人能想到一种在Three.js中绘制具有某种用户定义的“体积”的线系列(折线、曲线)的方法,例如宽度、厚度或半径吗?

这个问题可能是这个问题的重新陈述: Extruding a graph in three.js

考虑到我认为没有现成的方法,我很乐意参与创建一个简单的函数来回答这个问题。

但是,如果有现有的可行方法,请指出...

正如WestLangley建议的那样,一种可能的解决方案包括折线具有恒定的像素宽度——就像Three.js画布渲染器目前提供的那样。

这里显示了两个渲染器的比较:

Canvas and WebGL Lines Compared via GitHub Pages

通过jsFiddle比较Canvas和WebGL的线条

enter image description here

一个解决方案,能够指定线宽并在两个渲染器上产生类似的结果,将非常不错。
然而,还有其他方式可以考虑3D线条,其中线条具有实际的物理结构。它们会投下阴影,对事件做出响应。这些也需要进行研究。
以下是两个由多个网格组成的线示例的 GitHub 页面链接:

Spheres and Cubes Polyline

球和圆柱折线

这是一种“昂贵的解决方案。每个接头都由完整的球体组成。

Cubes Polylines

立方体折线

我猜想,将它们构建为平滑的单个网格可能会很复杂,需要解决各种问题。因此,在此期间,这里有一个链接,可以部分可视化具有宽度和高度的3D线:

在jsFiddle上的3D盒子线

3d box lines

目标是以低复杂度的方式编写代码,换句话说,面向菜鸟。因此,一个3D线条应该像添加球体或立方体一样简单易懂。几何+材质=网格>场景。而且几何应该在创建顶点和面时相当经济。
这些线条应该有宽度和高度。向上始终是Y方向。演示展示了这一点。但演示没有展示如何优雅地将角落连接起来...

一个分段线性的 TubeGeometry 是否足够?(您需要避免尖角。) - WestLangley
+1 支持这个——我们一直需要它,因为在 Windows 中行没有宽度。 - Jason Grout
非常有趣。在我的Android Nexus 5手机上,两个版本的线条都是相同的 - 可能会有几个像素宽,肯定比1像素宽。而且我无法改变宽度。在我的Chromebook Pixel上,两个版本的线条大约有5个像素宽 - 我也无法改变宽度。在我的Lenovo Nvidia GPU Win 8.1上使用Chrome,画布线条比WebGL粗得多,我可以编辑宽度。IE 11和最新的FF也是如此 - 但滑块不会更新宽度。真是一团糟! - Theo
我在代码中修复了一个问题。在Chromebook和Android Nexus手机上,使用canvas和WebGL渲染器都可以更新线条宽度。但在Windows系统中,使用三种浏览器时只有canvas渲染器会改变宽度,而WebGL渲染器中的线条仍保持一像素不变。 - Theo
@WestLangley,我已经添加了两个演示。第一个是“Canvas和WebGL线条”的jsFiddle版本,第二个标题为“3D Box lines” - 这是我们可以开始考虑放置一些实际代码来回答原始问题的地方。另外但相关的是,关于画布和WebGL线条演示,您提到演示在OSX上运行。在Mac上它适用于哪些浏览器?顺便说一下,我还注意到该演示在我的Android Nexus 5、Ubuntu 13.04上使用FF和两个Chromebook上运行。但不适用于Win 8.1。 - Theo
显示剩余11条评论
2个回答

9
我想出了一个可能的解决方案,我相信它满足你大部分的要求:

http://codepen.io/garciahurtado/pen/AGEsf?editors=001

enter image description here

这个概念非常简单:以“线框模式”渲染任意几何形状,然后应用全屏GLSL着色器来增加线框的厚度。
该着色器受到ThreeJS发行版中模糊着色器的启发,它基本上沿水平和垂直轴复制图像多次。我自动化了这个过程,并使副本数量成为用户定义参数,同时确保副本偏移1个像素。
在我的演示中,我使用了一个3D立方体网格(带有正交相机),但将其转换为折线应该很容易。
这个东西的真正精华在于自定义着色器(片段着色器部分):
    uniform sampler2D tDiffuse;
    uniform int edgeWidth;
    uniform int diagOffset;
    uniform float totalWidth;
    uniform float totalHeight;
    const int MAX_LINE_WIDTH = 30; // Needed due to weird limitations in GLSL around for loops
    varying vec2 vUv;

    void main() {
        int offset = int( floor(float(edgeWidth) / float(2) + 0.5) );
        vec4 color = vec4( 0.0, 0.0, 0.0, 0.0);

        // Horizontal copies of the wireframe first
        for (int i = 0; i < MAX_LINE_WIDTH; i++) {
            float uvFactor = (float(1) / totalWidth);
            float newUvX = vUv.x + float(i - offset) * uvFactor;
            float newUvY = vUv.y + (float(i - offset) * float(diagOffset) ) * uvFactor;  // only modifies vUv.y if diagOffset > 0
            color = max(color, texture2D( tDiffuse, vec2( newUvX,  newUvY  ) ));    
            // GLSL does not allow loop comparisons against dynamic variables. Workaround below
            if(i == edgeWidth) break; 
        }

        // Now we create the vertical copies
        for (int i = 0; i < MAX_LINE_WIDTH; i++) {
            float uvFactor = (float(1) / totalHeight);
            float newUvX = vUv.x + (float(i - offset) * float(-diagOffset) ) * uvFactor; // only modifies vUv.x if diagOffset > 0
            float newUvY = vUv.y + float(i - offset) * uvFactor;
            color = max(color, texture2D( tDiffuse, vec2( newUvX, newUvY ) ));  
            if(i == edgeWidth) break;
        }

        gl_FragColor = color;
    }

优点:

  • 除了线顶点之外,不需要额外的几何图形
  • 线条粗细可由用户定义
  • 全屏着色器对GPU影响较小
  • 可以完全在WebGL画布内实现

缺点:

  • 水平和垂直边缘的线条粗细接近像素级别,但对角线边缘略有偏差。这是由于使用的算法造成的,是解决方案的局限性。话虽如此,对于低线条粗细和复杂几何图形,肉眼几乎无法察觉。
  • 线条之间的连接处在足够粗的线条宽度下会显示间隙。您可以尝试Codepen演示以了解我的意思。我开始通过添加第二个“对角线通道”来解决这个问题,但变得有些复杂,我认为这只会在更高的线条粗细(+8像素)或极端线条角度下出现问题。如果您对此解决方案感兴趣,可以查看原始源代码以了解我的想法。
  • 由于这使用全屏滤镜,因此您只能使用WebGL上下文来显示具有此粗细度的对象。显示各种线宽将需要额外的渲染通道。

我不喜欢发表对话式评论,但是哇,这真的是一个非常好的回答,超出了预期。正反面的分析是一个很好的补充。希望我能给你更多的赞! - Jacksonkr

2
作为一种潜在的解决方案,您可以获取您的3D点,然后使用THREE.Vector3.project方法来计算屏幕空间坐标。然后简单地使用canvas及其lineTomoveTo操作。Canvas 2d上下文支持可变线条粗细。
var w = renderer.domElement.innerWidth;
var h = renderer.domElement.innerHeight;
vector.project(camera);
context2d.lineWidth = 3;
var x = (vector.x+1)*(w/2);
var y = h - (vector.y+1)*(h/2);
context2d.lineTo(x,y);

此外,我认为您不能使用同一个画布,因此必须在gl渲染上下文画布上方添加一个图层(另一个画布)。
如果您很少更改相机,则也可以构建由多边形组成的线,并根据相机变换更新其顶点位置。对于正交相机,这会最好地工作,因为只有旋转需要顶点位置操作。
最后,您可以禁用画布清除,并在圆圈或框内多次绘制您的线条。之后,您可以重新启用清除。这将需要几个额外的绘制操作,但这可能是最可扩展的方法。
线条不按预期工作的原因是ANGLE的工作方式,它在Chrome和Firefox中使用,通过DirectX模拟OpenGL。ANGLE的开发人员表示,WebGL规范仅要求支持1以下的线条粗细度,因此他们不认为这是一个bug,也没有打算“修复”它。然而,线条粗细应该在未使用ANGLE的非Windows操作系统上工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接