模型加载弹窗组件
<template>
<div
class="model-preview"
v-show="visible"
>
<!-- 标题 -->
<div class="popup-title">
模型查看
<img
class="popup-close"
:src="require(`@/assets/home/close.png`)"
@click="closeClick"
/>
</div>
<!-- 模型 -->
<div id="modelPreview" class="model-view">
<el-progress
type="circle"
v-if="isModelLoading"
:percentage="modelProgress"
></el-progress>
</div>
</div>
</template>
<script>
import satellite3D from "@/plugins/three/initSatellite.js";
import Bus from "@/plugins/bus/bus.js";
import { ElMessage } from "element-plus";
import { defineComponent, onMounted, ref, toRefs, watch, onBeforeUnmount, nextTick } from "vue";
export default defineComponent({
props: {
visible: {
type: Boolean,
default: false,
},
url: {
type: String,
default: "",
}
},
setup(props, { emit }) {
const { visible, url } = toRefs(props);
let isModelLoading = ref(false);
let modelProgress = ref(0);
// 监听面板类型,设置组件
watch(
() => visible.value,
(newVal) => {
if (newVal) {
url.value
? satelliteModel(url.value)
: satellite3D.clearSatelliteAnimation(); // clearSatelliteAnimation 停止动画渲染循环,清除场景数据
} else {
satellite3D.clearSatelliteAnimation();
}
},
{
immediate: true,
}
);
const satelliteModel = async (url) => {
await nextTick();
satellite3D.changeSatellite(url);
};
const closeClick = () => {
emit("close");
};
onMounted(() => {
// 是否关闭模型加载loading
Bus.$on("ModelPreview-modelLoading", (val) => {
isModelLoading.value = val;
!val && (modelProgress.value = 0);
});
// 模型加载进度更新
Bus.$on("ModelPreview-modelProgress", (val) => {
isModelLoading.value = true;
modelProgress.value = val;
});
// 浏览器不支持webGL
Bus.$on("ModelPreview-noWebgl", () => {
ElMessage({
message: "浏览器不支持webGL",
type: "error",
showClose: true,
});
satellite3D.clearSatelliteAnimation();
});
});
onBeforeUnmount(() => {
// 帧跟随撤销
satellite3D.destroyAnimation();
Bus.$off([
"ModelPreview-modelLoading",
"ModelPreview-modelProgress",
"ModelPreview-noWebgl",
]);
});
return {
isModelLoading,
modelProgress,
closeClick,
};
},
});
</script>
<style lang="stylus" scoped>
.model-preview
width 9.9rem
height 7.2rem
background #ffffff
border 0.01rem solid #e1e4eb
border-radius 0.08rem
box-shadow 0 0.02rem 0.08rem 0 #000
margin 0
position absolute
top 50%
left 50%
transform translateX(-50%) translateY(-50%)
z-index 99
.model-view
width 100%
height calc(100% - 0.4rem)
border-radius 0 0 0.08rem 0.08rem
:deep() .el-progress
width 100%
height 100%
display flex
justify-content center
.el-progress__text
font-size 0.16rem
color #fff
.popup-title
position relative
height 0.4rem
line-height 0.4rem
text-align center
font-size 0.14rem
color #5C6472
border-radius 0.08rem
.popup-close
position absolute
right 0.1rem
top 0.08rem
font-size 0.24rem
cursor pointer
</style>
模型加载模块文件
加载环境
import THREE from "@/plugins/three/three3D.js";
import Detector from "@/plugins/three/Detector.js";
import Bus from "@/plugins/bus/bus";
let init = false; // 是否已初始化
let dom, width, height;
let model, animationId;
let scene, camera, renderer, controls;
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.9); // 颜色,光照强度
ambientLight.castShadow = true; // 阴影
// 平行光
const directionalLight = new THREE.DirectionalLight("#ffffff");
directionalLight.castShadow = true;
directionalLight.position.set(5, 10, 7.5);
// 初始化
function initSatellite () {
// 检测浏览器是否支持webGL
if (Detector.webgl) {
dom = document.getElementById('modelPreview');
width = dom.clientWidth
height = dom.clientHeight
scene = new THREE.Scene(); // 新建场景
scene.add(ambientLight); // 添加环境光源
scene.add(directionalLight); // 添加平行光源
// 设置远景相机
camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 5000); // 透视摄像机PerspectiveCamera:Fov 视野角度 默认50,Aspect 长宽比,Near 近截面 默认0.1,Far 远截面 默认2000。当物体某些部分比摄像机的远截面远或者比近截面近的时候,这些部分将不会被渲染到场景中。
camera.position.set(0, 0, 5000); // 设置相机位置xyz。防止 scene.add() 后摄像机和模型处于同一位置。
// 创建渲染器
renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
});
renderer.setSize(width, height); // 设置窗口尺寸
renderer.setPixelRatio(window.devicePixelRatio); // 抗锯齿
renderer.outputEncoding = THREE.sRGBEncoding; // gltf 适配纹理
dom.appendChild(renderer.domElement); // 添加到box节点下:<canvas>
// 控制器
controls = new THREE.OrbitControls(camera, renderer.domElement); // 初始化控制器
setControls(controls) // 设置控制器
return true
} else {
Bus.$emit('ModelPreview-noWebgl')
return false
}
}
// 设置控制器
function setControls (controls) {
// 旋转
controls.rotateSpeed = 0.1; // 设置旋转速度
controls.enableDamping = true; // 使动画循环使用时阻尼或自转 意思是否有惯性
controls.autoRotate = true; // 是否自动旋转
controls.enableZoom = true; // 是否可以缩放
controls.minDistance = 1; // 设置相机距离原点的最近距离(最大显示)
controls.maxDistance = 3000; // 设置相机距离原点的最远距离(最小显示)
controls.enablePan = true; // 是否开启右键拖拽
controls.enabled = true; // 启用控制器
// window.addEventListener('resize', resize, false); // 自适应监听
controls.update(); // 更新控制器
}
切换模型
// 切换模型
async function changeSatellite (url) {
// 帧播放关闭
if (animationId) {
window.cancelAnimationFrame(animationId)
}
// 判断初次加载,加载场景
!init && (await initSatellite(), init = true)
model && scene.remove(model) // 移除旧模型
renderer.render(scene, camera) // 更新渲染器
let isTimer = false
// 加载模型
Bus.$emit('ModelPreview-modelLoading', true);
model = await loadGltf(url); // gltf
isTimer = true;
scene.add(model);
renderer.render(scene, camera) // 更新渲染器
// 不加定时器,进度条像没效果一样,不到100%就消失了
if (isTimer) {
let timer = setTimeout(() => {
Bus.$emit('ModelPreview-modelLoading', false);
modelAnimate(); // 渲染循环更新控制器
clearTimeout(timer);
}, 10);
} else {
modelAnimate();
}
}
加载模型 gltf
const loadGltf = (url) => {
return new Promise((resolve, reject) => {
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/gltf/')
// dracoLoader.setDecoderPath('/draco/')
dracoLoader.preload()
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader).load(url, (gltf) => { // 模型文件这里用的线上的
const gltfScene = gltf.scene
// 修改模型材质
gltfScene.traverse(function (child) {
if (child.isMesh) {
child.material.side = THREE.DoubleSide // 2 双面材质
child.frustumCulled = false; // 即使物体中心点看不到也不消失
}
});
setModelProperties(gltfScene)
resolve(gltfScene)
}, onProgress, function (xhr) { reject(xhr) })
});
}
加载模型 obj
// 加载 obj 模型
let loadModel = (name) => {
let path = '/models/OHS/', mtlPath = 'OHS.mtl', objPath = 'OHS.obj'; // 模型文件这里用的本地的
// http://www.webgl3d.cn/Three.js/
// 加载模型的 mtl 和 obj 数据
return new Promise((resolve, reject)=>{
// 加载 mtl
new THREE.MTLLoader().setPath(path).load(mtlPath, function (materials) {
materials.preload();
// 加载 obj
new THREE.OBJLoader().setMaterials(materials).setPath(path).load(objPath, function (object) {
setModelProperties(object)
resolve(object)
}, onProgress, function (xhr) { reject(xhr) })
});
});
}
加载进度
// 进度通知
function onProgress (xhr) {
let percentComplete = xhr.loaded / xhr.total * 100;
percentComplete = Math.round( percentComplete, 2 )
Bus.$emit('ModelPreview-modelProgress', percentComplete)
};
设置模型
// 设置模型旋转中心点
function setModelProperties (model) {
// 设置旋转中心点 gltf
model.children[0].children.forEach(item => {
item.geometry.computeBoundingBox();
item.geometry.center()
})
// 设置旋转中心点 obj
// object.children[0].geometry.computeBoundingBox();
// object.children[0].geometry.center()
// 位置
model.position.x = 0
model.position.y = 0
model.position.z = 0
// 旋转
model.rotation.x = 0
model.rotation.y = 0
model.rotation.z = 0
// 缩放
model.scale.x = 1
model.scale.y = 1
model.scale.z = 1
}
动画
// 时刻渲染/渲染循环 当程序运行时,如果你想要移动或者改变任何场景中的东西,都必须要经过这个动画循环。
const modelAnimate = function () {
// 使模型切换时,重新从正面开始旋转。单纯 controls.update() ,会从上一个模型旋转的角度继续旋转
let ohsClock = new THREE.Clock(); // 获取跟踪时间
let delta = ohsClock.getDelta(); // 获取当前秒数
controls.update(delta)
renderer.render(scene, camera)
animationId = requestAnimationFrame(modelAnimate) // 下一帧执行代码
}
// 停止动画渲染循环,清除场景数据(环境光保留)
const clearSatelliteAnimation = () => {
window.cancelAnimationFrame(animationId)
if (scene) {
scene.clear() // 连灯光等一并清除
scene.add(ambientLight) // 环境光保留
}
}
// 页面卸载,停止动画渲染循环,清除场景数据,init置为false。正常进入页面重新渲染卫星模型
const destroyAnimation = () => {
window.cancelAnimationFrame(animationId)
if (scene) {
scene.clear() // 连灯光等一并清除
scene.dispose() // 清除渲染器 所缓存的场景相关的数据。
init = false
}
}
export default {
initSatellite,
changeSatellite,
clearSatelliteAnimation,
destroyAnimation
}
模型查看器
可在另一个程序中打开模型进行查看,例如three.js提供的编辑器 three.js editor,或者glTF Viewer,以及babylonjs来预览模型。