three.js导入3D模型展示城市及高亮+房型弹出+弹窗

吐槽君 分类:javascript

说明: 公司需要做此项目,之前都没接触过,所以个人集合了所有能找到的资料做出了一个demo项目出来,希望对需要的各位有帮助。

1.先导入3D模型: obj+mtl文件

注意:

(1)在导入模型之前需要手动去修改mtl文件里面的路径,因为若是不改的话会导致材质加载失败的问题

(2)所有的变量最好不要在data里面定义,因为模型大一点的话会卡顿等。目前我是在外部定义的。

微信图片_20210408104643.png

模型需要放在根目录下,不然会读取不到。

2.使用three.js是由场景(网格模型(具体实物+材质)+光照(点光源(阴影)+环境光源))+相机+渲染(鼠标缩放拖动之类的)组成的。

我们先初始化

init() {
      let container = document.getElementById("container");
      /* 
        场景
      */
      scene = new THREE.Scene();
      /* 
        相机
      */
      //  scene.background = new THREE.Color( 0xa0a0a0 );
      camera = new THREE.PerspectiveCamera(
        75,
        container.clientWidth / container.clientHeight,
        1,
        1000
      );

      camera.position.set(292, 109, 268);
      camera.position.set(0, 0, 50);
    }
 

如果要拿图片做背景的话(直接添进初始化里面)
import BG from '../../public/xxxx.png'new THREE.TextureLoader().load(BG);

接着就是添加光源效果,如果不加的话会导致漆黑一片的效果。

// 环境光 能保持整体都是亮点
      let ambientLight = new THREE.AmbientLight(0x999999);
      // 点光源 就像灯泡一样的效果  白色灯光 亮度0.6
      let pointLight = new THREE.PointLight(0xffffff, 0.8);

      // 将灯光加入到场景中
      scene.add(ambientLight);
      // 将灯光加到摄像机中 点光源跟随摄像机移动
      // 为什么这样做  因为这样可以让后期处理时的辉光效果更漂亮
      camera.add(pointLight);

      // 我们将摄像机加入到场景中
      scene.add(camera);
 

接着就是在初始化控制器(鼠标左右键的操作)
new OrbitControls(camera, renderer.domElement);如果需要模型自动旋转的话autoRotate设为true就行了,他默认的是2.0, 你要是觉得快了可以调动autoRotateSpeed属性。
最大缩放效果与最小缩放效果是maxDistance/minDistance,他们的值是number。

3.加载模型

// 加载模型
    loadObj() {
      let _this = this;
      let manager = new THREE.LoadingManager();
      manager.addHandler(/\.dds$/i, new DDSLoader());

      mtlloader.load("/model/6/1.mtl", (materials) => {
        objloader.setMaterials(materials);
        materials.preload();
        objloader.load(
          "/model/6/1.obj",
          function (obj) {
            // oldChildren = _this.dealMeshMaterial(obj.children);
            obj.traverse((child) => {
              if (child instanceof THREE.Mesh) {
                child.material.transparent = true;
                child.material.reflectivity = 0.9;
              }
            });
            obj.scale.set(0.02, 0.02, 0.02);
            obj.position.set(0, -7, 0);
            scene.add(obj);
          },fn(), fn2());
      });
    },
    fn() // fn函数是成功调用
    fn2 // fn2失败时调用
 

如果需要加载进度的话就在fn函数获取进度值

4.点击高亮

当我们需要高亮时需要在初始化里面注册一个事件(本人觉得单机体验不好,就用了双击)
renderer.domElement.addEventListener("dblclick", this.mouseClick, false);

在加载模块时我是留住了每个模块的材质的

    /**
     * 留住每个模型的原材质
     */
    dealMeshMaterial(arrs) {
      let result = [];
      for (let i = 0; i < arrs.length; i++) {
        let obj = {
          // name: arrs[i].name,
          material: arrs[i].material,
          uuid: arrs[i].uuid,
        };
        result.push(obj);
      }
      return result;
    }
 

点击事件

mouseClick(event) {
      // 还原之前的 点击状态
      this.showDetailBox = false;
      this.restore(scene.children[2].children, oldChildren);
      // 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
      let intersects = this.getIntersects(event, event.clientX, event.clientY);
      // console.log(intersects);
      // 获取选中最近的 Mesh 对象
      if (
        intersects.length != 0 &&
        intersects[0].object instanceof THREE.Mesh
      ) {
        selectObject = intersects[0].object;
        if (this.isClick) {
          this.showObject(selectObject, event);
        } else {
          console.log(1111);
        }
      } else {
        // console.log(selectObject);
        this.colseData();
      }
    }
 

完整代码

