XR-Frame:引领小程序步入XR/3D新篇章‌

一、引言

随着硬件与渲染能力的提升,XR(混合现实)与 3D 交互已逐渐从 PC/Web 端扩展到移动平台。小程序由于其“即用即走、无需安装”的特性,也成为开发轻量级 XR/3D 应用的理想载体。然而,原生小程序并不直接支持 WebGL 或 Three.js 等主流 3D 框架,导致开发者需要跳过诸多底层细节才能在小程序里渲染3D场景。

XR-Frame 正是在这个背景下诞生的轻量级跨平台 3D 框架,针对微信/支付宝/百度等各种小程序环境进行深度适配,将 WebGL/Three.js 的精华封装为一套统一 API,让你只需编写少量几行代码,就能在小程序里轻松搭建、渲染并与 3D/AR 场景交互。本篇指南将带你从零开始,手把手教你如何使用 XR-Frame 在小程序中集成 3D 渲染与 XR 体验,涵盖以下主要内容:

  1. XR-Frame 框架概述
  2. 环境搭建与集成(微信小程序示例)
  3. 创建第一个 3D 场景:渲染一个可旋转的立方体
  4. 物体加载与交互:从 GLTF/GLB 模型导入到手势拖拽
  5. AR/VR 模式实战:人脸追踪与世界坐标锚点
  6. 性能优化与注意事项
  7. 常见问题解答

每个部分都配备了完整代码示例ASCII 图解,便于你快速理解底层原理与使用流程,帮助你迅速上手 XR-Frame,开启小程序 XR/3D 开发之旅。


二、XR-Frame 框架概述

2.1 XR-Frame 的定位与特点

  • 跨平台兼容:XR-Frame 基于 Three.js 内核,针对微信/支付宝/百度小程序的 WebGL 环境做了深度适配,也兼容 H5/Uniapp。你只需一套代码,就能在多个小程序平台运行。
  • 轻量封装:将底层 WebGL、渲染循环、纹理管理、交互事件封装为一套简洁 API,屏蔽不同小程序对 Canvas、WebGL 上下文获取的差异。
  • 内置 XR 模块:在 3D 渲染基础上,提供 AR(增强现实)与 VR(虚拟现实)模式封装,支持人脸/物体识别、世界坐标定位,让你在小程序里快速实现“把 3D 模型放到现实环境”或“进入全景 VR 模式”。
  • 资源加载可扩展:集成 GLTF/GLB、OBJ、FBX 等多种 3D 模型加载器,也支持远程加载和缓存机制。
  • 手势交互与物理模拟:提供封装触摸/陀螺仪事件处理,并可接入物理引擎(如 Ammo.js、Cannon.js)实现简单碰撞检测与重力仿真。

2.2 XR-Frame 核心模块结构

XR-Frame/
├─ core/               # 核心渲染与场景管理
│   ├─ Renderer.js     # 封装 WebGL 渲染循环与上下文
│   ├─ SceneManager.js # 管理 Three.js 场景、摄像机、灯光
│   ├─ XRManager.js    # XR 模式(AR/VR)切换与初始化
│   └─ InputManager.js # 手势、触摸与陀螺仪事件处理
├─ loader/             # 资源加载器封装
│   ├─ GLTFLoader.js   # GLTF/GLB 模型加载
│   ├─ TextureLoader.js# 纹理加载 & 缓存
│   └─ ...             # 其他 Loader(OBJ, FBX 等)
├─ utils/              # 工具函数
│   ├─ MathUtil.js     # 数学运算与坐标变换
│   ├─ PathUtil.js     # 路径处理(云上 & 本地)
│   └─ ...             
└─ index.js            # 对外统一导出入口(挂载到全局 XRFrame)

ASCII 图解:XR-Frame 模块关系

