通过THREE.Geometry.merge函数合并网格
1.demo效果
如上图,该demo支持以下功能:
- 可以通过objNum属性设置要创建的小立方体的个数。
- 可以通过勾选combined属性来设置是否启用THREE.Geometry.merge()函数合并立方体
2.涉及知识点
2.1 为什么合并网格
一般情况下可以使用组来操纵和管理大量网格。但是当对象的数量非常庞大时,性能就会成为一个瓶颈,因为在组里每个对象还是独立的,需要分别对它们进行渲染和处理,所以数量上来以后性能就会受到很大影响。这个时候three.js提供了一个方法可以将网格合并,来达到提升性能的目的
2.2 THREE.Geometry.merge()函数
现在的网格合并函数merge已经合成到THREE.Geometry
对象上,我们使用合并函数可以直接通过Geometry对象调用。THREE.Geometry.merge()
函数的可以将一个或多个几何体合并成一个几何体
以下是使用示例:
const cubeMaterial = new THREE.MeshNormalMaterial({
transparent: true,
opacity: 0.5
})
//创建方块1
const cubeGeometry1 = new THREE.BoxGeometry(10, 10, 10)
const cube1 = new THREE.Mesh(cubeGeometry1, cubeMaterial)
//创建方块2
const cubeGeometry2 = new THREE.BoxGeometry(20, 20, 20)
const cube2 = new THREE.Mesh(cubeGeometry, cubeMaterial)
const geometry = new THREE.Geometry()
//将方块1合并到geometry
cube1.updateMatrix()
geometry.merge(cube1.geometry, cube1.matrix)
//将方块2合并到geometry
cube2.updateMatrix()
geometry.merge(cube2.geometry, cube2.matrix)
//用合并后的几何对象创建网格
const mergeMesh = new THREE.Mesh(geometry, cubeMaterial)
//将使用合并方式创建的网格对象添加到场景
scene.add(mergeMesh )
2.3 网格合并产生的影响
- 合并后,若干个网格对象被合并成一个,性能大幅提升
- 与使用组或未合并相比较,合并后不能对每个对象进行单独的操作
3.实现要点
3.1 创建随机位置的小立方体
合并网格需要多个小立方体的网格对象,我们首先要实现一个创建随机位置立方体的方法,具体如下:
addCube () {
const cubeSize = 1.0
const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
const cubeMaterial = new THREE.MeshNormalMaterial({
transparent: true,
opacity: 0.5
})
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
cube.castShadow = true
cube.position.x = -60 + Math.round(Math.random() * 100)
cube.position.y = Math.round(Math.random() * 10)
cube.position.z = -150 + Math.round(Math.random() * 175)
return cube
}
3.2 合并网格对象
上面的步骤我们实现了一个创建随机位置方块的方法,这里我们使用for循环通过merge方法将创建的小方块依次合并到新的geometry几何对象,并使用它创建新的网格对象,最后添加到场景
const geometry = new THREE.Geometry()
for (let i = 0; i < this.properties.objNum.value; i++) {
const cubeMesh = this.addCube()
cubeMesh.updateMatrix()
geometry.merge(cubeMesh.geometry, cubeMesh.matrix)
}
this.scene.add(new THREE.Mesh(geometry, cubeMaterial))
3.3 重绘网格对象
当我们通过objNum属性调整要生成的小方块或勾选combined属性来调整是否合并对象时,页面需要重新绘制,这上需要先把上一次创建的网格对象先全部删掉,然后重新创建,具体如下:
redraw () {
const toRemove = []
this.scene.traverse(e => {
if (e instanceof THREE.Mesh) toRemove.push(e)
})
toRemove.forEach(e => {
this.scene.remove(e)
})
this.createCubes()
}
4.demo代码
<template>
<div>
<div id="container"></div>
<div class="controls-box">
<section>
<el-row>
<div v-for="(item,key) in properties" :key="key">
<div v-if="item&&item.name!=undefined">
<el-col :span="8">
<span class="vertice-span">{{item.name}}</span>
</el-col>
<el-col :span="13">
<el-slider v-model="item.value" :min="item.min" :max="item.max" :step="item.step" :format-tooltip="formatTooltip" @change="redraw"></el-slider>
</el-col>
<el-col :span="3">
<span class="vertice-span">{{item.value}}</span>
</el-col>
</div>
</div>
</el-row>
<el-row>
<el-checkbox v-model="properties.combined" @change="redraw">combined</el-checkbox>
</el-row>
</section>
</div>
</div>
</template>
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
export default {
components: {},
data () {
return {
properties: {
objNum: {
name: 'objNum',
value: 300,
min: 0,
max: 10000,
step: 1
},
combined: false
},
cube: null,
rotation: 0,
camera: null,
scene: null,
renderer: null,
controls: null
}
},
mounted () {
this.init()
},
methods: {
formatTooltip (val) {
return val
},
// 初始化
init () {
this.createScene() // 创建场景
this.createCubes() // 创建方块
this.createLight() // 创建光源
this.createCamera() // 创建相机
this.createRender() // 创建渲染器
this.createControls() // 创建控件对象
this.render() // 渲染
},
// 创建场景
createScene () {
this.scene = new THREE.Scene()
},
// 创建光源
createLight () {
// 添加聚光灯
const spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(-40, 60, 20)
spotLight.castShadow = true
this.scene.add(spotLight) // 聚光灯添加到场景中
// 环境光
const ambientLight = new THREE.AmbientLight(0x0c0c0c)
this.scene.add(ambientLight)
},
// 创建相机
createCamera () {
const element = document.getElementById('container')
const width = element.clientWidth // 窗口宽度
const height = element.clientHeight // 窗口高度
const k = width / height // 窗口宽高比
// PerspectiveCamera( fov, aspect, near, far )
this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000)
this.camera.position.set(-30, 40, 30) // 设置相机位置
this.camera.lookAt(new THREE.Vector3(5, 0, 0)) // 设置相机方向
this.scene.add(this.camera)
},
// 创建渲染器
createRender () {
const element = document.getElementById('container')
this.renderer = new THREE.WebGLRenderer()
this.renderer.setSize(element.clientWidth, element.clientHeight) // 设置渲染区域尺寸
this.renderer.setClearColor(0x3f3f3f, 1) // 设置背景颜色
element.appendChild(this.renderer.domElement)
},
addCube () {
const cubeSize = 1.0
const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
const cubeMaterial = new THREE.MeshNormalMaterial({
transparent: true,
opacity: 0.5
})
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
cube.castShadow = true
cube.position.x = -60 + Math.round(Math.random() * 100)
cube.position.y = Math.round(Math.random() * 10)
cube.position.z = -150 + Math.round(Math.random() * 175)
return cube
},
// 创建多个方块
createCubes () {
const cubeMaterial = new THREE.MeshNormalMaterial({
transparent: true,
opacity: 0.5
})
if (this.properties.combined) {
const geometry = new THREE.Geometry()
for (let i = 0; i < this.properties.objNum.value; i++) {
const cubeMesh = this.addCube()
cubeMesh.updateMatrix()
geometry.merge(cubeMesh.geometry, cubeMesh.matrix)
}
this.scene.add(new THREE.Mesh(geometry, cubeMaterial))
} else {
for (let i = 0; i < this.properties.objNum.value; i++) {
const cube = this.addCube()
this.scene.add(cube)
}
}
},
redraw () {
const toRemove = []
this.scene.traverse(e => {
if (e instanceof THREE.Mesh) toRemove.push(e)
})
toRemove.forEach(e => {
this.scene.remove(e)
})
this.createCubes()
},
animation () {
this.rotation += 0.005
this.camera.position.x = Math.sin(this.rotation) * 50
this.camera.position.z = Math.cos(this.rotation) * 50
this.camera.lookAt(this.scene.position)
},
render () {
this.animation()
this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.render)
},
// 创建控件对象
createControls () {
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
}
}
}
</script>
<style>
#container {
position: absolute;
width: 100%;
height: 100%;
}
.controls-box {
position: absolute;
right: 5px;
top: 5px;
width: 300px;
padding: 10px;
background-color: #fff;
border: 1px solid #c3c3c3;
}
.vertice-span {
line-height: 38px;
padding: 0 2px 0 10px;
}
</style>