Table of Contents

使用 Mega 插件实现遮挡

遮挡 (Occlusion) 是提升 AR 虚实融合沉浸感的关键技术。本文将指导您如何在 xr-frame 环境下,通过 EasyAR 云定位与标注实现遮挡效果。

开始之前

遮挡的实现方式

  • 离线建模:利用 Unity 编辑器在 Block 坐标系下,针对现实世界中的实体(如墙体、立柱、大型设备)创建 1:1 匹配的几何体;或通过对 Block 稠密模型进行裁剪与减面处理得到优化后的模型。

  • 运行时对齐:在 xr-frame 运行时,通过云定位将 Block 坐标系与现实空间对齐,并加载对应的几何体。

  • 材质替换:为这些几何体赋予特殊的遮挡材质。

  • 视觉效果:当 GPU 渲染其他虚拟物体时,会因深度测试未通过而自动剔除被遮挡部分的像素,从而使虚拟物体遵循现实物理空间的遮挡逻辑。

如何布置简单几何体的遮挡

  1. 对照稠密模型及全景图精确摆放方块标注。摆放后标注看起来就像是一面“墙”或者“柱子”。

    标注作为遮挡

  2. 修改标注的名称(如 occlusion_wall ),记录 ID ,上传标注。

  3. 在 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 及以上的建图结果。

  1. 将 Block 添加到云定位库后,记录对应的 Block ID,在该 Block 所在行的 操作 列中点击 遮罩文件下载。

    遮罩文件下载入口

  2. 在弹窗中选择 低精度,点击对应的 下载 按钮,下载 glb 格式的遮罩文件。

    下载遮罩文件

  3. 在数字内容创建软件(例如 Blender)中打开下载的模型。

    建图结果中的遮罩文件裁剪前

    裁剪模型,只保留需要用于遮挡的部分,保存为 glb 格式。处理时保持模型原有的位置、旋转、缩放和坐标系不变,以便在运行时与对应的 Block 对齐。

    建图结果中的遮罩文件裁剪后

  4. 准备裁剪后的 glb 文件。可以将文件上传到小程序可访问的 HTTPS 托管服务器并获取 URL,也可以将文件放到小程序工程的 miniprogram/assets/ 目录下,直接使用相对路径。使用托管服务器时,需要在小程序后台配置对应的下载合法域名,并将该域名添加到小程序白名单。

  5. 在组件的资源配置 sampleAssets.occlusionMesh.src 中填写模型地址。模型放在 assets/ 目录时使用相对路径,例如:

    src: "assets/occlusion_mesh_sd.glb",
    

    也可以填写托管服务器的 HTTPS URL,例如 https://your-domain.example/occlusion.glb。

    组件应实现模型加载、挂载、遮挡材质和资源释放逻辑。模型挂载到定位成功的 Block 节点下,并保持模型原有的位置、旋转和缩放。

    模型加载完成且定位成功后,在第一个 Block 节点下创建 XRNode,再挂载 XRGLTF 模型:

    showOcclusionMesh() {
        if (!scene) { console.error("Empty scene"); return; }
        const blockHolder = session?.blockHolder;
        if (!blockHolder) { console.warn("Session not initialized"); return; }
        if (occlusionMeshNode) { return; }
        const root = blockHolder.blocks[0]?.el;
        if (!root) { console.warn("Localization must succeed before showing the occlusion mesh"); return; }
        if (!scene.assets.getAsset("gltf", sampleAssets.occlusionMesh.assetId)) {
            console.warn("Occlusion mesh asset is not loaded");
            return;
        }
        const node = scene.createElement(xrFrameSystem.XRNode);
        root.addChild(node);
        const model = scene.createElement(xrFrameSystem.XRGLTF, {
            model: sampleAssets.occlusionMesh.assetId
        });
        node.addChild(model);
        node.getComponent(xrFrameSystem.Transform).visible = this.data.occlusionMeshVisible;
        occlusionMeshNode = node;
    },
    

    定位成功后,将模型挂载到 Block 节点下,无需手动设置空间位置。

    此方式直接显示下载的原始白模,保留模型默认的位置、旋转、缩放和可见材质。关于资源加载和节点挂载,详见 如何在 xr-frame 运行时加载 AR 场景下的 3D 内容。

  6. 在对应 Block 的实景中运行小程序,定位成功后显示遮罩文件,检查模型是否与实景贴合,以及虚拟物体是否被裁剪后保留的区域遮挡。

使用 Unity 导出的稠密模型

通过 Unity 中的 Mega Studio 导出 Block 稠密模型,再进行裁剪和减面,得到用于遮挡的白模。

  1. 在 Unity 场景中点击 Mega Block 节点,在 Inspector 面板中记录 BlockID

    记录BlockID

  2. 在 Mega Studio 的 Block 中选择导出。

    选择导出

  3. 修改导出选项后导出。

    导出选项

    图中 1 为 LOD 层级,层级越低模型越简单,面数越少,若需要最高的精度选择2,若能接受降低精度以减少面数选择 1 或者 0。

    图中 2 为导出贴图选项,由于我们只需要白模作为遮挡,不需要贴图。

  4. 将导出后的模型在数字内容创建软件(例如 Blender)中进行裁剪,减面,保存为 Glb。

    提示

    例子中使用的是 Blender 的 Decimate Modifier

    裁剪前

    裁剪并减面后:

    裁剪后

  5. 将遮挡用的 Glb 文件放到小程序工程的 miniprogram/assets/ 目录并使用相对路径,或挂载到可访问的 HTTPS 文件服务器并使用 URL。使用服务器 URL 时,需要在小程序后台配置对应的下载合法域名,并将该域名添加到小程序白名单。

  6. 在 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 小程序上遮挡的效果主要由以下几点影响:

  • 定位跟踪本身的精度
  • 模型摆放的准确程度
  • 模型本身的精度(如果不是简单的几何体)

在定位漂移时出现数公分未对齐的情况是正常的。

遮挡用的模型面数太多容易影响性能,建议只在必要区域使用,并且尽量使用简单的几何体作为遮挡。

后续步骤

相关主题