在UniApp中使用Three.js实现3D模型渲染的完整指南

前言

大家好!今天我要和大家分享如何在UniApp项目中集成Three.js来实现3D模型的渲染。Three.js是一个强大的JavaScript 3D库,而UniApp则是一个使用Vue.js开发多端应用的框架。本文将详细介绍如何将两者结合使用,让你的UniApp应用具备3D渲染能力。

技术栈

  • UniApp
  • Vue.js
  • Three.js
  • WebGL

环境准备

1. 创建UniApp项目

首先,我们需要创建一个新的UniApp项目:

vue create -p dcloudio/uni-preset-vue uniapp-threejs-demo

2. 安装依赖

在项目根目录下运行以下命令安装Three.js:

npm install three

项目实现

1. 创建3D渲染页面

在pages目录下创建一个新的页面three-demo.vue

<template>
  <view class="container">
    <canvas type="webgl" id="webgl" 
            @touchstart="onTouchStart"
            @touchmove="onTouchMove"
            @touchend="onTouchEnd"
            :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
    </canvas>
  </view>
</template>

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

export default {
  data() {
    return {
      canvasWidth: 375,
      canvasHeight: 600,
      renderer: null,
      camera: null,
      scene: null,
      cube: null,
      controls: null,
      canvas: null,
      animation: null
    }
  },
  onLoad() {
    // 获取设备信息以设置画布大小
    const info = uni.getSystemInfoSync();
    this.canvasWidth = info.windowWidth;
    this.canvasHeight = info.windowHeight;
    
    // 初始化3D场景
    this.$nextTick(() => {
      this.initThree();
    });
  },
  methods: {
    initThree() {
      // 获取canvas上下文
      const query = uni.createSelectorQuery().in(this);
      query.select('#webgl')
        .node()
        .exec((res) => {
          const canvas = res[0].node;
          this.canvas = canvas;
          
          // 创建渲染器
          this.renderer = new THREE.WebGLRenderer({
            canvas: canvas,
            antialias: true
          });
          this.renderer.setSize(this.canvasWidth, this.canvasHeight);
          this.renderer.setPixelRatio(uni.getSystemInfoSync().pixelRatio);
          
          // 创建场景
          this.scene = new THREE.Scene();
          this.scene.background = new THREE.Color(0xf0f0f0);
          
          // 创建相机
          this.camera = new THREE.PerspectiveCamera(
            75,
            this.canvasWidth / this.canvasHeight,
            0.1,
            1000
          );
          this.camera.position.z = 5;
          
          // 创建控制器
          this.controls = new OrbitControls(this.camera, canvas);
          this.controls.enableDamping = true;
          
          // 添加光源
          const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
          this.scene.add(ambientLight);
          
          const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
          directionalLight.position.set(2, 2, 5);
          this.scene.add(directionalLight);
          
          // 创建一个示例立方体
          const geometry = new THREE.BoxGeometry(1, 1, 1);
          const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
          this.cube = new THREE.Mesh(geometry, material);
          this.scene.add(this.cube);
          
          // 开始动画循环
          this.animate();
        });
    },
    
    animate() {
      this.animation = requestAnimationFrame(() => {
        this.animate();
      });
      
      // 旋转立方体
      if (this.cube) {
        this.cube.rotation.x += 0.01;
        this.cube.rotation.y += 0.01;
      }
      
      // 更新控制器
      if (this.controls) {
        this.controls.update();
      }
      
      // 渲染场景
      if (this.renderer && this.scene && this.camera) {
        this.renderer.render(this.scene, this.camera);
      }
    },
    
    // 触摸事件处理
    onTouchStart(event) {
      const touch = event.touches[0];
      const rect = this.canvas.getBoundingClientRect();
      const x = touch.clientX - rect.left;
      const y = touch.clientY - rect.top;
      
      // 在这里处理触摸开始事件
    },
    
    onTouchMove(event) {
      const touch = event.touches[0];
      const rect = this.canvas.getBoundingClientRect();
      const x = touch.clientX - rect.left;
      const y = touch.clientY - rect.top;
      
      // 在这里处理触摸移动事件
    },
    
    onTouchEnd() {
      // 在这里处理触摸结束事件
    }
  },
  
  onUnload() {
    // 清理资源
    if (this.animation) {
      cancelAnimationFrame(this.animation);
    }
    
    if (this.renderer) {
      this.renderer.dispose();
    }
    
    if (this.geometry) {
      this.geometry.dispose();
    }
    
    if (this.material) {
      this.material.dispose();
    }
  }
}
</script>

