使用 Mega 插件实现遮挡
遮挡 (Occlusion) 是提升 AR 虚实融合沉浸感的关键技术。本文将指导您如何在 xr-frame 环境下,通过 EasyAR 云定位与标注实现遮挡效果。
开始之前
遮挡的实现方式
离线建模:利用 Unity 编辑器在 Block 坐标系下,针对现实世界中的实体(如墙体、立柱、大型设备)创建 1:1 匹配的几何体;或通过对 Block 稠密模型进行裁剪与减面处理得到优化后的模型。
运行时对齐:在 xr-frame 运行时,通过云定位将 Block 坐标系与现实空间对齐,并加载对应的几何体。
材质替换:为这些几何体赋予特殊的遮挡材质。
视觉效果:当 GPU 渲染其他虚拟物体时,会因深度测试未通过而自动剔除被遮挡部分的像素,从而使虚拟物体遵循现实物理空间的遮挡逻辑。
如何布置简单几何体的遮挡
对照稠密模型及全景图精确摆放方块标注。摆放后标注看起来就像是一面“墙”或者“柱子”。

修改标注的名称(如
occlusion_wall),记录 ID ,上传标注。在 xr-frame 小程序中利用其内置几何体加载作为遮挡的标注。
在 EMA 加载的回调中使用
scene.createElement(xrFrameSystem.XRMesh,{})创建简单的几何体赋予easyar-occulusion材质。注意
easyar-occulusion材质的加载,注册,反注册,卸载由 AR Session 控制。
```ts
handleEmaResult(ema: easyar.ema.v0_5.Ema) {
let blockHolder: easyar.BlockHolder = session.blockHolder;
ema.blocks.forEach(emaBlock => {
const blockInfo: easyar.BlockInfo = {
id: emaBlock.id
};
// 若 Block 节点不存在,创建 Block 节点
blockHolder.holdBlock(blockInfo, easyarPlugin.toXRFrame(emaBlock.transform));
});
ema.annotations.forEach(annotation => {
if (annotation.type != mega.EmaV05AnnotationType.Node) {
return;
}
const nodeAnnotation = annotation as easyar.ema.v0_5.Node;
const xrNode: xrfs.XRNode = easyarPlugin.createXRNodeFromNodeAnnotation(nodeAnnotation, blockHolder);
const emaName: string = nodeAnnotation.name;
const geometryStr: string = nodeAnnotation.geometry === "cube" ? "cube" : "sphere";
const assetInfo = AnnotationMetaData[nodeAnnotation.id as keyof typeof AnnotationMetaData];
let model: xrfs.Element;
if (assetInfo) {
// GLTF部分
} else {
model = scene.createElement(
xrFrameSystem.XRMesh,
{
// 使用插件注册好的遮挡材质
material: "easyar-occlusion",
// 使用 xr-frame 内置几何体,此处也可以直接使用 "cube"
geometry: geometryStr,
name: emaName,
"receive-shadow": "false",
"cast-shadow": "false"
// 注意不要修改 Scale
}
);
xrNode.addChild(model);
}
})
}
```
有了遮挡后,这个熊猫就可以躲在墙后面跳舞了。
如何布置复杂几何体的遮挡
适用于异形设备、不规则建筑等需要高精度遮挡的场景。您可以使用建图结果中的遮罩文件,或通过 Unity 中的 Mega Studio 导出 Block 稠密模型,裁剪并减面得到用于遮挡的白模。
使用建图结果中的遮罩文件
通过云定位库的 遮罩文件下载 入口下载建图结果中的 glb 格式遮罩文件进行裁剪。裁剪后的模型在 xr-frame 中加载时不需要绕 Y 轴旋转。
注意
此下载方式仅适用于建图版本 9.6 及以上的建图结果。
将 Block 添加到云定位库后,记录对应的 Block ID,在该 Block 所在行的 操作 列中点击 遮罩文件下载。

在弹窗中选择 低精度,点击对应的 下载 按钮,下载
glb格式的遮罩文件。
在数字内容创建软件(例如 Blender)中打开下载的模型。

裁剪模型,只保留需要用于遮挡的部分,保存为
glb格式。处理时保持模型原有的位置、旋转、缩放和坐标系不变,以便在运行时与对应的 Block 对齐。如果同一个 Block 的遮罩文件包含多个 GLB 文件,可以在数字内容创建软件中进行合并,也可以分开裁剪后导出。运行时将它们挂载到同一个 Block 节点下。

