Three.js基础使用-案例

javascript 复制代码
<div ref="modelContainer" style="width:100%; height:600px;">

<script>
import * as THREE from 'three'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

export default {
  name: 'FbxModelPreview',
  data() {
    return {
      //fbx url
      modelUrl: '',

      // three实例
      scene: null,
      camera: null,
      renderer: null,
      controls: null,
      fbxModel: null,
      animateId: null
    }
  },
  created() {
        //初始化
        this.$nextTick(() => {
          this.initThree()
          this.loadFbxModel()
        })
   }
   
    methods: {
        // 3D模型
        initThree() {
            const container = this.$refs.modelContainer
            if (!container) return

            console.log('容器宽高', container.clientWidth, container.clientHeight)

            // 场景
            this.scene = new THREE.Scene()
            this.scene.background = new THREE.Color(0xffffff)

            // 相机
            this.camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 0.1, 2000)
            this.camera.position.set(10, 10, 15)

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

            // 灯光
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.6)
            this.scene.add(ambientLight)
            const dirLight = new THREE.DirectionalLight(0xffffff, 0.8)
            dirLight.position.set(20, 30, 20)
            this.scene.add(dirLight)

            // 轨道控制器 鼠标拖拽旋转缩放平移
            this.controls = new OrbitControls(this.camera, this.renderer.domElement)
            this.controls.enableDamping = true

            // 窗口自适应
            window.addEventListener('resize', this.onWindowResize)

            this.animate()
        },

        loadFbxModel() {
            const loader = new FBXLoader()
            loader.load(
                this.modelUrl,
                (object) => {
                    console.log('fbx加载成功', object)
                    this.fbxModel = object

                    // true:计算scale/旋转/位移后的真实包围盒
                    const box = new THREE.Box3().setFromObject(object, true)
                    const size = box.getSize(new THREE.Vector3())
                    console.log('模型原始size', size)

                    // 基于底座XZ缩放,忽略巨大Y
                    const maxXZ = Math.max(size.x, size.z)
                    const targetXZ = 8
                    const scale = targetXZ / maxXZ
                    object.scale.set(scale, scale, scale)

                    // ✅缩放后重新获取真实边界,必须带true
                    box.setFromObject(object, true)
                    const center = box.getCenter(new THREE.Vector3())

                    // 模型底部对齐y=0
                    object.position.x = -center.x
                    object.position.z = -center.z
                    object.position.y = -box.min.y

                    this.scene.add(object)

                    box.setFromObject(object, true)
                    box.getCenter(center)
                    this.controls.target.copy(center)

                    // ==========核心:相机自动适配模型,模型刚好完整显示在画布(图2效果)==========
                    const sphere = new THREE.Sphere()
                    box.getBoundingSphere(sphere)
                    const fov = this.camera.fov * (Math.PI / 180)
                    const cameraDistance = sphere.radius / Math.sin(fov / 2) * 1.3 //1.3留一点边距

                    // 相机斜45°观察高塔,初始视角就是正面斜视角,出来直接看到整根塔
                    this.camera.position.set(
                        center.x + cameraDistance * 0.7,
                        center.y + cameraDistance * 0.45,
                        center.z + cameraDistance * 0.7
                    )
                    this.camera.lookAt(center)

                    this.controls.minDistance = sphere.radius * 0.4
                    this.controls.maxDistance = cameraDistance * 3
                    this.controls.update()
                },
                (progress) => {
                    const percent = (progress.loaded / progress.total * 100).toFixed(1)
                    console.log('加载进度:' + percent + '%')
                },
                (err) => {
                    console.error('FBX加载失败', err)
                    this.$message.error('3D模型加载失败')
                }
            )
        },
        animate() {
            this.animateId = requestAnimationFrame(this.animate)
            if (!this.renderer || !this.scene || !this.camera || !this.controls) return
            this.controls.update()
            this.renderer.render(this.scene, this.camera)
        },

        onWindowResize() {
            const container = this.$refs.modelContainer
            if (!container || !this.camera || !this.renderer) return
            this.camera.aspect = container.clientWidth / container.clientHeight
            this.camera.updateProjectionMatrix()
            this.renderer.setSize(container.clientWidth, container.clientHeight)
        },

        // 关闭弹窗
        handleClose() {
            this.modelUrl= ''

            window.removeEventListener('resize', this.onWindowResize)
            if (this.animateId) cancelAnimationFrame(this.animateId)
            if (this.renderer) {
                this.renderer.dispose()
                this.$refs.modelContainer && (this.$refs.modelContainer.innerHTML = '')
            }
            this.scene = null
            this.camera = null
            this.renderer = null
            this.controls = null
            this.fbxModel = null
        },
    },
}
</script>

效果图(案例):

相关推荐
OpsEye6 小时前
大模型账单对账难:不同厂商 Token 统计口径差异怎么处理
javascript·ai编程
全栈项目管理程序猿6 小时前
ArcGIS JS 基础教程(32):体元系列 - VoxelSlice 体元切片
前端·javascript
WJ.Polar7 小时前
Java封装、继承、多态
java·开发语言
摇滚侠8 小时前
Java 中如何表示负数,有符号和无符号的区别,补码的计算规则,char byte short int long 的范围
java·开发语言
weixin_307779138 小时前
C++代码实现MATLAB中的fitrtree函数功能
开发语言·c++·算法·matlab
Hilaku8 小时前
AI 流式输出都用 SSE,什么时候必须换 WebSocket?
前端·javascript·程序员
奇牙coding8 小时前
Codex CLI 升级后多文件编辑静默失效:config.yaml 枚举变更排查与修复指南
java·前端·javascript·ai
爱读书的小胖8 小时前
Codewars:Rot13(C)解题分析
c语言·开发语言
鸽鸽程序猿8 小时前
【项目】【在线判题系统】b端题目增删改
开发语言·python
Lhappy嘻嘻8 小时前
读懂 JVM:内存模型、类加载机制、分代回收
java·开发语言·jvm