Three.js 优化之合并 Mesh(1)

参考:
Three.js Optimize Lots of Objects

Three.js 优化之合并 Mesh(1)

Three.js 优化方向

  • 模型不应该太大,添加到场景的时间太长
  • 模型loader可以使用woker优化
  • 合并 mesh

接下来使用合并 mesh的方式做一个例子:世界人口可视化:World Population

最终效果:
在这里插入图片描述

1、数据源

世界人口公开数据出处

数据格式:

ParameterDescriptionRequirements
NCOLSNumber of cell columns.Integer greater than 0.
NROWSNumber of cell rows.Integer greater than 0.
XLLCENTER or XLLCORNERX coordinate of the origin (by center or lower left corner of the cell)原点的X坐标.Match with Y coordinate type.
YLLCENTER or YLLCORNERY coordinate of the origin (by center or lower left corner of the cell)原点的Y坐标.Match with X coordinate type.
CELLSIZECell size.Greater than 0.
NODATA_VALUEThe input values to be NoData in the output raster.Optional. Default is -9999.

该人口数据有180行, 360列

// 行范围: latNdx: 0~179
// 列范围: lonNdx: 0~359
// xllcorner: 原点的X坐标 -180.
// yllcorner: 原点的Y坐标 -90.
// 经度计算: lonNdx + yllcorner
// 纬度计算: latNdx + xllcorner
// lon经度范围: -180~180
// lat维度范围: -90~90

// (180,90) z轴,
// (0, 0)   y轴,
// (90, -90) x轴
2、动态高度的长方体

1) 这里,坐标系的参考是y轴朝上:
在这里插入图片描述

2)先放一个立方体:

const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);

const box = new THREE.Mesh(geometry, material);
const material = new THREE.MeshBasicMaterial();
scene.add(box);

在这里插入图片描述

3)在x,y上让其固定缩放到小正方形,在z方向的高度动态调整。

// amount 该区域的人口数量
+ box.scale.set(0.005, 0.005, THREE.MathUtils.lerp(0.01, 0.5, amount));

在这里插入图片描述

如果我们以x轴或者y轴为旋转轴进行旋转,效果:
在这里插入图片描述

4)接着,放一个球进去。

function addSphere() {
    const geometry = new THREE.SphereBufferGeometry(1, 16, 16);
    const material = new THREE.MeshBasicMaterial({wireframe: true});
    let mesh = new THREE.Mesh(geometry, material)
    scene.add(mesh);
  }

在这里插入图片描述

5)让长方体沿着球面移动
长方体可以沿着经纬度移动。
在这里插入图片描述

// 球的半径
const radius = 1

// 经度助手, -180~180
const lonHelper = new THREE.Object3D();
scene.add(lonHelper);
lonHelper.name = "lonHelper"

// 纬度助手, -90~90
const latHelper = new THREE.Object3D();
lonHelper.add(latHelper);
latHelper.name = "latHelper"

// 位置助手, 将对象移动到球体的边缘
const positionHelper = new THREE.Object3D();
positionHelper.position.z = radius
positionHelper.name = "positionHelper"
latHelper.add(positionHelper);

positionHelper.add(box)

这样构建对象的层级关系:
在这里插入图片描述

原理就是:让经纬度的旋转作用在一个球上,然后将这种效果作用到小立方体上。

6)自身高度的偏移

还有一个小问题,这个长方体的一半还在球体的里面。
在这里插入图片描述

// 偏移自身高度的一半
+ geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(0, 0, boxDepth/2));

在这里插入图片描述

7) 多物体的构建
因为要创建很多小长方体,而每创建一个长方体, 就会多创建3个对象:positionHelper,lonHelper,latHelper
因此优化一下即可:

positionHelper.updateWorldMatrix(true, false);
box.applyMatrix4(positionHelper.matrixWorld);
3、小结

完整代码:

// import * as THREE from './lib/three.module.js'
import Stage from './Stage.js'
import {loadFile} from "./utils.js"

window.THREE = THREE