┌───────────────────────────────────┐
│           XRFrame 全局对象         │
│  ┌────────────┐  ┌──────────────┐ │
│  │ core/      │  │ loader/      │ │
│  │ ┌────────┐ │  │ ┌──────────┐ │ │
│  │ │Renderer│ │  │ │GLTFLoader│ │ │
│  │ └────────┘ │  │ └──────────┘ │ │
│  │ ┌────────┐ │  │ ┌──────────┐ │ │
│  │ │SceneMgr│ │  │ │TextureLd │ │ │
│  │ └────────┘ │  │ └──────────┘ │ │
│  │ ┌────────┐ │  │     ...      │ │
│  │ │XRMgr   │ │  └──────────────┘ │
│  │ └────────┘ │                   │
│  │ ┌────────┐ │  ┌──────────────┐ │
│  │ │InputMgr│ │  │ utils/       │ │
│  │ └────────┘ │  │ ┌──────────┐ │ │
│  └────────────┘  │ │MathUtil  │ │ │
│                  │ └──────────┘ │ │
│                  │      ...     │ │
│                  └──────────────┘ │
└───────────────────────────────────┘
  • Renderer:负责创建并维护 WebGL 渲染循环(requestAnimationFrame),更新场景并渲染到 Canvas。
  • SceneManager:封装 Three.js 场景 (THREE.Scene)、摄像机 (THREE.PerspectiveCamera) 与常用灯光(环境光、点光、平行光)的初始化与管理。
  • XRManager:当切换到 AR/VR 模式时,此模块负责初始化相机的 AR/NPC、VR眼镜渲染等功能,自动判断平台支持并加载相应库(如微信小程序的 wx.createCamera3D)。
  • InputManager:监听小程序 touchstart/touchmove/touchend、陀螺仪(wx.onGyroscopeChange)等事件,将原生事件转换为 Three.js 可识别的射线 (Raycaster) 或导航控制器。
  • Loader:基于 Three.js 自带的 GLTFLoaderTextureLoader,并针对小程序缓存,做了路径转换与异步读取的适配(如使用 wx.getFileSystemManager() 读取本地模型缓存)。
  • Utils:涵盖数学工具(向量、矩阵运算)、路径拼接、环境检测等。

在本指南后续章节中,我们会以代码示例的形式演示如何调用这些模块,快速构建一个可旋转的 3D 立方体场景,再逐步深入讲解 AR/VR 模式的应用。


三、环境搭建与集成(以微信小程序为例)

下面以微信小程序为示例,介绍如何在 uniapp 中集成 XR-Frame 并运行一个最简单的 3D Demo。

3.1 准备工作

  1. 环境要求

    • HBuilderX 3.x 或更高版本;
    • 微信开发者工具 1.02+(支持 WebGL);
    • 微信基础库版本 ≥ 2.10.0(部分 XR 接口需要该版本以上支持)。
  2. 下载 XR-Frame

    • 可通过 NPM 安装:

      npm install xr-frame --save
    • 或者直接将 XR-Framedist/xr-frame.min.js 拷贝到 static/xr-frame/ 目录下。
  3. 项目目录结构调整

    uniapp-project/
    ├─ components/          
    ├─ pages/
    │   └─ xr3d/           # 我们新建一个 xr3d 页面来演示 3D
    ├─ static/
    │   └─ xr-frame/
    │       └─ xr-frame.min.js
    ├─ App.vue
    ├─ main.js
    ├─ pages.json
    └─ manifest.json

3.2 引入 XR-Frame

pages/xr3d/xr3d.vue 页面中,我们需要在 scriptonLaunch 时把 xr-frame.min.js 注入到全局。示例如下:

<template>
  <view class="container">
    <!-- XR-Frame 渲染的 Canvas 容器 -->
    <!-- 指定 canvas-id,方便 XR-Frame 获取 WebGL 上下文 -->
    <canvas 
      canvas-id="xr-canvas" 
      type="webgl" 
      style="width: 100vw; height: 100vh;"
    ></canvas>
  </view>
</template>

<script>
export default {
  onReady() {
    // 1. 动态加载 xr-frame.min.js(若未在 main.js 全局引入)
    wx.getFileSystemManager().readFile({
      filePath: `${wx.env.USER_DATA_PATH}/_www/static/xr-frame/xr-frame.min.js`,
      encoding: 'utf8',
      success: (res) => {
        // 在小程序环境 eval 脚本,挂载到全局
        /* eslint-disable */
        eval(res.data);
        /* eslint-enable */
        // 此时全局应该存在 XRFrame 对象
        this.init3DScene();
      },
      fail: (err) => {
        console.error('加载 XR-Frame 失败:', err);
      }
    });
    // 2. 若你已在 main.js 或 App.vue 里通过 <script src> 全局引入,则可直接:
    // this.init3DScene();
  },
  methods: {
    init3DScene() {
      // 确认 XRFrame 已挂载到全局
      if (typeof XRFrame === 'undefined') {
        console.error('XRFrame 未加载');
        return;
      }
      // 3. 初始化 XR-Frame 渲染器,传入 canvasId
      const renderer = new XRFrame.core.Renderer({
        canvasId: 'xr-canvas',
        antialias: true,      // 是否开启抗锯齿
        alpha: true,          // 是否使用透明背景
        pixelRatio: 1,        // 可以根据设备屏幕密度适当调整
        width: wx.getSystemInfoSync().windowWidth,
        height: wx.getSystemInfoSync().windowHeight
      });

      // 4. 创建一个场景管理器
      const sceneMgr = new XRFrame.core.SceneManager(renderer);
      // 5. 创建默认摄像机:透视相机,fov 45,近裁剪 0.1,远裁剪 1000
      sceneMgr.createCamera({ fov: 45, near: 0.1, far: 1000 });
      // 6. 添加环境光和点光源
      sceneMgr.addAmbientLight(0xffffff, 0.6);
      sceneMgr.addPointLight(0xffffff, 1.0, { x: 10, y: 15, z: 10 });

      // 7. 创建一个初始 3D 对象:一个立方体
      const geometry = new XRFrame.THREE.BoxGeometry(2, 2, 2);
      const material = new XRFrame.THREE.MeshStandardMaterial({ color: 0x0077ff });
      const cube = new XRFrame.THREE.Mesh(geometry, material);
      cube.position.set(0, 1, -5); // 把立方体放在相机前方 5 个单位
      sceneMgr.addObject(cube);

      // 8. 启动渲染循环
      renderer.setAnimationLoop((delta) => {
        // delta 为每帧时间(秒)
        // 动态旋转立方体
        cube.rotation.y += delta * 0.5; // 每秒旋转 0.5 弧度
        // 渲染当前场景
        sceneMgr.render();
      });

      // 9. 监听触摸事件:触摸并拖拽控制立方体位置
      XRFrame.input.on('pan', (ev) => {
        // ev.deltaX, ev.deltaY 单位:像素
        const factor = 0.01;
        cube.position.x += ev.deltaX * factor;
        cube.position.y -= ev.deltaY * factor;
      });
    }
  }
};
</script>

