在UniApp中集成Three.js:打造跨平台3D可视化应用
引言
在最近的一个项目中,我们需要在UniApp应用中展示3D模型,并实现实时交互功能。经过技术选型和实践,我们选择了Three.js作为3D渲染引擎。本文将分享我们在UniApp中集成Three.js的完整过程,以及在鸿蒙系统上的适配经验。
技术栈选择
我们的技术栈组合如下:
- UniApp + Vue3:提供跨平台开发能力
- Three.js r150:3D渲染引擎
- Stats.js:性能监控
- GLTF Loader:3D模型加载
- HMS Core Graphics:鸿蒙图形加速
环境搭建
首先,我们需要在UniApp项目中安装必要的依赖:
# 安装Three.js
npm install three@0.150.0
# 安装类型声明(如果使用TypeScript)
npm install @types/three --save-dev
# 安装加载器和控制器
npm install three-orbit-controls
npm install three-gltf-loader
核心实现
1. 基础场景搭建
首先创建一个基础的3D场景组件:
<!-- components/ThreeScene.vue -->
<template>
<view class="three-container">
<canvas type="webgl"
id="threejs-canvas"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
></canvas>
</view>
</template>
<script lang="ts">
import { defineComponent, onMounted, onBeforeUnmount } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
export default defineComponent({
name: 'ThreeScene',
setup() {
let scene: THREE.Scene;
let camera: THREE.PerspectiveCamera;
let renderer: THREE.WebGLRenderer;
let controls: OrbitControls;
let canvas: any;
let animationFrameId: number;
// 初始化场景
const initScene = () => {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);
// 获取canvas上下文
const query = uni.createSelectorQuery();
query.select('#threejs-canvas')
.node()
.exec((res) => {
canvas = res[0].node;
// 初始化渲染器
renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: true
});
// 适配设备像素比
const pixelRatio = uni.getSystemInfoSync().pixelRatio;
renderer.setPixelRatio(pixelRatio);
// 设置渲染尺寸
const { windowWidth, windowHeight } = uni.getSystemInfoSync();
renderer.setSize(windowWidth, windowHeight);
// 初始化控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 添加光源
addLights();
// 添加示例模型
addSampleModel();
// 开始动画循环
animate();
});
};
// 添加光源
const addLights = () => {
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 10);
scene.add(directionalLight);
};
// 添加示例模型
const addSampleModel = () => {
// 创建一个简单的立方体
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshStandardMaterial({
color: 0x00ff00,
metalness: 0.5,
roughness: 0.5
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
};
// 动画循环
const animate = () => {
animationFrameId = requestAnimationFrame(animate);
// 更新控制器
controls.update();
// 渲染场景
renderer.render(scene, camera);
};
// 触摸事件处理
const handleTouchStart = (event: any) => {
const touch = event.touches[0];
controls.onTouchStart(touch);
};
const handleTouchMove = (event: any) => {
const touch = event.touches[0];
controls.onTouchMove(touch);
};
const handleTouchEnd = () => {
controls.onTouchEnd();
};
// 生命周期钩子
onMounted(() => {
initScene();
});
onBeforeUnmount(() => {
cancelAnimationFrame(animationFrameId);
renderer?.dispose();
});
return {
handleTouchStart,
handleTouchMove,
handleTouchEnd
};
}
});
</script>
<style>
.three-container {
width: 100%;
height: 100vh;
}
canvas {
width: 100%;
height: 100%;
}
</style>
2. GLTF模型加载器
对于复杂的3D模型,我们通常使用GLTF格式。以下是模型加载的实现:
// utils/modelLoader.ts
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
import type { Group } from 'three';
export class ModelLoader {
private loader: GLTFLoader;
constructor() {
this.loader = new GLTFLoader();
}
async loadModel(url: string): Promise<Group> {
try {
const gltf: GLTF = await new Promise((resolve, reject) => {
this.loader.load(
url,
resolve,
(xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
reject
);
});
const model = gltf.scene;
// 自动计算包围盒
model.traverse((child: any) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
return model;
} catch (error) {
console.error('模型加载失败:', error);
throw error;
}
}
}
3. 鸿蒙系统适配
在鸿蒙系统上,我们需要特别注意以下几点:
// platform/harmony/graphicsOptimizer.ts
export class HarmonyGraphicsOptimizer {
private graphicsAPI: any;
constructor() {
if (uni.getSystemInfoSync().platform === 'harmony') {
this.graphicsAPI = uni.requireNativePlugin('graphics');
}
}
optimize(renderer: THREE.WebGLRenderer) {
if (!this.graphicsAPI) return;
try {
// 启用硬件加速
this.graphicsAPI.enableHardwareAcceleration();
// 设置最佳性能模式
renderer.setPixelRatio(1); // 在鸿蒙系统上固定像素比
renderer.powerPreference = 'high-performance';
// 启用自定义帧率控制
this.graphicsAPI.setPreferredFrameRate(60);
} catch (error) {
console.warn('鸿蒙图形优化失败:', error);
}
}
}
性能优化
在实际应用中,我们采取了以下优化措施:
- 模型优化
- 使用LOD(Level of Detail)技术
- 压缩纹理资源
- 实现模型预加载
- 渲染优化
- 使用实例化渲染
- 实现视锥体剔除
- 优化光照计算
- 内存管理
- 及时释放资源
- 实现资源池
- 控制最大内存使用
实战案例:产品展示
以下是一个实际的产品3D展示组件:
<!-- components/ProductViewer.vue -->
<template>
<view class="product-viewer">
<three-scene ref="threeScene" />
<view class="controls">
<button @tap="rotateModel">旋转</button>
<button @tap="zoomIn">放大</button>
<button @tap="zoomOut">缩小</button>
</view>
</view>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import ThreeScene from './ThreeScene.vue';
import { ModelLoader } from '@/utils/modelLoader';
import type { Group } from 'three';
export default defineComponent({
components: {
ThreeScene
},
setup() {
const threeScene = ref(null);
let productModel: Group | null = null;
const loadProductModel = async () => {
const loader = new ModelLoader();
try {
productModel = await loader.loadModel('/static/models/product.gltf');
threeScene.value?.addToScene(productModel);
} catch (error) {
uni.showToast({
title: '模型加载失败',
icon: 'none'
});
}
};
const rotateModel = () => {
if (!productModel) return;
productModel.rotation.y += Math.PI / 2;
};
const zoomIn = () => {
threeScene.value?.zoomCamera(1.2);
};
const zoomOut = () => {
threeScene.value?.zoomCamera(0.8);
};
return {
threeScene,
rotateModel,
zoomIn,
zoomOut
};
}
});
</script>
常见问题与解决方案
在开发过程中,我们遇到了一些典型问题,这里分享解决方案:
- 内存泄漏
- 问题:长时间使用后内存占用过高
- 解决:实现完整的资源释放机制,包括几何体、材质、纹理等
- 触摸控制
- 问题:多点触控不流畅
- 解决:优化事件处理逻辑,实现事件节流
- 性能问题
- 问题:在低端设备上帧率不稳定
- 解决:实现自适应渲染质量,动态调整分辨率和细节级别
未来展望
随着WebGL和Three.js的发展,以及鸿蒙生态的完善,我们期待在以下方面有更多突破:
- 技术升级
- 支持WebGPU
- 优化渲染管线
- 提升AR/VR体验
- 功能扩展
- 支持物理仿真
- 添加后期处理
- 优化交互体验
总结
通过在UniApp中集成Three.js,我们不仅实现了跨平台的3D展示功能,还在鸿蒙系统适配方面积累了宝贵经验。希望本文的实践分享能为大家在类似项目开发中提供参考和启发。
记住,3D应用开发是一个需要不断优化和改进的过程,建议在实际项目中根据具体需求和设备特点进行针对性优化。同时,随着技术的发展,也要及时更新知识储备,保持对新技术的跟进和学习。