简介:面向微信小程序AR开发者的完整示例代码包,聚焦“扫描特定图片后触发显示GLB三维模型”这一核心交互场景,适合正在调研微信AR能力、需要快速跑通图像识别与模型展示链路的开发者。压缩包共含783个文件,大小约5.16MB,其中wxml/wxss/JSON/JS构成前端页面与交互逻辑,TS用于类型定义或功能扩展,GLB/BIN为三维模型及数据,其余为工程配置和版本控制文件,结构清晰便于定位修改。代码中已具备图像识别触发与模型加载展示的基础实现,可直接替换识别底图和GLB模型进行二次开发,省去从零搭建环境的成本。此外资源保留了完整的依赖与配置,适合作为微信小程序AR项目的起步模板。目前已有727人学习,适合有一定小程序基础、希望快速验证AR效果的开发者。 微信AR识别特定图片出现glb模型这个需求,我太熟悉了。这阵子项目组刚落地了一个类似的互动营销小游戏,拿着手机对准活动海报一扫,屏幕里直接浮现一个3D吉祥物,还能围着它转圈看。不少朋友问我这是怎么实现的,是不是用了什么昂贵的第三方SDK。其实微信官方早就把这条路铺好了,利用的就是小程序里的VPS能力(视觉定位系统,Visual Positioning Service)。今天就用一篇实操笔记,把从零到一做出"扫图出模型"的关键代码和踩坑记录都捋一遍,给想尝试的同学一条可以照着走的路。
这个方案说白了就是:小程序通过摄像头识别你指定的图片(触发图),识别成功后,在图片所在的三维位置生成一个虚拟锚点,然后在这个锚点上加载一个glb格式的3D模型。整个过程不需要用户下载额外App,扫完即用,比传统的WebAR体验更顺滑,识别稳定性也要好不少。适合做品牌互动、展览导览、产品展示、文创周边玩法等场景。如果你正好也需要在小程序里实现类似效果,这篇文章应该能替你省下不少自己摸索的时间。
先说说为什么最终选型直接用微信小程序的AR能力,而不是绕道走WebAR方案。
1. 微信AR的选型思路,为什么是VPS+glb
1.1 需求拆解:识别图片、定位场景、加载模型
我刚开始接到需求时,第一反应是找现成的WebAR库,比如AR.js、Mind AR这类,想着在小程序里套个WebView就能跑。但实测下来问题很多:一是小程序里WebView的相机权限和传感器API受限比较严重,二是纯前端做图像识别的稳定性在复杂光照下差得离谱,三是模型加载性能很不可控。反复对比之后,回到了官方给出的标准答案:VPS(视觉定位)+ 小程序AR相机组件 + glb模型。
整套逻辑拆开看是三个独立又串行的环节:
- 识别图片:提前在微信公众平台的后台上传一张"识别图"(也叫触发图/地图),微信视觉算法会提取这张图的特征点。用户扫码时,VPS会拿摄像头帧和这张特征图做匹配,匹配成功就返回一个坐标系统,在这个系统里,图片本身被固定为一个平面锚点。
- 定位相机:识别成功后,小程序端拿到的是相机相对这个锚点的位姿(位置和旋转),这个位姿会实时更新,所以你拿着手机前后左右移动,3D模型能稳稳钉在原地,不飘。
- 叠加模型:拿到稳定的位姿数据后,利用小程序AR组件加载glb模型,渲染在锚点上。glb是gltf的二进制封装,把所有贴图、骨骼、动画打包在一个文件里,加载快、体积小,是为移动端AR量身定制的格式。
这是一个官方支持和维护的技术链路,而不是我去找第三方库硬凑出来的野路子。后端算法、坐标系解算都不需要自己开发,只需要把"触发图"传上去,把"模型"准备好,中间的黏合代码其实很薄。
1.2 为什么模型格式选glb而不是gltf或obj
很多人在这一步会纠结。同样是3D文件,有的项目里源文件是max、blender、maya做的,导出时摆在你面前好几个格式,到底选啥?
先说结论:微信小程序AR组件最推荐的是glb。glb和gltf是同一个家族的,区别只在封装方式。gltf是JSON+外部资源(纹理图片、bin文件)分开的,glb则是把整个场景树、网格数据、材质、动画、纹理全部钳进一个二进制文件里。小程序跑起来之后,加载一个glb文件等同于一次网络请求,而不是要东拼西凑去依赖多个文件。文件少了,加载速度自然快,出错概率也低。
obj格式我不建议,它本质上只是几何信息描述,材质和动画支持有限,AR场景里想要旋转动画、点击交互这些后续扩展时,基本帮不上忙。而且obj文件通常很大,把高精度模型直接扔进小程序里,首包体积就是个大问题。glb则自带压缩潜力,可以通过gltf-transform等工具做Draco压缩,能把模型体积压掉60%-80%。实测一个5MB的原始模型,压缩到glb之后可能只有2MB不到,加载体验完全不一样。
2. 核心机制拆解:VPS怎么识别图片,怎么确定模型位置
2.1 VPS的底层逻辑,不是简单的"追踪图片"
很多人对AR的认知还停留在"摄像头识别到图片、贴一个虚拟物体在上面"这一步,觉得和扫二维码差不多。但VPS的思路不太一样——它不是追踪图片,而是建立一个以图片为中心的局部三维坐标系。
具体到微信的实现,大致流程是这样:
- 你在小程序管理后台上传触发图,微信视觉服务端会对这张图做特征提取,生成一个图中的"稀疏特征点云"并存入识别库。
- 用户端,小程序初始化AR能力时,会下载或同步该场景的识别配置。
- 摄像头实时帧送入VPS算法,算法在画面里找匹配的特征点,一旦匹配到足够数量的关键点(通常需要匹配超过一定阈值),就通过PNP算法(Perspective-n-Point,即通过多个2D-3D点对,求解出相机相对目标物体的位置和姿态)求解出当前相机与图片三者之间的位置、姿态关系。这个关系就是后面所有虚拟物体摆放的根基。
所以识别图最好选纹理清晰、对比度高、特征点丰富的图片。大白墙或者纯色渐变图一定识别不出来,因为压根没有特征点可抓。我一开始测试用的是一张偏暗的油画风格的图,在光线不足的室内反复失败,后来换了一张明度高、细节丰富的插画,识别率直线上升。
2.2 坐标同步:让模型"长"在图片上的关键
一旦拿到相机位姿,剩下的问题就是坐标对齐了。
VPS会给相机返回一个相对世界坐标系的位姿变换矩阵,模型加载时也要放置在这个坐标系里。通常我们会把模型挂在识别图的Anchor(锚点)节点下,锚点的原点恰好是图片正中心。这样模型就像从图片里"长"出来一样,你移动手机,模型和图片之间的相对位置纹丝不动。
这里有一个我要重点提醒的细节:模型的原点和尺寸缩放非常关键。如果模型在建模软件里是以厘米为单位的,而VPS的坐标系统以米为单位,那模型显示出来要么巨大到占据整个屏幕,要么小到几乎看不见。实操时一定要检查模型导入后的单位,必要时在代码里做一次缩放处理。还有模型原点,比如一个杯子模型,原点最好在杯子底部中心,这样放置到锚点上时,杯底刚好落在图面上,看起来才自然。如果原点在模型几何中心,杯子就会"悬浮"在图片上方半空,特别假。
3. 实操过程:从建小程序到扫出3D模型
3.1 你需要准备什么
- 一个微信小程序账号(个人主体暂不支持AR能力,企业主体才可以),并且需要开通类目,一般"文娱-其他视频"或"工具-效率"下可以配置;
- 微信公众平台后台开通"AR能力"的权限,并且创建AR场景;
- 一台支持ARCore或ARKit的安卓/iOS手机,且微信版本要比较新(基础库2.22.2以上);
- 一个glb格式的3D模型(可以从Sketchfab、任意3D建模软件导出,如果只是在网上找素材,注意看授权协议)。
3.2 后台上传触发图,配置AR场景
登录小程序管理后台,找到"功能-AR能力"或者"开发-开发管理-AR"的入口。新建一个"视觉定位"类型的AR场景,上传识别用的触发图,设定识别图的物理尺寸。这个物理尺寸不需要完全精确,但会影响模型的显示比例。比如海报实际打印出来宽50cm,这里就填0.5m。如果填的差太远,模型和真实物体的比例感会歪掉。
平台会为这个场景生成一个AR Scene ID,这就是后续代码里唯一需要关心的标识。
3.3 小程序端的代码骨架
以最常见的原生小程序开发为例,核心代码可以分成三块:初始化AR、绑定相机帧、加载glb模型。
第一段,初始化AR场景并注册帧回调:
// app.js 或 page 的 onReady 中 const ar = wx.createVPSContext({ sceneId: '你的AR场景ID', success: (res) => { console.log('VPS初始化成功', res) }, fail: (err) => { console.error('VPS初始化失败', err) } })这里有个坑,VPSContext初始化不是即时完成的,它需要先从服务端拉取场景识别配置。如果网络慢,可能需要等待一两秒,所以建议在页面加载时提前初始化,不要等用户已经举起手机扫图了才开始new。
第二段,把摄像头帧实时喂给AR引擎。微信原生方法是在页面上放置camera组件,并通过frame-size="medium"之类的属性开启帧数据回调:
<camera device-position="back" flash="off" resolution="medium" frame-size="medium" binderect="{{true}}" bindinitdone="onCameraInit" binderror="onCameraError" style="width: 100%; height: 100vh;" />然后在JS里监听帧:
onCameraInit() { this.vpsContext = wx.createVPSContext({ sceneId: '你的AR场景ID' }) this.vpsContext.startVPS({ camera: { width: 720, height: 1280, focalLength: 1.0 }, success: () => { console.log('VPS 开始识别') } }) } // 通过 camera 组件的 bindframe 事件回调获取图像帧 onFrame(e) { const frameData = e.detail.data this.vpsContext.detectFrame({ frame: frameData, success: (res) => { if (res && res.pose) { // 识别成功,拿到了相机位姿 } } }) }注意这里的detectFrame调用不能太频繁,每隔一帧处理一次即可,否则性能扛不住,特别是在中低端安卓机上,会出现明显卡顿和发热。
第三段,识别成功后加载glb模型到锚点下。我习惯用three.js封装在微信小程序里的实现方式,因为three.js本身就支持gltf/glb加载器,配合微信的AR组件能省不少事:
import * as THREE from 'three' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' import { VPSAnchor } from 'wechat-miniprogram-three-vps' // 识别成功后创建一个锚点,把模型挂上去 handleAnchorCreated(anchor) { const loader = new GLTFLoader() loader.load( 'https://your-cdn.com/model.glb', (gltf) => { const model = gltf.scene model.scale.set(0.5, 0.5, 0.5) // 根据实际尺寸调整 anchor.add(model) this.scene.add(anchor) }, undefined, (error) => console.error('模型加载失败', error) ) }如果用纯原生小程序AR组件而不依赖three.js,模型挂载方式会有些差异,但整体思路是一样的:拿到VPS回调返回的位姿信息后,把模型节点(或glb文件对应的实体)添加到锚点节点下面。
3.4 文件目录建议
我顺手分享一下我平时搭建这类小程序项目的目录,方便新手理解:
├── pages/ │ └── ar-page/ │ ├── index.js # AR处理逻辑 │ ├── index.wxml # camera布局 │ └── index.json # 页面配置 ├── utils/ │ └── three-adapter.js # three.js与小程序适配层 ├── models/ │ └── my-model.glb # 也可放云端,通过URL加载 └── app.js # 全局初始化实际生产里模型不会放在本地,都是扔CDN,走URL加载,方便后续换模型,不用重新发版。而且CDN带上合适的缓存策略,用户第二次进入时的加载速度会快很多。
4. 真实环境里的调试记录与踩坑排查
4.1 后台显示的触发图和物理标识的匹配关系
这是我个人遇到的第一个大坑:上传的触发图和手机扫到的图,必须保证是同一张图,但"同一张"不代表"一模一样"。
VPS的视觉特征匹配有一定的鲁棒性,比如你打印的海报色彩稍微偏一点色差,或者扫描时有轻微反光,都能识别出来。但如果你把图片裁剪成了不同比例,或者在手机屏幕上展示这张图(而非打印版),识别率就会大打折扣。因为手机屏幕的亮度变化会严重干扰特征提取。测试时建议直接拿打印好的物料测,不要在电脑屏幕上反复试,效果差距真的很大。
4.2 模型加载出来却是黑色的,怎么回事
这个问题很像游戏开发里的"模型黑面"。原因通常是材质光照设置不对:glb文件里如果材质是PBR(基于物理渲染)材质,在AR场景中需要配合环境光照信息。如果当前小程序AR渲染环境没有给足环境贴图,模型就会出现暗部死黑的情况。
我的处理办法是:在3D工具(比如Blender)里导出模型时,给模型加一个简单的环境光探头(Environment Lighting),或者在GLTF导出选项里勾选"Export Lights"。还不行的话,直接用代码补一个环境光:
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0) scene.add(ambientLight)4.3 识别成功后模型漂移
这个问题出现频率极高。明明第一帧识别得很准,结果手一抖,模型就滑出去了。
漂移的原因大多出在设备运动传感器和视觉定位结果的融合上。微信AR组件内部有VIO(视觉惯性里程计)算法,理论上会结合陀螺仪、加速度计的数据做相机追踪。但在弱光环境下,相机帧特征点变少,视觉约束变弱,陀螺仪的漂移就体现出来了。改善手段包括:
- 尽量在光照充足、纹理丰富的环境中测试;
- 避免快速甩动手机,初始化后先缓慢扫一遍周围,让算法充分收敛;
- 如果场景固定(比如展览现场),可以让用户第一次识别时保持静止2-3秒,让VPS做好初始对齐,之后再随意移动。
4.4 加载本地模型失败或白屏
有些同学直接把模型放在小程序的包目录里,代码写loader.load('/models/demo.glb'),结果加载不出来。
小程序里静态资源的加载路径跟传统Web端不一样,本地文件路径必须先通过wx.getFileSystemManager().readFile读取为ArrayBuffer,再交给GLTFLoader解析。直接写相对路径,GLTFLoader会试图走HTTP请求,拿不到本地文件。如果不想处理ArrayBuffer,就老老实实把模型传到CDN,用https://开头的完整URL加载,这也是我之前推荐的做法。
另外glb文件超过一定大小(微信小程序的包体限制),根本无法随包上传,后台审核也会被拦。所以无论如何,云端存储都是更优解。
4.5 测试时看不到相机画面或一直loading
这通常不是代码问题,而是基础库版本兼容问题。AR相关接口在低版本微信客户端上不存在,调用wx.createVPSContext会直接返回fail。可以先用wx.getSystemInfoSync()检查微信版本,低于要求时做降级提示,比如告诉用户"请升级微信到最新版本后体验"。还有就是开发者工具的模拟器不支持AR相机能力,必须拿真机调试,我最初在开发者工具里白折腾了半小时,最后发现完全没有意义。
5. 性能优化与体验打磨的几点心得
整个AR页面最关键的就是性能和首帧加载时间。我调试时用的中端安卓机(骁龙778G级别),如果不做优化,扫图成功到模型出现大约需要1.8秒;做了一轮优化后能压到1秒以内。主要做了这几件事:
- 模型压缩:gltf-transform配合Meshopt或Draco压缩,把模型文件从10MB压到2MB左右;
- CDN预加载:用户进入页面前,提前resolve DNS并建立连接,甚至可以在一个隐藏节点里预加载glb,等识别成功后再直接把缓冲好的数据传给渲染器;
- 纹理尺寸控制:一张2048x2048的贴图在手机上会被GPU降采样,道理上不如直接导出1024x1024,省显存还更快;
- 模型面数控制:移动端建议单个模型的三角面数控制在5万到10万以下,超出这个量级,顶点变换和片元着色的开销会明显拖帧率,哪怕渲染时看不出区别,掉电速度也会让你不得不重视。
还有一点,AR场景要记得提供用户引导。很多人第一次扫的时候手忙脚乱,要么离得太近,要么对不准识别图。我通常会在页面中央放一个半透明的取景框,提示"将海报置于框内扫描",识别成功后播放一个轻微震动,然后把取景框淡出。这个小小的交互设计,比任何代码调试都更能提升用户对这一功能的接受度。
说实话,微信小程序里的AR能力发展到现在已经相当成熟了,VPS+glb这套链路最大程度降低了普通开发者的上手门槛,后台传图、前端调引用、模型挂锚点,三步走就能出一版可用的方案。识别精度和稳定性虽然还比不上专业的原生AR SDK,但胜在免安装、触达快,非常适合落地在品牌营销和互动传播场景里。如果后续有更多玩法,比如多模型切换、点击交互、模型动画触发,方向也完全可以沿着现在这套架构扩展。希望这篇笔记能帮你少走一些弯路。
本文还有配套的精品资源,点击获取