vue项目里使用3D模型

1. 项目概述

基于Three.js和Vue.js实现的3D变压器可视化组件,主要展示了三维模型。

2.技术引用

首先引入技术文件依赖,先下载three.js,再引入到需要的页面和页面所需要的模型控件。 再将我们下载的3D模型,格式为.obj / .glb 最好也引入到页面。

js 复制代码
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";

3.div区域

要有3D渲染容器,canvas-container 作为Three.js渲染的主容器。

html 复制代码
 <div ref="canvasContainer" class="canvas-container">

4.data数据定义

Three.js实例:主要是一些场景、相机、渲染器、控制器等核心对象的初始化数据;

动画控制 :动画帧ID和状态标志

js 复制代码
data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      controls: null,
      square: null,
      animationId: null,
       isModelLoaded: false
    };
  },

5.生命周期钩子

组件挂载:初始化Three.js、加载模型、设置事件监听、启动动画循环 组件销毁:清理事件监听器、停止动画、释放Three.js资源

js 复制代码
mounted() {
  this.initThreeJS();
  this.loadModel();
  this.updateTags();
  // 监听窗口大小变化
  window.addEventListener('resize', this.onWindowResize);
  // 开始动画循环
  this.animate();
},
beforeDestroy() {
  // 清理事件监听器
  window.removeEventListener('resize', this.onWindowResize);
  // 停止动画循环
  cancelAnimationFrame(this.animationId);
  // 清理Three.js资源
  if (this.renderer) {
    this.renderer.dispose();
  }
}

6.three.js的初始化函数

主要用于

  1. 获取渲染容器尺寸并创建Three.js场景
  2. 设置场景背景色(深蓝色)
  3. 创建光源(环境光和方向光)提供照明
  4. 创建透视相机,设置位置和视角
  5. 创建WebGL渲染器,启用抗锯齿和透明度
  6. 初始化轨道控制器,启用阻尼效果使旋转更平滑
js 复制代码
  initThreeJS() {
      const container = this.$refs.canvasContainer;
      const width = container.clientWidth;
      const height = container.clientHeight;

      // 创建场景
      this.scene = new THREE.Scene();

      // 添加环境光(适中亮度,避免过度反光)
      const ambientLight = new THREE.AmbientLight(0xffffff, 1.8);
      this.scene.add(ambientLight);

      // 添加主方向光(适中强度)
      const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
      directionalLight.position.set(5, 10, 7.5);
      this.scene.add(directionalLight);

      // 添加辅助方向光(柔和补光)
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.8);
      directionalLight2.position.set(-5, 5, -5);
      this.scene.add(directionalLight2);

      // 创建相机
      this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
      this.camera.position.z = 3; // 增加相机与模型的距离
      this.camera.position.y = 1; // 提高相机高度

      // 创建渲染器
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setClearColor(0x000000, 0);
      this.renderer.setSize(width, height);
      container.appendChild(this.renderer.domElement);

      // 添加轨道控制器
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.dampingFactor = 0.05;
      this.controls.enableZoom = true;
      this.controls.autoRotate = true;
      this.controls.autoRotateSpeed = 0.5;
    },

7.3D模型的加载

根据模型文件类型(OBJ/GLB)选择合适的加载器 用户可以通过鼠标旋转、平移和缩放3D模型,动画循环确保场景持续渲染

