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 体验,涵盖以下主要内容:
- XR-Frame 框架概述
- 环境搭建与集成(微信小程序示例)
- 创建第一个 3D 场景:渲染一个可旋转的立方体
- 物体加载与交互:从 GLTF/GLB 模型导入到手势拖拽
- AR/VR 模式实战:人脸追踪与世界坐标锚点
- 性能优化与注意事项
- 常见问题解答
每个部分都配备了完整代码示例与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 自带的
GLTFLoader
、TextureLoader
,并针对小程序缓存,做了路径转换与异步读取的适配(如使用wx.getFileSystemManager()
读取本地模型缓存)。 - Utils:涵盖数学工具(向量、矩阵运算)、路径拼接、环境检测等。
在本指南后续章节中,我们会以代码示例的形式演示如何调用这些模块,快速构建一个可旋转的 3D 立方体场景,再逐步深入讲解 AR/VR 模式的应用。
三、环境搭建与集成(以微信小程序为例)
下面以微信小程序为示例,介绍如何在 uniapp 中集成 XR-Frame 并运行一个最简单的 3D Demo。
3.1 准备工作
环境要求
- HBuilderX 3.x 或更高版本;
- 微信开发者工具 1.02+(支持 WebGL);
- 微信基础库版本 ≥ 2.10.0(部分 XR 接口需要该版本以上支持)。
下载 XR-Frame
可通过 NPM 安装:
npm install xr-frame --save
- 或者直接将
XR-Frame
的dist/xr-frame.min.js
拷贝到static/xr-frame/
目录下。
项目目录结构调整
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
页面中,我们需要在 script
或 onLaunch
时把 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 关键步骤详解
加载 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>
,则无需在页面里重复加载。
- 小程序不允许直接在 WXML 中使用
创建 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
为渲染区域尺寸,一般设置为屏幕宽高。
初始化 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),为立方体产生阴影与高光。
创建立方体 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 单位,使其位于摄像机前方。
启动渲染循环
renderer.setAnimationLoop((delta) => { cube.rotation.y += delta * 0.5; sceneMgr.render(); });
setAnimationLoop
底层调用requestAnimationFrame
,并自动计算两帧间隔delta
(秒)。- 在回调中不断旋转立方体并渲染场景。
物体交互:监听触摸拖拽
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.deltaX
、ev.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 纹理 → 小程序 画面 │
└─────────────────────────────────────────┘
- 小程序解析 WXML,遇到
<canvas type="webgl">
,底层创建一个原生 WebGL 上下文。 - XR-Frame 的
Renderer
调用wx.createWebGLContext({ canvasId: 'xr-canvas' })
(微信专用 API)获取设备的 WebGLRenderingContext。 将该上下文传入
THREE.WebGLRenderer
,如:const canvas = wx.createCanvas({ canvasId: 'xr-canvas' }); const gl = canvas.getContext('webgl'); const threeRenderer = new THREE.WebGLRenderer({ context: gl, canvas });
- 接着通过 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()
内部会把scene
与camera
传给threeRenderer.render(scene, camera)
,完成一帧渲染。
4.5 交互:手势与陀螺仪
除了自动旋转,我们还想让用户通过拖拽来控制物体位置或摄像机视角。XR-Frame 的 InputManager
会在内部自动绑定小程序的 canvas
触摸事件,并将其转换为更高层次的交互事件(tap
、pan
、pinch
等)。示例中使用了 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' });
}
});
计算规范化设备坐标
pointer.x = (clientX / width) * 2 - 1;
pointer.y = - (clientY / height) * 2 + 1;
这样才能将触摸点映射到 Three.js 的 NDC 坐标系中。
Raycaster 与摄像机联动
raycaster.setFromCamera(pointer, sceneMgr.camera);
- 射线从“相机”发出,方向由
pointer
指定,自动计算在世界空间中的起点与方向向量。
- 射线从“相机”发出,方向由
与场景对象进行
intersectObject
检测models
可以是一个Group
,设置recursive: true
(第二个参数)表示对其所有子对象进行遍历检测。- 如果检测到交点数组
intersects
非空,则第一个元素即为“最近的交点”。
选中反馈
- 对被点击的 Mesh 设置
material.emissive
高光颜色,使其闪烁半秒后恢复原始颜色。
- 对被点击的 Mesh 设置
六、AR/VR 模式实战:人脸追踪与世界坐标锚点
XR-Frame 不仅能做到 3D 渲染,还集成了 AR/VR 功能。下面以微信小程序为例,演示如何在 AR 模式下将 3D 模型放置在现实世界的人脸或平面上。
6.1 AR 模式原理与流程
判断设备支持
- 微信小程序需基础库 ≥ 2.10.0 才支持
wx.createCamera3D
等接口。XR-Frame 会自动检测,如果不支持则回退到普通 3D 模式。
- 微信小程序需基础库 ≥ 2.10.0 才支持
创建 AR 摄像机
- 在 AR 模式下,摄像机会与设备后置摄像头画面绑定,实时将摄像头影像作为背景。
- SDK 会内置一个 AR Session 管理模块,提供世界坐标系与 3D 场景同步。
创建锚点(Anchor)
- 可以基于人脸检测、平面检测、图像识别等算法生成锚点(
Anchor
),用于打上“3D 模型将在此位置渲染”。
- 可以基于人脸检测、平面检测、图像识别等算法生成锚点(
将 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>
关键说明
<camera>
组件- 小程序原生
<camera device-position="back">
用于打开后置摄像头,并将实时画面作为 AR 背景。 cameraId="ar-camera"
对应XRManager.initAR({ cameraId: 'ar-camera' })
,让 XR-Frame 将摄像头数据与 3D 场景无缝融合。
- 小程序原生
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。
- 内部会调用微信小程序的
人脸锚点(face anchor)与面具绑定
const faceAnchor = XRFrame.XRManager.createAnchor('face'); faceAnchor.add(mask); sceneMgr.addObject(faceAnchor);
createAnchor('face')
:请求 SDK 开启人脸检测,当检测到人脸时,会在识别到的人脸中心自动更新该锚点节点的世界坐标。- 将面具模型添加到该锚点节点后,当用户移动时,面具会“贴合”人脸运动,达到 AR 效果。
渲染循环
- AR 模式下,摄像头背景与 3D 场景需要同时更新。只需调用
renderer.setAnimationLoop(() => sceneMgr.render())
,框架会在每帧自动从 AR Session 获取相机姿态与背景贴图。
- AR 模式下,摄像头背景与 3D 场景需要同时更新。只需调用
七、性能优化与注意事项
虽然 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 模式。
八、常见问题解答
为什么我的 XR-Frame 在某些机型上报错 “WebGL not supported”?
小程序里 WebGL 能否使用取决于底层系统与微信基础库,部分老旧机型或开发者工具预览可能不支持 WebGL。可在初始化前调用:
if (!XRFrame.core.Renderer.isWebGLSupported()) { uni.showToast({ title: '该设备不支持 WebGL', icon: 'none' }); return; }
- 在不支持 WebGL 的机型上,可提示用户切换到普通 2D 模式,或降级为静态图片替换 3D 场景。
如何在微信小程序中使用离线 GLTF 模型(本地缓存)?
- 首次加载时可调用
uni.downloadFile
下载 GLTF/GLB 到本地缓存目录,然后传递本地filePath
给GLTFLoader.load(filePath, ...)
。XR-Frame 已内置了该适配,无需手动转换。
- 首次加载时可调用
AR 模式下面具位置总是偏离?如何校准坐标?
- 可能是模型原点或姿态与人脸坐标不一致。可通过调整
mask.rotation
、mask.position
、mask.scale
使其贴合用户脸型。也可在建模时确保面具中心对齐到 (0,0,0)。
- 可能是模型原点或姿态与人脸坐标不一致。可通过调整
如何导入其他 3D 模型格式(OBJ、FBX)?
XR-Frame 的
loader/
目录里也封装了OBJLoader.js
、FBXLoader.js
,用法类似:const objLoader = new XRFrame.loader.OBJLoader(); objLoader.load('https://cdn.example.com/models/model.obj', (obj) => { sceneMgr.addObject(obj); });
- 注意:这些加载器会拉取附加的 MTL 或纹理文件,需保持模型文件夹结构一致。
为何
renderer.setAnimationLoop(null)
无效?循环依然在跑?在某些小程序环境下,需要显式调用
renderer.dispose()
才能彻底停止渲染与释放上下文。建议在页面onUnload
里做:renderer.setAnimationLoop(null); renderer.dispose(); XRFrame.input.offAll(); // 解绑所有输入事件 sceneMgr.dispose(); // 若有此方法则释放场景资源
如何使用陀螺仪控制摄像机旋转?
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,构建更丰富的交互应用。
评论已关闭