Three.js官方文檔:https://threejs.org/manual/#zh/load-obj
注意點(diǎn):obj文件需要放到public文件夾下的static文件夾中(路徑寫法錯(cuò)誤會(huì)導(dǎo)致模型不顯示褂痰,可以在network里查看文件是否被加載)
// 完整代碼
<template>
<div id="container"></div>
<el-slider
v-model="value"
show-input
:min="1"
:max="100"
style="
width: 50%;
position: absolute;
bottom: 20%;
left: 50%;
transform: translateX(-50%);
"
@change="onChange"
/>
</template>
<script>
import * as THREE from "three";
import Stats from "stats-js";
import * as Dat from "dat-gui";
import TrackballControls from "three-trackballcontrols";
// import { OBJLoader } from "three-obj-mtl-loader";
// import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
export default {
name: "LoadObjMtl",
data() {
return {
step: 0,
value: 0,
};
},
mounted() {
this.renderer = "";
this.camera = "";
this.scene = "";
this.light = "";
this.gui = "";
this.axesHelper = "";
this.stats = "";
this.trackballControls = "";
this.clock = "";
this.jeepCar = "";
this.controls = "";
this.objLoader = "";
this.mtlLoader = "";
this.gltfLoader = "";
this.plane = "";
// 執(zhí)行
this.execute();
// 窗口大小變化
window.onresize = this.onWindowResize;
},
methods: {
initScene() {
this.scene = new THREE.Scene();
},
initCamera() {
this.camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
5000
);
// 設(shè)置相機(jī)位置
this.camera.position.x = -400;
this.camera.position.y = 400;
this.camera.position.z = 400;
// 設(shè)置相機(jī)指向的位置 默認(rèn)0银室,0,0
this.camera.lookAt(this.scene.position);
},
initHelper() {
// this.axesHelper = new THREE.AxesHelper(100);
// this.scene.add(this.axesHelper);
},
initRender() {
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize(window.innerWidth, window.innerHeight);
// 告訴渲染器需要陰影效果
// this.renderer.shadowMap.enabled = true;
// this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 設(shè)置背景色
this.renderer.setClearColor(new THREE.Color("rgb(61,61,61)"));
document
.querySelector("#container")
.appendChild(this.renderer.domElement);
},
initStats() {
this.stats = new Stats();
document.body.appendChild(this.stats.dom);
},
getWorldCenterPosition(box, scalar = 0.5) {
return new THREE.Vector3()
.addVectors(box.max, box.min)
.multiplyScalar(scalar);
},
explodeModel(model, scalar) {
model.traverse(function (value) {
// @ts-ignore
if (!value.isMesh || !value.userData.originPosition) return;
const distance = value.userData.worldDir
.clone()
.multiplyScalar(value.userData.worldDistance.length() * scalar);
const offset = new THREE.Vector3().subVectors(
value.userData.meshCenter,
value.userData.originPosition
);
const center = value.userData.explodeCenter;
const newPos = new THREE.Vector3()
.copy(center)
.add(distance)
.sub(offset);
const localPosition = value.parent?.worldToLocal(newPos.clone());
localPosition && value.position.copy(localPosition);
});
},
initExplodeModel(modelObject) {
if (!modelObject) return;
// 計(jì)算模型中心
const explodeBox = new THREE.Box3();
explodeBox.setFromObject(modelObject);
const explodeCenter = this.getWorldCenterPosition(explodeBox);
const meshBox = new THREE.Box3();
const that = this;
// 遍歷整個(gè)模型跑慕,保存數(shù)據(jù)到userData上徒坡,以便爆炸函數(shù)使用
modelObject.traverse(function (value) {
if (
value.isMark ||
value.isMarkChild ||
value.isLine ||
value.isSprite
) {
return;
}
if (value.isMesh) {
meshBox.setFromObject(value);
const meshCenter = that.getWorldCenterPosition(meshBox);
// 爆炸方向
value.userData.worldDir = new THREE.Vector3()
.subVectors(meshCenter, explodeCenter)
.normalize();
// 爆炸距離 mesh中心點(diǎn)到爆炸中心點(diǎn)的距離
value.userData.worldDistance = new THREE.Vector3().subVectors(
meshCenter,
explodeCenter
);
// 原始坐標(biāo)
value.userData.originPosition = value.getWorldPosition(
new THREE.Vector3()
);
// mesh中心點(diǎn)
value.userData.meshCenter = meshCenter.clone();
value.userData.explodeCenter = explodeCenter.clone();
}
});
},
initModel() {
// 實(shí)例化mtl loader
// this.mtlLoader = new MTLLoader();
const that = this;
// 加載mtl
// this.mtlLoader.load("objs/jeep/jeepCar.mtl", function(materials) {
// materials.preload()
// that.objLoader.setMaterials(materials)
// // 加載obj
// that.objLoader.load("objs/jeep/jeepCar.obj", function(obj) {
// // 模型文件太大撕氧,縮小一下比例,方便顯示
// obj.scale.set(0.1, 0.1, 0.1)
// // 設(shè)置可以投影
// obj.children.forEach(item => {
// item.castShadow = true
// item.receiveShadow = true
// })
// that.jeepCar = obj
// // 添加到場(chǎng)景
// that.scene.add(that.jeepCar)
// })
// })
// 實(shí)例化obj loader
this.objLoader = new OBJLoader();
this.objLoader.load("static/f019d04068.obj", function (obj) {
console.log(obj);
obj.traverse((node) => {
node.material = new THREE.MeshLambertMaterial({
color: "#72757A",
emissive: "#000000",
shininess: 150
});
});
obj.scale.set(1, 1, 1);
obj.children[0].material.color.set(0xccd072);
// obj.children[4].material.color.set(0xe1a07f);
obj.children.forEach((item) => {
item.castShadow = true;
item.receiveShadow = true;
});
obj.castShadow = true;
obj.receiveShadow = true;
that.jeepCar = obj;
// 添加到場(chǎng)景
that.scene.add(that.jeepCar);
that.initExplodeModel(that.jeepCar);
});
// 創(chuàng)建一個(gè)幾何平面喇完,作為地板伦泥,400,400
const planeGeometry = new THREE.PlaneGeometry(400, 400);
// 創(chuàng)建帶顏色材質(zhì),更換為MeshLambertMaterial材質(zhì)锦溪,去掉網(wǎng)格結(jié)構(gòu)
const planeMaterial = new THREE.MeshLambertMaterial({
color: "rgb(110,110,110)",
});
this.plane = new THREE.Mesh(planeGeometry, planeMaterial);
// 平面開啟接收陰影效果
this.plane.receiveShadow = true;
// 設(shè)置平面角度和位置
this.plane.rotation.x = -0.5 * Math.PI;
this.plane.position.x = 0;
this.plane.position.y = 0;
this.plane.position.z = 0;
// 添加平面
// this.scene.add(this.plane);
// this.gltfLoader = new GLTFLoader();
// this.gltfLoader.load("static/gltf/scene.gltf", function (gltf) {
// console.log(gltf);
// gltf.scene.scale.set(80, 80, 80);
// that.jeepCar = gltf.scene;
// // 添加到場(chǎng)景
// that.scene.add(gltf.scene);
// that.initExplodeModel(gltf.scene);
// });
},
onChange(val) {
console.log(val);
this.explodeModel(this.jeepCar, val);
},
initLight() {
// 為場(chǎng)景所有物體添加基礎(chǔ)光源
const ambientLight = new THREE.AmbientLight(0x72757a, 2);
this.scene.add(ambientLight);
// 添加聚光燈光源
const spotLight = new THREE.SpotLight(0xffffff, 2, 1000);
spotLight.shadow.mapSize.set(2048, 2048);
spotLight.position.set(-300, 0, -200);
// 開啟投影
// spotLight.castShadow = true;
this.scene.add(spotLight);
const anotherSpotLight = new THREE.SpotLight(0xffffff, 2, 1000);
anotherSpotLight.shadow.mapSize.set(2048, 2048);
anotherSpotLight.position.set(300, 0, 300);
this.scene.add(anotherSpotLight);
const spotLightHelper = new THREE.SpotLightHelper(spotLight);
this.scene.add(spotLightHelper);
// const light = new THREE.DirectionalLight(0xe8b73b, 2, 1000); // 光源顏色
// light.shadow.mapSize.set(2048, 2048);
// light.position.set(100, 100, 0);
// this.scene.add(light);
// const directionalLightHelper = new THREE.SpotLightHelper(light);
// this.scene.add(directionalLightHelper);
},
initGui() {
// 為帶貼圖MTL的OBJ模型添加UI控制(xy坐標(biāo)不脯,xyz角度)
this.controls = {
positionX: -18,
positionZ: -18,
rotationX: 0,
rotationY: 45,
rotationZ: -15,
};
const gui = new Dat.GUI();
// 設(shè)置允許操作范圍
gui.add(this.controls, "positionX", -200, 200);
gui.add(this.controls, "positionZ", -200, 200);
gui.add(this.controls, "rotationX", -360, 360);
gui.add(this.controls, "rotationY", -360, 360);
gui.add(this.controls, "rotationZ", -360, 360);
},
initControls() {
this.trackballControls = new TrackballControls(
this.camera,
this.renderer.domElement
);
this.trackballControls.rotateSpeed = 1.0;
this.trackballControls.zoomSpeed = 1;
this.trackballControls.panSpeed = 1;
this.trackballControls.noZoom = false;
this.trackballControls.noPan = false;
this.trackballControls.staticMoving = true;
this.trackballControls.dynamicDampingFactor = 0.3;
this.trackballControls.keys = [65, 83, 68];
},
initClock() {
this.clock = new THREE.Clock();
},
render() {
this.trackballControls.update(this.clock.getDelta());
this.stats.update();
// UI事件
if (this.jeepCar) {
this.jeepCar.position.x = this.controls.positionX;
this.jeepCar.position.z = this.controls.positionZ;
// 這里設(shè)置的角度,需要轉(zhuǎn)換為弧度
this.jeepCar.rotation.x = this.angle2Radian(this.controls.rotationX);
this.jeepCar.rotation.y = this.angle2Radian(this.controls.rotationY);
this.jeepCar.rotation.z = this.angle2Radian(this.controls.rotationZ);
}
// render using requestAnimationFrame
requestAnimationFrame(this.render);
this.renderer.render(this.scene, this.camera);
},
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
},
// 角度轉(zhuǎn)弧度(弧度 = π / 180 * 角度)
angle2Radian(angle) {
return (Math.PI / 180) * angle;
},
execute() {
// 初始化場(chǎng)景
this.initScene();
// 初始化攝像頭
this.initCamera();
// 初始化三維坐標(biāo)系
this.initHelper();
// 初始化輔助UI
this.initGui();
// 初始化幀數(shù)顯示工具
this.initStats();
// 初始化時(shí)鐘工具
this.initClock();
// 初始化模型
this.initModel();
// 初始化渲染器
this.initRender();
// 初始化光源
this.initLight();
// 初始化控制器
this.initControls();
// 執(zhí)行渲染
this.render();
},
},
};
</script>