Vue3+Three.js 3D模型导入,小白也能在页面展示一个3D模型了

大家好,我是鱼樱!!!

关注公众号【鱼樱AI实验室】持续每天分享更多前端和AI辅助前端编码新知识~~

目前,3D模型的格式有成千上万种可供选择,但每一种格式都具有不同的目的、用途以及复杂性。 虽然three.js已经提供了多种导入工具, 但是选择正确的文件格式以及工作流程将可以节省很多时间,以及避免遭受很多挫折。某些格式难以使用,或者实时体验效率低下,或者目前尚未得到完全支持。

1. 支持的文件格式

1.1 推荐格式

  • glTF (.gltf, .glb)
    • 行业标准格式
    • 支持材质、动画、骨骼等
    • 文件体积小,加载快
    • 支持二进制格式(.glb)

1.2 其他常用格式

  • FBX (.fbx)

    • 支持复杂动画和骨骼
    • 文件较大
    • 需要额外加载器
  • OBJ (.obj)

    • 简单的几何体和材质
    • 广泛支持
    • 不支持动画
  • COLLADA (.dae)

    • 支持复杂场景
    • XML格式,文件大
    • 加载较慢

2. 模型加载器

2.1 GLTFLoader (推荐)

javascript 复制代码
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'

const loader = new GLTFLoader()
loader.load(
  'model.gltf',
  (gltf) => {
    scene.add(gltf.scene)
  },
  (progress) => {
    console.log('加载进度:', progress)
  },
  (error) => {
    console.error('加载错误:', error)
  }
)

2.2 加载管理器

javascript 复制代码
const manager = new THREE.LoadingManager()
manager.onProgress = (url, loaded, total) => {
  const progress = (loaded / total) * 100
}
const loader = new GLTFLoader(manager)

3. 性能优化

3.1 模型优化

  • 减少多边形数量
  • 压缩纹理
  • 使用 LOD (Level of Detail)
  • 合并小型网格
  • 删除不可见部分

3.2 加载优化

  • 使用 draco 压缩
  • 预加载重要模型
  • 延迟加载次要模型
  • 使用模型实例化

3.3 渲染优化

javascript 复制代码
// 实例化渲染
const instancedMesh = new THREE.InstancedMesh(
  geometry,
  material,
  instanceCount
)

4. 常见问题处理

4.1 材质问题

  • 检查材质路径
  • 确保纹理加载完成
  • 处理透明度
  • 调整光照设置

4.2 模型方向和缩放

javascript 复制代码
model.rotation.set(x, y, z)
model.scale.set(1, 1, 1)
model.position.set(0, 0, 0)

4.3 动画处理

javascript 复制代码
const mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(gltf.animations[0])
action.play()

// 在动画循环中更新
mixer.update(deltaTime)

5. 最佳实践

5.1 资源管理

  • 及时释放不用的资源
  • 使用加载管理器跟踪进度
  • 实现错误处理和重试机制
javascript 复制代码
// 释放资源
model.traverse((node) => {
  if (node.isMesh) {
    node.geometry.dispose()
    node.material.dispose()
  }
})

5.2 交互优化

  • 添加加载进度提示
  • 实现模型交互控制
  • 添加相机控制器
javascript 复制代码
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
const controls = new OrbitControls(camera, renderer.domElement)

5.3 响应式处理

javascript 复制代码
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(window.innerWidth, window.innerHeight)
})

6. 调试工具

6.1 性能监控

  • Stats.js 监控帧率
  • Memory 监控内存使用
  • Scene Explorer 查看场景结构

6.2 开发工具

  • Three.js Editor
  • Blender 导出插件
  • glTF Validator

7. 注意事项

  1. 文件大小

    • 合理压缩模型和纹理
    • 考虑使用 LOD
    • 分块加载大型模型
  2. 跨域问题

    • 配置正确的 CORS 头
    • 使用相对路径
    • 检查服务器设置
  3. 内存管理

    • 及时释放资源
    • 监控内存使用
    • 避免内存泄漏
  4. 兼容性(结尾附代码了)

    • 检查 WebGL 支持
    • 提供降级方案
    • 测试不同设备

Vue3 + three 导入3d模型案例

参考代码

html 复制代码
<!--
 * @Description: Three.js 3D模型加载组件
-->
<template>
  <div class="three-container" ref="container">
    <!-- 加载提示 -->
    <div v-if="loading" class="loading">
      <div class="loading-text">加载模型中... {{ loadingProgress }}%</div>
    </div>
  </div>
</template>

<script setup>
import * as THREE from 'three'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { onMounted, onBeforeUnmount, ref } from 'vue'

const container = ref(null)
const loading = ref(true)
const loadingProgress = ref(0)
let scene, camera, renderer, controls
let animationFrameId
let model = null

