效果图
在项目中升级threejs版本,原版本中用到了Geometry.merge()方法。升级后的threejs删除了Geometry相关类,转而使用BufferGeometry实现生成几何体。BufferGeometry中的merge()方法使用后,并未将多个几何体进行合并,故自实现了合并多个几何体的功能。
本文中的合并几何体函数只对position、normal、uv、groups进行了合并,合并的几何体为shape拉伸的多个几何体,且为表面和侧立面分别使用不同的material。
以下为实现合并多个几何体方法代码(方法参照javascript - How to merge two BufferGeometries in one BufferGeometry in Three.JS? - Stack Overflow):
const mergeBufferGeometry = function (objects) {
const sumPosArr = new Array();
const sumNormArr = new Array();
const sumUvArr = new Array();
const modelGeometry = new THREE.BufferGeometry();
let sumPosCursor = 0;
let sumNormCursor = 0;
let sumUvCursor = 0;
let startGroupCount = 0;
let lastGroupCount = 0;
for (let a = 0; a < objects.length; a++ )
{
const posAttArr = objects[a].geometry.getAttribute('position').array;
for (let b = 0; b < posAttArr.length; b++)
{
sumPosArr[b + sumPosCursor] = posAttArr[b];
}
sumPosCursor += posAttArr.length;
const numAttArr = objects[a].geometry.getAttribute('normal').array;
for (let b = 0; b < numAttArr.length; b++)
{
sumNormArr[b + sumNormCursor] = numAttArr[b];
}
sumNormCursor += numAttArr.length;
const uvAttArr = objects[a].geometry.getAttribute('uv').array;
for (let b = 0; b < uvAttArr.length; b++)
{
sumUvArr[b + sumUvCursor] = uvAttArr[b];
}
sumUvCursor += uvAttArr.length;
const groupArr = objects[a].geometry.groups;
for (let b = 0; b < groupArr.length; b++)
{
startGroupCount = lastGroupCount
modelGeometry.addGroup(startGroupCount, groupArr[b].count, groupArr[b].materialIndex)
lastGroupCount = startGroupCount + groupArr[b].count
}
}
modelGeometry.setAttribute('position', new THREE.Float32BufferAttribute(sumPosArr, 3 ));
sumNormArr.length && modelGeometry.setAttribute('normal', new THREE.Float32BufferAttribute(sumNormArr, 3 ));
sumUvArr.length && modelGeometry.setAttribute('uv', new THREE.Float32BufferAttribute(sumUvArr, 2 ));
return modelGeometry
}
1)objects 传参:需要合并的几何体数组,mesh数组
2)position 合并:将 objects 数组中几何体的 position 合并至一个数组中,最后将全部 position 添加至新创建的 modelGeometry 中并返回
3)normal 合并:将 objects 数组中几何体的 normal 合并至一个数组中,最后将全部 normal 添加至新创建的 modelGeometry 中并返回
4)uv 合并:将 objects 数组中几何体的 uv 合并至一个数组中,最后将全部 uv 添加至新创建的 modelGeometry 中并返回
5)groups 合并:单个 groups:{ start: Integer, count: Integer, materialIndex: Integer },合并 groups 需要重新计算 start 的值(注意此处的 count 个数并不是 position 个数),下一个 start 的值总是等于上一个 start + count 。本文中 groups 来源为创建ExtrudeGeometry几何体时,传入了表面和侧立面的 material。
生成地图3D图形代码部分
drawChinaMap() {
const chinaGroup = new THREE.Group()
const bumpMap = new THREE.TextureLoader().load(require('./assets/img/normal1.jpg'), texture => {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.offset.set( 0, 0.5 )
texture.repeat.set( 0.01, 0.01 )
})
chinaGeometry.features.forEach(chinaItem => {
let oneCityLineGroup = new THREE.Group()
let oneCityLineArr = []
let oneCityShape
let oneCityShapeArr = []
let material1 = new THREE.ShaderMaterial({
uniforms: Shader1.uniforms,
vertexShader: Shader1.vertexShader,
fragmentShader: Shader1.fragmentShader,
side: THREE.DoubleSide,
transparent: true,
// blending: THREE.AdditiveBlending,
})
let material2 = new THREE.MeshStandardMaterial({
// color: '#7293b5',
bumpMap: bumpMap,
// map: bumpMap,
opacity: 0.8,
bumpScale: 10,
roughness: 1,
metalness: 0.0,
})
chinaItem.geometry.coordinates.forEach(chinaChildItem => {
if (chinaItem.geometry.type === "MultiPolygon") {
chinaChildItem.forEach((item) => {
const oneCityShape = this.drawShape(item, [material2, material1])
oneCityShapeArr.push(oneCityShape)
})
} else {
const oneCityShape = this.drawShape(chinaChildItem, [material2, material1])
oneCityShapeArr.push(oneCityShape)
}
})
const newGeometry = this.mergeBufferGeometry(oneCityShapeArr)
oneCityShape = new THREE.Mesh(newGeometry, [material2, material1])
oneCityShape.name = chinaItem.properties.name
this.outlinePass.selectedObjects.push(oneCityShape)
chinaGroup.add(oneCityShape)
})
chinaGroup.rotateX(-Math.PI/2)
chinaGroup.scale.set(0.0015, 0.0015, 0.0015)
this.scene.add(chinaGroup)
}
拉伸shape任意几何体方法如下:
drawShape(position, material) {
const pointArr = []
const shape = new THREE.Shape()
const extrudeSettings = {
steps: 2,
depth: 100000,
curveSegments: 128,
bevelEnabled: true,
bevelThickness: 1,
bevelSize: 1,
bevelOffset: 10,
bevelSegments: 1
};
position.forEach(point => {
const coord = geographicToVector2(point)
pointArr.push([coord[0]-center[0], coord[1] - center[1]]);
})
pointArr.forEach((p, i) => {
i === 0 ? shape.moveTo(p[0], p[1]) : shape.lineTo(p[0], p[1])
})
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)
return new THREE.Mesh(geometry, material)
}