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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20738386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 19:10:44  来源:igfitidea点击:

How to create a Three.js 3D line series with width and thickness?

javascriptthree.jsgeometrylines

提问by Theo

Is there a way to create a Three.js 3D line series with width and thickness?

有没有办法创建一个具有宽度和厚度的 Three.js 3D 线系列?

Even though the Three.js line object supports linewidth, this attribute is not yet supported in all browsers on all platforms in WebGL.

尽管 Three.js line 对象支持 linewidth,但在 WebGL 的所有平台上的所有浏览器中尚不支持此属性。

Here's where you set linewidth in Three.js:

这是您在 Three.js 中设置线宽的地方:

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

The Three.js ribbon object - which had width - has recently been dropped.

Three.js 功能区对象 - 具有宽度 - 最近已被删除。

The Three.js tube object generates 3D extrusions but - being Bezier-based - the lines do not pass through the control points.

Three.js 管对象生成 3D 挤压,但 - 基于贝塞尔 - 线不通过控制点。

Can anybody think of a method of drawing a line series (polylines, plotlines) in Three.js that has some sort of user definable 'bulk' such as width, thickness or radius?

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

This question may be a restating of this question: Extruding a graph in three.js.

这个问题可能是对这个问题的重述: Extruding a graph inthree.js

Given that I do not think that there is a readily available method, I would be happy to participate in an effort to create a simple function that responds to this question.

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

But a response that points to an existing workable method would be cool...

但是指向现有可行方法的回应会很酷......

As WestLangley suggests, one possible solution includes the polyline being of constant pixel width - as is currently available with the Three.js canvas renderer.

正如 WestLangley 所建议的,一种可能的解决方案包括具有恒定像素宽度的折线 - 目前可用于 Three.js 画布渲染器。

A comparison of the two renderers is shown here:

两个渲染器的比较如下所示:

Canvas and WebGL Lines Compared via GitHub Pages

通过 GitHub Pages 比较 Canvas 和 WebGL 行

Canvas and WebGL Lines Compared via jsFiddle

通过 jsFiddle 比较 Canvas 和 WebGL 行

enter image description here

在此处输入图片说明

A solution where you could specify linewidth and similar results occurred on both renderers would be very cool.

一个可以指定线宽并且在两个渲染器上出现类似结果的解决方案会非常酷。

There are, however, other ways of thinking of 3D lines where lines have actual physical constructs. They cast shadows, they respond to events. These also need to be looked into.

然而,对于 3D 线,还有其他思考方式,其中线具有实际的物理构造。他们投下阴影,他们对事件做出反应。这些也需要研究。

Here are links to GitHub Pages with two demos of lines made up of multiple meshes:

以下是 GitHub Pages 的链接,其中包含由多个网格组成的两个线条演示:

Spheres and Cubes Polyline

球体和立方体折线

Sphere and Cylinder Polylines

球体和圆柱体多段线

An 'expensive solution. Each joint is made up of a full sphere.

一个'昂贵的解决方案。每个关节都由一个完整的球体组成。

Cubes Polylines

立方体折线

Cubes Polylines

立方体折线

My guess is that building either of these as smooth single meshes will be complex to problems to solve. So in the meantime here is a link to a partial visualization of 3D lines that are wide and have height:

我的猜测是,将其中任何一个构建为平滑的单个网格对于解决问题来说都很复杂。所以与此同时,这里有一个链接,指向宽高的 3D 线的部分可视化:

3D Box Line on jsFiddle

jsFiddle 上的 3D 框线

3d box lines

3d 框线

The goal is have to code 'with a low level of complexity - in other words - for dummies'. Thus a 3D line should be as easy and as familiar as adding a sphere or cube. Geometry + material = mesh > scene. And the geometry should be quite economical in terms of creating vertices and faces.

目标是必须“以低复杂度 - 换句话说 - 为傻瓜编码”。因此,3D 线应该像添加球体或立方体一样简单和熟悉。几何体 + 材质 = 网格 > 场景。并且几何体在创建顶点和面方面应该非常经济。

The lines should have width and height. Up is always in the Y direction. The demo shows this. What the demo does not show is corners being mitred nicely...

线条应该有宽度和高度。向上总是在 Y 方向。该演示显示了这一点。演示没有显示的是角落被很好地斜接......

回答by Garcia Hurtado

I cooked up a possible solution which I believe meets most of your requirements:

我制定了一个可能的解决方案,我相信它可以满足您的大部分要求:

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

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

enter image description here

在此处输入图片说明

The concept is fairly simple: render any arbitrary geometry in "wireframe mode", then apply a full screen GLSL shader to it to add thickness to the wireframe lines.

这个概念相当简单:在“线框模式”中渲染任意几何图形,然后对其应用全屏 GLSL 着色器以增加线框线条的粗细。

