Three.js 之自定义几何体(1)

自定义几何体
查看demo

背景

最近业余在折腾Three.js,其中碰到一个问题是如何在给定的一组三维坐标点,画出一个自定义几何体。

基础知识

  • 对三维空间有所了解。
  • 知道如何运行一个Three.js项目。
  • 了解Three.js的代码结构。

说明

  • 本文是在描述使用Three.js来自定义几何体。
  • 以下统称Three.js为Three。
  • 建议在PC端阅读。

目的

查阅多篇文章后觉得国内很少有写自定义形状的文章,通过文字和代码说明让读者可了解到通过Three.js自定义一个形状;以此记录此文供后来人参考。

在纸上画一个长方体

我们使用铅笔在一个三维坐标上画一个长方体时需要知道8个点,如下蓝色坐标点分别时A、B、C、D、E、F、G、H ; 三维坐标中的8个点

我们使用红色铅笔按照顺序链接8个点,如下就形成了长方体的结构。 三维坐标中的8个点

其实在连线时,我们可以将每个面的四个点,拆分为两两组合的三角形; 如A、B、D、C这个面可以拆分为ABD、BDC,见如下图(1)、(2) 三维坐标中的8个点 图(1) 三维坐标中的8个点 图(2)

然后我们再在长方体的面上涂一层颜色。 三维坐标中的8个点

以上我们就通过铅笔画出了一个长方体。
总结:3个点形成三角形,多个三角形形成面,多个面形成几何体。请继续往下看,你会发现Three中画几何体是一样的。

使用Three画一个长方体

在Three中,有一个类叫THREE.Geometry,是THREE的核心类,所有的几何图形都是基于THREE.Geometry扩展,看官方说明

三维坐标中的8个点 以上说明是THREE.Geometry可直接自定义几何形状,用编码易用性来降低了性能,需要性能高的可使用BufferGeometries。

  1. 首先实例一个几何图形。
    let geometry = new THREE.Geometry();
  2. 然后我们准备好几个顶点,并将顶点存入到vertices数组中,也就是我们需要将所有顶点存放到vertices中。
    let A = new THREE.Vector3(0, 0, 0.5);
    geometry.vertices.push(A);
    let B = new THREE.Vector3(1, 0, 0.5);
    geometry.vertices.push(B);
    let C = new THREE.Vector3(0, 0.5, 0.5);
    geometry.vertices.push(C);
    let D = new THREE.Vector3(1, 0.5, 0.5);
    geometry.vertices.push(D);
    let E = new THREE.Vector3(1, 0, -0.5);
    geometry.vertices.push(E);
    let F = new THREE.Vector3(1, 0.5, -0.5);
    geometry.vertices.push(F);
    let G = new THREE.Vector3(0, 0.5, -0.5);
    geometry.vertices.push(G);
    let H = new THREE.Vector3(0, 0, -0.5);
    geometry.vertices.push(H);

    由于vertices 是一个数组,A、B、C、D、E、F、G、H所在索引分别为0、1、2、3、4、5、6、7。

  3. 我们使用点对应的索引定义三角形,多个三角形形成面;如下面的代码形成ABDC面。
    // ABC三角形
    let ABC = new THREE.Face3(0, 1, 2); // 其中 0, 1, 2是三个点在vertices中的索引
    geometry.faces.push(ABC);
    
    // BDC三角形
    let BDC = new THREE.Face3(1, 3, 2); // 其中 1, 3, 2是三个点在vertices中的索引
    geometry.faces.push(BDC);

    通过以上两个三角形,我们可以组合成一个四边形ABDC面。

    如上操作BDFE、DCGF等剩余面,我们就可以得到长方体的六个面;从而配置成了一个长方体。

  4. 我们再将画出来的长方体定位在坐标系中心
    geometry.center();
  5. 最后我们给这个物体上颜色,类似铅笔涂颜色一样,这里使用具有光照效果的MeshLambertMaterial。
    let material = new THREE.MeshLambertMaterial({
        side: THREE.DoubleSide, //两面可见
        color: 0xdfdfdf,  //颜色
    });
    this.customCube = new THREE.Mesh(geometry, material);

demo完整代码