准备裁剪后的
glb文件。可以将文件上传到小程序可访问的 HTTPS 托管服务器并获取 URL,也可以将文件放到小程序工程的miniprogram/assets/目录下,直接使用相对路径。使用托管服务器时,需要在小程序后台配置对应的下载合法域名,并将该域名添加到小程序白名单。在组件的资源配置
sampleAssets.occlusionMesh.src中填写模型地址。模型放在assets/目录时使用相对路径,单个 GLB 只需保留一项;如果下载的遮罩文件包含多块 GLB 地图,则为每个需要加载的分块添加一项,并设置唯一的assetId和对应的src。例如:const occlusionMeshAssets = [ //单个 glb 文件 { assetId: "occlusionMesh00", type: "gltf", src: "assets/Low_Poly_occlusion.glb", options: {} }, //多个 glb 文件 { assetId: "occlusionMesh01", type: "gltf", src: "assets/Low_Poly_occlusion_part1.glb", options: {} }, { assetId: "occlusionMesh02", type: "gltf", src: "assets/Low_Poly_occlusion_part2.glb", options: {} } ];文件名以实际下载或裁剪后保存的文件为准。
src也可以填写托管服务器的 HTTPS URL,例如https://your-domain.example/occlusion.glb。组件应实现模型加载、挂载、遮挡材质和资源释放逻辑。模型挂载到定位成功的 Block 节点下,并保持模型原有的位置、旋转和缩放。
在 xr-frame 小程序中加载作为遮挡的 GLTF。
模型加载完成且至少成功定位一次后,在第一个 Block 节点下创建公共
XRNode,再为每个已加载的分块创建XRGLTF,并将其网格材质替换为 Mega 插件提供的easyar-occlusion。下面是合并模型挂载与材质替换后的完整方法,可用于替换 sample 中的showOcclusionMesh():showOcclusionMesh() { if (!scene) { console.error("Empty scene"); return; } const blockHolder = session?.blockHolder; if (!blockHolder) { console.warn("Empty block holder. Session not initialized."); return; } if (occlusionMeshNode) { return; } const root = blockHolder.blocks[0]?.el; if (!root) { console.warn("Empty block: localization must succeed at least once before showing the occlusion mesh"); return; } const loadedMeshes = occlusionMeshAssets.filter(meshAsset => scene.assets.getAsset('gltf', meshAsset.assetId)); if (loadedMeshes.length === 0) { console.warn("Occlusion mesh asset is not loaded"); return; } const occlusionMaterial = scene.assets.getAsset("material", "easyar-occlusion"); if (!occlusionMaterial) { console.warn("Occlusion material is not loaded"); return; } const node = scene.createElement(xrFrameSystem.XRNode); root.addChild(node); for (const meshAsset of loadedMeshes) { const occlusionMesh = scene.createElement(xrFrameSystem.XRGLTF, { model: meshAsset.assetId }); node.addChild(occlusionMesh); // 挂载节点并完成 Transform 设置后,再替换遮挡材质。 // 建图结果中的遮罩文件保留原有坐标,无需绕 Y 轴旋转。 occlusionMesh.getComponent(xrFrameSystem.GLTF).meshes.forEach((mesh: any) => { mesh.setData({ neverCull: true, material: occlusionMaterial }); }); } node.getComponent(xrFrameSystem.Transform).visible = this.data.occlusionMeshVisible; occlusionMeshNode = node; },所有分块共享父节点
occlusionMeshNode,通过其Transform.visible统一控制显示与隐藏,无需分别设置空间位置。注意
easyar-occlusion材质的加载、注册、反注册和卸载由 AR Session 控制,应在 Session 启动后获取并使用。在对应 Block 的实景中运行小程序,等待资源加载完成且定位成功后,打开 遮罩文件 开关,检查虚拟物体是否被模型保留的区域正确遮挡。
使用 Unity 导出的稠密模型
通过 Unity 中的 Mega Studio 导出 Block 稠密模型,再进行裁剪和减面,得到用于遮挡的白模。
在 Unity 场景中点击 Mega Block 节点,在 Inspector 面板中记录 BlockID

在 Mega Studio 的 Block 中选择导出。

修改导出选项后导出。

图中 1 为 LOD 层级,层级越低模型越简单,面数越少,若需要最高的精度选择2,若能接受降低精度以减少面数选择 1 或者 0。
图中 2 为导出贴图选项,由于我们只需要白模作为遮挡,不需要贴图。
将导出后的模型在数字内容创建软件(例如 Blender)中进行裁剪,减面,保存为
Glb。提示
例子中使用的是 Blender 的 Decimate Modifier

裁剪并减面后:

将遮挡用的
Glb文件放到小程序工程的miniprogram/assets/目录并使用相对路径,或挂载到可访问的 HTTPS 文件服务器并使用 URL。使用服务器 URL 时,需要在小程序后台配置对应的下载合法域名,并将该域名添加到小程序白名单。在 xr-frame 小程序中加载作为遮挡的 GLTF。
首先加载遮挡用的 GLTF 模型,然后使用
scene.createElement(xrFrameSystem.XRGLTF,options)创建 GLTF 模型。使用
assets.getAsset("material", "easyar-occlusion")获取材质对象使用
model.getComponent(xrFrameSystem.GLTF).meshes.forEach((m: any) => {m.setData({ neverCull: true, material: occlusionMaterial });}修改 GLTF 模型的材质。注意
easyar-occulusion材质的加载,注册,反注册,卸载由 AR Session 控制。
```ts
const sampleAssets = {
occlusion1: {
assetId: "occlusion1",
type: "gltf",
src: "url/occlusion1.glb",
options: {}
}
}
async loadAsset() {
if (!scene) {console.error("Empty scene"); return;}
try {
await scene.assets.loadAsset(sampleAssets.occlusion1);
} catch (err) {
console.error(`Failed to load assets: ${err.message}`);
}
},
addOcclusion() {
model = scene.createElement(
xrFrameSystem.XRGLTF,
{
"model": assetInfo.assetId,
"anim-autoplay": assetInfo.animation ? assetInfo.animation : "",
"scale": assetInfo.scale ? assetInfo.scale : "1 1 1",
name: "tree"
}
);
const blockID = "aaaa1234-bbbb-cccc-dddd-eeeeee123456" //此处应填写 Block ID
if (!blockHolder.getBlockById(blockParent.id)) {
// 若没有存在的 Block 节点,则创建一个
blockHolder.holdBlock({
id: blockID
})
}
// 获取 xr-frame 场景中的 Block 节点
let blockElement = blockHolder.getBlockById(blockParent.id).el;
// 将裁剪后的遮挡模型挂载到 Block 节点下,作为其子节点
blockElement.addChild(model);
/**
* 由于 GLTF 加载器的行为不同,为了保证模型在 xr-frame 上的朝向 与 Unity 的渲染结果完全一致
* 有时需要对加载后的模型原地绕 Y 轴旋转 180 度
*/
let modelTransform = model.getComponent(xrFrameSystem.Transform);
let currentRotation = modelTransform.quaternion.clone();
let targetRotation = currentRotation.multiply(new xrFrameSystem.Quaternion().setValue(0, 1, 0, 0));
modelTransform.quaternion.set(targetRotation);
//注意必须在修改 Transform 后修改材质
if (assetInfo.assetId == 'occlusion1') {
//获取 mega 插件提供的遮挡材质
let occlusionMaterial = scene.assets.getAsset("material", "easyar-occlusion");
//修改遮挡材质
model.getComponent(xrFrameSystem.GLTF).meshes.forEach((m: any) => {
m.setData({ neverCull: true, material: occlusionMaterial });
});
}
}
```
注意
这里使用 Mega Block 稠密模型进行裁剪后作为遮挡不需要使用标注同步空间位置,这是因为在数字内容创建软件(如 Blender) 中,可以在不改变坐标系定义的情况下对模型进行减免和裁剪。
若需要精确摆放自己制作的 GLTF 模型遮挡,请参考如何摆放与空间对齐的遮挡模型
最终实机运行效果见文章顶部视频。
遮挡的效果预期
xr-frame 小程序上遮挡的效果主要由以下几点影响:
- 定位跟踪本身的精度
- 模型摆放的准确程度
- 模型本身的精度(如果不是简单的几何体)
在定位漂移时出现数公分未对齐的情况是正常的。
遮挡用的模型面数太多容易影响性能,建议只在必要区域使用,并且尽量使用简单的几何体作为遮挡。