背景

弊社ではXR事業の取り組みとして Buralit というプロダクトを開発しています。

現在、このプロダクトはPCやスマホなどの従来型のデバイスでの使用を前提としており、ヘッドマウントディスプレイなどの没入型のデバイスには対応していない状態です。

XR Kaigi 2024 への出展に際し、没入型デバイスで似たようなことができないかを試してみましたので、その際の気づきなどをフィードバックしたいと思います。

Buralitって何?

Buralitは、360度カメラで撮影した動画や静止画をベースとしたバーチャル空間を、アバターで自由に回遊できる観光メタバースサービスです。

ユーザーはアバターを使ってバーチャル空間内の日本地図上を移動制限なくシームレスに探索し、各地の観光スポットを見て回ったり、空間内でグループを作ってコミュニケーションをとりながら探索することができます。

どうやって対応したの?

Buralitは React Three Fiber(R3F) というライブラリを使って実装されています。

このライブラリのエコシステムには @react-three/xr というライブラリがあり、R3FアプリケーションをXRデバイス向けに対応させることができます。

今回はこのライブラリを使って、Buralitライクなデモ用アプリケーションを作成してみました。

こちら のサンプルアプリケーションをベースとしていますが、UIまわりを大幅に改修しています。

どんなものを作ったの?

もともとのBuralitでは3Dマップ上を自由に動き回れるのですが、今回のデモアプリでは観光スポット内に入ったところをVR表示化しました。

これはブラウザでの非VRモードでの表示ですが、Enter VRをクリックしてVRモードに切り替えると、以下のようになります。

作ってみてどうだった?

VR表示そのものは、 @react-three/xrのガイド にあるように、コンポーネントを<XR>で囲うことで実現できました。

ただ、やはりそのままとはいかず、いくつか調査・手直しが必要な部分が出てきました。

 

UI表示

HTML要素が表示できない

R3Fでは、@react-three/drei<Html>コンポーネントを使うことで、3D空間上にHTML要素を配置することができます。

ただし、@react-three/xrでのVRモードでの表示時には、HTML要素を画面内に表示できないという制約があります。 FAQ でも触れられていますが、VRモードでHTML要素を混在させるには、 @react-three/uikit に置き換える必要があります。

 

日本語フォントが使用できない

@react-three/uikitのデフォルトのフォントは日本語に対応していません。ドキュメントに カスタムフォントの適用手順 が掲載されていたため、その手順に従って日本語に対応したフォントを追加しました。

 

非VR表示時に回転させたくないものは<FullScreen>を使う

@react-three/uikitのコンポーネントは、基本的に <Root> で囲うこととされています。ただし、<Root>で囲ったものは3D空間の一部とされてしまうため、メニュー表示のような要素を<Root>配下にすると、カメラの操作によりUIの横や裏側が見えてしまいます。

たとえば、 前述の画面イメージ の左下には移動先を選ぶためのメニューがありますが、これを<Root>配下にすると、下のようにカメラ操作によって回転できてしまいます。

非VR表示の動きとしては、こうしたメニューはカメラによって回転させず、ウインドウにくっついていてほしいです。

これを実現するため、現在表示がVRか非VRかを判断して、非VR表示時には<FullScreen>で囲うように実装しています。

 

クリックイベントの取得方法

VR表示の状態で、コントローラからのレイ(光線)を、クリックしたいUIに重ねてコントローラのトリガーをクリックすると、onClickイベントが発火されます。これは、R3Fおよび@react-three/xrで自動的に判断されます。

今回のデモアプリでは、これに加えて

  • 右コントローラの A ボタンでスポットを選択する画面の開閉
  • 右コントローラの B ボタンでリアクションを選択する画面の開閉

をできるようにしていますが、このような動きをするには自前での実装が必要です。

コントローラのどのボタンが押されたかはuseXRControllerButtonEventで判断できます。たとえば、右手コントローラのAボタンが押されたときの動きを定義したい場合は、以下のようにします。

// コントローラの状態を監視
const rightHandState = useXRInputSourceState("controller", "right");

// あるボタンが押されたときに反応する処理
useXRControllerButtonEvent(rightHandState!, "a-button", (state) => {
  if (state === "pressed") {
// Aボタンを押したときの処理
  }
});

state === "pressed" についてですが、ボタンを押した後、しばらくすると useXRControllerButtonEventdefaultというイベントを再検知するようになっています。「押したイベントを検知したときだけ」動くように、 pressed を条件に入れています。

VR化してみて思うこと

実際にヘッドマウントディスプレイで見てみたところ、以下のように感じました。

  • ブラウザで見るのに比べると、やはり「その場にいる感じ」を強く感じるようになる
  • 一方で、被写体が近くにある画像(室内や近くに物があるケース)だと、奥行きがないことが目立ってしまい、やや体験を損なう印象
    • 現在Buralitで使っている画像はEquirectangular形式の1枚画像であり、奥行きが再現されていない状態

ヘッドマウントディスプレイでの体験を向上するには、画像を2枚にして視差による立体感を感じさせるなど、より3次元に最適化した調整が必要だと感じました。

まとめ

React Three Fiber@react-three/xrによるVRデモアプリ開発についてご紹介しました。

見慣れた画像でも、視界全体に広がると大きく印象が変わって全く新しい体験になると感じる一方、PCやスマホ上での操作に比べるとコントローラの操作にもどかしさを感じる部分もあり、一長一短だなと実感しました。

また機会があれば、没入型デバイスを使ったプロダクト開発を進めていければと思っています。

皆様の参考になりましたら幸いです。