<style>
.container {
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
}
</style>

3.3 关键步骤详解

  1. 加载 XR-Frame 脚本

    • 小程序不允许直接在 WXML 中使用 <script src>,所以需要通过 wx.getFileSystemManager().readFile 读取 static/xr-frame/xr-frame.min.js 内容后 eval,将 XRFrame 挂载到全局。
    • 如果你使用了 uniapp CLI 或其他方式可以直接在 main.js 里通过 import XRFrame from 'xr-frame',并在 App.vue 里加 <script src="/static/xr-frame/xr-frame.min.js"></script>,则无需在页面里重复加载。
  2. 创建 Renderer 实例

    const renderer = new XRFrame.core.Renderer({
      canvasId: 'xr-canvas',
      antialias: true,
      alpha: true,
      pixelRatio: 1,
      width: windowWidth,
      height: windowHeight
    });
    • canvasId 对应 <canvas canvas-id="xr-canvas">,用于获取 WebGL 上下文。
    • antialias 决定是否开启抗锯齿,若不开启渲染质量略差但性能稍好。
    • alpha 允许透明背景,以便叠加到小程序原生 UI 之上。
    • pixelRatio 默认为设备像素比,但小程序缓存 WebGL 时常常需要设置为 1,以避免性能瓶颈。
    • width/height 为渲染区域尺寸,一般设置为屏幕宽高。
  3. 初始化 SceneManager

    const sceneMgr = new XRFrame.core.SceneManager(renderer);
    sceneMgr.createCamera({ fov: 45, near: 0.1, far: 1000 });
    sceneMgr.addAmbientLight(0xffffff, 0.6);
    sceneMgr.addPointLight(0xffffff, 1.0, { x: 10, y: 15, z: 10 });
    • createCamera:创建 PerspectiveCamera,并自动将其挂载到场景中。
    • addAmbientLight:添加环境光,用于整体基础照明。
    • addPointLight:添加一个点光源,放在坐标 (10,15,10),为立方体产生阴影与高光。
  4. 创建立方体 Mesh 并添加到场景

    const geometry = new XRFrame.THREE.BoxGeometry(2, 2, 2);
    const material = new XRFrame.THREE.MeshStandardMaterial({ color: 0x0077ff });
    const cube = new XRFrame.THREE.Mesh(geometry, material);
    cube.position.set(0, 1, -5);
    sceneMgr.addObject(cube);
    • BoxGeometry(2,2,2):创建边长为 2 的立方体。
    • MeshStandardMaterial:PBR 标准材质,能够响应灯光。
    • cube.position.set(0, 1, -5):将立方体向上移动 1 单位、向后移动 5 单位,使其位于摄像机前方。
  5. 启动渲染循环

    renderer.setAnimationLoop((delta) => {
      cube.rotation.y += delta * 0.5;
      sceneMgr.render();
    });
    • setAnimationLoop 底层调用 requestAnimationFrame,并自动计算两帧间隔 delta(秒)。
    • 在回调中不断旋转立方体并渲染场景。
  6. 物体交互:监听触摸拖拽

    XRFrame.input.on('pan', (ev) => {
      const factor = 0.01;
      cube.position.x += ev.deltaX * factor;
      cube.position.y -= ev.deltaY * factor;
    });
    • XRFrame.input.on('pan', handler) 自动将小程序原生 touchstart/touchmove/touchend 事件转换为“平移”手势,并返回 ev.deltaXev.deltaY(触摸增量像素)。
    • 根据触摸增量实时更新立方体位置,实现拖拽交互。

四、创建第一个 3D 场景:渲染可旋转的立方体

