说起 Three.js 在 WebXR 上的版本要求,有个问题经常被低估——很多人以为随便装个新版就行,结果在 Oculus Quest 2 上一跑就崩,查半天才发现是渲染循环没切换,或者控制器 API 根本不兼容。这些坑,本质上都指向同一个根结:Three.js 对 WebXR 的支持并不是线性累积的,而是一系列有断裂和重塑的迭代史。
r125:一条隐形的分水岭
社区里普遍把 r125 视为“稳定起点”,这个说法不是没根据的。在 r125 之前,renderer.xr 对象虽然存在,但内部对 session 生命周期的管理相当粗糙,尤其是从 inline 切换到 immersive-vr 时,资源释放经常出问题,导致帧率骤降甚至 context 丢失。r125 重写了 WebXRManager 的底层逻辑,最直观的变化是 setAnimationLoop 被强制接管——如果你还沿用旧例程里的 requestAnimationFrame,VR 模式下直接停摆。这一点在迁移老项目时几乎成了必踩的坑。
控制器模型的版本断层
手柄模型的加载机制是另一个敏感点。在 r128 之前,XRControllerModelFactory 返回的模型坐标系与手柄握持点高度不一致,需要手动做矩阵矫正;r128 开始,模型内部就预乘了 gripSpace 的逆矩阵,直接挂载到 controllerGrip 就能对齐。但这也导致一个兼容问题:如果你锁定在 r128 以上,原先写的矫正代码反而会让手柄偏移。更尴尬的是,一些基于 r112 的早期教程里,addEventListener('selectstart', ...) 的写法需要绑定在 controller 对象上,而 r135 之后,XRControllerModelFactory 内部事件分发逻辑调整,直接挂载可能失效,必须改用 renderer.xr.addEventListener 的 session 回调注册——版本混用的后果就是“有时能抓,有时不能”。
沉浸式 AR 的最低门槛
如果你把目标瞄准手机端的沉浸式 AR,Three.js 的版本底线直接拔到 r152。因为 WebXR 的 immersive-ar session 在这之前无法正确实现 hit-test(命中测试),导致虚拟物体像贴在屏幕上,根本无法落地。r152 补全了对 XRHitTestSource 的封装,才让基于现实平面放置物体的工作流跑通。这意味着,哪怕只是做个简单的 AR 摆件,用 r140 都无能为力,必须升级。
推荐锁定策略
目前生产环境里比较稳健的作法是锁定在 0.160.0 或 0.161.0。这两个版本不仅把 KTX2 纹理的 basis transcoder 集成得更顺滑,而且配合最新的 three/addons,对 Meta Quest 3 的 hand-tracking 也有原生支持。如果设备还覆盖老款 Oculus Go 这种只支持 WebVR 的常见“温饱型”头显,那只能无奈地把版本压在 r114 以下——然而 r114 的 WebVR 补丁早已无人维护,这条路基本等于主动往坟里钻。
版本这玩意儿,从来不是越新越好,而是越匹配你的硬件和设备矩阵越好。在动第一行代码之前,先对着上表把你头显的浏览器 WebXR 支持度扫一遍,比什么优化都管用。

r125那坑踩过,换setAnimationLoop折腾了一晚上
想问下Oculus Quest2现在用哪个版本稳?
控制器模型那个确实,升级r130后之前写的矫正全废了,气得我