参考:
Three.js Optimize Lots of Objects
Three.js 优化方向
接下来使用合并 mesh
的方式做一个例子:世界人口可视化:World Population。
世界人口公开数据出处
数据格式:
Parameter | Description | Requirements |
---|---|---|
NCOLS | Number of cell columns. | Integer greater than 0. |
NROWS | Number of cell rows. | Integer greater than 0. |
XLLCENTER or XLLCORNER | X coordinate of the origin (by center or lower left corner of the cell)原点的X坐标. | Match with Y coordinate type. |
YLLCENTER or YLLCORNER | Y coordinate of the origin (by center or lower left corner of the cell)原点的Y坐标. | Match with X coordinate type. |
CELLSIZE | Cell size. | Greater than 0. |
NODATA_VALUE | The 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)先放一个立方体:
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));
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);
}
// 球的半径
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);
完整代码:
// 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()
}