上面演示了如何在小程序中利用 XR-Frame 渲染一个会自动旋转且可拖拽的立方体。下面我们针对每一步进行更详细的说明与 ASCII 图解。

4.1 Canvas 与 WebGL 上下文获取

在小程序中,要渲染 WebGL,必须在 WXML(Weixin XML)中声明 <canvas type="webgl">,并给定一个 canvas-id。示例:

<canvas 
  canvas-id="xr-canvas" 
  type="webgl" 
  style="width: 100vw; height: 100vh;"
></canvas>
  • type="webgl":告诉微信开发者工具以 WebGL 上下文渲染。(若不写 type,默认为 2D Canvas)
  • canvas-id="xr-canvas":用于在 JS 里通过 wx.createCanvasContext('xr-canvas') 或框架底层获取 WebGLRenderingContext。

ASCII 图解:Canvas 渲染流程

┌─────────────────────────────────────────┐
│         小程序渲染流程(WXML → 渲染)   │
│                                         │
│  1. WXML 解析 → 创建 DOM Tree           │
│     <canvas canvas-id="xr-canvas" ...> │
│                                         │
│  2. 原生 渲染引擎 创建 WebGL 上下文       │
│     (WebGLRenderingContext)             │
│                                         │
│  3. XR-Frame Renderer 获取 WebGLContext  │
│     → 初始化 Three.js WebGLRenderer      │
│                                         │
│  4. XR-Frame SceneManager 管理 THREE.Scene │
│                                         │
│  5. 渲染循环:SceneManager.render() →   │
│     WebGLRenderer.render(scene, camera)  │
│                                         │
│  6. 最终帧输出到 Canvas 纹理 → 小程序 画面  │
└─────────────────────────────────────────┘
  1. 小程序解析 WXML,遇到 <canvas type="webgl">,底层创建一个原生 WebGL 上下文。
  2. XR-Frame 的 Renderer 调用 wx.createWebGLContext({ canvasId: 'xr-canvas' })(微信专用 API)获取设备的 WebGLRenderingContext。
  3. 将该上下文传入 THREE.WebGLRenderer,如:

    const canvas = wx.createCanvas({ canvasId: 'xr-canvas' });
    const gl = canvas.getContext('webgl');
    const threeRenderer = new THREE.WebGLRenderer({ context: gl, canvas });
  4. 接着通过 Three.js 渲染管线,完成顶点/片元着色器编译、网格构建、光照计算,将渲染结果输出到 Canvas。

4.2 场景、摄像机与灯光

在一个最简单的 3D 场景中,至少需要:

  • 场景(Scene):承载所有 3D 对象。
  • 摄像机(Camera):决定观察角度与透视方式。
  • 光源(Light):让材质产生阴影与高光,不加光源会显示全黑或灰度。

XR-Frame 封装了这些步骤。其伪代码流程如下:

// 伪代码:XR-Frame SceneManager 内部实现
class SceneManager {
  constructor(renderer) {
    this.renderer = renderer;
    this.scene = new THREE.Scene();
    // 创建一个透视摄像机
    this.camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
    this.scene.add(this.camera);
  }
  createCamera(opts) {
    // 可定制 FOV、远近裁剪平面、位置等
    this.camera.fov = opts.fov;
    this.camera.updateProjectionMatrix();
  }
  addAmbientLight(color, intensity) {
    const light = new THREE.AmbientLight(color, intensity);
    this.scene.add(light);
  }
  addPointLight(color, intensity, position) {
    const light = new THREE.PointLight(color, intensity);
    light.position.set(position.x, position.y, position.z);
    this.scene.add(light);
  }
  addObject(obj) {
    this.scene.add(obj);
  }
  render() {
    this.renderer.render(this.scene, this.camera);
  }
}

在我们的示例中,sceneMgr.createCamera()addAmbientLight()addPointLight()addObject() 这些 API 都是一行代码就能搞定的封装,省去了手动写 camera.position.set(...)scene.add(camera) 等重复步骤。

4.3 物体创建与 Mesh 绑定

Three.js 中创建立方体需先构造几何体 (BoxGeometry) 和材质 (MeshStandardMaterial),再通过 Mesh 组合成一个可渲染网格(Mesh)。XR-Frame 将 THREE 暴露在全局,可直接调用:

const geometry = new XRFrame.THREE.BoxGeometry(2, 2, 2);
const material = new XRFrame.THREE.MeshStandardMaterial({ color: 0x0077ff });
const cube = new XRFrame.THREE.Mesh(geometry, material);
cube.position.set(0, 1, -5);
sceneMgr.addObject(cube);
  • BoxGeometry(width, height, depth):生成一个指定尺寸的立方体几何。
  • MeshStandardMaterial:PBR 材质,能与环境光、点光等配合,显示金属感、漫反射等特效。
  • Mesh(geometry, material):将几何和材质绑定为网格对象;必须调用 scene.add() 才会被渲染。

