Setting mesh.layers.set(1) does not work
It used to be the case that we can choose to which eye an object shows, by setting the layers.set(1) or 2.
This does not work in the most recent release anymore.
const mode = useXR(({ mode }) => mode);
// emulate the isPresenting flag of v5
const isPresenting = mode === 'immersive-ar' || mode === 'immersive-vr';
useLayoutEffect(() => {
if (!isPresenting) return;
if (typeof layers === 'number' && isPresenting) {
// console.log('setting layers.', layers);
planeRef.current.layers.set(layers);
}
}, [layers, isPresenting]);
setting the layers to 1, or 2 does not work anymore.
@geyang since pmndrs/xr does not do anything in regards to threejs layers, I'd assume this is a problem that also occurs in vanilla threejs. If you have a reason to suspect this is a pmndrs/xr bug, please let me know! :)
@geyang In case you haven't solved it, I also ran into this issue. It happens because WebXRManager.updateCamera gets called every frame, and the left and right camera's end up inheriting a layer mask of 6 (layers 1 and 2 enabled), instead of having different masks.
I worked around it by setting xr.cameraAutoUpdate to false, calling updateCamera manually on each frame via the useFrame callback, and then right after calling updateCamera, setting the camera masks manually.
@edhyah thanks for adding this here. Might be something that should be PRed in threejs. What do you think?
@edhyah thanks a lot! It looks like this issue is related: https://discourse.threejs.org/t/a-possible-bug-with-object3d-layers-in-webxr/77256
Thanks @bbohlender !
Hi @Mugen87 @mrxz, it looks like PR#29742 is causing this issue.
Maybe we need a PR into three.js?
since pmndrs/xr does not do anything in regards to threejs layers, I'd assume this is a problem that also occurs in vanilla threejs.
This issue does not happen in vanilla Three, see the webxr_vr_panorama and webxr_vr_video examples. Both of these make use of layers 1 and 2 for rendering different content for the left and right eyes.
It happens because WebXRManager.updateCamera gets called every frame, and the left and right camera's end up inheriting a layer mask of 6 (layers 1 and 2 enabled), instead of having different masks.
The left and right cameras should inherit the layer mask from the "user" camera with the addition of layer 1 for the left sub camera, and layer 2 for the right sub camera. So unless the camera used to render has both layers 1 and 2 enabled, the layer masks of the left and right cameras should differ.
I'm not familiar with the inner workings of @react-three/xr, but the following line seems suspicious:
https://github.com/pmndrs/xr/blob/38386f2a39f1761ae98412d4f6dc0183587ea2d8/packages/react/xr/src/xr.tsx#L88
Could it be possible that this camera is used in subsequent render(scene, camera) calls? If that's the case the "user" camera would end up being the XR camera, which would explain the issue.
@mrxz yes that is true, but why would that make a difference? If the layers of cameraXR are not changed somewhere, they should be the same then a new camera provided by the user, right?
@mrxz yes that is true, but why would that make a difference? If the layers of cameraXR are not changed somewhere, they should be the same then a new camera provided by the user, right?
@bbohlender The old behaviour in Three.js was to ignore any layer mask set on the camera passed to the render function when in an immersive session. Basically using fixed layer masks for the XR camera, the left camera and the right camera. So it didn't matter which camera was passed (when it comes to layer masks)
This however made using layers other than 0, 1 and 2 very annoying and broke the notion that the renderer.render(scene, camera) call could remain unchanged when entering and exiting an immersive session. From Three.js r170 onwards the layer mask of the passed camera is now respected.
But by passing the renderer.xr.getCamera() back into the render call, the scene camera from which the layer mask is copied and the destination camera (the XR camera) end up being the same, causing a feedback loop. Basically the XR camera gets a union of the left and right cameras (by default layers 0, 1, 2) for culling purposes.
Worth noting that even before this change you're not supposed to use the XR camera in this way, as for example the near and far plane are also inherited from the user/scene camera. For custom camera logic there's always to option of disabling renderer.xr.cameraAutoUpdate and handling it manually. (Though Three.js does not really make this particularly ergonomic)
@mrxz I see, thanks for the explanation. I need to think more about this. In general I don't like the idea of acting like you are rendering with the old camera, but under the hood its a different camera that is synced. Will still probably need to fix this by introducing a seperate fake xr camera :)
@bbohlender Not the biggest fan of it either, but it does make the default case very easy. Implicitly it also works with existing logic that expects a singular camera object that is kept up to date (e.g. for spatial audio, billboarding, raycasting in handheld AR)
But beyond that you'll end up distinguishing between XR and non-XR code paths for other parts of your app/experience anyway, in which case handling the camera explicitly would be nicer IMO. My personal pet peeve is that people use the camera's position and orientation as a means to know the headset's position and orientation, but since this is only updated at the start of render, any logic before it will be based on one-frame stale data.
Anyway, introducing a separate fake xr camera should be totally fine. Just be aware of things like AudioListeners needing to be updated.
@bbohlender @mrxz You two are the best! ❤
so now it seems that it is clear that this is an issue with this project instead of the upstream 3js, what are the next steps we can take to patch this?
@bbohlender bumping this up, shall we remove the line responsible?