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 按钮,重新导出到一个独立目录。
- 暂时移除自定义按钮里的
xr.requestSession(),让插件提供的入口负责进入 VR。 - 在同一台 Quest、同一个浏览器和相同部署条件下测试。
- 如果示例正常,再逐步恢复自定义模板和场景,观察哪一步开始失败。
如果使用的是 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 或修改某个浏览器开关。先确认会话由谁创建、谁负责配置渲染层和驱动每一帧,才能把问题定位到具体环节。
备注:内容仅供参考。