4.4 动画循环(渲染循环)

在 3D 场景中,为了实现动画效果,必须持续调用渲染循环。XR-Frame 封装了一个 setAnimationLoop 方法,底层对应 requestAnimationFrame

renderer.setAnimationLoop((delta) => {
  // delta 为距离上一次渲染的时间间隔(单位:秒)
  cube.rotation.y += delta * 0.5;  // 每秒旋转 0.5 弧度
  sceneMgr.render();              // 渲染当前帧
});
  • 这样我们就不需手动写 function tick() 自己调用 requestAnimationFrame(tick)
  • render() 内部会把 scenecamera 传给 threeRenderer.render(scene, camera),完成一帧渲染。

4.5 交互:手势与陀螺仪

除了自动旋转,我们还想让用户通过拖拽来控制物体位置或摄像机视角。XR-Frame 的 InputManager 会在内部自动绑定小程序的 canvas 触摸事件,并将其转换为更高层次的交互事件(tappanpinch 等)。示例中使用了 pan(平移)来拖动立方体:

XRFrame.input.on('pan', (ev) => {
  // ev.deltaX 与 ev.deltaY 为像素增量
  const factor = 0.01;
  cube.position.x += ev.deltaX * factor;
  cube.position.y -= ev.deltaY * factor;
});

若要实现缩放(捏合缩放),可监听 pinch 事件:

XRFrame.input.on('pinch', (ev) => {
  // ev.scale 大于1:放大;小于1:缩小
  cube.scale.multiplyScalar(ev.scale);
});
  • pan:一次拖拽产生一连串的 deltaX/deltaY,适合平移或物体拾取拖动。
  • pinch:两指捏合产生一个 scale(基于上一次记录的比例),适合缩放物体或相机。
  • tap:单击或双击事件,可用于选中物体,结合 Three.js 的 Raycaster 检测点击命中。

五、物体加载与交互:从 GLTF 模型导入到射线拾取

在真实项目中,常常需要将美术同学提供的 3D 模型(如 GLTF/GLB)加载进来,并实现点击选中、拖拽旋转等交互。下面演示一个加载远端 GLTF 模型并实现点击选中 Mesh 的示例。

5.1 GLTF/GLB 模型加载

首先,我们假设已有一个托管在 CDN 或 OSS 上面的 scene.gltf 文件。使用 XR-Frame 内置的 GLTFLoader,即可一行代码加载到场景:

const loader = new XRFrame.loader.GLTFLoader();
loader.load('https://cdn.example.com/models/scene.gltf', (gltf) => {
  // gltf.scene 为 THREE.Group
  gltf.scene.position.set(0, 0, -3); // 放在相机前方 3 个单位
  sceneMgr.addObject(gltf.scene);
});

ASCII 图解:GLTFLoader 加载流程

┌───────────────────────────────────────────────┐
│   loader.load(url, onLoadCallback)           │
│   1. 发起网络请求获取 .gltf/.glb 文件         │
│   2. 底层解析 JSON 或 二进制 二次加载缓冲区     │
│   3. 生成 THREE.BufferGeometry、Materials等    │
│   4. 返回 gltf.scene(THREE.Group)           │
└───────────────────────────────────────────────┘
  • XR-Frame 的 GLTFLoader 其实是对 Three.js 官方 GLTFLoader 的一次封装,内部针对小程序环境处理了路径与缓存。
  • 加载完成后可以直接把 gltf.scene 添加到场景中进行渲染。

5.2 射线拾取(Raycaster)与交互

加载完模型后,假设我们想通过点击或触摸事件来【选中】模型中的某个 Mesh,并高亮它或弹出详情。可以使用 Three.js 的 Raycaster 来实现射线拾取。示例如下:

// 在 init3DScene 内部
const raycaster = new XRFrame.THREE.Raycaster();
const pointer = new XRFrame.THREE.Vector2();

// 假设 models 為已加载的 THREE.Group
const models = gltf.scene;
sceneMgr.addObject(models);

