【WebXR】WebXR Device APIを使ってMeta Questのブラウザで動く空間を作ってみよう!【PlayCanvas】

目次

はじめに

GMOグローバルサイン・ホールディングス所属の羽賀@mxcn3です。「前回の記事」を書いたあとから、フルボディトラッキングでVRのジムに通い続けていました。結果、半年間で8.3kg程体重を落とすことができました。

業務では、ゲームエンジンPlayCanvasのコミュニティマネージャーやテクニカルサポートをさせていただいております。JavaScriptを利用してブラウザ上でVRの体験を1から作成する手順を紹介します。

Meta Questのブラウザで動く空間を作ってみよう!

今回は、WebXR Device APIを利用してコントローラーによる視点移動のできる空間を作成します。VRのシーンの描画部分にはWebGLのライブラリ「PlayCanvas」を利用します。

WebXR Device APIについて

WebXR Device APIは対応しているブラウザにて利用する事ができるJavaScript APIです。このAPIを利用することでVRの他にも、ブラウザ上で動作するARやMRのコンテンツを作成する事ができます。

WebXR Device APIの各ブラウザの対応状況について

PlayCanvasについて

シーンの構築は、PlayCanvasを利用します。PlayCanvasは、デスクトップとモバイルブラウザ向けに作られたWebGL/HTML5ゲームエンジンです。豊富な機能を揃えた3Dエンジンとクラウドホスティングされた開発環境およびツールセットを備えています。PlayCanvas社は2017年よりSnap.Incに買収されましたが、現在も開発は継続されています

プロジェクトの作成

このような手順でシーンを作成します。

  1. 空のプロジェクトをVRに対応させる
  2. Meta Questのコントローラーに対応させる
  3. ジョイスティックでの視点移動を追加する

プロジェクトの作成はPlayCanvas エディターの開発環境を利用します。ソフトウェアのインストール等は必要ありませんが、アカウントが必要となります。無料で利用ができますので興味がある方はご登録をお願いいたします。

1. PlayCanvasエディタから新規作成

PlayCanvas」を利用してプロジェクトの新規作成をします。プロジェクト名は、お好きなプロジェクト名を入力してください。

2. 新規作成されたプロジェクト

新規作成されたプロジェクトはエディタ上で確認できます。作成した「Blank Project」では、このような空のシーンが構築されています。こちらのビジュアルエディタで開発を進めていきます。

3. 起動

現在のシーンはこのような状態です。PlayCanvasエディターから開発中のプロジェクトの起動ができます。初期状態で起動した結果がこちらとなります。初期シーンではこのようなキューブが1つ描画されております。

1. 空のプロジェクトをVRに対応させる

空の状態プロジェクト(シーン)をVRに対応したシーンに変更していきます。

PlayCanvasでVRのシーンを作成する手順

  1. 端末がVRに対応しているかを確認
  2. WebXR Device APIのメソッド実行してVRのシーンに入る

今回は最低限のシーンの状態でVRに入ることのみを行いますが、通常であればこのタイミングで3Dモデルのインポートやマテリアル・テクスチャの設定などを行いシーンの構築を行います。

1. カメラを選択

PlayCanvasのエディタからCameraを選択します。

2. 「ADD COMPONENT」をクリックしコンポーネントを追加

カメラを選択するとエディタに「ADD COMPONENT」というボタンが表示されます。
「ADD COMPONENT」をクリックしSCRIPTを追加します。

3. スクリプトを追加

次に、エディタからスクリプト作成します。画像の入力欄にvr-starter-kitと入力してください。入力後エンターキーを押すことで「vr-starter-kit.js」というファイルが新規作成されます。

4. コードエディタを起動

「vr-starter-kit.js」をエディター上でクリックしエディタを起動してください。

PlayCanvasでWebXRに対応させるためコードエディタからこちらのスクリプトを追加します。

5. VRに対応させるコードを追加

下記のコードをPlayCanvasのコードエディタに貼り付け、エディタ上で保存をしてください。

class VRStarterKit extends pc.ScriptType {
  initialize() {
    this.app.isSupported = this.app.xr.supported && pc.XRTYPE_VR; // ① 起動時XRのサポートをしているか確認
    if (!this.app.isSupported) return;
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.enterVr, this); // マウスがクリックされた際にenterVr関数を実行
    if (this.app.touch) {
      this.app.touch.on(pc.EVENT_TOUCHSTART, this.enterVr, this); // タッチ端末にてがタッチされた際にenterVr関数を実行
    }
  }
  async enterVr() {
    // ② カメラコンポーネントのstartXr実行しVRのシーンへ切り替える
    this.entity.camera.startXr(pc.XRTYPE_VR, "local-floor", {
      callback: (error) => {
        if (error !== null) alert(error);
      },
    });
  }
}