<style>
.container {
  width: 100%;
  height: 100vh;
}
</style>

2. 加载3D模型

下面展示如何加载外部3D模型(以GLTF格式为例):

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

// 在methods中添加加载模型的方法
methods: {
  loadModel() {
    const loader = new GLTFLoader();
    
    loader.load(
      '/static/models/model.gltf',  // 模型路径
      (gltf) => {
        // 加载成功
        const model = gltf.scene;
        this.scene.add(model);
        
        // 调整模型位置和大小
        model.scale.set(0.5, 0.5, 0.5);
        model.position.set(0, 0, 0);
        
        // 自动计算模型包围盒,调整相机位置
        const box = new THREE.Box3().setFromObject(model);
        const center = box.getCenter(new THREE.Vector3());
        const size = box.getSize(new THREE.Vector3());
        
        const maxDim = Math.max(size.x, size.y, size.z);
        const fov = this.camera.fov * (Math.PI / 180);
        let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
        
        this.camera.position.z = cameraZ * 1.5;
        this.camera.lookAt(center);
        
        // 更新控制器
        this.controls.target.copy(center);
        this.controls.update();
      },
      (progress) => {
        // 加载进度
        console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%');
      },
      (error) => {
        // 加载错误
        console.error('Error loading model:', error);
      }
    );
  }
}

性能优化建议

  1. 模型优化:
    • 减少模型面数
    • 压缩纹理大小
    • 使用LOD(Level of Detail)技术
  2. 渲染优化:
    // 在初始化渲染器时添加配置
    this.renderer = new THREE.WebGLRenderer({
      canvas: canvas,
      antialias: true,
      powerPreference: "high-performance",
      precision: "mediump"
    });
    
    // 开启阴影时要谨慎
    this.renderer.shadowMap.enabled = true;
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  3. 内存管理:
    // 在不需要时释放资源
    dispose() {
      // 释放几何体
      this.scene.traverse((object) => {
        if (object.geometry) {
          object.geometry.dispose();
        }
        
        // 释放材质
        if (object.material) {
          if (Array.isArray(object.material)) {
            object.material.forEach(material => material.dispose());
          } else {
            object.material.dispose();
          }
        }
      });
      
      // 释放渲染器
      if (this.renderer) {
        this.renderer.dispose();
      }
      
      // 清除场景
      while(this.scene.children.length > 0) {
        this.scene.remove(this.scene.children[0]);
      }
    }

常见问题解决方案

  1. 跨平台兼容性问题
    // 检测WebGL支持
    checkWebGL() {
      try {
        const canvas = document.createElement('canvas');
        return !!(window.WebGLRenderingContext && 
          (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
      } catch(e) {
        return false;
      }
    }
  2. 触摸控制优化
    // 优化移动端触摸控制
    initTouchControls() {
      this.controls.enableDamping = true;
      this.controls.dampingFactor = 0.05;
      this.controls.rotateSpeed = 0.5;
      this.controls.pinchToZoom = true;
      this.controls.touchAngularSpeed = 8;
      this.controls.touchMoveSpeed = 8;
    }
  3. 性能监控
    // 添加性能监控
    initStats() {
      const Stats = require('three/examples/jsm/libs/stats.module');
      this.stats = new Stats();
      document.body.appendChild(this.stats.dom);
      
      // 在animate循环中添加
      if (this.stats) {
        this.stats.update();
      }
    }

总结

在UniApp中集成Three.js可以让我们的应用具备强大的3D渲染能力。本文介绍了基本的集成步骤、模型加载、性能优化等关键内容。希望这些内容对你有所帮助!

以下是一些可以进一步探索的方向:

  1. 添加更多交互效果
  2. 实现更复杂的材质和光照效果
  3. 优化大型模型的加载和渲染
  4. 实现更多的触摸控制功能

参考资料

如果你在实践过程中遇到任何问题,欢迎在评论区留言讨论!

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值