const initScene = () => {
  // 创建场景
  scene = new THREE.Scene()
  scene.background = new THREE.Color(0x333333)

  // 创建相机
  camera = new THREE.PerspectiveCamera(
    75,
    container.value.clientWidth / container.value.clientHeight,
    0.1,
    1000
  )
  camera.position.set(0, 5, 10)

  // 创建渲染器
  renderer = new THREE.WebGLRenderer({ antialias: true })
  renderer.setSize(container.value.clientWidth, container.value.clientHeight)
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.shadowMap.enabled = true
  container.value.appendChild(renderer.domElement)

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

  // 添加灯光
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
  scene.add(ambientLight)

  const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
  directionalLight.position.set(5, 5, 5)
  directionalLight.castShadow = true
  scene.add(directionalLight)

  // 添加地面
  const groundGeometry = new THREE.PlaneGeometry(20, 20)
  const groundMaterial = new THREE.MeshStandardMaterial({ 
    color: 0x999999,
    roughness: 0.8,
  })
  const ground = new THREE.Mesh(groundGeometry, groundMaterial)
  ground.rotation.x = -Math.PI / 2
  ground.receiveShadow = true
  scene.add(ground)

  // 加载3D模型
  const loader = new GLTFLoader()
  
  // 添加加载进度处理
  const loadingManager = new THREE.LoadingManager()
  loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
    loadingProgress.value = Math.round((itemsLoaded / itemsTotal) * 100)
  }
  
  // 使用加载管理器创建加载器
  const gltfLoader = new GLTFLoader(loadingManager)
  
  // 加载模型
  gltfLoader.load(
    // 模型路径 - 使用 Three.js 官方示例模型
    'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf',
    // 加载成功回调
    (gltf) => {
      model = gltf.scene
      model.position.set(0, 2, 0)
      model.traverse((node) => {
        if (node.isMesh) {
          node.castShadow = true
        }
      })
      scene.add(model)
      loading.value = false
    },
    // 加载进度回调
    (xhr) => {
      console.log(`模型加载进度: ${(xhr.loaded / xhr.total) * 100}%`)
    },
    // 加载错误回调
    (error) => {
      console.error('模型加载失败:', error)
      loading.value = false
    }
  )

  // 动画循环
  const animate = () => {
    animationFrameId = requestAnimationFrame(animate)
    controls.update()
    
    if (model) {
      model.rotation.y += 0.005
    }
    
    renderer.render(scene, camera)
  }
  animate()
}

// 处理窗口大小变化
const handleResize = () => {
  if (!renderer || !camera) return
  
  const width = container.value.clientWidth
  const height = container.value.clientHeight
  
  camera.aspect = width / height
  camera.updateProjectionMatrix()
  renderer.setSize(width, height)
}

onMounted(() => {
  initScene()
  window.addEventListener('resize', handleResize)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', handleResize)
  cancelAnimationFrame(animationFrameId)
  controls?.dispose()
  
  if (model) {
    model.traverse((node) => {
      if (node.isMesh) {
        node.geometry.dispose()
        node.material.dispose()
      }
    })
  }
  
  scene?.traverse((object) => {
    if (object instanceof THREE.Mesh) {
      object.geometry.dispose()
      object.material.dispose()
    }
  })
  renderer?.dispose()
})
</script>

<style scoped>
.three-container {
  width: 100%;
  height: 100vh;
  position: relative;
}

.loading {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: white;
  text-align: center;
}

.loading-text {
  font-size: 1.2em;
  margin-bottom: 1em;
}
</style> 

WebGL兼容性检查工具

封装成一个工具类了

js 复制代码
/**
 * WebGL兼容性检查工具
 * 检查浏览器是否支持WebGL 2
 */
import WebGL from 'three/addons/capabilities/WebGL.js'

/**
 * 检查WebGL2支持状态
 * @returns {Object} 包含支持状态和错误信息的对象
 */
export function checkWebGL2Support() {
  if (WebGL.isWebGL2Available()) {
    return {
      supported: true
    }
  } else {
    return {
      supported: false,
      reason: WebGL.getWebGL2ErrorMessage()
    }
  }
} 

至此,一个3d模型便加载进来了~是不是也不是很复杂呢代码~~~

相关推荐
徐子颐7 分钟前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭20 分钟前
如何理解HTML语义化
前端·html
jump68043 分钟前
url输入到网页展示会发生什么?
前端
诸葛韩信1 小时前
我们需要了解的Web Workers
前端
brzhang1 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu1 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花1 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋1 小时前
场景模拟:基础路由配置
前端
六月的可乐1 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程
一 乐2 小时前
智慧党建|党务学习|基于SprinBoot+vue的智慧党建学习平台(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·学习