// 点击或触摸结束后触发拾取
XRFrame.input.on('tap', (ev) => {
  // 1. 将触摸坐标转换到 WebGL 规范化设备坐标(-1 ~ +1)
  const canvasW = wx.getSystemInfoSync().windowWidth;
  const canvasH = wx.getSystemInfoSync().windowHeight;
  pointer.x = (ev.clientX / canvasW) * 2 - 1;
  pointer.y = - (ev.clientY / canvasH) * 2 + 1;

  // 2. 设置射线
  raycaster.setFromCamera(pointer, sceneMgr.camera);

  // 3. 射线与模型进行交叉检测
  const intersects = raycaster.intersectObject(models, true);
  if (intersects.length > 0) {
    const hit = intersects[0]; // 最近的交点
    const mesh = hit.object;   // 被点击的 Mesh
    // 4. 高亮或显示信息
    mesh.material.emissive = new XRFrame.THREE.Color(0xff0000);
    setTimeout(() => {
      mesh.material.emissive = new XRFrame.THREE.Color(0x000000);
    }, 500);
    uni.showToast({ title: `选中:${mesh.name || mesh.uuid}`, icon: 'none' });
  }
});
  1. 计算规范化设备坐标

    • pointer.x = (clientX / width) * 2 - 1;
    • pointer.y = - (clientY / height) * 2 + 1;
      这样才能将触摸点映射到 Three.js 的 NDC 坐标系中。
  2. Raycaster 与摄像机联动

    raycaster.setFromCamera(pointer, sceneMgr.camera);
    • 射线从“相机”发出,方向由 pointer 指定,自动计算在世界空间中的起点与方向向量。
  3. 与场景对象进行 intersectObject 检测

    • models 可以是一个 Group,设置 recursive: true(第二个参数)表示对其所有子对象进行遍历检测。
    • 如果检测到交点数组 intersects 非空,则第一个元素即为“最近的交点”。
  4. 选中反馈

    • 对被点击的 Mesh 设置 material.emissive 高光颜色,使其闪烁半秒后恢复原始颜色。

六、AR/VR 模式实战:人脸追踪与世界坐标锚点

XR-Frame 不仅能做到 3D 渲染,还集成了 AR/VR 功能。下面以微信小程序为例,演示如何在 AR 模式下将 3D 模型放置在现实世界的人脸或平面上。

6.1 AR 模式原理与流程

  1. 判断设备支持

    • 微信小程序需基础库 ≥ 2.10.0 才支持 wx.createCamera3D 等接口。XR-Frame 会自动检测,如果不支持则回退到普通 3D 模式。
  2. 创建 AR 摄像机

    • 在 AR 模式下,摄像机会与设备后置摄像头画面绑定,实时将摄像头影像作为背景。
    • SDK 会内置一个 AR Session 管理模块,提供世界坐标系与 3D 场景同步。
  3. 创建锚点(Anchor)

    • 可以基于人脸检测、平面检测、图像识别等算法生成锚点(Anchor),用于打上“3D 模型将在此位置渲染”。
  4. 将 3D 模型附着到锚点

    • 每一帧渲染前,AR 摄像机会更新锚点在真实世界中的 3D 坐标,XR-Frame 会自动将该坐标转换到 Three.js 坐标系,并让模型跟随锚点移动。

ASCII 图解:AR 模式渲染流程

┌───────────────────────────────────────────────┐
│                 AR 调用流程                 │
│ 1. XRFrame.XRManager.initAR({ cameraId, ... })│
│ 2. 创建 AR 摄像机 & 世界坐标系 tracking        │
│ 3. 当检测到人脸/平面 时,生成 Anchor (x,y,z)   │
│ 4. 在 SceneManager 中为模型创建一个 AnchorNode │
│ 5. 每帧:AR 更新 AnchorWorldTransform           │
│    XR-Frame 内部更新 SceneManager 对应 Node     │
│ 6. THREE.Camera 自动渲染背景摄像头画面+3D场景    │
└───────────────────────────────────────────────┘

6.2 代码示例:基于人脸追踪的 AR 模式

以下示例演示如何在微信小程序中调用 XR-Frame 的 AR 模块,将一个 3D 面具模型实时“戴到”用户脸上。示例中假设存在 face_mask.glb 模型,该模型在 Y 轴对齐人脸中心。

<template>
  <view class="container">
    <!-- 用于 AR 的摄像头实时预览 -->
    <camera 
      device-position="back" 
      flash="off" 
      id="ar-camera" 
      style="width:100vw;height:100vh;position:absolute;top:0;left:0;"
    ></camera>
    <!-- 上层 Canvas 用于 3D 渲染 -->
    <canvas 
      canvas-id="ar-canvas" 
      type="webgl" 
      style="width:100vw;height:100vh;position:absolute;top:0;left:0;"
    ></canvas>
  </view>
</template>