pc.registerScript(VRStarterKit);

プロジェクトを確認してみよう

デモURL
実機がお手元にある方は実機でアクセスをしてみてください。
また、実機がない場合はエミュレーターでも確認できます。

エミュレーター(拡張機能)を利用を利用した確認方法

パソコンのChromeを利用している場合には確認の為にWebXR API Emulatorの拡張機能を入れる必要があります。拡張機能を入れた状態で確認をするとこのようにシーンが表示されるかと思います。エミュレーター(Oculus)の視点を移動させることでヘッドマウントディスプレイをかぶった際の動きでどのように動作するか確認できます。

Android端末での見え方について


Androidで確認をするとこのような形でシーンを閲覧することができます。この状態で、「VRゴーグル」を使用することでVRのシーンを閲覧できます。

追加したコードの解説

① 端末がVRに対応しているかを確認

今回のプロジェクトでは、pc.app.xr.supportedpc.XRTYPE_VRに対応しているかどうかを確認しています。this.app.isSupportedの場合にその他のVR関係の処理を追加していきます。

this.app.isSupported = this.app.xr.supported && pc.XRTYPE_VR; // 起動時XRのサポートをしているか確認
    // WebXR(VR)のAPIに対応してる場合
if (this.app.isSupported) {
}

② カメラのstartXrメソッドを実行してVRのシーンに入る

カメラエンティティ内のstartXrを利用してVRのシーンに入ります。このメソッドを実行するためには、事前にユーザーから入力が必要です。そのため次にUIを作成して、ユーザーがボタンをクリックしたらメソッドを実行するようにします。

startXrメソッドについて

startXr(type, spaceType, options) {
   this.system.app.xr.start(this, type, spaceType, options);
}

startXrでは実行するとブラウザのnavigator.xr.requestSessionを実行しておりtype / spaceType / optionsの3つの引数を与えることができます。

引数 説明 備考
type 実行するXRセッションのタイプを指定します。VRの場合はXRTYPE_VRまたはXRTYPE_INLINEを渡して実行します。 WebXR セッションの起動と停止
spaceType 着席 / ルームスケールの指定をします。 XRReferenceSpaceType / Types of XR experiences
options オプション(コールバック等を設定できます) xr-manager.js

type

実行するXRセッションのタイプを指定します。VRの場合はXRTYPE_VRまたはXRTYPE_INLINEを渡して実行します。

spaceType

この引数で渡す値によってVRの体験を変えることができます。今回のプロジェクトでは着席時の利用を想定しているlocal-floorを設定しています。

Optionsについて(コールバックの設定)

// vr-starter-kit.js
 {
    callback: (error) => {
      if (error !== null) alert(error);
    },
  }

XRのシーンに入るタイミングでオプションで設定しているcallback を呼ばれます。エラーが発生している場合にはerrorの値にnull以外が渡されます。

今回はそのタイミングでエラーがある場合にはアラートを出しています。

その他、PlayCanvasのスクリプティングについて

JavaScriptで記述することができます。詳しくはスクリプトの構造をご覧ください。

2. Meta Questのコントローラーに対応させる

コントローラーへの対応

コントローラーを持たないWebXRの端末への対応はできました。Meta Questのコントローラーのへの対応をします。vr-starter-kit.jsのコードを書き換えて実装します。

1. コードを書き換える

先程と同様にコードエディタを起動してvr-starter-kit.jsのコードを書き換えます。

2. vr-starter-kit.jsを書き換える

class VRStarterKit extends pc.ScriptType {
  initialize() {
    this.app.isSupported = this.app.xr.supported && pc.XRTYPE_VR;
    if (!this.app.isSupported) return;
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.enterVr, this); 
    if (this.app.touch) {
      this.app.touch.on(pc.EVENT_TOUCHSTART, this.enterVr, this);
    }
    this.player = new pc.Entity("player");
    this.entity.parent.addChild(this.player);

    this.controllers = []; 
    this.app.xr.input.on("add", this.createController, this);
  }

  async enterVr() {
    this.entity.reparent(this.player);
    this.entity.camera.startXr(pc.XRTYPE_VR, "local-floor", {
      callback: (error) => {
        if (error !== null) alert(error);
      },
    });
  }

  // ① コントローラーをVRシーンに追加
  createController(inputSource) {
    // コントローラー用のエンティティを作成
    const controller = new pc.Entity();
    controller.addComponent("render", { type: "sphere" });
    controller.setLocalScale(0.25, 0.25, 0.25);
    controller.setPosition(0, 0, 0);
    this.player.addChild(controller);

    // コントローラーの情報を登録
    controller.inputSource = inputSource;
    this.controllers.push(controller);

    // コントローラー削除の処理
    inputSource.on("remove", () => {
      // コントローラー削除された際の処理を追加
      this.controllers.splice(this.controllers.indexOf(controller), 1);
      controller.destroy();
    });
  }

  // ② コントローラーの位置を同期
  update() {
    for (let i = 0; i < this.controllers.length; i++) {
      const inputSource = this.controllers[i].inputSource;
      if (inputSource.grip) {
        this.controllers[i].setLocalPosition(inputSource.getLocalPosition()); // コントローラーの位置を合わせる
      }
    }
  }
}

