Three.js+Vue3实现仪器虚拟拆解+ 解决反光问题!(重磅)

以下代码支持:各类仪器虚拟拆解、仪器虚拟展示。 

仪器拆解关键代码如下:

import {DragControls} from "three/addons";
let dragControls = null;// 声明 拖拽 变量
const objects = []; // 用于存储可以拖拽的对象
let mouse = new THREE.Vector2();//拆解模块


            model.traverse((child) => {
              if (child.isMesh) {
                //深度写入 将位置信息写入 防止透视或丢失
                child.material.depthWrite = true;
              }
              objects.push(child); // 将可拖拽对象添加到数组中
            });
          }


    // 8. 拆解模块
    // 初始化Raycaster
    raycaster = new THREE.Raycaster();
    // 初始化DragControls
    dragControls = new DragControls(objects, camera, renderer.domElement);
    dragControls.addEventListener('dragstart', (event) => {
      controls.enabled = false; // 禁用OrbitControls
    });
    dragControls.addEventListener('dragend', (event) => {
      controls.enabled = true; // 启用OrbitControls
    });
    // 监听鼠标单击事件
    renderer.domElement.addEventListener('click', onClick, false);

// 鼠标点击事件处理函数
const onClick = (event) => {
  event.preventDefault();

  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(objects);

  if (intersects.length > 0) {
    const selectedObject = intersects[0].object;
    console.log('选中物体:', selectedObject);
  }
};


 完整代码如下

<template>
  <div class="container">
    <el-container>
      <el-header style="margin: 0; padding: 0; height: 70px;">
        <el-row>
          <el-col :span="8">图标区域</el-col>
          <el-col :span="8">
            <div style="display: flex;justify-content: center;">
              <el-menu
                  mode="horizontal"
                  :ellipsis="false"
                  @select="handleSelect"
                  class="custom-menu"
              >
                <el-menu-item index="0">
                  <div class="icon-container">
                    <i class="bx bx-home icon"></i>
                    <span class="icon-text">原始视图</span> <!-- 图标下方的文字 -->
                  </div>
                </el-menu-item>
                <el-menu-item index="1">
                  <div class="icon-container">
                    <i class="bx bx-grid-alt icon"></i>
                    <span class="icon-text">爆炸视图</span> <!-- 图标下方的文字 -->
                  </div>
                </el-menu-item>
                <el-menu-item index="2">
                  <div class="icon-container">
                    <i class="bx bxs-hand-up icon"></i>
                    <span class="icon-text">拆解部件</span> <!-- 图标下方的文字 -->
                  </div>
                </el-menu-item>
                <el-menu-item index="3">
                  <div class="icon-container">
                    <i class="bx bx-redo icon"></i>
                    <span class="icon-text">返回</span> <!-- 图标下方的文字 -->
                  </div>
                </el-menu-item>
              </el-menu>
            </div>
          </el-col>
          <el-col :span="8">
            <div>
              <!-- 右侧区域(如果有的话)-->
            </div>
          </el-col>
        </el-row>
      </el-header>
      <el-main style="margin: 0;padding: 0;height: 100%;">
        <div ref="sceneContainer" class="three-scene-container"></div>
      </el-main>
    </el-container>
  </div>

</template>

<script lang="js" setup>
import {onMounted, onUnmounted, ref} from 'vue';
import * as THREE from 'three';
// 引入扩展库GLTFLoader.js 读取GLTF模型文件
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
//进行旋转
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import WebGL from 'three/addons/capabilities/WebGL.js';
import router from "@/router";
import {DragControls} from "three/addons";
// 使用 refs 来引用 DOM 元素
const sceneContainer = ref(null);
let handleResize = null; // 声明 handleResize 变量
let controls = null; // 声明 视角旋转 变量
let camera = null; // 声明 相机 变量
let scene = null; // 声明 屏幕 变量
let renderer = null; // 声明 渲染器 变量
let raycaster = null;// 声明 拆解射线 变量
let dragControls = null;// 声明 拖拽 变量
const objects = []; // 用于存储可以拖拽的对象
let mouse = new THREE.Vector2();//拆解模块
const modelUrl = ref('/3DMODEL/SwitchingMatrix.glb')


