threejs引入glb模型展示3d效果
<template>
<!-- 三维画布 -->
<div class="textbox">
<div class="draw" ref="draw" id="three_div"/>
<div v-if="loadSuccess" class="progress">
<div class="progress-bar progress-bar-danger progress-bar-striped active" :style="{width:progress}">
<div class="progress-value">60%</div>
</div>
</div>
</div>
</template>
<script>
import * as THREE from "three"; //三维
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; //控制器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; //控制器
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; //控制器
import { OBJLoader } from "@/utils/OBJLoader";
import { MTLLoader } from "@/utils/MTLLoader";
export default {
name: 'drawCanvas',
data () {
return {
// 声明渲染器
renderer:"",
// 声明相机
camera:"",
// 声明场景
scene:"",
// 声明几何体
geometry:"",
// 声明材质
material: "",
// 声明网格
mesh: "",
// 声明相机控制器
controls:"",
clientWidth:'',
clientHeight:'',
//模型组
group:[],
progress:0,
loadSuccess:false
}
},
mounted(){
this.init()
},
methods:{
//初始化函数
init() {
// 初始化渲染器
this.initRenderer();
// 初始化场景
this.initScene();
// 初始化相机
this.initCamera();
//引入模型
this.initgltfLoader();
// this.initOBJLoader()
//地面
// this.addGround()
// 初始化光源
this.initLight();
// 初始化动画
this.animate();
// 添加事件
this.addmeth();
},
//初始化渲染器
initRenderer(){
//实例化渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
// 设置渲染区域尺寸
this.renderer.setSize(
this.$refs.draw.offsetWidth,
this.$refs.draw.offsetHeight
);
// 告诉渲染器需要阴影效果
this.renderer.shadowMap.enabled = true
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
// 设置背景色
this.renderer.setClearColor(0x000000, 0) // 设置背景颜色
this.$refs.draw.appendChild(this.renderer.domElement);
},
//初始化场景
initScene(){
//实例化场景
this.scene = new THREE.Scene();
// this.scene.background = new THREE.Color( 0xe0e0e0 );
// this.scene.fog = new THREE.Fog( 0xe0e0e0, 20, 100 );
// 红线是X轴,绿线是Y轴,蓝线是Z轴
var axesHelper = new THREE.AxesHelper(100);
// axesHelper.position.set(100,100,100);
this.scene.add(axesHelper);
},
//初始化相机
initCamera(){
this.clientWidth = this.$refs.draw.clientWidth
this.clientHeight = this.$refs.draw.clientHeight
const k = this.clientWidth / this.clientHeight; //窗口宽高比
this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000);
// this.camera.lookAt(new THREE.Vector3())
this.camera.position.set(0, 40, 100);
// 创建相机控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
},
//添加坐标
randAxis(axis){
return axis + 1;
},
//引入外部模型obj
initOBJLoader(){
let _this = this
// var objLoader = new OBJLoader();
// objLoader.setPath('/static/obj/');//模型路径
// objLoader.load('afs_15.obj', function(object) {
// _this.scene.add(object);//加入场景
// _this.renderer.render( _this.scene, _this.camera );
// });
var onProgress = function(xhr) {
if (xhr.lengthComputable) {
var percentComplete = xhr.loaded / xhr.total * 100;
// console.log(percentComplete)
}
};
var onError = function(xhr) {
console.log(xhr)
};
var mtlLoader = new MTLLoader(); // 模型1
mtlLoader.setPath('/static/obj/'); // obj mtl文件的目录
mtlLoader.load('96.mtl', function(materials) {
materials.preload();
var objLoader = new OBJLoader();
objLoader.setMaterials(materials);
objLoader.setPath('/static/obj/');
objLoader.load('96.obj', function(object) {
object.position.x = -10;
object.position.y = 0;
object.position.z = 0;
object.scale.set(0.1, 0.05, 0.1); // 长 高 宽 比例
_this.scene.add(object);
}, onProgress, onError);
});
},
//引入外部模型 gltf
initgltfLoader(){
const gltfLoader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/static/draco/gltf/');// 这个是加载draco算法,这样才能解析压缩后的gltf模型格式.
gltfLoader.setDRACOLoader( dracoLoader );
const connture = new THREE.TextureLoader().load("/static/glb/white.jpg");
const blue2 = new THREE.TextureLoader().load("/static/glb/blue48.png");
const num = '192'
gltfLoader.load("/static/glb/AFS"+num+".glb", (gltf) => {//SimpleSkinning afs_mini
let model = gltf.scene;
console.log(model)
model.scale.set(1,1, 1); //缩放
model.position.set(0, -30, 0)
// model.position.copy(new THREE.Vector3(100, -50, -40)); //基于原点的位置,没有需求改为0即可
const portGroup = model.children && model.children[0].children.find(item => item.name === 'PORT_GROUP')
if(num == '24'){
portGroup.children.forEach(item => {
item.children.forEach(i => {
i.material = new THREE.MeshPhongMaterial({color: 0xFFFFFF});
i.material.map = blue2
})
})
} else {
portGroup.children.forEach(item => {
item.children.forEach(i => {
i.material = new THREE.MeshPhongMaterial({color: 0xFFFFFF});
i.material.map = connture
})
})
}
console.log(portGroup)
this.scene.add(model);
}, (xhr) => {
const percentage = Math.floor(xhr.loaded / xhr.total * 100)
this.progress = percentage + '%'
});
},
//添加光源
initLight() {
// 平行光源
// const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.7 );//模拟远处类似太阳的光源
// directionalLight.color.setHSL( 0.1, 1, 0.95 );
// directionalLight.position.set(50, 100, 100);
// this.scene.add(directionalLight)
//全局环境光
const ambientLight = new THREE.AmbientLight( '#ffffff', 0.1 );
this.scene.add( ambientLight );
// 点光源
const pointLight = new THREE.PointLight( '#ffffff', 1 );
pointLight.position.set(100, 300, 300);
this.camera.add( pointLight );
this.scene.add( this.camera );
},
//地面
addGround(){
const mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2000, 2000 ), new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } ) );
mesh.rotation.x = - Math.PI / 2;
this.scene.add( mesh );
const grid = new THREE.GridHelper( 200, 40, 0x000000, 0x000000 );
grid.material.opacity = 0.2;
grid.material.transparent = true;
this.scene.add( grid );
},
//运行动画
animate(){
requestAnimationFrame(this.animate); //循环调用函数
// 刷新相机控制器
this.controls.update();
this.renderer.render(this.scene, this.camera);
},
// 添加全局方法
addmeth() {
// 监听窗口尺寸变化
window.addEventListener("resize", this.changeSize, false);
window.addEventListener("click", this.portClick, false);
},
// 监听尺寸变化
changeSize() {
// 重置渲染器输出画布canvas尺寸
this.renderer.setSize(
this.$refs.draw.offsetWidth,
this.$refs.draw.offsetHeight
);
var k = this.$refs.draw.offsetWidth / this.$refs.draw.offsetHeight; //窗口宽高比
//重置相机投影的相关参数
this.camera.aspect = k;
// 如果相机的一些属性发生了变化,
// 需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
this.camera.updateProjectionMatrix();
},
//点击端口
portClick(event){
//保持原事件
event.preventDefault();
this.getIntersects(event.layerX, event.layerY)
},
getIntersects(layerX, layerY) {
// 建立射线
const raycaster = new THREE.Raycaster()
// 建立一个空物体
const mouseVector = new THREE.Vector3()
const x = (layerX / this.clientWidth) * 2 - 1
const y = -(layerY / this.clientHeight) * 2 + 1
mouseVector.set(x, y, 1)
raycaster.setFromCamera(mouseVector, this.camera)
raycaster.params.Line.threshold = 0.01
let intersections = []
intersections = raycaster.intersectObjects(this.scene.children,true);
let selectedObject = null, // 被选中的模型
origin = null // 被选中的模型与射线的交点,用于确定挂牌位置
console.log(intersections)
if(intersections.length>0){
for ( var i = 0; i < intersections.length; i++ ) {
// 遍历线相交模型
if(intersections[i].object instanceof THREE.Mesh){
// 取第一个(距离最近)的相交Mesh类型模型
// 如果要排除地面等参照模型,也可在此处添加判断条件
selectedObject = intersections[i].object
origin = intersections[i].point
break
}
}
}
// console.log(selectedObject, origin)
// 存在与射线相交模型
if(selectedObject){
this.outlineObj(selectedObject)
}
},
outlineObj (selectedObjects) {
console.log(selectedObjects)
const occupy = new THREE.TextureLoader().load("/static/glb/occupy.jpg");
if(selectedObjects.parent.name.includes('Port_')){
selectedObjects.parent.children.forEach(item => {
item.material.map = occupy
})
}
},
}
}
</script>
<style lang='less' scoped>
.textbox{
width:1000px;
height:1000px;
position: relative;
}
.draw {
width: 100%;
height: 100%;
}
//进度条
.progress {
height: 17px;
background: #262626;
padding: 3px;
overflow: visible;
border-radius: 20px;
border-top: 1px solid #000;
border-bottom: 1px solid #7992a8;
margin-top: 50px;
position: absolute;
top:45%;
left:40%;
width: 300px;
.progress-bar {
border-radius: 20px;
position: relative;
animation: animate-positive 2s;
float: left;
width: 0;
height: 100%;
font-size: 12px;
line-height: 20px;
color: #fff;
text-align: center;
background-color: #2962f9;
-webkit-transition: width .6s ease;
-o-transition: width .6s ease;
transition: width .6s ease;
}
.active {
animation: reverse stripes 0.40s linear infinite, animate-positive 2s;
}
.progress-value {
display: none;
padding: 3px 7px;
font-size: 13px;
color: #fff;
border-radius: 4px;
background: #191919;
border: 1px solid #000;
position: absolute;
top: -40px;
right: -10px;
}
.progress-value:after {
content: "";
border-top: 10px solid #191919;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
position: absolute;
bottom: -6px;
left: 26%;
}
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
-webkit-background-size: 40px 40px;
background-size: 40px 40px;
}
@-webkit-keyframes stripes {
from {
background-position: 40px 0
}
to {
background-position: 0 0
}
}
@-o-keyframes stripes {
from {
background-position: 40px 0
}
to {
background-position: 0 0
}
}
@keyframes stripes {
from {
background-position: 40px 0
}
to {
background-position: 0 0
}
}
@-webkit-keyframes animate-positive {
0% {
width: 0;
}
}
@keyframes animate-positive {
0% {
width: 0;
}
}
</style>