pc.registerScript(VRStarterKit);

追加したコードの解説

  1. コントローラーをVRシーンに追加
  2. コントローラーの位置を同期

こちらの2つの処理を追加しました。

① コントローラーをVRシーンへ追加

コントローラーが追加された場合にVRシーンに配置する処理を追加しています。コントローラーの存在しないAndroid端末などではコントローラーは追加されません。

コントローラーの追加を検知
this.app.xr.input.on("add", this.createController, this);
コントローラーをVRシーンに配置
const controller = new pc.Entity();
controller.addComponent("render", { type: "sphere" });
controller.setLocalScale(0.25, 0.25, 0.25);
controller.setPosition(0, 0, 0);
this.player.addChild(controller);

プリミティブな形状の球体(shpere)を追加し、コントローラーと1対1で対応させます。

② コントローラーの位置を同期

毎フレーム位置を同期
 this.controllers[i].setLocalPosition(inputSource.getLocalPosition()); // コントローラーの位置を合わせる

PlayCanvasのスクリプトではupdate内に記述することで毎フレーム呼び出すことができます。このタイミングで接続されているコントローラーを位置を同期します。

動作の確認(コントローラーへの対応)

デモURL

この状態で動作を確認してみます。エミュレーターでもコントローラーがありますので、そちらを利用してみると、VRのシーン上でコントローラーが同期されていることがわかります。

3. ジョイスティックでの視点移動を追加する

最後に、コントロールのジョイスティックを使った視点移動を実装します。
この章を実装することでヘッドマウントディスプレイを被りながら両手にコントローラーを持った状態でシーンに入ることで自由にVRシーンを内を移動できるようになります。

1. コードを書き換える

vr-starter-kit.js内のコードをupdateを一部コードに書き換えます。

update() {
  for (let i = 0; i < this.controllers.length; i++) {
    const inputSource = this.controllers[i].inputSource;
    if (inputSource.grip) {
      this.controllers[i].setLocalPosition(inputSource.getLocalPosition()); // コントローラーの位置を合わせる
      // ①追加部分 ここから
      if (inputSource.handedness === pc.XRHAND_LEFT) {
        this.player.translateLocal(0, 0, inputSource.gamepad.axes[3] * 0.1);
      } else if (inputSource.handedness === pc.XRHAND_RIGHT) {
        this.player.rotateLocal(0, -inputSource.gamepad.axes[2] * 0.75, 0);
        // 追加部分 ここまで
      }
    }
  }
}

追加したコードの解説

  1. ジョイスティック(コントローラー)の入力について
  2. 左のジョイスティックで移動
  3. 右のジョイスティック操作で回転

① ジョイスティック(コントローラー)の入力について

左右のコントローラーの判定はinputSource.handednessのプロパティから確認できます。

ジョイスティックの値はinputSource.gamepad.axesとしてアクセスできます。押し込んだ強さによって-1 ~ 1の値が取得できます。

ブラウザ上でコントローラー挙動が確認出来る便利なサイトWebXR Input Profile ViewerもありますのでMeta Quest以外に対応する場合にはこちらを確認してください。

② 左手の処理(移動)

左手の処理は移動となります。左のジョイスティックが前に押された場合に、PlayCanvasのエンティティのメソッド(translateLocal)を利用して前後に移動するようにしています。

this.player.translateLocal(0, 0, inputSource.gamepad.axes[3] * 0.1);

③ 右手の処理(回転)

右手の処理は回転となります。(rotatelocal)を実行してキャラクターの視点を変えます。

 this.player.rotateLocal(0, -inputSource.gamepad.axes[2] * 0.75, 0);

動作の確認(ジョイスティックへの対応)

デモURL

これでMeta Questで動く下VRのシーンを作成する事ができました。このような流れでWebXR Device APIとWebGLのライブラリと組み合わせることでVRのシーンを作ることができました。

作成したプロジェクトはAndroid、Oculus Browser、PC + ヘッドマウントディスプレイ(Steam VR)で遊ぶことができます。

あとがき

今回はWebXRのプロジェクトを作成してみました。PlayCanvasなどWebGLのライブラリでは3Dモデルのインポート機能などもありますので、こちらを拡張をしていただくことで自分の好きなVRのシーンが構築できるかと思います。

なにかありましたら@mxcn3へお願いします。