onMounted(async () => {
  //检测是否支持WebGL2
  if (WebGL.isWebGL2Available() ) {
    //初始化
    // 确保 ref 指向的 DOM 元素已存在
    if (!sceneContainer.value) return;
    // 1. 创建场景
    scene = new THREE.Scene();
    // 2. 创建相机(相机是 3D 场景中至关重要的一部分,它就像你的眼睛,决定你看到场景的角度和范围。)
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.set(0, 0.15, 0.55); // 调整相机位置,确保能看到模型
    // 3. 创建渲染器(渲染器负责把 3D 场景渲染成 2D 图像,并显示在网页上。)
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColor("#E6E6E6"); // 设置背景颜色(类比舞台幕布)
    sceneContainer.value.appendChild(renderer.domElement); // 将渲染器的 canvas 添加到页面
    // 4. 添加光源
// 增加定向光的强度,并重新定位光源以照亮不同方向
    const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5); // 提高强度
    directionalLight.position.set(50, 50, 50); // 正面光源
    scene.add(directionalLight);
    const additionalLight1 = new THREE.DirectionalLight(0xffffff, 2.5); // 提高强度
    additionalLight1.position.set(-50, 50, -50); // 左后方光源
    scene.add(additionalLight1);
    const additionalLight2 = new THREE.DirectionalLight(0xffffff, 2.5); // 提高强度
    additionalLight2.position.set(50, -50, -50); // 右前方光源
    scene.add(additionalLight2);
// 为底部和后方增加更多的光源
    const additionalLight3 = new THREE.DirectionalLight(0xffffff, 1.0); // 适当调整强度
    additionalLight3.position.set(0, -50, 0); // 从下方照射
    scene.add(additionalLight3);
    const additionalLight4 = new THREE.DirectionalLight(0xffffff, 1.0); // 适当调整强度
    additionalLight4.position.set(0, 50, -50); // 从后面照射
    scene.add(additionalLight4);
// 通过增加点光源增加局部亮度
    const pointLight1 = new THREE.PointLight(0xffffff, 2.0, 50); // 提高点光源强度
    pointLight1.position.set(0, 10, 0); // 设置点光源位置
    scene.add(pointLight1);
// 更强的环境光
    const ambientLight = new THREE.AmbientLight(0xffffff, 1.0); // 增加环境光强度
    scene.add(ambientLight);
// 半球光,增强场景的柔和光照
    const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.5); // 增加半球光的强度
    hemisphereLight.position.set(0, 200, 0); // 从顶部照射
    scene.add(hemisphereLight);


    // 5. 初始化 OrbitControls
    controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true; // 启用阻尼效果
    controls.dampingFactor = 0.25; // 阻尼系数 数值越大减速越快 让转动更加丝滑 需搭配update
    controls.screenSpacePanning = false; // 禁用屏幕空间平移

    // 修改鼠标按键行为
    controls.mouseButtons = {
      LEFT: THREE.MOUSE.ROTATE,   // 左键旋转
      MIDDLE: THREE.MOUSE.PAN,    // 中键平移
      RIGHT: THREE.MOUSE.ROTATE   // 右键旋转
    };


    // 5. 窗口大小监听(提前设置,确保即使模型未加载也能适应窗口变化)
    // 现在定义 handleResize 函数
    handleResize = () => {
      renderer.setSize(window.innerWidth, window.innerHeight);
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
    };

    // 监听窗口大小变化
    window.addEventListener("resize", handleResize);

    // 6. 渲染函数(此时可以暂时只渲染背景或初始内容)
    function animate() {
      controls.update(); // 更新 OrbitControls,必要时启用阻尼效果
      renderer.render(scene, camera);
    }
    renderer.setAnimationLoop(animate);


    // 7. 加载 3D 模型
    const loader = new GLTFLoader();
    await loader.load(
        modelUrl.value,
        (gltf) => {
          console.log("模型加载成功");
          scene.add(gltf.scene); // 将模型添加到场景
          const model = gltf.scene;
          if (modelUrl.value == "/3DMODEL/Modem.glb") {
            model.traverse((child) => {
              if (child.isMesh) {
                if (child.material.name.includes("不锈钢")) {
                  //深度写入 将位置信息写入 防止透视或丢失
                  child.material.depthWrite = true;
                } else {
                  child.material.depthWrite = true;
                  child.material.roughness = 0.6
                }
                objects.push(child); // 将可拖拽对象添加到数组中
              }
            })
            return;
          } else {
            // 遍历模型的每个子节点以调整材质设置(解决模型丢失问题)
            model.traverse((child) => {
              if (child.isMesh) {
                //深度写入 将位置信息写入 防止透视或丢失
                child.material.depthWrite = true;
              }
              objects.push(child); // 将可拖拽对象添加到数组中
            });
          }

        },
        undefined,
        (error) => {
          console.error("模型加载失败:", error);
        }
    )

    // 8. 拆解模块
    // 初始化Raycaster
    raycaster = new THREE.Raycaster();
    // 初始化DragControls
    dragControls = new DragControls(objects, camera, renderer.domElement);
    dragControls.addEventListener('dragstart', (event) => {
      controls.enabled = false; // 禁用OrbitControls
    });
    dragControls.addEventListener('dragend', (event) => {
      controls.enabled = true; // 启用OrbitControls
    });
    // 监听鼠标单击事件
    renderer.domElement.addEventListener('click', onClick, false);
  }


  //WebGL不支持提示
  else {
    const warning = WebGL.getWebGL2ErrorMessage();
    document.getElementById( 'container' ).appendChild( warning );
  }
});


