• WebGL管网展示(及TubeGeometry优化)


    前言

    管路展示在三维场景中很常见。比如地下管网,建筑里面的水果,暖通管道等等的展示。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VIaL8EZK-1659517949929)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/832b7acc7a8d4b8a8e4a8e2f4b3fdb90~tplv-k3u1fbpfcp-watermark.image?)]
    建立管路的方式主要两种:

    • 通过3DMax C4D Blender等建模工具进行建模。
    • 通过路径数据,程序生成三维管路。

    如果需要动态的通过数据生成管路,只能采用第二种方式来生成。

    生成管路的方式

    在THREE中,通过TubeGeometry可以生成管路。
    TubeGeometry可以沿着一条三维样条曲线拉伸出一根管子。可以通过指定顶点来定义路径。创建TubeGeometry管道几何体时可输入以下参数:

    • path(必填)该属性用一个THREE.SplineCurve对象来指定管道遵循的路径
    • segments 该属性指定管道的分段数,路径越长指定的段数应该越多,默认值是64
    • radius 该属性指定管道的半径,默认值是1
    • radiusSegments 否 该属性指定管道截面的分段数,段数越多管道看上去越光滑,默认值是8
    • closed
    • 该属性来设置管道是否收尾连接,默认值false

    比如:

    const points = [{
              x: 0,
              y: 0,
              z: 0
            },
            {
              x: 100,
              y: 0,
              z: 0
            },
            {
              x: 100,
              y: 0,
              z: 200
            },
          ];
          
      const path = createPath(points);
    
      const pipeGeometry = new THREE.TubeGeometry(path, 256, radius, 64);
      const pipeMaterial = new THREE.MeshPhongMaterial({
        color
      });
      const pipe = new THREE.Mesh(pipeGeometry, pipeMaterial);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    效果如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVYbiLeQ-1659517949932)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/854ff4a89e4d4be8bccd83cc22bff0e5~tplv-k3u1fbpfcp-watermark.image?)]

    TubeGeometry的优化

    TubeGeometry 通过指定一个path路径和分段数来创建管路的几何数据。TubeGeometry的分段是平均分配,这会导致一个问题,如果路径很长很复杂,分段数量需要需要很大才能保证几何体的准确性。 然而,管路大部分都是直线,只有少量的弯曲的管路。 如果一条直线管路,其实只需取路径的起始点和介绍点就可以对路径进行完整的描述,只有弯曲的管路,才需要把路径分成很多端,分别取每一个分段点的数据,才能较好的完成曲线的描述。 如果采用平均分段的方式,势必有很多分段落在直线的部分,导致资源的浪费,同时分段数量要求高,创建的几何数据会比较庞大臃肿,影响程序的加载、绘制效率、显存等等。 比如,以下系列点都组成两条直线:

    const points = [{
              x: 0,
              y: 0,
              z: 0
            },
            {
              x: 100,
              y: 0,
              z: 0
            },
            {
              x: 100,
              y: 0,
              z: 200
            },
          ];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    分段数是4的时候:

    new THREE.TubeGeometry(path, 4, radius, 64);
    
    • 1

    得到的是如下结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kGAilvdT-1659517949933)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4feb79b374774800a96c7220499aea46~tplv-k3u1fbpfcp-watermark.image?)]

    本来两段直线,变成了3段直线的效果,原因就是分片数量不够:
    增加分段数:

    new THREE.TubeGeometry(path, 256, radius, 64);
    
    • 1

    效果如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quD8fAg2-1659517949934)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9c9a5d93026a4f5ca8f7e0f34243b4b9~tplv-k3u1fbpfcp-watermark.image?)]

    为了绘制两条直线组成的路径,需要256的分段数量,顶点数量可想而知。

    所以我们可以有一种优化思路,对于路径的直线部分,不需要分段,只需要去起始点和终点即可,分段数只分配给曲线的部分。 这样的分段方式,可以是分段的数量得到最合理的应用。 基于此,我们改造出一个新的类:PathTubeGeometry。
    改造后的类,绘制上述两条直线,不需要分片数(分片数量为1),如下:

    new PathTubeGeometry(path, 1, radius, 64);
    
    • 1

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZNviGHIS-1659517949934)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4d5b8450ff66429db5df81551544d787~tplv-k3u1fbpfcp-watermark.image?)]

    极大的节约了资源。

    拐角弯管

    两条直线连接的地方,可以通过加上一点圆角的效果来增加管路的美观度,通过下面代码可以自动根据已有路径生成带弯管的路径。

     function getRoundCornerPath(oldPath, radius, path = new Path3D()) {
          let points = oldPath.points;
          points = removeDup(points);
          let p0 = points[0];
          path.moveTo(p0);
          for (let i = 1; i < points.length - 1; i++) {
            let pre = points[i - 1],
              p = points[i],
              next = points[i + 1];
            let sub1 = p.clone().sub(pre).setLength(radius),
              sub2 = next.clone().sub(p).setLength(radius);
            let v1 = p.clone().sub(sub1),
              v2 = p.clone().add(sub2);
            path.lineTo(v1);
            path.curveTo(p.x, p.y, p.z, v2.x, v2.y, v2.z);
          }
          let pl = points[points.length - 1];
          path.lineTo(pl);
          return path;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    结语

    关注公号“ITMan彪叔” 可以添加作者微信进行交流,及时收到更多有价值的文章。

  • 相关阅读:
    关于iview select 绑定两个值的方法
    带你走进Cflow (一)
    2023年CCF非专业级别软件能力认证第二轮 (CSP-J)入门级C++语言试题
    C语言:static,volatile,const,extern,register,auto, 栈stack结构
    Docker 启动alpine镜像中可执行程序文件遇到 not found
    vuecli项目实战--管理系统
    python面试题三
    【机器学习】特征工程之特征选择
    提高倾斜摄影三维模型顶层合并构建效率的技术方法初探
    Indeterminate form
  • 原文地址:https://blog.csdn.net/netcy/article/details/126144580