fix(lab): AR Passthrough black screen — transparent canvas + immersive-ar detection + dynamic AR background

- gl.alpha:true + setClearColor(0,0) no WebGL renderer
- ARBackground detecta environmentBlendMode='additive' e vira transparente
- Tenta navigator.xr.requestSession('immersive-ar') antes do fallback immersive-vr
- local-floor required + hand-tracking/plane-detection/hit-test optional
This commit is contained in:
Hermes
2026-08-04 23:35:10 +00:00
parent 871b37de3b
commit f54efbf734
+64 -8
View File
@@ -316,10 +316,11 @@ const PhysicsScene = ({
// =================================================================== // ===================================================================
const SceneContent = ({ locked, resetSignal }: { locked: boolean; resetSignal: number }) => ( const SceneContent = ({ locked, resetSignal }: { locked: boolean; resetSignal: number }) => (
<> <>
<color attach="background" args={["#0a0a0a"]} /> {/* Em modo AR Passthrough, NÃO pinta cor de fundo — ela veda o passthrough */}
<fog attach="fog" args={["#0a0a0a", 5, 18]} /> {/* Em modo desktop (fora do XR), pinta fundo escuro pra não ficar transparente estranho */}
<PerspectiveCamera makeDefault position={[0, 1.5, 3]} fov={50} /> <ARBackground />
<ambientLight intensity={0.4} />
<ambientLight intensity={0.6} />
<directionalLight <directionalLight
position={[5, 5, 5]} position={[5, 5, 5]}
intensity={1.2} intensity={1.2}
@@ -327,7 +328,7 @@ const SceneContent = ({ locked, resetSignal }: { locked: boolean; resetSignal: n
shadow-mapSize={[1024, 1024]} shadow-mapSize={[1024, 1024]}
/> />
{/* Grid estilo HLA */} {/* Grid estilo HLA — funciona em ambos os modos */}
<Grid <Grid
position={[0, 0.001, 0]} position={[0, 0.001, 0]}
args={[20, 20]} args={[20, 20]}
@@ -359,6 +360,21 @@ const SceneContent = ({ locked, resetSignal }: { locked: boolean; resetSignal: n
</> </>
); );
// ===================================================================
// FUNDO DINÂMICO — VR imersivo fica preto; AR Passthrough fica transparente
// ===================================================================
const ARBackground = () => {
const session = useXR((s) => s.session);
const isAR = session && "environmentBlendMode" in session && (session as any).environmentBlendMode === "additive";
if (isAR) return null; // AR = transparente (mostra passthrough do Quest 3)
return (
<>
<color attach="background" args={["#0a0a0a"]} />
<fog attach="fog" args={["#0a0a0a", 5, 18]} />
</>
);
};
// =================================================================== // ===================================================================
// JOYSTICK HANDLER — gamepad pro controle fora do XR // JOYSTICK HANDLER — gamepad pro controle fora do XR
// =================================================================== // ===================================================================
@@ -410,7 +426,39 @@ export default function QuestCubeLab() {
useEffect(() => { useEffect(() => {
if (autoEnter) { if (autoEnter) {
store.enterXR?.().catch(() => {}); // Meta Quest 3 suporta immersive-ar (Passthrough AR). Tenta primeiro.
// Fallback pra immersive-vr se AR não disponível (óculos sem passthrough).
(async () => {
try {
if ('xr' in navigator) {
const xr = (navigator as any).xr;
const supported: boolean = await xr.isSessionSupported('immersive-ar').catch(() => false);
if (supported) {
const sessionInit: XRSessionInit = {
requiredFeatures: ['local-floor'],
optionalFeatures: ['hand-tracking', 'plane-detection', 'hit-test'],
};
await xr.requestSession('immersive-ar', sessionInit);
return;
}
}
// Fallback: usa o XR controller padrão do store
const ctrl: any = store as any;
const enterFn = ctrl.enterAR || ctrl.enterVR || ctrl.enterXR;
try {
await enterFn.call(ctrl, 'immersive-vr' as any);
} catch {
// ignore
}
} catch {
// qualquer erro, tenta o enter padrão
try {
await Promise.resolve(store.enterXR?.());
} catch {
// ignore
}
}
})();
} }
}, [autoEnter]); }, [autoEnter]);
@@ -473,9 +521,17 @@ export default function QuestCubeLab() {
Rapier Physics ativo Half-Life: Alyx-like Rapier Physics ativo Half-Life: Alyx-like
</div> </div>
{/* Canvas com XR */} {/* Canvas com XR — gl.alpha:true permite passthrough por trás */}
<XR store={store}> <XR store={store}>
<Canvas shadows> <Canvas
shadows
gl={{ alpha: true, antialias: true, powerPreference: "high-performance" }}
camera={{ position: [0, 1.5, 3], fov: 50 }}
onCreated={({ gl }) => {
gl.setClearColor(0x000000, 0); // transparente
gl.xr.enabled = true;
}}
>
<SceneContent locked={locked} resetSignal={resetSignal} /> <SceneContent locked={locked} resetSignal={resetSignal} />
</Canvas> </Canvas>
</XR> </XR>