You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
5.6 KiB
Vue

3 weeks ago
<template>
<div class="div3D" ref="div3D" v-loading="loading" element-loading-text="渲染模型..."></div>
<!-- 固定位置的控制按钮 -->
<div class="control-buttons">
<el-button
type="warning"
@click="tranlate(new Vector3(28, 10, -10), new Vector3(0, 0, 0))"
:icon="Refresh"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
//导入threejs
import {
AxesHelper,
Color,
PerspectiveCamera,
Scene,
WebGLRenderer,
EquirectangularReflectionMapping,
Group,
Vector3,
AmbientLight,
DirectionalLight,
PointLight
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import gsap from 'gsap';
import { Star, Bell, MagicStick, Refresh, Compass } from '@element-plus/icons-vue';
const loading = ref(true); // 添加 loading 状态
const div3D = ref<HTMLDivElement>();
//场景
const scene = new Scene();
//相机
const camera = new PerspectiveCamera(
45,
div3D.value?.clientWidth! / div3D.value?.clientHeight!,
0.05,
200
); // 初始比例设为1
camera.position.set(30, 15, -15);
camera.lookAt(0, 0, 0);
scene.add(camera);
//渲染器
const render = new WebGLRenderer({
antialias: true, // 关闭抗锯齿提升性能
powerPreference: 'high-performance', // 使用高性能GPU
stencil: false, // 如果不需要模板缓冲区
depth: true,
logarithmicDepthBuffer: true // 对大场景有帮助
});
render.setClearColor(new Color('#131519'));
// 启用渲染优化
render.shadowMap.enabled = false; // 如果不需要阴影
//添加控制器
const controls = new OrbitControls(camera, render.domElement);
// 禁用滚轮缩放
// controls.enableZoom = false;
// controls.autoRotate = false;
// controls.enablePan = false; // 禁用右键平移
controls.autoRotateSpeed = 1;
controls.dampingFactor = 0.2;
controls.rotateSpeed = 0.3; // 降低旋转速度(默认为 1.0
controls.enableDamping = true;
controls.enableRotate = true;
controls.minDistance = 1; // 允许更近距离观察模型细节
// 设置视角限制向上45度向下45度
// 从正前方开始计算向上最大45度向下最大45度
controls.minPolarAngle = Math.PI / 4; // 向上最多45度 (π/4)
controls.maxPolarAngle = (Math.PI * 3) / 4; // 向下最多45度 (3π/4)
// 大场景优化配置
const startTime = Date.now();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
loader.setDRACOLoader(dracoLoader);
let meshArr: Group[] = [];
loader.load('/models/substation/substation.gltf', (gltf: any) => {
scene.add(gltf.scene);
const loadTime = Date.now() - startTime;
// 立即渲染一帧
controls.update();
render.render(scene, camera);
console.log(`大场景模型已加载并显示,总耗时:${loadTime}ms`);
loading.value = false;
});
// 环境光:增强整体亮度
const ambientLight = new AmbientLight('#ffffff', 1.2); // 强度从 0.6 提高到 1.0
scene.add(ambientLight);
// 平行光
const directionalLight = new DirectionalLight('#ffffff', 1.4); // 强度从 0.2 提高到 0.8
scene.add(directionalLight);
directionalLight.position.set(20, 20, 10);
// // 点光源
const pointLight = new PointLight('#ffffff', 1.4, 1800); // 强度从 0.5 提高到 1.0
scene.add(pointLight);
pointLight.position.set(0, 40, 0);
// //环境贴图
// const rgbELoader = new RGBELoader().load(
// '/models/texture/Alex_Hart-Nature_Lab_Bones_2k.hdr',
// (envMap: any) => {
// envMap.mapping = EquirectangularReflectionMapping;
// scene.environment = envMap;
// }
// );
//补间动画
const timeline1 = gsap.timeline();
const timeline2 = gsap.timeline();
//相机移动
function tranlate(position: Vector3, target: Vector3) {
timeline1.to(camera.position, {
x: position.x,
y: position.y,
z: position.z,
duration: 1,
ease: 'power2.inOut'
});
timeline2.to(controls.target, {
x: target.x,
y: target.y,
z: target.z,
duration: 1,
ease: 'power2.inOut'
});
}
//物体
// const axes = new AxesHelper(20);
// scene.add(axes);
// 监听容器大小变化
function handleResize() {
if (!div3D.value) return;
const container = div3D.value;
const width = container.clientWidth;
const height = container.clientHeight;
// 更新相机比例
camera.aspect = width / height;
camera.updateProjectionMatrix();
// 更新渲染器大小
render.setSize(width, height);
}
function init() {
requestAnimationFrame(init);
controls.update();
render.render(scene, camera);
}
onMounted(() => {
// 初始化时设置正确大小
handleResize();
// 启动渲染循环
init();
// 将渲染器添加到容器
div3D.value?.appendChild(render.domElement);
// 添加容器大小监听
const resizeObserver = new ResizeObserver(handleResize);
if (div3D.value) {
resizeObserver.observe(div3D.value);
}
// 保存观察器引用以便销毁
(window as any)._resizeObserver = resizeObserver;
});
onUnmounted(() => {
// 清理资源
if ((window as any)._resizeObserver) {
(window as any)._resizeObserver.disconnect();
delete (window as any)._resizeObserver;
}
});
</script>
<style scoped>
.div3D {
margin: 0;
padding: 0;
/* width: 500px;
height: 500px; */
width: 100%;
height: 100%;
position: relative;
}
.control-buttons {
position: absolute;
top: 50%;
left: 2%;
transform: translateY(-50%); /* 垂直居中 */
display: flex;
flex-direction: column;
gap: 20px;
z-index: 100;
align-items: flex-start; /* 确保所有按钮左对齐 */
}
</style>