1. Uniapp与three.js的强强联合

在移动端和H5开发领域,Uniapp凭借其跨平台特性已经成为很多开发者的首选。而three.js作为WebGL的封装库,让3D开发变得触手可及。当这两者相遇,就能创造出令人惊艳的3D交互体验。我去年做过一个电商项目,需要在商品详情页展示3D模型,就是用的这个技术组合,效果相当不错。

要在Uniapp中使用three.js,首先需要安装three.js库。这里有个小技巧:建议锁定特定版本,避免后续版本更新导致兼容性问题。我推荐使用0.149.0版本,这个版本在Uniapp中表现稳定:

npm install --save three@0.149.0

安装完成后,需要在页面中引入必要的模块。除了基础的THREE对象外,我们还需要引入OrbitControls(用于模型交互控制)以及模型加载器:

import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js'
import {FBXLoader} from 'three/examples/jsm/loaders/FBXLoader.js'

2. 场景初始化与模型加载

2.1 创建基础场景

初始化场景是3D展示的第一步,这里有几个关键点需要注意。首先是相机的选择,我推荐使用PerspectiveCamera(透视相机),因为它更符合人眼的视觉效果。在实际项目中,我发现相机位置的设置对最终效果影响很大:

init() {
    this.scene = new THREE.Scene()
    this.camera = new THREE.PerspectiveCamera()
    this.camera.position.set(500, 0, 700)
    this.camera.lookAt(this.scene.position)
}

渲染器的配置也很关键。在移动端,我们需要特别注意性能优化。开启抗锯齿(antialias)会让边缘更平滑,但会消耗更多性能。根据我的测试,在高端手机上可以开启,但在低端设备上建议关闭:

this.renderer = new THREE.WebGLRenderer({
    alpha: true,  // 透明背景
    antialias: true  // 抗锯齿
})
this.renderer.setPixelRatio(window.devicePixelRatio * 2)

2.2 光源设置技巧

光源的设置直接影响模型的视觉效果。我通常使用四束方向光组合的环境光方案,这样可以在保证基础亮度的同时,让模型有更好的立体感:

let ambientLight = new THREE.AmbientLight(0xffffff, 1)
this.scene.add(ambientLight)

const directional_light = new THREE.DirectionalLight(0xffffff, 1)
directional_light.position.set(0, 1, 0)
this.scene.add(directional_light)

// 添加四束方向光
let a=1, b=0.6, c=10
let lights = [
    new THREE.DirectionalLight(0xffffff,b).position.set(-a,-a,a*c).normalize(),
    new THREE.DirectionalLight(0xffffff,b).position.set(a,-a,-a*c).normalize(),
    new THREE.DirectionalLight(0xffffff,b).position.set(-a,a,-a*c).normalize(),
    new THREE.DirectionalLight(0xffffff,b).position.set(a,a,a*c).normalize()
]
lights.forEach(light => this.scene.add(light))

3. 模型加载与优化

3.1 FBX模型加载实战

FBX格式在3D建模领域非常常见,特别适合包含动画的模型。在加载FBX模型时,我建议添加加载进度回调,这对大模型特别有用:

fbxLoader() {
    let that = this
    const loader = new FBXLoader()
    loader.load(
        this.modelUrl,
        function(mesh) {
            that.scene.add(mesh)
            that.ownerInstance.callMethod('onload')
        },
        function(xhr) {
            console.log((xhr.loaded / xhr.total * 100) + '% loaded')
        },
        function(error) {
            console.error('FBX加载错误:', error)
        }
    )
}

3.2 GLB模型加载技巧

GLB是glTF的二进制格式,体积更小,加载更快。在实际项目中,我发现GLB格式特别适合移动端使用。加载GLB时,可以访问模型的各个组成部分:

