最近有朋友需要渲染模型,就帮忙写了个简单的案例,下面分享下代码

image.png
代码块分享
<template>
<div class="threejs-container">
<div class="title">
<p>{{ findRes.title }}</p>
</div>
<div class="subTitle"><img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAASCAYAAADVCrdsAAAAAXNSR0IArs4c6QAABWlJREFUSEudVWtsFFUUPufOY9+z0+5ud9stFMqjYIUKqdpUQcSEGIIRNMUafxgTgxITEwn+gBi7+E9/KJHEB2pEQzQWKwkIJiZGfDQBedSW0m1X+to+tq/tln3v7My9Zna7pTRgwfNr5s45537znfOdgzDPmM9HoEk/aGKIyCJtH8jh4EhDOsPKDRYLM9tsUyazdKq49pVgzjfn6mOIwObnuddn1AMYA4RDPkSfjxYSMMZ4//f79nSev7okNDic5nkCpatWiDWb6qNOq+sze/3L04zpWJHl4mft/wCaA6EHD51+wyuYLVWjoeniRCRqG+wJrhgfGM6KVnPOLx6OaGsf3mDbtGv7D/29g36HgEVWFw27Hn0vNgc+x+a9sZNL7tsC/N63Dz4xPDhaN9w7aklEk1psJk7T8aRCRBGBUgBCgFIKLq8bV95flboxGQaeZ7zDLSWtkjFmNHDdBhnaHXVHdEBz7NyJmTx7DODQIcyBGDqz79mRoam6/q5AIpPK0HRSgWxGQ+D5PICCEQKysxgANGK0WkEQjIwgcJqaInYbD3KxmHI5zJfjidSFtS+eDM+V6UQDga7qW/pGL33o7AFXLJmsxKGf9q+PhKPPd1+8ltI0Dacmoqhfm++62xghYBRFxhtFIECAF3mQ3E5QMwqkEjeIRWTGMq/tRpFb7iotdV84c+TwxO4ToC3MFPr9gIuj5kZFSf2BwbNv7v2no7csOjWlDvRNoN6Ai5leljxBFAjJ+0uyxIo9TkgrWZqOxgVJFnmX26463Y6As9QxFhlPxDmDkVJEPjYddVlsUr3dUfyXe+tbLdjT8nrTcCCI1/0DoJdhNudiOHI9wjSVahoQBOSYRlFRFWq2WUEUBZqMJ5mqqGAvshrMFiPHG8Ws0SQgME4o8ZbwTq/7z28+vHzKd+6cij0t+3x9nQHo8w/kGu+ujABQhTK7w2TwlhcpyLFoJq0lbDazlNUoRxkxKIrKUw252EwcOI5nUpGdys6ijE2WIqVLvb86Nh/sKowH7Dm5v2mg8zoGrgV0du/KVJWykhIbV1O3ZJg3iD9WNX4bAjjKA+yhiKix5gbRn7VUIGIZMC7OCwbRaLJo9pVLe21rXpsuzJaCcrD/53de7W3vKBvvH9YmxmZQb7RbFLEAFuF5UNMKq6n1io9tWfUR1n8yoqtA1xljzeTK56dXG018tcVisVOVIG8VEtk4myDx6dblLx1L5/5+wSzB+LWjDwQ6uhuv/taaSCcznJJWAUiBEp33m/To5bLKVl07rKzMYNhYX/Gd+8nj7eePvyCt8sjVwb6phxQVvIwRzWgy/m2XLDMagSyiiMgLU5VPvdt+O6pzc2Ly4seNHa3na3suXIpqWQ3zvZEfToQnWFBAXg8Alfctg4lgiKyscmQqVpSHkhnN3tcz6kYUWHlFSff62rWnYP2Bmfw0WtxyIC59ukfwrqvaORK4vqG3oxtSiXjuNrPFRGPxpBqfSeZVoxOjUpBLZCjxemA8GEIto4ixWEqRHRK3advGvkqH52t83Key5gbulgH1H6McZ+vJmpsbuK01OzzjbZdrVSVlUtKppEnkwqMjY3X+tqCdJ6hRQvQJypS0AmbZijabjUYjUQpIhW1PP6JYPMLR0s1HJvWaz1+Gi3Ext/0KG3F+AANA/1fP7W39pXtJZDqZ4DkkokkQlq32MEkyq6GhsBmAh3UPrlbtxVKLd/v7bYz5COLNbbwYAP37HIi8ZhnOHc2u9s4vdtWoWeWZ0ZGoUa+Hx+uIlXjl0xnGR0yCuBmIgSlZ7cqyHYf9t/uRewZxhwD0f7mzYk11mZzIijA5MjO2fPexsUIZCzEL3+/m8oLPv9MIlKW079FBAAAAAElFTkSuQmCC"
alt="">
<p class="subTitle-val">{{ findRes.subTitle }}</p> <img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAASCAYAAADVCrdsAAAAAXNSR0IArs4c6QAABWNJREFUSEulVl1sFFUUvvfO7Ozs7Ox2t2W7bekf7UJLm7YgFYqCRJ+EFIMmbSJGYjARf1580PhkmD6ZGJ80JgZ/iSFqq8QEE1BKINRoIrQCpU2xFcqy3XbbnXZ3drs7O3PnXnOn3f4AsYgn2czezNxzvvOde75zIfifRimAoEuB4OhRCiGE6sAnO1LqzFPZTNZBsW45JWf/XFg923rkmEkphaCnE4GOHgIhoPnQ8GEw2IEBAF1dClQUhTDnEEI6O3i8OTw8dLD/7AU9ndCoIAqgtrleCjXX/lTz9Ht9bF8++Mr/MO/gQcBQRUGLGdtZsPWtrUaA44i7cvsj09cvXj7cd/J0IJfOYkESIcYYmCaGoaY6I7A+8HdxcWEmEPRGr98I39j18hcpth8qCrEzsmkCEKykKA8qnzVbs/fd3R1ce3l5iRrPthmYDyTn0r65uOZ0SG4NOXjP1Yv9iBACAPshZD/ZWvK6nT6fB/qDflQdqowBJ/051P7hdZuR0ZNv7dz43Ae/5zNbxchRheaBdXd0cDs7QINpgS1qwqibnc4iLQuAIIjYsixq5HKcqevYMDBIJzSA7qKWUkJEwQFcboG6vB5n047NUHTwJ6oOfDQMp851PY9NbCU17sfGTsW4uyzRU69IThesVicTu6cm4pXTMR1Z0KV7A4UgrSZARtMAQQgSjCk2DGjo97hYcMlIwQAIIg/8BW4i+T1C0/ZGdXwg8im8dV7xIT33ajymErdLvCQ6kE6gZVFqCZIkFExNxDYmYnPByWjCMjEy3QUeysoXj0SBYWBo070QAwCev4eBVUkhADAmQJJEECz1k7JQhWtDfdX39pm4c0Yp1Oe1Q+Gx27UJNTHPIYizqQw0DJNLpzLGfCqLXW4J8gLH6RkDmIYOnQ4REsgKDy2eR6z+NqA1DSHA2KoJlVB/WYlQt6Xu6lJ3RE8pEhFw22ws3jo3M+tKzSUdybkUdAg8kgtECxBKXBJnAEpNSqiR03M5p4uX02nTe+emyqpB2Dlc0xbL4iuUwfraclS9uXZmsTsW+p4dQkopF/vl3cpsOhM0TN2ysMEBEzNlGW946cvYYhAEwDEEgJ+MffdDSyyitV+5FOYsSumaOBACrHWLi320pLbKUd/aNLgkVgutaKuJrQHXTrxWI4liC4RUotSSRQFlpyenZsrXF/0Z2PdxjH2XF5yh4wf2jgzO7I5GkjlAFtr+34xVrbQiQDY0Nbpb9mzrWbVhyWn367LgkFqxrgOehwJCwKFOxasgB2sxNvTCgOcvj9v1x43Lw6NPKhfw0FfPvnDt0kQD75SNaDgKEc8v6ITdFYyb/FlhtQDA6xOp5CvgHtu/b16biX+2Jupl0aLw1pk3N02OTrZP3o4Fg6UFudJK/x2LmGBk4HZ5Im0Rr08G4yMRwAvLIFg3IEbxYpuykkuyi2vbv0+obqz/Vq578ep9QSwNpbs4ZRJLh94vGfj1yuELpy+zFnbwogAkWTYLA156c2gcYmIHtY3lHyz1Q4yJAxvYZqWotBiGmuuzxeXVvUXb32AiyeT6wY12d3Cws8ea7Xtn13D/2DO/9V7RikrWQY5DUEskOSuHCS/wcFGumXPhib1bx80sHeBlYSNCTioV+SZqdmwbhIWdyfzc+k8gVsKdPvd2x9jg+KNjIxHL63PCdes8WvhmTJqe1CxMsGVhSBu2lnlCm8r6Nh/65lR+WC2Xd3miPjQIel7hIyn1cadb3JDNzKteWeiLhaf3qDNa66yaEtwyD8srvJEskD9vOfT1/MIEVhbvEMtdaEvDgxdj9Zf3uwKwIdd2UKqu2OT3zMc0OjIaHm890ptceXe4X7x/AJTdu1DramFgAAAAAElFTkSuQmCC"
alt="">
</div>
<div ref="canvasContainer"></div>
<div v-if="isLoading" class="loading-overlay">
<div class="loading-bar"
:style="{ background: `conic-gradient(#4caf50 ${loadingProgress}%, #eee ${loadingProgress}%)` }">
<span class="loading-text">{{ loadingProgress.toFixed(0) }}%</span>
</div>
</div>
</div>
</template>
<script>
import * as THREE from 'three';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
export default {
name: 'MyView',
data() {
return {
findRes: {},
isLoading: true,
loadingProgress: 0
}
},
methods: {
initSence() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
this.$refs.canvasContainer.appendChild(renderer.domElement);
// 添加光源
const lightPower = this.findRes.lightPower || 1
const ambientLight = new THREE.AmbientLight(0xffffff, lightPower);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, lightPower);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
const pointLight = new THREE.PointLight(0xffffff, 2, 100);
pointLight.position.set(5, 5, 5);
scene.add(pointLight);
const loadingManager = new THREE.LoadingManager();
loadingManager.onStart = () => {
this.isLoading = true;
};
loadingManager.onLoad = () => {
//this.loadingProgress = 100;
setTimeout(() => {
this.isLoading = false;
}, 500)
};
loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
this.loadingProgress = Math.floor((itemsLoaded / itemsTotal) * 10) + 90; // 更新进度
};
const loader = this.findRes.modelPath.endsWith('.gltf') || this.findRes.modelPath.endsWith('.glb') ?
new GLTFLoader(loadingManager) : new FBXLoader(loadingManager); // 根据文件类型选择加载器
let object; // 声明一个变量来存储加载的模型
let isMouseControlled = false; // 添加标志来跟踪鼠标控制状态
loader.load(this.findRes.modelPath, (loadedObject) => {
object = loadedObject.scene || loadedObject; // 将加载的模型赋值给变量
scene.add(object);
}, (xhr) => {
// 使用 xhr 对象获取更精确的加载进度
const percentComplete = Math.round((xhr.loaded / xhr.total) * 100);
this.loadingProgress = percentComplete>90 ? 90 : percentComplete;
//console.log('xhr',percentComplete);
}, (error) => {
console.error('加载模型时出错:', error);
});
const cameraRng = this.findRes.cameraRng ? this.findRes.cameraRng : [0, 20, 22]
//console.log('cameraRng', cameraRng);
camera.position.set(...cameraRng);
const controls = new OrbitControls(camera, renderer.domElement);
// 监听鼠标控制事件
controls.addEventListener('start', () => {
isMouseControlled = true; // 开始控制时设置标志
});
const animate = function () {
requestAnimationFrame(animate);
if (object && !isMouseControlled) {
object.rotation.y += 0.005; // 使模型自动旋转
}
renderer.render(scene, camera);
};
animate();
}
},
mounted() {
const routeParams = this.$route.query;
//const findRes = window.config.lists.find(item => item.id == routeParams.id);
//console.log('obj:', findRes);
const findRes = {
id: 1,
title: '解放战争时期粟裕大将使用的臂搁',
subTitle: '新四军江南指挥部纪念馆',
modelPath: "/model/1/model.fbx",
cameraRng: [0, 3, 30], //相机配置
lightPower:2 //光源
}
this.findRes = findRes || window.config.lists[0];
this.initSence();
}
};
</script>
<style scoped>
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
/* 确保在最上层 */
}
.loading-bar {
width: 100px;
/* 圆形的直径 */
height: 100px;
/* 圆形的直径 */
border-radius: 50%;
/* 使其成为圆形 */
position: relative;
/* 使文本能够绝对定位 */
transition: background 0.5s ease;
/* 添加过渡效果 */
background: conic-gradient(gray 0%, transparent 0%);
/* 默认颜色为灰色 */
}
.loading-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* 居中对齐 */
color: #000;
/* 文本颜色为黑色 */
font-size: 16px;
/* 文本大小 */
font-weight: bold;
/* 文本加粗 */
background: #fff;
/* 白色背景 */
border-radius: 50%;
/* 使其成为圆形 */
width: 80px;
/* 圆形的直径 */
height: 80px;
/* 圆形的直径 */
display: flex;
align-items: center;
justify-content: center;
/* 居中对齐文本 */
}
.threejs-container {
width: 100vw;
height: 100vh;
position: relative;
overflow: hidden;
background: url('/pc_bg.png') 50% / cover no-repeat;
z-index: 1;
}
.threejs-container .title {
position: absolute;
top: 20px;
left: 50%;
z-index: 10;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
font-size: 26px;
font-weight: 700;
color: #ffeac0;
text-align: center;
display: -webkit-box;
display: -ms-flexbox;
display: flex
}
.threejs-container .subTitle {
position: absolute;
top: 80px;
left: 50%;
z-index: 10;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
font-size: 22px;
color: #ffeac0;
opacity: .5;
text-align: center;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.subTitle-val {
margin: 0px 10px;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 2;
}
</style>