<template>
<div id="container">
<div class="prog" v-if="isShow">
<el-progress type="circle" :percentage="num"></el-progress>
</div>
<div class="btn" v-show="isButton">
<el-button type="primary" @click="handleStructure">查看结构</el-button>
</div>
<div
class="detail-box"
ref="detailBox"
:style="boxStyle"
v-if="showDetailBox"
>
<h4>房屋信息</h4>
<div class="essential">
<span>面积:85平米</span>
<span>居住人口:3人</span>
<span>房间号:201</span>
</div>
<div class="title">人员信息:</div>
<div class="info">
<p>
<span>张三</span
><img
src="../assets/bf0a4016fa4caae69888892bbdfbe8c7.jpeg"
alt=""
/><span>35岁</span> <span>四川省成都市天府新区xx街道xxx</span
><span>18912345678</span>
</p>
<p>
<span>李四</span
><img
src="../assets/d46e7601c27061f9b5c9007bf0719b37.jpg"
alt=""
/><span>20岁</span> <span>四川省成都市天府新区xx街道xxx</span
><span>18912345678</span>
</p>
<p>
<span>王五</span
><img
src="../assets/d599c952302208f9419849bf28c6a391.jpeg"
alt=""
/><span>36岁</span> <span>四川省成都市天府新区xx街道xxx</span
><span>18912345678</span>
</p>
</div>
<div class="title">车辆信息:</div>
<div class="infoTow">
<p>
<span>车牌:川A·12345</span><span>车位:A-001</span
><span>车主:张三</span>
</p>
</div>
</div>
</div>
</template>
<script>
import * as THREE from "three";
import { OBJLoader, MTLLoader } from "three-obj-mtl-loader";
// import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DDSLoader } from "three/examples/jsm/loaders/DDSLoader";
const OrbitControls = require("three-orbit-controls")(THREE);
let scene = null;
let camera = null;
let renderer = null;
let oldChildren = null;
let mtlloader = new MTLLoader();
let objloader = new OBJLoader();
let selectObject = null;
let selectObj = null;
let selectObjTow = null;
let cube = null;
export default {
name: "vue-three",
data() {
return {
// 弹框
showDetailBox: false,
// 加载
num: 0,
isShow: true,
list: "",
boxStyle: {
left: 0,
right: 0,
},
uuid: "",
isClick: true,
isButton: false,
};
},
methods: {
// 初始化场景
init() {
let container = document.getElementById("container");
/* 
场景
*/
scene = new THREE.Scene();
/* 
相机
*/
//  scene.background = new THREE.Color( 0xa0a0a0 );
camera = new THREE.PerspectiveCamera(
75,
container.clientWidth / container.clientHeight,
1,
10000
);
camera.position.z = 25;
camera.position.set(292, 109, 268);
camera.position.set(0, 0, 50);
/* 
渲染器
*/
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer.domElement);
new OrbitControls(camera, renderer.domElement);
//  controls.autoRotate = true;
//  controls.target = new THREE.Vector3(0,-100,0);
renderer.domElement.addEventListener("dblclick", this.mouseClick, false);
},
// 加载模型
loadObj() {
let _this = this;
let manager = new THREE.LoadingManager();
manager.addHandler(/\.dds$/i, new DDSLoader());
mtlloader.load("/model/6/1.mtl", (materials) => {
objloader.setMaterials(materials);
materials.preload();
objloader.load(
"/model/6/1.obj",
function (obj) {
oldChildren = _this.dealMeshMaterial(obj.children);
obj.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.material.transparent = true;
child.material.reflectivity = 0.9;
}
});
obj.scale.set(0.02, 0.02, 0.02);
obj.position.set(0, -7, 0);
scene.add(obj);
},
this.onProgress
);
});
},
/**
* 留住每个模型的原材质
*/
dealMeshMaterial(arrs) {
let result = [];
for (let i = 0; i < arrs.length; i++) {
let obj = {
// name: arrs[i].name,
material: arrs[i].material,
uuid: arrs[i].uuid,
};
result.push(obj);
}
return result;
},
// 加载进度
onProgress(xhr) {
this.num = parseInt((xhr.loaded / xhr.total) * 100) - 0;
if (parseInt((xhr.loaded / xhr.total) * 100) - 0 === 100) {
this.isShow = false;
}
},
// 灯光效果
light() {
// 环境光 能保持整体都是亮点
let ambientLight = new THREE.AmbientLight(0x999999);
// 点光源 就像灯泡一样的效果  白色灯光 亮度0.6
let pointLight = new THREE.PointLight(0xffffff, 0.8);
// 将灯光加入到场景中
scene.add(ambientLight);
// 将灯光加到摄像机中 点光源跟随摄像机移动
// 为什么这样做  因为这样可以让后期处理时的辉光效果更漂亮
camera.add(pointLight);
// 我们将摄像机加入到场景中
scene.add(camera);
},
// 动画效果
animate() {
// camera.position.x += 1
requestAnimationFrame(this.animate);
renderer.render(scene, camera);
},
/**
* 点击事件
*/
mouseClick(event) {
// 还原之前的 点击状态
this.showDetailBox = false;
this.restore(scene.children[2].children, oldChildren);
// 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
let intersects = this.getIntersects(event, event.clientX, event.clientY);
// console.log(intersects);
// 获取选中最近的 Mesh 对象
if (
intersects.length != 0 &&
intersects[0].object instanceof THREE.Mesh
) {
selectObject = intersects[0].object;
if (this.isClick) {
this.showObject(selectObject, event);
} else {
console.log(1111);
}
} else {
// console.log(selectObject);
this.colseData();
}
},
// 关闭弹窗
colseData() {
this.isClick = false;
this.showDetailBox = false;
this.restore(scene.children[2].children, oldChildren);
if (selectObjTow) {
let i = selectObj.position.x;
let backTime = setInterval(() => {
selectObjTow.position.set(i, 0, 0);
i -= 250;
if (i < 0) {
this.isClick = true;
selectObj = null;
selectObjTow = null;
clearInterval(backTime);
}
}, 1);
}
},
// 查看与取消结构
handleStructure() {
this.isButton = false;
this.isClick = false;
this.showDetailBox = false;
this.restore(scene.children[2].children, oldChildren);
if (selectObjTow) {
let j = selectObjTow.position.x;
let backTime = setInterval(() => {
selectObjTow.position.set(j, 0, 0);
j -= 250;
if (j < 0) {
this.isClick = true;
clearInterval(backTime);
}
}, 1);
}
let i = 0;
let timeMove = setInterval(() => {
selectObj.position.set(i, 0, 0);
i += 250;
if (i >= 35000) {
selectObjTow = selectObj;
this.isClick = true;
clearInterval(timeMove);
}
}, 1);
},
/**
* 展示点击内容
*/
showObject(obj, event) {
// console.log(obj);
if (selectObjTow && obj.uuid === selectObjTow.uuid) {
let i = selectObj.position.x;
let backTime = setInterval(() => {
selectObjTow.position.set(i, 0, 0);
i -= 250;
if (i < 0) {
this.isClick = true;
selectObj = null;
selectObjTow = null;
clearInterval(backTime);
}
}, 1);
} else {
this.showDetailBox = true;
this.isButton = true;
// this.uuid = obj.uuid
selectObj = obj;
// 高亮效果
obj.material = new THREE.MeshPhongMaterial({
color: 0xfff,
transparent: true,
opacity: 0.4,
// map: oldOneMaterial.material.map,
});
}
},
/**点击事件,高亮的原理是之前先备份一份原材质,在点击之前先还原,最后通过new THREE.材质map 使用原来记录的材质map添加一个高亮颜色就可以了*/
restore(arrsNew, arrsOld) {
for (let i = 0; i < arrsNew.length; i++) {
for (let j = 0; j < arrsOld.length; j++) {
if (arrsNew[i].uuid === arrsOld[j].uuid) {
arrsNew[i].material = arrsOld[j].material;
break;
}
}
}
},
/**
* 将屏幕坐标转换为3d 坐标
*/
getIntersects(event, x, y) {
var mainCanvas = event.path[0];
event.preventDefault();
this.boxStyle.top = y + 30 + "px";
this.boxStyle.left = x + 50 + "px";
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
x =
((event.clientX - mainCanvas.getBoundingClientRect().left) /
mainCanvas.offsetWidth) *
2 -
1;
y =
-(
(event.clientY - mainCanvas.getBoundingClientRect().top) /
mainCanvas.offsetHeight
) *
2 +
1;
mouse.set(x, y, 0.05);
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(scene.children, true);
return intersects;
},
},
mounted() {
this.init();
this.light();
this.loadObj();
this.animate();
},
};
</script>
<style  scoped>
#container {
width: 100%;
margin: 0 auto;
height: 100vh;
overflow: hidden;
}
.prog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50, -50);
}
.detail-box {
width: 300px;
/* height: 400px; */
/* background: rgba(red, green, blue, 0.1); */
background-color: #fff;
position: absolute;
border: 1px solid #124859;
}
.detail-box {
width: 34.25rem;
box-sizing: border-box;
padding: 10px 10px;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 0.5rem;
z-index: 999;
}
.detail-box h4 {
text-align: center;
margin: 0;
}
.essential {
width: 100%;
font-size: 16px;
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.title {
text-align: left;
font-size: 18px;
margin: 10px 0;
}
.info,
.infoTow {
width: 100%;
font-size: 16px;
}
.info p {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0;
}
.infoTow p {
display: flex;
justify-content: space-around;
align-items: center;
margin: 0;
}
img {
width: 60px;
height: 60px;
}
.btn {
position: fixed;
top: 10px;
right: 10px;
}
</style>

回复

我来回复
  • 暂无回复内容