PAYATHON 2026

Unity WebXR 构建为何在 Quest Browser 中卡在三点加载界面?

数据阿明

结论

优先检查手动创建的 XRSession 是否接入了 Unity 的 WebXR 渲染流程。现有代码能够请求会话,但没有展示导出插件如何接管该会话并提交画面,这个衔接点需要验证。

仅凭三点加载界面,无法确定问题是由 Unity 6.3、Quest Browser 更新还是插件兼容性引起的。还需要查看 XREnabler.Enable 的实现、WebXR 导出插件及其版本,以及设备浏览器中的运行日志。

为什么请求成功后仍然没有画面?

原代码的关键流程是:

xr.requestSession("immersive-vr").then((session) => {
    xrSession = session;
    enableVR(true);
});

随后向 Unity 发送的只是一个整数:

unityInstance.SendMessage("XREnabler", "Enable", 1);

这段调用没有把 session 对象传给 Unity。需要继续检查 XREnabler.Enable 及相关 JavaScript 桥接代码,确认它们是否使用了同一个会话。

requestSession() 成功只表示会话已建立。以 XRWebGLLayer 路径为例,还需要配置渲染层,并在 XRSession.requestAnimationFrame() 驱动的帧回调中绘制。Unity 导出插件通常负责这些工作。普通网页的 requestAnimationFrame() 在沉浸式模式下可能无法继续运行,不能直接替代 XR 帧循环。WebXR 工作组说明

如果插件没有接管新会话,或者仍在使用另一条渲染循环,可能出现进入沉浸式环境后场景始终不显示的情况。这是根据现有代码提出的排查假设,根因尚未确认。

先用插件自带模板验证

最有价值的对照实验,是使用当前已安装插件版本附带的 WebGLTemplate、示例场景和进入 VR 按钮,重新导出到一个独立目录。

  1. 暂时移除自定义按钮里的 xr.requestSession(),让插件提供的入口负责进入 VR。
  2. 在同一台 Quest、同一个浏览器和相同部署条件下测试。
  3. 如果示例正常,再逐步恢复自定义模板和场景,观察哪一步开始失败。

如果使用的是 De-Panther 的 WebXR Export,其安装说明要求在 Project Settings > XR Plug-in Management > WebGL > Plug-in Providers 中启用 WebXR Export,并通过 Window > WebXR > Copy WebGLTemplates 复制模板。其他导出插件应按各自版本的说明配置。WebXR Export 安装说明

基于 Unity VR template 创建场景,本身不能证明 WebGL 构建中的 WebXR 插件已经初始化完成。

还要检查是否重复请求了会话:例如 HTML 先调用 requestSession(),而 XREnabler.Enable 又让插件创建会话。如果已有活动或待处理的沉浸式会话,再次请求会被拒绝,可能得到 InvalidStateError。WebXR Device API 规范

给现有入口补充诊断日志

原代码只捕获了 createUnityInstance() 的失败,没有捕获 isSessionSupported() 和 requestSession() 的失败。因此,“没有看到错误”还不足以排除会话初始化失败。

如果需要保留手动请求路径做诊断,可以暂时用下面的代码替换原来的 enterVRButton.onclick。这段代码用于观察现有流程,不会补齐 Unity 的 XR 渲染集成:

enterVRButton.onclick = async function () {
    if (!xr || xrSession) return;

    enterVRButton.disabled = true;

    try {
        const session = await xr.requestSession("immersive-vr");
        xrSession = session;

        console.log("XR session created", session);

        session.addEventListener("visibilitychange", () => {
            console.log("XR visibility", session.visibilityState);
        });

        session.addEventListener("end", () => {
            xrSession = null;
            enterVRButton.disabled = false;
            console.log("XR session ended");
            enableVR(false);
        });

        console.log("Calling XREnabler.Enable(1)");
        enableVR(true);

        setTimeout(() => {
            if (xrSession !== session) return;

            console.log("XR render state", {
                visibilityState: session.visibilityState,
                baseLayer: session.renderState.baseLayer,
                layers: session.renderState.layers
            });
        }, 1000);
    } catch (error) {
        console.error("XR startup failed", error.name, error.message, error);
    } finally {
        enterVRButton.disabled = xrSession !== null;
    }
};

同时给支持检测补上失败处理:

xr.isSessionSupported("immersive-vr")
    .then((supported) => {
        enterVRButton.disabled = !supported;
        console.log("immersive-vr supported", supported);
    })
    .catch((error) => {
        enterVRButton.disabled = true;
        console.error("XR support check failed", error);
    });

在设备浏览器的远程调试控制台中,按以下顺序检查:

  • 是否执行到 XR session created;如果没有,先处理请求失败的问题。
  • Unity 中的 XREnabler 对象是否存在、对应脚本是否挂载、Enable 方法是否实际执行。
  • 插件内部是否收到同一个会话,是否完成渲染层初始化并开始 XR 帧循环。
  • 进入 VR 后是否发生 WebGL context 丢失或 Unity 运行时异常。

baseLayer 为 null 需要结合插件实现判断;使用 WebXR Layers API 的实现可能通过 layers 配置渲染层。单次延时日志也不能证明每一帧都在正常提交,不要据此直接下结论。

修正 Polyfill 回退分支

原代码还存在一处独立问题:

webxrPolyfill = new WebXRPolyfill();
tempXR = webxrPolyfill;

如果使用的是 immersive-web 的 webxr-polyfill,构造器用于补充浏览器 API。初始化后应重新读取 navigator.xr 作为调用入口,而不是把 Polyfill 实例当成 XRSystem。webxr-polyfill 使用说明

function getXR() {
    if (!navigator.xr && typeof WebXRPolyfill !== "undefined") {
        webxrPolyfill = new WebXRPolyfill();
    }

    return navigator.xr || null;
}

如果设备上原本已有 navigator.xr,这个回退分支不会执行,因此不能直接解释当前故障。

如何区分代码回归和环境变化?

把以前能够运行的完整构建重新放到当前设备上测试,比只比较提交记录更有判断价值:

  • 旧构建仍能运行、新构建失败:重点比较插件版本、锁文件、模板、XR 配置和构建设置。
  • 旧构建也开始失败:继续检查浏览器或系统变化、部署资源和缓存,以及插件在当前环境下的表现。
  • 插件自带示例正常、自定义入口失败:重点检查 XREnabler.Enable 与会话管理流程。

部署时使用完整的一组构建产物,避免新旧 HTML、loader、framework、Wasm 和 data 文件混用;必要时清除站点缓存,并检查项目是否注册了 Service Worker。

目前没有足够依据要求降级 Unity 或修改某个浏览器开关。先确认会话由谁创建、谁负责配置渲染层和驱动每一帧,才能把问题定位到具体环节。

备注:内容仅供参考。