gblLoader() {
    let that = this
    const loader = new GLTFLoader()
    loader.load(
        this.modelUrl,
        function(gltf) {
            that.mesh = gltf.scene
            let model = gltf.scene
            that.scene.add(model)
            
            // 可以访问模型的动画
            if(gltf.animations && gltf.animations.length) {
                that.mixer = new THREE.AnimationMixer(model)
                gltf.animations.forEach(clip => {
                    that.mixer.clipAction(clip).play()
                })
            }
            
            that.ownerInstance.callMethod('onload')
        }
    )
}

4. 交互控制与性能优化

4.1 轨道控制器配置

OrbitControls是实现模型交互的核心组件。通过合理配置,可以实现旋转、缩放、平移等操作。在实际项目中,我发现限制操作范围可以提升用户体验:

change() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.controls.minDistance = 300  // 最小缩放距离
    this.controls.maxDistance = 1000  // 最大缩放距离
    
    // 禁用阻尼效果可以提升性能,但会降低交互流畅度
    this.controls.enableDamping = true
    this.controls.dampingFactor = 0.05
    
    this.controls.addEventListener('change', () => {
        this.renderer.render(this.scene, this.camera)
    })
}

4.2 自动旋转与性能平衡

自动旋转是展示3D模型的常用功能,但需要注意性能问题。我通常会在requestAnimationFrame中控制渲染频率:

animate() {
    if (this.renderer) {
        let T = this.clock.getDelta()
        let renderT = 1 / 30  // 控制渲染频率为30fps
        this.timeS = this.timeS + T
        
        if(this.timeS > renderT) {
            this.controls.update()
            this.renderer.render(this.scene, this.camera)
            this.timeS = 0
        }
        
        requestAnimationFrame(this.animate)
        this.controls.autoRotate = true
        this.controls.autoRotateSpeed = 15
    }
}

4.3 跨平台兼容性处理

在Uniapp中使用three.js有个重要限制:在APP端不能直接运行,必须使用renderjs技术。这是我在实际项目中踩过的坑。解决方案是在H5端使用标准three.js代码,在APP端通过renderjs实现:

// 在template中
<view id="threeView" v-if="isH5"></view>
<view v-else>
    <!-- APP端使用renderjs实现 -->
</view>

// 在script中
mounted() {
    this.isH5 = process.env.VUE_APP_PLATFORM === 'h5'
    if(this.isH5) {
        this.initThree()
    }
}

5. 高级优化技巧

5.1 模型压缩与预处理

在实际项目中,模型文件大小直接影响加载速度和性能。我推荐使用以下工具对模型进行预处理:

  • 使用Blender对FBX模型进行减面处理
  • 使用glTF-Pipeline压缩GLB文件
  • 对纹理图片进行适当压缩

5.2 内存管理与垃圾回收

three.js中的对象需要手动释放内存。特别是在单页应用中,离开页面时要记得清理:

beforeDestroy() {
    this.scene.traverse(object => {
        if(object.geometry) {
            object.geometry.dispose()
        }
        if(object.material) {
            if(Array.isArray(object.material)) {
                object.material.forEach(m => m.dispose())
            } else {
                object.material.dispose()
            }
        }
    })
    this.renderer.dispose()
}

5.3 响应式设计

在移动端,屏幕尺寸各异,需要确保3D场景能够自适应:

window.addEventListener('resize', () => {
    const element = document.getElementById('threeView')
    this.camera.aspect = element.clientWidth / element.clientHeight
    this.camera.updateProjectionMatrix()
    this.renderer.setSize(element.clientWidth, element.clientHeight)
})

6. 实战经验分享

在最近的一个电商项目中,我们需要展示家具的3D模型。开始时直接加载原始FBX文件,在低端手机上出现了明显的卡顿。经过优化,我们采取了以下措施:

  1. 将模型面数从5万减到1万
  2. 将纹理尺寸从2048x2048降到1024x1024
  3. 添加了加载进度提示
  4. 实现了按需加载,只有用户点击查看时才加载3D模型

优化后,加载时间从原来的8秒降到了2秒以内,用户体验大幅提升。这个案例告诉我,在移动端做3D展示,性能优化和用户体验同样重要。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