The shader is inspired by the blur shaders in the ThreeJS distro, which essentially copy the image a bunch of times along the horizontal and vertical axis. I automated that process and made the number of copies a user defined parameter, while ensuring that the copies were offset by 1 pixel.

着色器的灵感来自 ThreeJS 发行版中的模糊着色器,它本质上是沿着水平和垂直轴多次复制图像。我自动化了这个过程,并将副本数量设为用户定义的参数,同时确保副本偏移 1 个像素。

I used a 3D cube mesh in my demo (with an ortho camera), but it should be trivial to convert it to a poly line.

我在演示中使用了 3D 立方体网格(使用正射相机),但将其转换为折线应该很简单。

The real meat and potatoes of this thing is in the custom shader (fragment shader portion):

这东西真正的肉和土豆在自定义着色器(片段着色器部分)中:

    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;
    }

Pros:

优点:

  • No need for additional geometry beyond the line vertices
  • Line thickness is user definable
  • A full screen shader should be relatively gentle on the GPU
  • Can be implemented fully within the WebGL canvas
  • 不需要超过线顶点的额外几何体
  • 线条粗细可由用户定义
  • 全屏着色器在 GPU 上应该相对温和
  • 可以在 WebGL 画布中完全实现

Cons:

缺点:

  • Line thickness is close to pixel perfect on horizontal and vertical edges, but slightly off on diagonal edges. This is due to the algorithm used and is a limitation of the solution. Having said that, for low line thickness and complex geometries, this is barely noticeable with the naked eye.
  • The joints between lines will show gaps for large enough line thickness. You can play with the Codepen demo to see what I mean. I started to implement a solution to this by adding a second "diagonal pass", but it got a little hairy and I think this would only be an issue for higher line thicknesses (+8 pixels) or extreme line angles. If you are interested in this solution, you can look at the original source to see where I was going with it.
  • Since this uses a full screen filter, you can only use the WebGL context for displaying objects of this thickness. Showing various line widths would require additional rendering passes.
  • 水平和垂直边缘的线条粗细接近像素完美,但在对角线边缘略微偏离。这是由于所使用的算法造成的,并且是解决方案的限制。话虽如此,对于低线宽和复杂的几何形状,肉眼几乎无法察觉。
  • 线条之间的接头将显示足够大的线条粗细的间隙。您可以使用 Codepen 演示来了解我的意思。我开始通过添加第二个“对角线通道”来解决这个问题,但它有点毛茸茸的,我认为这只会是更高线粗(+8 像素)或极端线角的问题。如果您对此解决方案感兴趣,可以查看原始来源以了解我的用途。
  • 由于这使用了全屏过滤器,因此您只能使用 WebGL 上下文来显示此厚度的对象。显示各种线宽需要额外的渲染过程。

回答by travnik

As a potential solution. You could take your 3d points, then use THREE.Vector3.projectmethod to figure out screen-space coordinates. Then simply use canvas and it's lineToand moveTooperations. Canvas 2d context does support variable line thickness.

作为一个潜在的解决方案。您可以获取 3d 点,然后使用THREE.Vector3.project方法来计算屏幕空间坐标。然后简单地使用画布和它的lineTomoveTo操作。画布 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);

Also, i don't think you can use the same canvas for that, so it would have to be a layer (another canvas) above your gl rendering context canvas.

另外,我认为您不能为此使用相同的画布,因此它必须是 gl 渲染上下文画布上方的一个图层(另一个画布)。

If you have infrequent camera changes - it is also possible to construct line out of polygons and update it's vertex positions based on camera transform. For orthographic camera this would work best as only rotations would require vertex position manipulation.

如果您不经常更换相机 - 也可以用多边形构造线并根据相机变换更新它的顶点位置。对于正交相机,这将最有效,因为只有旋转需要顶点位置操作。

Lastly, you could disable canvas clearing and draw your lines several times with offset inside a circle or a box. After that you can re-enable clearing. This would require several extra draw operations, but it's probably the most scalable approach.

最后,您可以禁用画布清除并在圆圈或框内偏移多次绘制线条。之后,您可以重新启用清除。这将需要一些额外的绘制操作,但它可能是最具可扩展性的方法。

The reason lines don't work as you'd expect out of the box is due to how ANGLE works, it's used in Chrome and in Firefox to my knowledge, it emulates OpenGL via DirectX. Guys from ANGLE state that WebGL spec only requires support of line thickness up-to 1, so they do not see it as a bug and don't intend to "fix" it. Line thickness should work on non-windows OSs though, where ANGLE is not used.

线条不能像您期望的那样开箱即用的原因是由于 ANGLE 的工作方式,据我所知,它在 Chrome 和 Firefox 中使用,它通过 DirectX 模拟 OpenGL。来自 ANGLE 的人表示 WebGL 规范只需要支持高达 1 的线宽,所以他们不认为这是一个错误,也不打算“修复”它。线条粗细应该适用于不使用 ANGLE 的非 Windows 操作系统。