class App {
  constructor() {
    window.lm = this
    this.stage = new Stage("#app")
    this.stage.run()
    this.stage.camera.position.set(0,0,5)
    this.parseData = this.parseData.bind(this)
    this.addBoxes = this.addBoxes.bind(this)
    this.addSphere()
    this.loadData()
  }

  addBoxes(file) {
    let aaa = 0
    const scene = this.stage.scene
    const {min, max, data} = file;
    const range = max - min;

    // make one box geometry
    const boxWidth = 1;
    const boxHeight = 1;
    const boxDepth = 1;
    const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);
    geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(0, 0, 0.5));

    // 这些助手将使放置盒子变得容易。 我们可以将lon辅助程序沿其Y轴旋转到经度
    const lonHelper = new THREE.Object3D();
    scene.add(lonHelper);
    lonHelper.name = "lonHelper"

    // 我们将latHelper沿其X轴旋转到纬度
    const latHelper = new THREE.Object3D();
    lonHelper.add(latHelper);
    latHelper.name = "latHelper"

    // 位置助手将对象移动到球体的边缘
    const positionHelper = new THREE.Object3D();
    positionHelper.position.z = 1;
    positionHelper.name = "positionHelper"
    latHelper.add(positionHelper);

    // latNdx: 0~179
    // lonNdx: 0~359
    // xllcorner: 原点的X坐标 -180.
    // yllcorner: 原点的Y坐标 -90.
    data.forEach((row, latNdx) => {
      row.forEach((value, lonNdx) => {
        if (value === undefined) {
          return;
        }
        const amount = (value - min) / range;
        const material = new THREE.MeshBasicMaterial();
        const hue = THREE.MathUtils.lerp(0.7, 0.3, amount);
        const saturation = 1;
        const lightness = THREE.MathUtils.lerp(0.4, 1.0, amount);
        material.color.setHSL(hue, saturation, lightness);
        const mesh = new THREE.Mesh(geometry, material);
        mesh.name = "box"
        scene.add(mesh);

        // adjust the helpers to point to the latitude and longitude
        // 调整助手以指向经度和纬度
        lonHelper.rotation.y = THREE.MathUtils.degToRad(lonNdx + file.xllcorner);
        latHelper.rotation.x = THREE.MathUtils.degToRad(latNdx + file.yllcorner);


        // use the world matrix of the position helper to position this mesh.
        // 使用位置助手的世界矩阵来定位该网格。
        positionHelper.updateWorldMatrix(true, false);
        mesh.applyMatrix4(positionHelper.matrixWorld);

        mesh.scale.set(0.005, 0.005, THREE.MathUtils.lerp(0.01, 0.5, amount));
      });
      

    });
  }

  parseData(text) {
    const data = [];
    const settings = {data};
    let max;
    let min;
    // split into lines
    text.split('\n').forEach((line) => {
      // split the line by whitespace
      const parts = line.trim().split(/\s+/);
      if (parts.length === 2) {
        // only 2 parts, must be a key/value pair
        settings[parts[0]] = parseFloat(parts[1]);
      } else if (parts.length > 2) {
        // more than 2 parts, must be data
        const values = parts.map((v) => {
          const value = parseFloat(v);
          if (value === settings.NODATA_value) {
            return undefined;
          }
          max = Math.max(max === undefined ? value : max, value);
          min = Math.min(min === undefined ? value : min, value);
          return value;
        });
        data.push(values);
      }
    });
    return Object.assign(settings, {min, max});
  }

  addSphere() {
    const loader = new THREE.TextureLoader();
    const texture = loader.load('./texture/world.jpg');
    const geometry = new THREE.SphereBufferGeometry(1, 32, 32);
    const material = new THREE.MeshBasicMaterial({map: texture, wireframe: false});
    let mesh = new THREE.Mesh(geometry, material)
    mesh.name = "sphere"
    this.stage.scene.add(mesh);
  }


  loadData() {
    loadFile('./data/gpw_v4_population_count_rev11_2020_1_deg.asc')
    .then(this.parseData)
    .then(this.addBoxes);
  }
}

window.onload = () => {
  let app = new App()
}

目前效果:
在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值