前言:

        前端通过three工具来实现3d模型的展示

实现效果:

安装插件:

npm i three @types/three

实现代码:

模型放到public里面

https://dtmall-tel.alicdn.com/edgeComputingConfig/upload_models/1591673169101/RobotExpressive.glb

加载glb文件

 const loader = new GLTFLoader();
const modelUrl = '/model/demo.glb';

初始化创建画布,画布设置背景颜色

 // 初始化场景
    scene = new THREE.Scene();
    scene.background = new THREE.Color('#f8f8f8');//大背景

    const width = container.value.clientWidth;
    const height = container.value.clientHeight;

源码:

<template>
  <div id="container" ref="container"></div>
</template>

<script setup>
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { ref, onMounted, onUnmounted, shallowRef } from 'vue';

// 使用 shallowRef 或普通变量避免深度响应式
const container = ref(null);

// 使用普通变量而不是 ref
let scene = null;
let camera = null;
let renderer = null;
let controls = null;
let mixer = null;
let animationId = null;
const initThree = () => {
  try {
    if (!container.value) return;

    // 初始化场景
    scene = new THREE.Scene();
    scene.background = new THREE.Color('#f8f8f8');//大背景

    const width = container.value.clientWidth;
    const height = container.value.clientHeight;

    // 初始化相机
    camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
    camera.position.set(0, 0, 5);

    // 初始化渲染器
    renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true
    });
    renderer.setSize(width, height);
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;

    container.value.innerHTML = '';
    container.value.appendChild(renderer.domElement);

    // 添加光源
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
    scene.add(ambientLight);

    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);

    // 添加轨道控制
    controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.05;

    // 加载模型
    loadModel();

    // 启动动画循环
    animate();
  } catch (error) {
    console.error('初始化Three.js时发生错误:', error);
  }
}

const loadModel = () => {
  const loader = new GLTFLoader();
  const modelUrl = '/model/demo.glb';

  loader.load(
      modelUrl,
      (gltf) => {
        console.log('模型加载成功', gltf);

        const model = gltf.scene;
        scene.add(model);

        // 调整模型位置和缩放
        model.position.set(0, -1, 0);
        model.scale.set(1, 1, 1);

        // 设置动画混合器
        if (gltf.animations && gltf.animations.length) {
          mixer = new THREE.AnimationMixer(model);
          gltf.animations.forEach((clip) => {
            mixer.clipAction(clip).play();
          });
        }

        controls.update();
      },
      (xhr) => {
        const progress = (xhr.loaded / xhr.total) * 100;
        console.log(`加载进度: ${progress.toFixed(2)}%`);
      },
      (error) => {
        console.error('模型加载失败:', error);

        // 创建替代几何体
        const geometry = new THREE.BoxGeometry(1, 1, 1);
        const material = new THREE.MeshBasicMaterial({
          color: 0x00ff00,
          wireframe: true
        });
        const cube = new THREE.Mesh(geometry, material);
        scene.add(cube);
      }
  );
}

const animate = () => {
  animationId = requestAnimationFrame(animate);

  // 更新动画混合器
  if (mixer) {
    const delta = 0.016;
    mixer.update(delta);
  }

  // 更新控制器
  if (controls) {
    controls.update();
  }

  // 渲染场景
  if (renderer && scene && camera) {
    renderer.render(scene, camera);
  }
}

const onWindowResize = () => {
  if (!container.value || !camera || !renderer) return;

  const width = container.value.clientWidth;
  const height = container.value.clientHeight;

  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  renderer.setSize(width, height);
}

const cleanup = () => {
  // 停止动画循环
  if (animationId) {
    cancelAnimationFrame(animationId);
  }

  // 清理 Three.js 资源
  if (renderer) {
    renderer.dispose();
    // 清理 DOM
    if (renderer.domElement && renderer.domElement.parentNode) {
      renderer.domElement.parentNode.removeChild(renderer.domElement);
    }
  }

  // 清理场景
  if (scene) {
    scene.traverse((object) => {
      if (object.isMesh) {
        if (object.geometry) {
          object.geometry.dispose();
        }
        if (object.material) {
          if (Array.isArray(object.material)) {
            object.material.forEach(material => material.dispose());
          } else {
            object.material.dispose();
          }
        }
      }
    });
  }
}

onMounted(() => {
  // 使用 nextTick 确保 DOM 已渲染
  setTimeout(() => {
    initThree();
  }, 100);

  window.addEventListener('resize', onWindowResize);
});

onUnmounted(() => {
  window.removeEventListener('resize', onWindowResize);
  cleanup();
});
</script>

<style lang="scss" scoped>
#container {
  width: 100%;
  height: 400px;
  border: 1px solid #ccc;
}

#container > canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}
</style>

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