みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Three.js + Reactで作ったサイトにImmersive Webのwebxr-button.jsを設置する 🔘

immersive-web.github.io


ことの発端

普段はThree.jsでWebVRコンテンツをつくっているので、Three.jsのVRButton.jsを使っています。

github.com

しかし、以前、gamepadにアクセスしようとした際、sessionが隠蔽されており、外部からスマートにアクセスする方法が見つからず、なかなか苦労した経験がありました。

blog.kimizuka.org

そんな折、Immersive Webのサンプルをみてみると、ものすごく簡単に実装されているようで、VRモードに切り替えるボタンのみ、Immersive Webのwebxr-button.jsに切り替えようと思った次第です。

github.com


実装

webxr-button.jsをTypeScriptに対応させるために型を定義しまくるのが非常に難儀だったので、JavaScriptのままjsxで実装しました。
ブラウザが WebXR Device API に対応している場合のみ、ボタンを表示するようにしています。

WebXrButton.jsx

import styled from 'styled-components';
import { useEffect, useRef, useState } from 'react';
import { WebXRButton } from '~/scripts/immersive/webxr-button';

const Wrapper = styled.span`
  position: relative;

  &[data-renderer='false'] {
    .webvr-ui-title {
      display: none !important;
    }

    .webvr-ui-svg {
      display: none;
    }
  }

  &[data-renderer='true'] {
    .webvr-ui-svg-error {
      display: none;
    }
  }

  button {
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    justify-content: center;
    width: 176px; height: 56px;
    background: #FAFAFA;
    cursor: pointer;
  }

  .webvr-ui-title {
    margin-left: 8px;
  }
`;

export default function WebXrButton({ renderer }) {
  const wrapperRef = useRef(null);
  const [ xrButton, setXrButton ] = useState(null);
  const [ supportedXr, setSupportedXr ] = useState(false);

  useEffect(() => {
    if (!!renderer) {
      setXrButton(new WebXRButton({
        color: '#212121',
        injectCSS: false,
        onRequestSession: onRequestSession,
        onEndSession: onEndSession
      }));

      if (navigator.xr) {
        (async () => {
          const supported = await navigator.xr.isSessionSupported('immersive-vr');

          setSupportedXr(supported);
        })();
      }
    }
  }, [renderer]);

  useEffect(() => {
    if (!xrButton) {
      return;
    }

    xrButton.enabled = supportedXr;

    if (xrButton.enabled && wrapperRef.current) {
      wrapperRef.current.innerHTML = '';
      wrapperRef.current.appendChild(xrButton.domElement);
    }
  }, [xrButton, supportedXr, wrapperRef]);
  
  function onRequestSession() {
    if (!renderer) {
      return;
    }

    const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking' ] };

    navigator.xr.requestSession('immersive-vr', sessionInit).then(onSessionStarted);
  }

  function onSessionStarted(session) {
    renderer.xr.setSession(session);
  }

  function onEndSession(session) {
    session.end();
  }

  return (
    <Wrapper
      ref={ wrapperRef }
      data-renderer={ !!renderer }
      className="web-xr-button"
    ></Wrapper>
  );
}

実装のポイントとしては、

Three.jsのrendererを、

renderer.xr.setSession(session);

と、xr.setSessionにsessionを渡してあげるだけでOKです。

ただし、Three.js側で、

renderer.xr.enabled = true;

と。XRを有効にしておく必要があります。


DEMO

r136

kimizuka.org

一応、Three.jsのr127とr136で検証しました。