<body>
  <style type="text/css">
      html, body, canvas{
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
  <canvas></canvas>
    <script src="https://cdn.bootcss.com/three.js/91/three.min.js"></script>
    <script type="text/javascript">
      (function(){
        let canvas = document.querySelector('canvas');

        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;


        let renderer = new THREE.WebGLRenderer({
              context: canvas.getContext("webgl"),
              antialias: true
          });
          renderer.shadowMap.enabled = true;
          renderer.shadowMap.type = THREE.PCFSoftShadowMap;
          renderer.setSize(canvas.width, canvas.height);
          renderer.setClearColor(0xffffff, 1);

          scene = new THREE.Scene();
          scene.background = new THREE.Color(0xaaaaaa);
      let AxesHelper = new THREE.AxesHelper(5);
          scene.add(AxesHelper);

      let camera = new THREE.OrthographicCamera(
        -2,
        2,
        canvas.height/canvas.width * 2,
        -canvas.height/canvas.width* 2,
        0.2,
        100
      );
      camera.position.set(5, 5, 20);
      camera.lookAt(scene.position);
      scene.add(camera);

      let directionalLight = new THREE.DirectionalLight(0xffffff, 1, 100);
      directionalLight.position.set(0, 0, 2);
      scene.add(directionalLight);

      // 自定义几何体
      let geometry = new THREE.Geometry();
      let A = new THREE.Vector3(0, 0, 0.5);
      geometry.vertices.push(A);
      let B = new THREE.Vector3(1, 0, 0.5);
      geometry.vertices.push(B);
      let C = new THREE.Vector3(0, 0.5, 0.5);
      geometry.vertices.push(C);
      let D = new THREE.Vector3(1, 0.5, 0.5);
      geometry.vertices.push(D);
      let E = new THREE.Vector3(1, 0, -0.5);
      geometry.vertices.push(E);
      let F = new THREE.Vector3(1, 0.5, -0.5);
      geometry.vertices.push(F);
      let G = new THREE.Vector3(0, 0.5, -0.5);
      geometry.vertices.push(G);
      let H = new THREE.Vector3(0, 0, -0.5);
      geometry.vertices.push(H);

      // [A,B,C,D,E,F,G,H]
      // [0,1,2,3,4,5,6,7]
      // 前面
      let ABC = new THREE.Face3(0, 1, 2); // 其中 0, 1, 2是三个点在vertices中的索引
      geometry.faces.push(ABC);
      let CDB = new THREE.Face3(2,3,1);
      geometry.faces.push(CDB);
      // 后面
      let EFG = new THREE.Face3(4,5,6);
      geometry.faces.push(EFG); 
      let EHG = new THREE.Face3(4,7,6);
      geometry.faces.push(EHG); 
      // 上面
      let CDF = new THREE.Face3(2,3,5);
      geometry.faces.push(CDF); 
      let CFG = new THREE.Face3(2,5,6);
      geometry.faces.push(CFG); 
      // 下面
      let ABE = new THREE.Face3(0,1,4);
      geometry.faces.push(ABE); 
      let AHE = new THREE.Face3(0,7,4);
      geometry.faces.push(AHE); 
      // 右侧面
      let BDE = new THREE.Face3(1,3,4);
      geometry.faces.push(BDE); 
      let DFE = new THREE.Face3(3,5,4);
      geometry.faces.push(DFE); 
      // 左侧面
      let ACH = new THREE.Face3(0,2,7);
      geometry.faces.push(ACH); 
      let CGH = new THREE.Face3(2,6,7);
      geometry.faces.push(CGH); 

      // 让geometry在坐标系居中
          geometry.center()

      // 计算出颜色,使得geometry在光照下能看到多个面
      geometry.computeFaceNormals();

      // 开始渲染
      let material = new THREE.MeshLambertMaterial({
          side: THREE.DoubleSide, //两面可见
          color: 0xdfdfdf, //三角面颜色
      });
      let customCube = new THREE.Mesh(geometry, material);
      scene.add(customCube);

      renderer.render(scene, camera);

      animate(); // 让自定义的几何体动起来

      function animate() {
        customCube.rotation.x += 0.01;
        customCube.rotation.y += 0.02;
        customCube.rotation.z += 0.01;
        renderer.render(scene, camera);
        window.requestAnimationFrame(animate);
      }

      })();
    </script>
</body>

知道以上方法后你可以在三维空间画想要的多面几何体。并结合JS循环可减少上面的代码量。

以上内容如有错误请指正。

This article is original, licensed under a Creative Commons Attribution 4.0 International license