<script>
export default {
  onReady() {
    // 1. 先加载 XR-Frame 脚本,略同前面
    wx.getFileSystemManager().readFile({
      filePath: `${wx.env.USER_DATA_PATH}/_www/static/xr-frame/xr-frame.min.js`,
      encoding: 'utf8',
      success: () => {
        this.initARFace();
      }
    });
  },
  methods: {
    async initARFace() {
      if (typeof XRFrame === 'undefined') return;
      // 2. 初始化 Renderer 与 SceneManager
      const renderer = new XRFrame.core.Renderer({
        canvasId: 'ar-canvas',
        antialias: true,
        alpha: true,
        width: wx.getSystemInfoSync().windowWidth,
        height: wx.getSystemInfoSync().windowHeight
      });
      const sceneMgr = new XRFrame.core.SceneManager(renderer);
      // 3. 创建 AR 摄像机
      const arCamera = await XRFrame.XRManager.initAR({
        cameraId: 'ar-camera', // 绑定小程序 <camera> 组件
        scene: sceneMgr.scene,
        camera: sceneMgr.camera
      });
      // 4. 加载 GLB 面具模型
      const loader = new XRFrame.loader.GLTFLoader();
      loader.load('https://cdn.example.com/models/face_mask.glb', (gltf) => {
        const mask = gltf.scene;
        mask.scale.set(1.2, 1.2, 1.2); // 适当放大或缩小
        // 5. 创建一个人脸锚点节点:当检测到人脸时,会把 mask 绑定到人脸中心
        const faceAnchor = XRFrame.XRManager.createAnchor('face');
        faceAnchor.add(mask);
        sceneMgr.addObject(faceAnchor);
      });
      // 6. 渲染循环:ARCamera 会自动更新相机投影与背景
      renderer.setAnimationLoop(() => {
        sceneMgr.render();
      });
    }
  }
};
</script>

<style>
.container {
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
}
camera, canvas {
  background: transparent;
}
</style>

关键说明

  1. <camera> 组件

    • 小程序原生 <camera device-position="back"> 用于打开后置摄像头,并将实时画面作为 AR 背景。
    • cameraId="ar-camera" 对应 XRManager.initAR({ cameraId: 'ar-camera' }),让 XR-Frame 将摄像头数据与 3D 场景无缝融合。
  2. initAR 方法

    const arCamera = await XRFrame.XRManager.initAR({
      cameraId: 'ar-camera',
      scene: sceneMgr.scene,
      camera: sceneMgr.camera
    });
    • 内部会调用微信小程序的 wx.createCamera3D(或同类 API)创建 AR Session。
    • 将真实摄像头图像作为 Three.js 背景贴图,并实时更新 sceneMgr.camera 的投影矩阵以匹配相机 FOV。
  3. 人脸锚点(face anchor)与面具绑定

    const faceAnchor = XRFrame.XRManager.createAnchor('face');
    faceAnchor.add(mask);
    sceneMgr.addObject(faceAnchor);
    • createAnchor('face'):请求 SDK 开启人脸检测,当检测到人脸时,会在识别到的人脸中心自动更新该锚点节点的世界坐标。
    • 将面具模型添加到该锚点节点后,当用户移动时,面具会“贴合”人脸运动,达到 AR 效果。
  4. 渲染循环

    • AR 模式下,摄像头背景与 3D 场景需要同时更新。只需调用 renderer.setAnimationLoop(() => sceneMgr.render()),框架会在每帧自动从 AR Session 获取相机姿态与背景贴图。

七、性能优化与注意事项

虽然 XR-Frame 已做过一定优化,但在小程序环境中,仍需留意以下几点,以确保流畅的 3D/AR 体验。

7.1 渲染分辨率与帧率控制

  • 降低渲染分辨率(pixelRatio)

    const renderer = new XRFrame.core.Renderer({
      canvasId: 'xr-canvas',
      pixelRatio: 1, // 强制设置为 1,可降低 GPU 负担
      width, height
    });

    小程序中的 WebGL 上下文对高分屏适配有限,若使用 pixelRatio: devicePixelRatio,会导致帧率急剧下降。一般建议 pixelRatio 设置为 1 或 1.5。

  • 控制渲染频次
    对于静态场景,无需每帧都渲染。可使用 renderer.setAnimationLoop(false) 暂停渲染,只有在交互(旋转、移动、AR 姿态更新)时手动调用 render()

7.2 模型与材质优化

  • 简化几何体
    对于移动端小程序,尽量减少顶点数过多的模型。GLTF 模型导出时可对网格进行 LOD(Level of Detail)或简化网格。
  • 压缩纹理
    采用 PVR、ASTC 等压缩纹理格式,或使用 WebP/JPEG 做贴图压缩,减少纹理大小。
  • 合并材质与纹理
    如果场景中有多个材质相似的物体,可在建模阶段将纹理合并到一张大图中,减少材质切换次数。

7.3 避免内存泄漏

  • 销毁资源
    在页面卸载时,需手动释放 Three.js 对象、几何体、材质等,以免内存持续增大。示例:

    onUnload() {
      renderer.setAnimationLoop(null);
      sceneMgr.scene.traverse((obj) => {
        if (obj.geometry) obj.geometry.dispose();
        if (obj.material) {
          if (Array.isArray(obj.material)) {
            obj.material.forEach(mat => mat.dispose());
          } else {
            obj.material.dispose();
          }
        }
      });
      renderer.dispose();
    }
  • 取消事件监听
    如果使用 XRFrame.input.on(...) 注册了事件,在卸载时要调用 XRFrame.input.off('pan')off('tap') 等解绑。