// 鼠标点击事件处理函数(拆解)
const onClick = (event) => {
  event.preventDefault();
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(objects);
  if (intersects.length > 0) {
    const selectedObject = intersects[0].object;
    console.log('选中物体:', selectedObject);
  }
};

onUnmounted(() => {
  // 确保在组件卸载时移除事件监听器
  if (handleResize) {
    window.removeEventListener("resize", handleResize);
  }
});


//上方按钮
const handleSelect = (key) => {
  switch (key) {
    case "0":
      resetScene(); // 回到最初始状态
      break;
    case "1":
      break;
    case "2":
      break;
    case "3":
      router.push({ path: '/layout/InstrumentLibNew' });
      break;
  }
}
//原始视图
const resetScene = () => {
  // 1. 重置相机位置
  camera.position.set(0, 0.15, 0.55);  // 初始位置
  camera.rotation.set(-Math.PI / 6, 0, 0); // 初始旋转

  // 2. 重置视角
  controls.reset(); // Reset OrbitControls

  // 3. 重置模型

  // 如果场景中包含任何需要清理的状态,比如物理、动画等,可以在此重置
  // 如果场景中的某些物体不想完全重置,可以忽略它们的操作。
  renderer.clear();  // 清除渲染器的缓存或内容,确保不会出现旧内容
};


</script>

<style scoped>

.container {
  height: 100vh;
  overflow: hidden;
}
.three-scene-container {
  width: 100%;
  height: 100%;
}


.custom-menu {
  background-color: white !important; /* 白色背景 */
  border-bottom: none !important; /* 去除底部灰色线条 */
  margin-top: 10px;
}
/* 设置选中项的背景色和底部线条颜色 */
.custom-menu .el-menu-item.is-active {
  background-color: rgba(244, 67, 54, 0) !important; /* 红色的浅背景 */
  border-bottom: 4px solid #f44336 !important; /* 红色底部线条 */
}

.custom-menu .el-menu-item.is-active .icon {
  color: #f44336 !important; /* 选中项图标颜色为红色 */
}

.custom-menu .el-menu-item.is-active .icon-text {
  color: #f44336 !important; /* 选中项文字颜色为红色 */
}

.icon-container {
  margin-right: 10px;
  margin-left: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 15px; /* 增加底部间距 */
}
.icon {
  color: #c7000b; /* 图标颜色 */
  font-size: 40px; /* 图标大小 */
}
.icon-text {
  margin-top: 5px;
  font-size: 14px; /* 文字大小 */
  line-height: 1; /* 行高 */
}
/* 鼠标悬停时的样式 */
.custom-menu .el-menu-item:hover {
  background-color: rgba(244, 67, 54, 0) !important; /* 红色浅背景 */
  border-bottom: 4px solid #f44336 !important; /* 红色底部线条 */
}


.custom-menu .el-menu-item:hover .icon-text {
  color: #f44336 !important; /* 悬停时文字颜色为红色 */
}

</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值