普通视图
玩转小程序AR-实战篇
《玩转小程序AR》系列教程
声明: 本文所载内容仅限于学习交流之目的。所有抓包内容、敏感网址及数据接口均已进行脱敏处理,严禁将其用于商业或非法用途。任何因此产生的后果,作者不承担任何责任。若涉及侵权,请及时联系作者以便立即删除。
逆向小程序
接着前文: 《玩转小程序AR-基础篇》,体验过原神官方AR小程序后,我也比较好奇他们实现AR Live2d 动画的原理。
![]()
出于技术学习的目的,在开源社区搜寻微信小程序反编译工具,发现 KillWxapkg 和 unveilr 暂时仍可使用
逆向 某神AR 小程序
首先我们需要找到某神AR微信小程序的本地小程序包地址
注意:本人电脑 Mac (Windows电脑同理)
- 进入电脑微信小程序目录
# Mac 目录地址
cd /Users/你的电脑用户名/Library/Containers/com.tencent.xinWeChat/Data/Documents/app_data/radium/Applet/packages
# Windows 目录地址
cd C:\Users\你的电脑用户名\AppData\Roaming\Tencent\xwechat\radium\Applet\packages
# 打开文件夹
open .
如果发现目录下有较多文件夹,建议先把所有的文件夹都删除,为后续定位要逆向的小程序做好准备
![]()
- 用电脑微信打开需要逆向的小程序
![]()
- 再次打开微信小程序目录
![]()
这时候,恭喜你已经定位到了小程序的AppID了!
- 反编译小程序
![]()
小程序文件夹下的__APP__.wxapkg就是编译后的小程序,现在我们需要使用工具反编译它了
- unveilr 工具
# 安装反编译工具
npm i unveilr -g
# 运行反编译命令
unveilr wx -i wxb2618d769d6f5143 "/Users/你的电脑用户名/Library/Containers/com.tencent.xinWeChat/Data/Documents/app_data/radium/Applet/packages/wxb2618d769d6f5143/3/__APP__.wxapkg" -f
- KillWxapkg 工具
./KillWxapkg -id="wxb2618d769d6f5143" -in="/Users/你的电脑用户名/Library/Containers/com.tencent.xinWeChat/Data/Documents/app_data/radium/Applet/packages/wxb2618d769d6f5143/3" -restore
于是在小程序文件夹下,就新生成了一个反编译后的源码文件夹
![]()
当然,反编译后的源码也不是100%还原:
- 缺失
wxml -
js代码被babel转码后,语义不是特别清晰
![]()
![]()
当然,在AI的加持下,如今这些问题已经完全难不倒我们了。AI分分钟就能根据混淆过的js原始逻辑,还原清晰可读的语义化代码
![]()
从源码中可以看到,原神AR小程序使用的正是XR-FRAME框架
逆向 某cube 小程序插件
官方提供的kivicube插件
{
"usingComponents": {
"kivicube-scene": "plugin://kivicube/kivicube-scene"
},
"disableScroll": true,
"navigationStyle": "custom"
}
<kivicube-scene
wx:if="{{showAR}}"
class="kivicube"
scene-id="{{sceneId}}"
bind:ready="ready"
bind:error="error"
bind:downloadAssetStart="downloadStart"
bind:downloadAssetProgress="downloadProgress"
bind:downloadAssetEnd="downloadEnd"
bind:loadSceneStart="loadStart"
bind:loadSceneEnd="loadEnd"
bind:sceneStart="sceneStart"
bind:openUrl="openUrl"
bind:photo="photo"
/>
使用同样的方法,我们可以得到小程序插件逆向的源码
![]()
通过源码分析可得知,Kivicube 使用的是底层VisionKit + 自研的Threejs封装
![]()
着色器
Shader,中文称为“着色器”,是一种在图形处理单元(GPU)上运行的计算机程序,用于定义和控制图形渲染过程中的各种视觉效果
使用 GLSL 的着色器(shader),GLSL 是一门特殊的有着类似于 C 语言的语法,在图形管道 (graphic pipeline) 中直接可执行的 OpenGL 着色语言。
更多详情见MDN上的解释:GLSL Shaders
XR-FRME 提供了自定义效果的能力: 定制一个效果
序列帧 SHADER
XR-FRAME 官方的《序列帧动画(雪碧图、GIF)》示例,实现了一个简单的可配置的序列帧效果
而原神小程序AR的源码中,正是使用了这个序列帧效果实现了伪Live2d
首先我们需要将序列帧动画合成到一张M行*N列大小的PNG图片上(注意:微信小程序最大能渲染8000x8000左右分辨率的序列帧图片)
![]()
如上图所示,我们这次的图片是8行x4列,共32张序列帧图片合成而来
源码示例: 序列帧 SHADER
<xr-scene bind:ready="handleReady">
<xr-assets></xr-assets>
<xr-node>
<xr-node node-id="center" />
<xr-mesh visible="{{meshesVisible}}" id="animation-mesh" node-id="animation-mesh" position="0 0 0" scale="1 1 1.3" rotation="90 0 0" geometry="plane" />
</xr-node>
<xr-camera target="center" clear-color="0.4 0.8 0.6 1" position="0 0 2.5" camera-orbit-control />
</xr-scene>
Component({
/**
* 组件的初始数据
*/
data: {
meshesVisible: false
},
/**
* 组件的方法列表
*/
methods: {
handleReady: function ({ detail }) {
const xrFrameSystem = wx.getXrFrameSystem()
const createFrameEffect = (scene) => {
return scene.createEffect({
name: 'frame-effect',
properties: [
{
key: 'columCount', // 列数
type: xrFrameSystem.EUniformType.FLOAT,
default: 1
},
{
key: 'rowCount', // 行数
type: xrFrameSystem.EUniformType.FLOAT,
default: 1
},
{
key: 'during', // 持续时间
type: xrFrameSystem.EUniformType.FLOAT,
default: 1
}
],
images: [
{
key: 'u_baseColorMap',
default: 'white',
macro: 'WX_USE_BASECOLORMAP'
}
],
// 透明物体需要大于`2500`!
defaultRenderQueue: 2501,
passes: [
{
renderStates: {
blendOn: false,
depthWrite: true,
cullOn: false,
// 基础库 v3.0.1 开始 默认的 plane 切为适配 cw 的顶点绕序
},
lightMode: 'ForwardBase',
useMaterialRenderStates: true,
shaders: [0, 1]
}
],
shaders: [
// 顶点着色器 Vertex shaders
`#version 100
precision highp float;
precision highp int;
attribute vec3 a_position;
attribute highp vec2 a_texCoord;
uniform mat4 u_view;
uniform mat4 u_projection;
uniform mat4 u_world;
varying highp vec2 v_uv;
void main()
{
v_uv = a_texCoord;
gl_Position = u_projection * u_view * u_world * vec4(a_position, 1.0);
}`,
// 片段着色器 Fragment shaders
`#version 100
precision highp float;
precision highp int;
uniform sampler2D u_baseColorMap;
uniform highp float u_gameTime;
uniform highp float rowCount;
uniform highp float columCount;
uniform highp float during;
varying highp vec2 v_uv;
void main()
{
float loopTime = mod(u_gameTime, during);
float tickPerFrame = during / (columCount * rowCount);
float columTick = mod(floor(loopTime / tickPerFrame), columCount);
float rowTick = floor(loopTime / tickPerFrame / columCount);
vec2 texCoord = vec2(v_uv.x / columCount + (1.0 / columCount) * columTick , v_uv.y / rowCount + (1.0 / rowCount) * rowTick);
vec4 color = texture2D(u_baseColorMap, texCoord);
gl_FragColor = color;
}`
],
});
}
xrFrameSystem.registerEffect('frame-effect', createFrameEffect)
this.scene = detail.value
this.loadAsset()
},
async loadAsset() {
const xrFrameSystem = wx.getXrFrameSystem();
const xrScene = this.scene;
await xrScene.assets.loadAsset({
type: 'texture',
assetId: 'lzy',
src: 'https://assets.xxxx.com/resources/cdn/20251022/0ac5e7c80c0fc262.png',
})
// 第一个参数是效果实例的引用,第二个参数是默认`uniforms`
const frameMaterial = xrScene.createMaterial(
// 使用定制的效果
xrScene.assets.getAsset('effect', 'frame-effect'),
{ u_baseColorMap: xrScene.assets.getAsset('texture', 'lzy') }
)
// 可以将其添加到资源系统中备用
xrScene.assets.addAsset('material', 'frame-effect', frameMaterial)
const meshElement = xrScene.getElementById('animation-mesh').getComponent(xrFrameSystem.Mesh)
frameMaterial.setFloat('columCount', 4)
frameMaterial.setFloat('rowCount', 8)
frameMaterial.setFloat('during', 1)
frameMaterial.alphaMode = "BLEND"
meshElement.material = frameMaterial
this.setData({
meshesVisible: true
})
},
}
})
![]()
透明视频 SHADER
一般的透明视频:
- 自带透明通道的视频格式: mov (小程序默认不支持
mov格式播放) - 特殊处理后的左右分屏视频格式: mp4 (小程序默认支持
mp4格式播放)
- 左边是视频的 RGB
- 右边是视频的 Alpha
- 左右叠加即可渲染透明视
更多详情见前文《更高效的web动效解决方案 - 背景视频》
![]()
XR-FRAME 官方的《过滤黑色背景视频》示例,正好演示了左右分屏视频的过滤黑色背景能力
源码示例: 透明视频 SHADER
<xr-scene bind:ready="handleReady">
<xr-assets bind:progress="handleAssetsProgress" bind:loaded="handleAssetsLoaded">
<xr-asset-load type="video-texture" asset-id="lzy" src="https://assets.xxxx.com/resources/cdn/20251022/bd7cb6ba6546d697.mp4" options="autoPlay:true,loop:true" />
<xr-asset-material asset-id="removeBlack-mat" effect="removeBlack" />
</xr-assets>
<xr-node>
<xr-node node-id="center" />
<xr-node wx:if="{{loaded}}">
<xr-mesh node-id="video-item" position="0 0 0" rotation="90 0 0" scale="1 1 1.3" geometry="plane" material="removeBlack-mat" uniforms="u_videoMap: video-lzy" />
</xr-node>
</xr-node>
<xr-camera target="center" clear-color="0.4 0.8 0.6 1" position="0 0 3" camera-orbit-control />
</xr-scene>
const xrFrameSystem = wx.getXrFrameSystem();
xrFrameSystem.registerEffect('removeBlack', scene => scene.createEffect({
name: "removeBlack",
images: [{
key: 'u_videoMap',
default: 'white',
macro: 'WX_USE_VIDEOMAP'
}],
defaultRenderQueue: 2000,
passes: [{
"renderStates": {
cullOn: false,
blendOn: true,
blendSrc: xrFrameSystem.EBlendFactor.SRC_ALPHA,
blendDst: xrFrameSystem.EBlendFactor.ONE_MINUS_SRC_ALPHA,
cullFace: xrFrameSystem.ECullMode.BACK,
},
lightMode: "ForwardBase",
useMaterialRenderStates: true,
shaders: [0, 1]
}],
shaders: [
// 顶点着色器 Vertex shaders
`#version 100
uniform highp mat4 u_view;
uniform highp mat4 u_viewInverse;
uniform highp mat4 u_vp;
uniform highp mat4 u_projection;
uniform highp mat4 u_world;
attribute vec3 a_position;
attribute highp vec2 a_texCoord;
varying highp vec2 v_UV;
void main()
{
v_UV = a_texCoord;
vec4 worldPosition = u_world * vec4(a_position, 1.0);
gl_Position = u_projection * u_view * worldPosition;
}`,
// 片段着色器 Fragment shaders
`#version 100
precision mediump float;
precision highp int;
varying highp vec2 v_UV;
#ifdef WX_USE_VIDEOMAP
uniform sampler2D u_videoMap;
#endif
void main()
{
#ifdef WX_USE_VIDEOMAP
// 左右分屏透明视频处理:
// 左半边 (0-0.5) 为彩色内容,右半边 (0.5-1.0) 为透明度遮罩
// 1. 采样左半边获取 RGB 颜色
vec2 colorUV = vec2(v_UV.x * 0.5, v_UV.y);
vec4 color = texture2D(u_videoMap, colorUV);
// 2. 采样右半边获取 Alpha 遮罩
vec2 alphaUV = vec2(v_UV.x * 0.5 + 0.5, v_UV.y);
vec4 alphaSample = texture2D(u_videoMap, alphaUV);
float alpha = alphaSample.r; // 使用红色通道作为透明度(灰度值)
// 3. 输出颜色 + 遮罩透明度(不做伽马校正,避免变暗)
gl_FragData[0] = vec4(color.rgb, alpha);
#else
gl_FragData[0] = vec4(1.0, 1.0, 1.0, 1.0);
#endif
}
`],
}));
Component({
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
handleReady({
detail
}) {
console.log('handleReady', detail.value)
},
handleAssetsProgress({ detail }) {
console.log('assets progress', detail.value)
},
handleAssetsLoaded({ detail }) {
console.log('assets loaded', detail.value)
this.setData({ loaded: true })
},
}
})
![]()
实战案例
![]()
源码示例: 序列帧 SHADER
同层渲染
目前XR-FRAME尚未支持和小程序的UI元素混写,但我们可以使用同层方案
<view>
<demo8
disable-scroll
id="main-frame"
width="{{renderWidth}}"
height="{{renderHeight}}"
style="width:{{width}}px;height:{{height}}px;top:{{top}}px;left:{{left}}px;"
bind:arTrackerSwitch="handleTrackerSwitch"
markerImg="{{markerImg}}"
/>
<view class="marker-tip-container" hidden="{{hiddenTip}}">
<view class="marker-img-container">
<image mode="aspectFit" class="marker-img" src="{{markerImg}}" />
</view>
<view class="marker-text-container">
<text class="marker-text">请对准识别图</text>
</view>
</view>
</view>
demo8是XR-FRAME组件,它和viewUI组件处在同一层,这既是所谓的同层渲染方案。而同层方案,就必然涉及到组件通信
XR-FRAME组件需要将AR的识别状态同步给父级,父级根据不同的AR状态,展示不同的UI界面。而这些通信方式,和传统的组件通信方式基本一致:父级传递函数和属性到子级,子级通过执行回调函数传递数据
框架维护
比较尴尬的是,核心技术负责人已离开团队,XR-FRAME框架处于暂停维护状态
![]()
资料
- 演示源码:github.com/deepred5/xr…
- XR-FRAME 文档: developers.weixin.qq.com/miniprogram…
- VKSession 文档:developers.weixin.qq.com/miniprogram…
玩转小程序AR-基础篇
背景
某天需求方扔了个链接:【原神官方】AR 立牌,询问小程序是否能够实现类似的AR效果。
作为打工的人的我,于是被迫补起:早已过了风口(bushi)的AR知识。
调研
实体 AR 立牌
斥巨资在闲鱼淘了个多莉的角色立牌,实际体验了下完整的交互流程,AR的氛围感还是不错的。
![]()
AR在线制作平台
KIVICUBE 提供了可视化的3D场景搭建和动画编排系统:零基础也可创造和发布酷炫的AR作品。
![]()
![]()
官方同时提供了大量创意模版供大家一键使用
![]()
技术方案
在微信小程序的技术体系下,官方提供了两种解决方案:
小程序也在基础库 2.20.0 版本开始提供了开发 AR 功能的能力,即 VisionKit。VisionKit 包含了 AR 在内的视觉算法
xr-frame是一套小程序官方提供的XR/3D应用解决方案,基于混合方案实现,性能逼近原生、效果好、易用、强扩展、渐进式、遵循小程序开发标准
简单来说:
- VisionKit 是底层视觉引擎,提供能力但需要自己拼装。
- XR-FRAME 是完整开发框架,整合了渲染、交互与视觉能力,适合快速实现产品化的 AR 效果。
虽然理论上我们完全可以基于 VisionKit 并结合 Three.js 或自研的 WebGL 渲染逻辑来实现 AR 效果,但这种方案开发成本较高:
开发者需要自行处理相机帧与渲染管线的衔接、追踪状态管理、姿态矩阵更新等底层逻辑,整体工程量大且维护复杂。(Kivicube 正是使用此方案,确保自研的 AR 可视化搭建平台的产物能兼容各大平台:微信小程序、原生网页H5)
而 XR-FRAME 在此基础上进一步封装了完整的 3D 渲染与交互体系,提供了大量开箱即用的能力,让开发者可以像写前端组件一样,以声明式方式快速构建 AR 场景,而不必深入到底层渲染细节。
因此最后决定:先用 XR-FRAME 快速交付第一版 MVP 小程序
XR-FRAME 指南
由于是小程序开发,所以学习 XR-FRAME 的前提,需要有小程序基础知识
而官方的两个在线文档,内容还是略显单薄,强烈建议直接参考官方 《xr-frame系统的示例集》 的源码进行实战学习
本教程配套代码:xr-frame-tutorial
XR 组件 模版代码
创建 XR 组件
![]()
{
"component": true,
"usingComponents": {},
"renderer": "xr-frame"
}
<xr-scene>
<xr-camera clear-color="0.4 0.8 0.6 1" />
</xr-scene>
使用 XR 组件
![]()
{
"usingComponents": {
"demo1": "../../components/demo1"
},
"disableScroll": true,
"renderer": "webview"
}
<view>
<demo1
disable-scroll
id="main-frame"
width="{{renderWidth}}"
height="{{renderHeight}}"
style="width:{{width}}px;height:{{height}}px;top:{{top}}px;left:{{left}}px;"
/>
</view>
Page({
data: {
left: 0,
top: 0,
width: 0,
height: 0,
renderWidth: 0,
renderHeight: 0,
},
onLoad(options) {
const info = wx.getSystemInfoSync()
const width = info.windowWidth
const height = info.windowHeight
const dpi = info.pixelRatio
this.setData({
width,
height,
renderWidth: width * dpi,
renderHeight: height * dpi
});
},
})
渲染 XR 组件
![]()
渲染效果:绿色背景的画布 Canvas
![]()
3D 渲染
源码示例: 3D 渲染
最基础的 3D 渲染 思维导图
![]()
添加物体
添加一个 立方体 (Cube)物体
<xr-scene>
<xr-mesh node-id="cube" geometry="cube" />
<!-- 设置 target 指向 cube,让相机始终看向这个立方体-->
<xr-camera clear-color="0.4 0.8 0.6 1" position="1 3 4" target="cube" camera-orbit-control />
</xr-scene>
物体黑色是因为在我们没有给xr-mesh指定材质时,用的是基于PBR效果的默认材质,需要光照
临时解决方案: 创建一个不需要光照的材质,并且使用这个材质
<xr-scene>
<xr-asset-material asset-id="simple" effect="simple" uniforms="u_baseColorFactor:0.8 0.4 0.4 1" />
<xr-mesh node-id="cube" geometry="cube" material="simple" />
<xr-camera clear-color="0.4 0.8 0.6 1" position="1 3 4" target="cube" camera-orbit-control />
</xr-scene>
![]()
添加光照
<xr-scene>
<xr-mesh node-id="cube" geometry="cube" uniforms="u_baseColorFactor:0.8 0.4 0.4 1" />
<!-- 一个环境光 -->
<xr-light type="ambient" color="1 1 1" intensity="1" />
<!-- 一个主平行光 -->
<xr-light type="directional" rotation="40 70 0" color="1 1 1" intensity="3" cast-shadow />
<xr-camera clear-color="0.4 0.8 0.6 1" position="1 3 4" target="cube" camera-orbit-control />
</xr-scene>
![]()
添加纹理
纹理 Texture 是 GPU 中的图像,供着色器采样使用。在框架中其一般被作为材质的一部分uniforms使用
<xr-scene>
<xr-assets bind:progress="handleAssetsProgress" bind:loaded="handleAssetsLoaded">
<!-- 加载纹理 -->
<xr-asset-load type="texture" asset-id="waifu" src="https://mmbizwxaminiprogram-1258344707.cos.ap-guangzhou.myqcloud.com/xr-frame/demo/waifu.png" />
<!-- 材质中使用纹理 -->
<xr-asset-material asset-id="mat" uniforms="u_baseColorMap: waifu" />
</xr-assets>
<!-- 使用材质 -->
<xr-mesh node-id="cube" geometry="cube" material="mat" cast-shadow />
<!-- 一个环境光 -->
<xr-light type="ambient" color="1 1 1" intensity="1" />
<!-- 一个主平行光 -->
<xr-light type="directional" rotation="40 70 0" color="1 1 1" intensity="3" cast-shadow />
<xr-camera clear-color="0.4 0.8 0.6 1" position="1 3 4" target="cube" camera-orbit-control />
</xr-scene>
![]()
我们继续新加一个 平面 (Plane) 物体
<xr-assets bind:progress="handleAssetsProgress" bind:loaded="handleAssetsLoaded">
<xr-asset-load type="texture" asset-id="deepred" src="https://avatars.githubusercontent.com/u/13102815" />
<xr-asset-material asset-id="mat2" uniforms="u_baseColorMap: deepred" />
</xr-assets>
<xr-mesh node-id="plane" geometry="plane" material="mat2" position="0 1 0" />
![]()
添加动画
{
"setting": {
"ignoreDevUnusedFiles": false,
"ignoreUploadUnusedFiles": false
}
}
![]()
添加一个无限旋转的动画
- keyframe 中定义了帧动画具体的行为,本质上类似于 CSS 中的 keyframes
- animation 中定义了帧动画的播放配置,本质上类似于 CSS 中的 animation
注意:旋转的值是弧度!!!
360度等于2π弧度,约等于 2 * 3.14
{
"keyframe": {
"logo": {
"0": {
"rotation": [0, 0, 0]
},
"100": {
// 注意是弧度,360度等于2π,约等于6.28
"rotation": [6.28, 0, 0]
}
}
},
"animation": {
"logo": {
"keyframe": "logo",
"duration": 4,
"ease": "linear",
"loop": -1
}
}
}
<!-- 加载动画资源 -->
<xr-asset-load asset-id="anim" type="keyframe" src="/assets/animation.json"/>
<xr-mesh node-id="plane" geometry="plane" material="mat2" position="0 1 0" anim-keyframe="anim" anim-autoplay="clip:logo" />
![]()
添加 3D 模型
支持 GLTF 格式模型
<!-- 加载 GLTF 模型 -->
<xr-asset-load type="gltf" asset-id="miku" src="https://mmbizwxaminiprogram-1258344707.cos.ap-guangzhou.myqcloud.com/xr-frame/demo/miku.glb" />
<!-- 渲染 GLTF 组件 -->
<xr-gltf model="miku" position="-0.15 0.75 1" scale="0.07 0.07 0.07" rotation="0 180 0" anim-autoplay />
![]()
AR 系统
AR 能力,需要调用摄像头权限
微信开发者工具无法模拟,请使用真机预览模式
![]()
Plane 识别
![]()
源码示例: Plane 模式
Plane 模式是 AR 中的平面检测与追踪功能
常见的交互流程:
- 扫描阶段
- 用户移动手机摄像头
- AR 系统分析画面,识别水平或垂直的平面
- 常见平面:地板、桌面、墙壁、床面等
- 检测阶段
- 找到平面后,显示视觉指示器(视频中的蓝色圆环)
- 指示器跟随检测到的平面移动
- 放置阶段
- 用户点击屏幕
- 虚拟物体被"放置"到现实世界的平面上
<!-- AR system 系统开启 Plane 模式 -->
<xr-scene ar-system="modes:Plane" bind:ready="handleReady">
<xr-assets bind:loaded="handleAssetsLoaded">
<xr-asset-load type="gltf" asset-id="anchor" src="https://mmbizwxaminiprogram-1258344707.cos.ap-guangzhou.myqcloud.com/xr-frame/demo/ar-plane-marker.glb" />
<xr-asset-load type="gltf" asset-id="miku" src="https://mmbizwxaminiprogram-1258344707.cos.ap-guangzhou.myqcloud.com/xr-frame/demo/miku.glb" />
<xr-asset-load type="texture" asset-id="deepred" src="https://avatars.githubusercontent.com/u/13102815" />
<xr-asset-material asset-id="mat" uniforms="u_baseColorMap: deepred" />
</xr-assets>
<xr-light type="ambient" color="1 1 1" intensity="1" />
<xr-light type="directional" rotation="40 70 0" color="1 1 1" intensity="3" cast-shadow />
<!-- AR 追踪器 对应也要开启 Plane 模式 -->
<xr-ar-tracker mode="Plane">
<!-- 找到平面后,显示视觉指示器 -->
<xr-gltf model="anchor"></xr-gltf>
</xr-ar-tracker>
<!-- 首先隐藏虚拟物体 -->
<xr-node node-id="setitem" visible="false">
<xr-gltf model="miku" anim-autoplay scale="0.07 0.07 0.07" rotation="0 180 0" />
<xr-mesh node-id="plane" geometry="plane" material="mat" scale="0.3 0.3 0.3" />
</xr-node>
<!-- camera 的 background 设置成 ar -->
<xr-camera clear-color="0.4 0.8 0.6 1" background="ar" is-ar-camera />
</xr-scene>
Component({
methods: {
handleAssetsLoaded: function ({ detail }) {
wx.showToast({ title: '点击屏幕放置模型' });
this.scene.event.add('touchstart', () => {
// 虚拟物体被"放置"到现实世界的平面
this.scene.ar.placeHere('setitem', true);
});
},
handleReady: function ({ detail }) {
this.scene = detail.value;
},
}
})
Marker 识别
![]()
源码示例: Marker 模式
Marker 模式,能够识别预先设定的目标物体(定义为 Marker,包括2D平面物体和3D物体),进行视觉跟踪与定位,通过在目标物体周围渲染虚拟物体,从而实现AR功能。
模型与现实物体保持空间位置关系(即:3D 层级 效果)。
Marker 分类
- 2D Marker,仅适用于平面类物体,用户上传一张平面物体的俯视图像作为目标物体,算法运行时识别该平面物品,并渲染出相关虚拟物体。2D Marker可以理解为特殊的 3D Marker。
- 3D Marker,相比于2D Marker,能够识别3D物体,不局限于平面物体,具有更广的使用范围,算法运行前,需要手动制作3D Marker的识别目标文件(.map文件),然后算法运行时载入该文件用于识别
<xr-scene ar-system="modes:Marker" bind:ready="handleReady">
<xr-assets bind:loaded="handleAssetsLoaded">
<xr-asset-load type="video-texture" asset-id="hikari" options="loop:true" src="https://assets.xxxx.com/resources/cdn/20250925/1c85f5b5ecde29ea.mp4" />
<xr-asset-load type="texture" asset-id="deepred2" src="https://assets.xxxx.com/resources/cdn/20250925/c737cf37d083d543.png" />
<xr-asset-material asset-id="mat" effect="simple" uniforms="u_baseColorMap: video-hikari" />
<xr-asset-material asset-id="mat3" effect="simple" uniforms="u_baseColorMap: deepred2" />
</xr-assets>
<xr-node wx:if="{{loaded}}">
<xr-ar-tracker mode="Marker" bind:ar-tracker-switch="handleTrackerSwitch" src="https://assets.xxxx.com/resources/cdn/20250925/fe8dad0e9800ef81.jpg">
<xr-mesh node-id="mesh-plane" geometry="plane" material="mat" scale="1.7778 1 1" position="0 0.1 0" />
<xr-mesh node-id="plane" geometry="plane" material="mat3" position="0.1 0.8 0.1" scale="0.12 0.12 0.12" />
</xr-ar-tracker>
</xr-node>
<xr-camera clear-color="0.4 0.8 0.6 1" background="ar" is-ar-camera />
</xr-scene>
Component({
methods: {
handleReady: function ({ detail }) {
this.scene = detail.value;
},
handleAssetsLoaded: function ({ detail }) {
this.setData({ loaded: true });
},
handleTrackerSwitch: function ({ detail }) {
// 获取追踪状态
const active = detail.value;
const video = this.scene.assets.getAsset('video-texture', 'hikari');
// 识别到物体,播放视频
// 识别中,暂停视频
active ? video.play() : video.stop();
}
}
})
OSD 识别
源码示例: OSD 模式
![]()
OSD(One-shot Detection)Marker 识别模式,可以看成是一种特殊的 Marker 模式
:在摄像头画面上“贴图叠加”的模式,主要以 屏幕坐标系(2D) 为主,不真正感知空间深度
<xr-scene ar-system="modes:OSD" bind:ready="handleReady">
<xr-assets bind:loaded="handleAssetsLoaded">
<xr-asset-material asset-id="mat" effect="simple" states="alphaMode:BLEND" />
<xr-asset-material asset-id="text-simple" effect="simple" />
</xr-assets>
<xr-node>
<xr-ar-tracker mode="OSD" src="https://assets.xxxx.com/resources/cdn/20250925/fe8dad0e9800ef81.jpg">
<xr-node rotation="0 180 0">
<xr-mesh node-id="text-wrap" geometry="plane" material="mat" position="0 1 0" scale="1 1 0.5" rotation="90 0 0" uniforms="u_baseColorFactor:0.875 0.616 0.624 1" />
<xr-text node-id="text-name" position="-0.25 1.05 0" scale="0.1 0.1 0.1" material="text-simple" value="美食家蜜蜂" />
</xr-node>
</xr-ar-tracker>
</xr-node>
<xr-camera clear-color="0.4 0.8 0.6 1" background="ar" is-ar-camera />
</xr-scene>
人脸识别
源码示例: 人脸识别
<!-- AR system 系统开启 Face 模式 同时开启前置摄像头 -->
<xr-scene ar-system="modes:Face;camera:Front">
<xr-assets>
<xr-asset-load type="gltf" asset-id="mask" src="https://mmbizwxaminiprogram-1258344707.cos.ap-guangzhou.myqcloud.com/xr-frame/demo/jokers_mask_persona5.glb" />
</xr-assets>
<!-- AR 追踪器 对应也要开启 Face 模式 -->
<!-- auto-sync 是一个数字数组,用于将对应顺序的子节点绑定到某个特征点上 -->
<xr-ar-tracker mode="Face" auto-sync="43">
<!-- 包含识别成功后需要展示的场景 -->
<xr-gltf model="mask" rotation="0 180 0" scale="0.5 0.5 0.5" />
</xr-ar-tracker>
<xr-light type="ambient" color="1 1 1" intensity="1" />
<xr-light type="directional" rotation="40 70 0" color="1 1 1" intensity="3" />
<!-- AR 相机 -->
<xr-camera background="ar" is-ar-camera />
</xr-scene>
除了人脸识别(Face),还有 人体识别(Body),人手识别(Hand),可以参考官方文档学习
总结
到这里,我们已经把 XR-FRAME 的基础能力都过了一遍:从创建场景、添加物体、设置光照,到加载模型和动画,再到四种 AR 识别模式(Plane、Marker、OSD、Face)。理论上,你已经可以做出一个简单的 AR 效果了。
但理论和实际总是有差距的。在下一篇实战篇中,我会分享更高阶的知识点和实际项目中的经验:
- Shader 着色器:实现自定义视觉效果,让 AR 场景更炫酷
- 同层渲染:XR 组件和小程序原生组件互相通信,实现复杂的 UI 交互
- 企业级方案:性能优化、状态管理、多设备兼容等实战经验