three.js渲染智慧城市二(建筑渲染篇)

512 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天

设置渲染的原点

在上一篇中我们讲到如何将经纬度和渲染坐标进行转换,我们都知道经纬度[0,0] 的位置是在非洲几内亚湾,而我们渲染的其实是中国的城市,为了简化,我们自己进行一次转化:将我们需要渲染的经纬度的一个点设置为渲染的原点,然后在后续开发中的遇到的经纬度,先转化为渲染坐标,然后在和原点进行一次距离换算。

//选定杭州的某个点的经纬度
 const data_center=[120.17219180819616,30.247646581986757];
 //计算中心点的位置,也就是原点的相对坐标,getMercator是上节中经纬度转化方法
 const com_center = getMercator(data_center);
 //开发的经纬度和渲染的相对坐标计算
  const getThreePosition=(positonArry)=> {
      const local_position = this.getMercator(positonArry);
      local_position.x -= this.com_center.x;
      local_position.y -= this.com_center.y;
      return local_position;
    }

建筑数据准备

杭州数据源:
gw.alipayobjects.com/os/rmsporta…
这是我找到的杭州建筑数据,部分数据格式如下:

    {
    "type": "FeatureCollection",
    "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
    "features": [
    { "type": "Feature", "properties": { "floor": 2 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 120.196545181928684, 30.254913414850879 ], [ 120.196587916040812, 30.254913555556769 ], [ 120.1965879187985, 30.254946779300354 ], [ 120.196679873650595, 30.254946619826882 ], [ 120.196679871643028, 30.254922432441454 ], [ 120.196878098908684, 30.25492768203145 ], [ 120.196883037303849, 30.254760614569687 ], [ 120.19671699669172, 30.254756741591784 ], [ 120.196717003790695, 30.254779696221956 ], [ 120.19661499594217, 30.25477458790802 ], [ 120.196609908044962, 30.254876578931778 ], [ 120.196545178852986, 30.254876360053494 ], [ 120.196545181928684, 30.254913414850879 ] ] ] ] } },
    { "type": "Feature", "properties": { "floor": 6 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 120.196969105954409, 30.25492744908205 ], [ 120.197119195586595, 30.254927461032878 ], [ 120.197114224173092, 30.254978570322315 ], [ 120.197275099142189, 30.254983553591565 ], [ 120.197275091435102, 30.254890679812995 ], [ 120.197130115077073, 30.254890579232381 ], [ 120.197130110842593, 30.254839554065057 ], [ 120.19696909866822, 30.254839656033688 ], [ 120.196969105954409, 30.25492744908205 ] ] ] ] } }]}

简单的解释下这个数据结构,所有的数据在features中,里面是一个个建筑的信息,properties.floor的值是楼层数也就是建筑物的高度,coordinates[0][0]里面的就是建筑物的几个平面顶点的经纬度,是个N边形。

开始渲染建筑

拿到数据以后我们先写个方法把建筑信息取出来

//data_buildInfos刚刚拿到的json数据,
   data_buildInfos.features.forEach((items, i) => {
   //buildSet渲染的方法(顶点位置,楼层数)
        buildSet(items.geometry.coordinates[0][0], items.properties.floor);
      });

然后编写渲染的代码(项目用的是vue)

  buildSet(positionArry, floor) {
      // 假设1层楼高度为10
      const floorHeight = 10 * floor;
      const cubeGeometry = new THREE.Geometry();
      const vertices = [];
      const faces = [];
      const buttomShape = new THREE.Shape();
      positionArry.forEach((item, i) => {
        // 获取相对原点的位置
        const local_position = this.getThreePosition(item);
        // three画shape的方法,画建筑物的底
        if (i === 0) {
          buttomShape.moveTo(local_position.x, local_position.y);
        } else {
          buttomShape.lineTo(local_position.x, local_position.y);
        }
        // 顶点存起来
        vertices.push(new THREE.Vector3(local_position.x, 0, local_position.y));
        vertices.push(new THREE.Vector3(local_position.x, floorHeight, local_position.y));
        // 画建筑物侧面的三角形,2个三角形就是一个四边形
        if (i === positionArry.length - 1) {
          faces.push(new THREE.Face3(2 * i, 0, 1));
          faces.push(new THREE.Face3(2 * i, 1, 2 * i + 1));
        } else {
          faces.push(new THREE.Face3(2 * i, 2 * i + 2, 2 * i + 3));
          faces.push(new THREE.Face3(2 * i, 2 * i + 3, 2 * i + 1));
        }
      });
      cubeGeometry.vertices = vertices;
      cubeGeometry.faces = faces;
      // 生成法向量,用来贴图
      this.assignUVs(cubeGeometry);
      cubeGeometry.computeFaceNormals();
      // 贴图
      const local_texture = new THREE.TextureLoader().load('./images/casement.png');
      const material12 = new THREE.MeshPhongMaterial({
        map: local_texture,
        side: THREE.DoubleSide,
      });
      const mesh1 = new THREE.Mesh(cubeGeometry, material12);
      mesh1.castShadow = true;
      this.scene.add(mesh1);
      // 允许重复
      local_texture.wrapS = THREE.RepeatWrapping;
      local_texture.wrapT = THREE.RepeatWrapping;
      local_texture.repeat.set(2, Math.ceil(0.5 * floor));
      const material1 = new THREE.MeshBasicMaterial({ color: '#333', side: THREE.DoubleSide });
      const geometry = new THREE.ShapeGeometry(buttomShape);
      const mesh = new THREE.Mesh(geometry, material1);
      mesh.rotateX(Math.PI * 0.5);
      const meshed = mesh.clone();
      this.scene.add(mesh);
      meshed.position.y = floorHeight;
      this.scene.add(meshed);
      local_texture.needsUpdate = true;
    }
    assignUVs(geometry) {
      const faces = geometry.faces;
      geometry.faceVertexUvs[0] = [];
      for (let i = 0; i < faces.length; i++) {
        if (i % 2 === 0) {
          geometry.faceVertexUvs[0].push([
            new THREE.Vector2(0, 0),
            new THREE.Vector2(1, 0),
            new THREE.Vector2(1, 1),
          ]);
        } else {
          geometry.faceVertexUvs[0].push([
            new THREE.Vector2(0, 0),
            new THREE.Vector2(1, 1),
            new THREE.Vector2(0, 1),
          ]);
        }
      }
      geometry.uvsNeedUpdate = true;
    }

这样就渲染好了建筑,下次我们渲染道路和道路的流光。