7.4 AR 模式特殊注意

  • 权限申请
    AR 模式下需要使用摄像头访问权限。在小程序的 app.json 或者对应页面的 json 配置里,需要声明:

    {
      "permission": {
        "scope.camera": {
          "desc": "你的应用需要使用摄像头进行 AR 渲染"
        }
      }
    }

    用户第一次进入页面时会弹出授权对话框。

  • 基础库兼容
    仅在微信基础库 ≥ 2.10.0 支持 AR 功能,其他小程序(支付宝/百度)需要替换相应平台的 AR 接口或回退至普通 3D 模式。

八、常见问题解答

  1. 为什么我的 XR-Frame 在某些机型上报错 “WebGL not supported”?

    • 小程序里 WebGL 能否使用取决于底层系统与微信基础库,部分老旧机型或开发者工具预览可能不支持 WebGL。可在初始化前调用:

      if (!XRFrame.core.Renderer.isWebGLSupported()) {
        uni.showToast({ title: '该设备不支持 WebGL', icon: 'none' });
        return;
      }
    • 在不支持 WebGL 的机型上,可提示用户切换到普通 2D 模式,或降级为静态图片替换 3D 场景。
  2. 如何在微信小程序中使用离线 GLTF 模型(本地缓存)?

    • 首次加载时可调用 uni.downloadFile 下载 GLTF/GLB 到本地缓存目录,然后传递本地 filePathGLTFLoader.load(filePath, ...)。XR-Frame 已内置了该适配,无需手动转换。
  3. AR 模式下面具位置总是偏离?如何校准坐标?

    • 可能是模型原点或姿态与人脸坐标不一致。可通过调整 mask.rotationmask.positionmask.scale 使其贴合用户脸型。也可在建模时确保面具中心对齐到 (0,0,0)。
  4. 如何导入其他 3D 模型格式(OBJ、FBX)?

    • XR-Frame 的 loader/ 目录里也封装了 OBJLoader.jsFBXLoader.js,用法类似:

      const objLoader = new XRFrame.loader.OBJLoader();
      objLoader.load('https://cdn.example.com/models/model.obj', (obj) => {
        sceneMgr.addObject(obj);
      });
    • 注意:这些加载器会拉取附加的 MTL 或纹理文件,需保持模型文件夹结构一致。
  5. 为何 renderer.setAnimationLoop(null) 无效?循环依然在跑?

    • 在某些小程序环境下,需要显式调用 renderer.dispose() 才能彻底停止渲染与释放上下文。建议在页面 onUnload 里做:

      renderer.setAnimationLoop(null);
      renderer.dispose();
      XRFrame.input.offAll(); // 解绑所有输入事件
      sceneMgr.dispose();      // 若有此方法则释放场景资源
  6. 如何使用陀螺仪控制摄像机旋转?

    • XR-Frame 的 InputManager 内置支持陀螺仪事件,使用方式类似:

      XRFrame.input.on('gyro', (ev) => {
        // ev.alpha, ev.beta, ev.gamma
        const rotationQuaternion = new XRFrame.THREE.Quaternion();
        rotationQuaternion.setFromEuler(new XRFrame.THREE.Euler(ev.beta, ev.gamma, ev.alpha));
        sceneMgr.camera.quaternion.copy(rotationQuaternion);
      });
    • 调用前需开启陀螺仪监听:wx.startGyroscope({interval: 'game'}); 并在卸载时 wx.stopGyroscope()

九、结语

至此,你已经完成了从环境搭建XR-Frame 核心模块解读,到3D 渲染基础模型加载与交互AR 模式实战性能优化与常见问题的全方位实战指南。通过本文示例代码与 ASCII 图解,你应该能够在微信小程序(或其他兼容小程序平台)中:

  • 快速集成 XR-Frame,实现一个自转并可拖拽的 3D 立方体;
  • 加载远端 GLTF/GLB 模型,并使用射线拾取(Raycaster)实现选中与高亮;
  • 在 AR 模式下将 3D 面具贴合到人脸上,或将模型放置在真实世界平面中进行浏览;
  • 优化渲染分辨率、合理释放资源,保证在移动设备上流畅运行;

XR-Frame 的出现,让小程序开发者无需深度了解底层 WebGL、着色器与原生 AR SDK,就能“拿来即用”地创建沉浸式 3D/AR 体验。接下来,你可以尝试将更复杂的场景(多模型场景、物理碰撞、多人联机协作)接入 XR-Frame,构建更丰富的交互应用。

最后修改于:2025年06月10日 12:08

评论已关闭

推荐阅读

DDPG 模型解析,附Pytorch完整代码
2024年11月24日
DQN 模型解析,附Pytorch完整代码
2024年11月24日
AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日