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:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user