js 复制代码
 loadGLBModel() {
      const transformerType = this.currentTransformerType || this.type;
      const isOBJ = this.modelPath.endsWith(".obj");

      this.camera.position.set(0, 1, 3);

      if (isOBJ) {
        const loader = new OBJLoader();

        loader.load(
          this.modelPath,
          (object) => {
            // 保存模型引用
            this.model = object;

            // 给模型添加材质(OBJ文件不包含材质信息)
            const material = new THREE.MeshPhongMaterial({
              color: 0xffffff, // 白色
              emissive: 0xcccccc, // 微弱的自发光(浅灰色)
              emissiveIntensity: 0.15, // 降低自发光强度
              shininess: 30, // 降低光泽度,减少反光
              specular: 0x333333, // 降低高光反射
            });

            this.model.traverse((child) => {
              if (child instanceof THREE.Mesh) {
                child.material = material;
              }
            });

            // 计算模型的边界盒以自动居中和缩放
            const box = new THREE.Box3().setFromObject(this.model);
            const center = box.getCenter(new THREE.Vector3());
            const size = box.getSize(new THREE.Vector3());

            // 计算最大尺寸
            const maxDim = Math.max(size.x, size.y, size.z);
            const scale = 2.5 / maxDim; // 让模型适配到2.5个单位大小

            // 应用缩放
            this.model.scale.set(scale, scale, scale);

            // 重新计算边界盒(应用缩放后)
            box.setFromObject(this.model);
            box.getCenter(center);

            // 将模型中心移到原点
            this.model.position.set(-center.x, -center.y, -center.z);

            // 将模型添加到场景
            this.scene.add(this.model);

            // 标记模型加载完成
            this.isModelLoaded = true;
            
            console.log("OBJ模型加载成功", {
              model: this.model,
              原始尺寸: size,
              缩放比例: scale,
            });
          },
          // 进度回调
          (xhr) => {
            console.log("OBJ加载进度: " + (xhr.loaded / xhr.total) * 100 + "%");
          },
          // 错误回调
          (error) => {
            console.error("加载OBJ模型失败:", error);
          }
        );
      } else {
        // 原有GLB模型加载逻辑
        const loader = new GLTFLoader();

        loader.load(
          this.modelPath,
          (gltf) => {
            // 保存模型引用
            this.model = gltf.scene;

            // 完全保留模型原始材质,不做任何修改
            // GLB模型自带材质和颜色,直接使用即可

            // 设置模型位置、缩放和旋转
            this.model.position.set(0, -1.2, 0);
            this.model.scale.set(0.6, 0.6, 0.6);

            // 将模型添加到场景
            this.scene.add(this.model);

            // 标记模型加载完成
            this.isModelLoaded = true;
          },
          // 进度回调
          (xhr) => {
            console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
          },
          // 错误回调
          (error) => {
            console.error("加载GLB模型失败:", error);
          }
        );
      }
    },

8.动画循环

  1. 使用requestAnimationFrame创建动画循环
  2. 更新轨道控制器状态
  3. 渲染3D场景
js 复制代码
animate() {
      this.animationId = requestAnimationFrame(this.animate);
      if (this.controls) {
        this.controls.update();
      }
    },

9.窗口大小响应

  1. 获取更新后的容器尺寸
  2. 更新相机的宽高比并重新计算投影矩阵
  3. 调整渲染器大小以匹配容器
js 复制代码
onWindowResize() {
      const container = this.$refs.canvasContainer;
      const width = container.clientWidth;
      const height = container.clientHeight;

      if (this.camera) {
        this.camera.aspect = width / height;
        this.camera.updateProjectionMatrix();
      }

      if (this.renderer) {
        this.renderer.setSize(width, height);
      }

    },
相关推荐
一 乐3 小时前
口腔健康系统|口腔医疗|基于java和小程序的口腔健康系统小程序设计与实现(源码+数据库+文档)
java·数据库·vue.js·小程序·毕设
golang学习记3 小时前
从0死磕全栈之使用 Next.js 构建高性能单页应用(SPA)
前端
Java陈序员3 小时前
完全开源!一款基于 SpringBoot + Vue 构建的社区平台!
vue.js·spring boot·github·社区
好奇的候选人面向对象3 小时前
基于 Element Plus 的 TableColumnGroup 组件使用说明
开发语言·前端·javascript
小纯洁w3 小时前
vue3.0 使用el-tree节点添加自定义图标造成加载缓慢的多种解决办法
前端·javascript·vue.js
叫我詹躲躲3 小时前
Vue 3 ref 与 reactive 选哪个?
前端·vue.js
程序员Sunday3 小时前
Vite 要收费啦?虚拟 DOM 要取消啦?尤雨溪这次玩了把大的!
前端·vue.js
云枫晖3 小时前
webpack系列-plugin
前端·webpack
啃火龙果的兔子3 小时前
前端八股文es6篇
前端·ecmascript·es6