基于three.js和vue的glb格式3D模型批量加载
前段时间写一个小项目的时候用到了three.js,发现网上的教程很乱很杂学习的很困难,官方文档又不够具体,所以我想写几篇关于three.js的学习经验。
一、GLTF模型加载器的导入
import {
GLTFLoader
} from 'three/examples/jsm/loaders/GLTFLoader.js'
要想使用GLTF模型加载器就得先导入GLTFLoader,这样才能使用方法load方法加载模型。记得在script标签下引入。
二、模型资源放置
如上图所示,根目录是3d,glb格式的3D模型放在3dmodel目录中,3dmodel目录放在public目录下。这样放置的目的是在路径上无需添加public/,路径格式如下所示。
var address = ['/3dmodel/Landing jet1.glb', '/3dmodel/Landing jet2.glb', '/3dmodel/Landing jet3.glb','/3dmodel/Landing jet4.glb', '/3dmodel/Landing jet5.glb']
三、gltf模型加载器批量加载模型
glft模型加载器可以加载gltf、glb格式的模型。在第一节将所有模型的地址存放在数组内,只需要一个循环就可以实现对模型的批量加载了。代码如下:
function loadGLTF() {
let i = 0
for (i; i < address.length; i++) {
gltfLoader.load(address[i], (gltf) => {
let model = gltf.scene
scene.add(model)//将模型加入到场景中
})
}
}
四、模型展示
上图看上去是一个模型,其实是多个模型的叠加,通过对各个模块位置的调整,来显示一个看上去完整的一个模型。部分模块图如下:
五、其他种类模型加载
[Three.js 中文教程 | 参考手册 | 使用指南 | 动画特效实例 | 踏得网 (techbrood.com)](https://techbrood.com/threejs/docs/)
搜索Loader,查看其他格式的3D模型的加载器。
六、处理模型的马赛克
function resizeRendererToDisplaySize(renderer) {
let canvas = renderer.domElement
var width = window.innerWidth
var height = window.innerHeight
var canvasPixelWidth = canvas.width / window.devicePixelRatio
var canvasPixelHeight = canvas.height / window.devicePixelRatio
let needResize =canvasPixelWidth !== width || canvasPixelHeight !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
如果没有添加以上代码会出现如下图的情况!
七、注意事项及附录
本文针对于3D模型的批量加载,没有介绍关于camera、scene等内容,建议场景搭建完成后使用。如果想复现所有的功能建议把我的几篇关于three.js的文章看一遍。
import * as THREE from 'three'
import {
GLTFLoader
} from 'three/examples/jsm/loaders/GLTFLoader.js'
import {
OrbitControls
} from 'three/examples/jsm/controls/OrbitControls'
export default {
mounted() {
this.initThree()
},
methods: {
initThree() {
addEventListener('click', onClick, false)
addEventListener('resize', onWindowResize, false)
const scene = new THREE.Scene()
scene.background = new THREE.Color('#eee')
scene.fog = new THREE.Fog('#eee', 20, 100)
const canvas = document.querySelector('#three')
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true
})
renderer.shadowMap.enabled = true
const camera = new THREE.PerspectiveCamera(
50,
window.innerWidth / window.innerHeight,
0.1,
1000
)
camera.position.z = 10
camera.position.x = -10
camera.position.y = 10
var address = ['/3dmodel/Landing jet5.glb']
// var address = ['/3dmodel/Landing jet1.glb'
// , '/3dmodel/Landing jet2.glb', '/3dmodel/Landing jet3.glb',
// '/3dmodel/Landing jet4.glb', '/3dmodel/Landing jet5.glb'
// ]
const gltfLoader = new GLTFLoader()
const m = 5;
const n = 19;
var uuidArray = new Array(m); //存放各个模型中不同组成部分唯一标识id的二维数组
for (var x = 0; x < m; x++) {
uuidArray[x] = new Array(n)
}
// var uuidLength = [19, 17, 14, 10, 12]
var group = new THREE.Group() //将不同的对象组合起来,例如将俩个对象组合起来,设置一个位置,俩个对象的位置都发生改变
function loadGLTF() {
let i = 0
let j = 0
for (i; i < address.length; i++) {
let array = []
gltfLoader.load(address[i], (gltf) => {
j++
console.log("分隔")
let model = gltf.scene
// 遍历模型每部分
model.traverse((o) => {
console.log(o)//输出模型的组成部分
array.push(o.uuid)
//将图片作为纹理加载
let explosionTexture = new THREE.TextureLoader().load(
'/3dmodel/textures/darkblue.jpg'
)
//调整纹理图的方向
explosionTexture.flipY = false
//将纹理图生成基础网格材质(MeshBasicMaterial)
let material = new THREE.MeshBasicMaterial({
map: explosionTexture,
})
// //给模型每部分上材质
o.material = material
if (o.isMesh) {
o.castShadow = true
o.receiveShadow = true
}
})
scene.add(model)
})
uuidArray[i] = array
}
}
loadGLTF()
//加载GLFT及GLB模型
const hemLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6)
hemLight.position.set(0, 48, 0)
scene.add(hemLight)
const dirLight = new THREE.DirectionalLight(0xffffff, 0.6)
//光源等位置
dirLight.position.set(-10, 8, -5)
//可以产生阴影
dirLight.castShadow = true
dirLight.shadow.mapSize = new THREE.Vector2(1024, 1024)
scene.add(dirLight)
let floorGeometry = new THREE.PlaneGeometry(8000, 8000)
let floorMaterial = new THREE.MeshPhongMaterial({
color: 0x857ebb,
shininess: 0,
})
let floor = new THREE.Mesh(floorGeometry, floorMaterial)
floor.rotation.x = -0.5 * Math.PI
floor.receiveShadow = true
floor.position.y = -3
scene.add(floor)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
function onClick(event) {
let selectObject
//获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
let intersects = getIntersects(event)
//获取选中最近的Mesh对象
//instance坐标是对象,右边是类,判断对象是不是属于这个类的
if (intersects.length !== 0 && intersects[0].object.type === 'Mesh') {
// intersects[0].object.material.color.set(0x00FF00)
//console.log(intersects[0].object.material.color);
selectObject = intersects[0].object
let num = judgeModule(selectObject.uuid)
if (num[0] > 0) { //接口,判断点击了哪个子系统的哪个部件
console.log("选中了第" + num[0] + "个子系统" + "的第" + num[1] + "部件")
}
changeMaterial(selectObject)
// console.log(judgeClick(intersects[0].object.parent.parent.parent))
} else {
console.log('未选中 Mesh!')
}
}
function judgeModule(uuid) {
let num = [-1, -1]
let i, j
for (i = 0; i < uuidArray.length; i++) {
for (j = 0; j < uuidArray[i].length; j++) {
if (uuidArray[i][j] == uuid) { //妈的双等于写成单等于了,找半天没找到
num[0] = i + 1 //返回子系统的编号,数组下标加一是编号
num[1] = j + 1 //返回子系统中部件的编号
}
}
}
return num //代表没有点中子系统
}
function getIntersects(event) {
event.preventDefault()
// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault
//console.log("event.clientX:" + event.clientX);
//console.log("event.clientY:" + event.clientY);
//声明 rayCaster 和 mouse 变量
let rayCaster = new THREE.Raycaster()
let mouse = new THREE.Vector2()
//通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1
mouse.x = ((event.clientX - canvas.getBoundingClientRect().left) / canvas.offsetWidth) * 2 - 1
mouse.y = -((event.clientY - canvas.getBoundingClientRect().top) / canvas.offsetHeight) * 2 + 1
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
rayCaster.setFromCamera(mouse, camera)
//获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。
//+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。
let intersects = rayCaster.intersectObjects(scene.children, true)
//返回选中的对象
return intersects
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
}
function changeMaterial(object) {
let material = new THREE.MeshLambertMaterial({
color: 0xffffff * Math.random(),
transparent: object.material.transparent ? false : true,
opacity: 0.8
});
object.material = material;
}
function animate() {
controls.update()
renderer.render(scene, camera)
requestAnimationFrame(animate)
if (resizeRendererToDisplaySize(renderer)) {
let canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
}
animate()
function resizeRendererToDisplaySize(renderer) {
let canvas = renderer.domElement
var width = window.innerWidth
var height = window.innerHeight
var canvasPixelWidth = canvas.width / window.devicePixelRatio
var canvasPixelHeight = canvas.height / window.devicePixelRatio
let needResize =
canvasPixelWidth !== width || canvasPixelHeight !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
},
},
}
Comments | NOTHING