WebXR Device API を使用して拡張現実(AR)アプリを作成する

1. 始める前に

この Codelab では、AR ウェブアプリの作成例について説明します。JavaScript を使用して、現実世界に存在するかのように見せる 3D モデルをレンダリングします。

AR とバーチャル リアリティ(VR)の機能を組み合わせた WebXR Device API を使用します。ここでは、インタラクティブなウェブで実行されるシンプルな AR アプリを作成するために、WebXR Device API の AR 拡張機能を使用します。

AR とは

AR とは、コンピュータで生成したグラフィックと現実世界との融合を表現するために一般的に使用される用語です。スマートフォン ベースの AR の場合は、コンピュータ グラフィックスをライブカメラ フィードにうまく重ねることを表します。AR 対応デバイスであるスマートフォンは、現実世界を移動する際にこの効果を現実的なものとして維持するため、デバイス自身が移動している現実世界を理解して、3D 空間におけるポーズ(位置と向き)を決定する必要があります。これには、環境でのサーフェスの検知やライティングの推定が含まれます。

AR は、Google の ARCore と Apple の ARKit がリリースされて以降、自撮りフィルタや AR ベースのゲームなどのアプリで広く使用されるようになりました。

作成するアプリの概要

この Codelab では、拡張現実を使用して現実世界にモデルを配置するウェブアプリを作成します。作成するアプリの機能は次のとおりです。

  1. 対象デバイスのセンサーを使用して、環境内での位置と向きを特定、追跡する
  2. ライブカメラ ビュー上に合成された 3D モデルをレンダリングする
  3. ヒットテストを実行して、現実世界で検出されたサーフェスの上にオブジェクトを配置する

学習内容

  • WebXR Device API の使用方法
  • 基本的な AR シーンの構成方法
  • AR ヒットテストを使用してサーフェスを見つける方法
  • 実際のカメラフィードと同期した 3D モデルを読み込んでレンダリングする方法
  • 3D モデルに基づいてシャドウをレンダリングする方法

この Codelab では、AR API を中心に説明します。関連のない概念やコードブロックについては詳しく触れず、対応するリポジトリ コードが提供されています。

必要なもの

AR デバイスで [試してみる] をクリックして、この Codelab の最初のステップを試してください。「ブラウザに AR 機能がありません」というメッセージがページに表示された場合は、Android デバイスに Google Play 開発者サービス(AR)がインストールされていることをご確認ください。

2. 開発環境を設定する

コードをダウンロードする

  1. 次のリンクをクリックして、この Codelab のすべてのコードをワークステーションにダウンロードします。

  1. ダウンロードした zip ファイルを解凍すると、ルートフォルダ(ar-with-webxr-master)が展開されます。ルートフォルダには、この Codelab の複数のステップのディレクトリと、必要なすべてのリソースが含まれています。

step-03 フォルダと step-04 フォルダには、この Codelab の 3 番目と 4 番目のステップの目標とする終了状態、および final の結果が含まれています。これらは参照用に用意されています。

コーディング作業はすべて、work ディレクトリで行います。

ウェブサーバーをインストールする

  1. 独自のウェブサーバーを自由に使用できます。ウェブサーバーをまだ設定していない場合のため、このセクションでは、Chrome 用にウェブサーバーを設定する手順について説明しています。
    目的のアプリをワークステーションにまだインストールしていない場合は、Chrome ウェブストアからインストールできます。

  1. Chrome アプリ用のウェブサーバーをインストールしてから、chrome://apps に移動してウェブサーバーのアイコンをクリックします。

ウェブサーバーのアイコン

次にこのダイアログが表示され、ローカル ウェブサーバーを構成できます。

Chrome ウェブサーバーを構成する

  1. [フォルダの選択] をクリックし、ar-with-webxr-master フォルダを選択します。これにより、ウェブサーバー ダイアログ([ウェブサーバーの URL] セクション)でハイライト表示されている URL を使用して、処理中の作業を行うことができます。
  2. [オプション(再起動が必要)] で、[index.html を自動的に表示] チェックボックスをオンにします。
  3. [ウェブサーバー] を [Stop] に切り替えてから、[Started] に戻します。Chrome ウェブサーバーを再起動する
  4. 少なくとも 1 つのウェブサーバーの URL(http://127.0.0.1:8887 - デフォルトの localhost URL)を確認します。

ポート転送をセットアップする

ワークステーションで localhost:8887 にアクセスするときに、ワークステーションの同じポートにアクセスするように AR デバイスを構成します。

  1. 開発ワークステーションで、chrome://inspect にアクセスし、[ポート転送] をクリックします(chrome://inspect)。
  2. [ポート転送設定] ダイアログで、ポート 8887 を localhost:8887 に転送します。
  3. [ポート転送を有効にする] チェックボックスをオンにします。

ポート転送を構成する

設定内容を確認する

接続をテストする

  1. USB ケーブルで AR デバイスをワークステーションに接続します。
  2. Chrome の AR デバイスで、アドレスバーに「http://localhost:8887」と入力します。AR デバイスは、開発用ワークステーションのウェブサーバーにこのリクエストを転送します。ファイルのディレクトリが表示されます。
  3. AR デバイス上で、step-03 をクリックしてブラウザに step-03/index.html ファイルを読み込みます。

[拡張現実を開始] ボタンを含むページが表示されます。

ただし、サポートされていないブラウザに関するエラーページが表示される場合は、お使いのデバイスに互換性がない可能性があります。

ARCore がサポートされている

ARCore がサポートされていない

AR デバイスからウェブサーバーへの接続が可能になりました。

  1. [拡張現実を開始] をクリックします。ARCore をインストールするようにプロンプトが表示される場合があります。

ARCore のインストールのプロンプト

AR アプリを初めて実行したときに、カメラの権限に関するプロンプトが表示されます。

Chrome でのカメラへの権限のリクエスト権限ダイアログ

問題がなければ、カメラフィードの上に立方体が重なったシーンが表示されます。カメラで解析する世界が多いほど、シーン認識が向上するため、動き回ることで結果を安定させることができます。

3. WebXR を構成する

このステップでは、WebXR セッションと基本的な AR シーンを設定する方法について説明します。HTML ページには、基本的な AR 機能を有効にするための CSS スタイル設定と JavaScript が用意されています。これにより、設定プロセスが迅速化され、Codelab で AR 機能に集中できるようになります。

HTML ページ

既存のウェブ技術を使用して、従来のウェブページに AR 体験をビルドします。このエクスペリエンスでは全画面表示のレンダリング キャンバスを使用するため、HTML ファイルの複雑度を下げることができます。

AR 機能を使用するにはユーザー ジェスチャーが必要です。そのため、[AR を開始] ボタンとサポート対象外のブラウザ メッセージを表示するためのマテリアル デザイン コンポーネントがいくつかあります。

work ディレクトリにすでにある index.html ファイルは次のようになります。これは実際のコンテンツのサブセットです。このコードをファイルにコピーしないでください。

<!-- Don't copy this code into your file! -->
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Building an augmented reality application with the WebXR Device API</title>
    <link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
    <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>

    <!-- three.js -->
    <script src="https://unpkg.com/three@0.123.0/build/three.js"></script>
    <script src="https://unpkg.com/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script>

    <script src="../shared/utils.js"></script>
    <script src="app.js"></script>
  </head>
  <body>
  <!-- Information about AR removed for brevity. -->

  <!-- Starting an immersive WebXR session requires user interaction. Start the WebXR experience with a simple button. -->
  <a onclick="activateXR()" class="mdc-button mdc-button--raised mdc-button--accent">
    Start augmented reality
  </a>

</body>
</html>

主要な JavaScript コードを開く

アプリの出発点は app.js です。このファイルには、AR 体験を設定するためのボイラープレートが含まれています。

作業ディレクトリにすでにアプリコード(app.js)が含まれています。

WebXR と AR のサポートを確認する

ユーザーが AR を利用するには、navigator.xr と必要な XR 機能が存在することを確認してください。navigator.xr オブジェクトは WebXR Device API のエントリ ポイントであるため、デバイスが対応している場合に存在します。また、"immersive-ar" セッション モードがサポートされていることを確認します。

問題がなければ、[拡張現実を入力] ボタンをクリックすると XR セッションの作成が試みられます。それ以外の場合は、(shared/utils.js で)onNoXRDevice() が呼び出され、AR サポートがないことを示すメッセージが表示されます。

このコードはすでに app.js に存在するため、変更する必要はありません。

(async function() {
  if (navigator.xr && await navigator.xr.isSessionSupported("immersive-ar")) {
    document.getElementById("enter-ar").addEventListener("click", activateXR)
  } else {
    onNoXRDevice();
  }
})();

XRSession をリクエストする

[拡張現実を入力] をクリックすると、コードによって activateXR() が呼び出されます。これにより、AR 体験が開始されます。

  1. app.jsactivateXR() 関数を見つけます。一部のコードが省略されています。
activateXR = async () => {
  // Initialize a WebXR session using "immersive-ar".
  this.xrSession = /* TODO */;

  // Omitted for brevity
}

WebXR へのエントリポイントは XRSystem.requestSession() を使用します。immersive-ar モードを使用して、レンダリングされたコンテンツを実際の環境で表示できるようにします。

  1. "immersive-ar" モードを使用して this.xrSession を初期化します。
activateXR = async () => {
  // Initialize a WebXR session using "immersive-ar".
  this.xrSession = await navigator.xr.requestSession("immersive-ar");

  // ...
}

XRReferenceSpace を初期化する

XRReferenceSpace は、仮想世界内のオブジェクトに使用される座標系を表します。'local' モードは、ビューアの近くに原点があり、安定したトラッキングが行われる基準空間を使用した AR エクスペリエンスに最適です。

次のコードを使用して、onSessionStarted()this.localReferenceSpace を初期化します。

this.localReferenceSpace = await this.xrSession.requestReferenceSpace("local");

アニメーション ループを定義する

  1. window.requestAnimationFrame と同様に、XRSessionrequestAnimationFrame を使用してレンダリング ループを開始します。

フレームごとに、onXRFrame がタイムスタンプと XRFrame で呼び出されます。

  1. onXRFrame の実装を完了します。フレームが描画されたら、次の行を追加して次のリクエストをキューに追加します。
// Queue up the next draw request.
this.xrSession.requestAnimationFrame(this.onXRFrame);
  1. グラフィック環境を設定するコードを追加します。onXRFrame の末尾に次の行を追加します。
// Bind the graphics framebuffer to the baseLayer's framebuffer.
const framebuffer = this.xrSession.renderState.baseLayer.framebuffer;
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer);
this.renderer.setFramebuffer(framebuffer);
  1. ビューアのポーズを決定するには、XRFrame.getViewerPose() を使用します。この XRViewerPose は、空間におけるデバイスの位置と向きを示します。また、現在のデバイスに正しく表示されるように、シーンをレンダリングするすべてのビューポイントを記述する XRView の配列も含まれています。ステレオスコープ VR ではビューが 2 つ(左右の目に 1 つずつ)ありますが、AR デバイスのビューは 1 つだけです。
    pose.views の情報は、仮想カメラのビュー マトリックスと投影行列を構成するために最もよく使用されます。これは、3D でのシーンの配置方法に影響します。カメラを構成すると、シーンをレンダリングできます。
  2. onXRFrame の末尾に次の行を追加します。
// Retrieve the pose of the device.
// XRFrame.getViewerPose can return null while the session attempts to establish tracking.
const pose = frame.getViewerPose(this.localReferenceSpace);
if (pose) {
  // In mobile AR, we only have one view.
  const view = pose.views[0];

  const viewport = this.xrSession.renderState.baseLayer.getViewport(view);
  this.renderer.setSize(viewport.width, viewport.height);

  // Use the view's transform matrix and projection matrix to configure the THREE.camera.
  this.camera.matrix.fromArray(view.transform.matrix);
  this.camera.projectionMatrix.fromArray(view.projectionMatrix);
  this.camera.updateMatrixWorld(true);

  // Render the scene with THREE.WebGLRenderer.
  this.renderer.render(this.scene, this.camera);
}

テスト

アプリを実行します。開発用デバイスで、work/index.html にアクセスします。カメラフィードに立方体に浮かんでいて、デバイスを動かすと視点が変わります。動かすほど、追跡の精度が向上するため、デバイスでどのように機能するかを確認してみてください。

アプリの実行に問題がある場合は、概要開発環境の設定をご覧ください。

4.ターゲティング レチクルを追加する

基本的な AR シーンを設定したら、ヒットテストを使用して実世界での操作を開始します。このセクションでは、ヒットテストをプログラムし、それを使って実世界でのサーフェスを探します。

ヒットテストについて

一般的に、ヒットテストでは空間内のある点から一方向に直線を投影し、いずれかの対象オブジェクトと交差するかどうかを判断します。この例では、デバイスを実際の場所に向けます。デバイスのカメラから出て、目の前の現実世界にまっすぐに入る光線を想像してみてください。

WebXR Device API を使用すると、この光線が実世界のどのオブジェクトにも重なっているかどうかを判断できます。判断の際は、基盤となる AR 機能と実世界に対する理解が役立ちます。

ヒットテストの説明

追加機能がある XRSession をリクエストする

ヒットテストを実施するには、XRSession をリクエストする際に追加の機能が必要になります。

  1. app.js で、navigator.xr.requestSession を見つけます。
  2. 次のように、特徴 "hit-test""dom-overlay"requiredFeature として追加します。
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"]
});
  1. DOM オーバーレイを構成します。次のように、AR カメラビューの上に document.body 要素を重ねます。
this.xrSession = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["hit-test", "dom-overlay"],
  domOverlay: { root: document.body }
});

モーション プロンプトを追加する

ARCore は、環境についての十分な理解がなされている場合に最も効果的に機能します。これは、「自己位置推定と環境地図作成の同時実行」(SLAM)と呼ばれるプロセスで実現されます。このプロセスでは、視覚的に区別された特徴点を使用して、ロケーションと環境特性の変化を計算します。

前の手順の "dom-overlay" を使用して、カメラ ストリームの上にモーション プロンプトを表示します。

ID stabilization<div>index.html に追加します。この <div> は、手ぶれ補正ステータスを示すアニメーションをユーザーに表示し、SLAM プロセスを強化するためにデバイスとともに移動するよう促します。これは、ユーザーが AR に入ると表示され、レチクルが <body> クラスで制御されるサーフェスを検出すると非表示になります。

  <div id="stabilization"></div>

</body>
</html>

レチクルを追加する

レチクルを使用して、デバイスのビューが指している場所を示します。

  1. app.js で、setupThreeJs()DemoUtils.createCubeScene() 呼び出しを空の Three.Scene() に置き換えます。
setupThreeJs() {
  // ...

  // this.scene = DemoUtils.createCubeScene();
  this.scene = DemoUtils.createLitScene();
}
  1. 新しいシーンに、衝突点を表すオブジェクトを入力します。提供された Reticle クラスは、shared/utils.js でレチクルモデルの読み込みを処理します。
  2. setupThreeJs() のシーンに Reticle を追加します。
setupThreeJs() {
  // ...

  // this.scene = DemoUtils.createCubeScene();
  this.scene = DemoUtils.createLitScene();
  this.reticle = new Reticle();
  this.scene.add(this.reticle);
}

ヒットテストを実行するには、新しい XRReferenceSpace を使用します。この基準空間は、ビューアの視点から視線に合わせて光線が作成される新しい座標系を示します。この座標系は XRSession.requestHitTestSource() で使用され、ヒットテストを計算できます。

  1. 以下を app.jsonSessionStarted() に追加します。
async onSessionStarted() {
  // ...

  // Setup an XRReferenceSpace using the "local" coordinate system.
  this.localReferenceSpace = await this.xrSession.requestReferenceSpace("local");

  // Add these lines:
  // Create another XRReferenceSpace that has the viewer as the origin.
  this.viewerSpace = await this.xrSession.requestReferenceSpace("viewer");
  // Perform hit testing using the viewer as origin.
  this.hitTestSource = await this.xrSession.requestHitTestSource({ space: this.viewerSpace });

  // ...
}
  1. この hitTestSource を使用して、フレームごとにヒットテストを行います。
    • ヒットテストの結果がない場合、ARCore では環境を理解するための時間を十分に確保できていません。このような場合は、ユーザーには手ぶれ補正(<div>)を使用してデバイスを移動するようにプロンプトが表示されます。
    • 結果がある場合は、レチクルをその場所に移動します。
  2. onXRFrame を変更してレチクルを移動します。
onXRFrame = (time, frame) => {
  // ... some code omitted ...
  this.camera.updateMatrixWorld(true);

  // Add the following:
  const hitTestResults = frame.getHitTestResults(this.hitTestSource);

  if (!this.stabilized && hitTestResults.length > 0) {
    this.stabilized = true;
    document.body.classList.add("stabilized");
  }
  if (hitTestResults.length > 0) {
    const hitPose = hitTestResults[0].getPose(this.localReferenceSpace);

    // update the reticle position
    this.reticle.visible = true;
    this.reticle.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z)
    this.reticle.updateMatrixWorld(true);
  }
  // More code omitted.
}

画面タップ時の動作を追加する

XRSession は、プライマリ アクションを表す select イベントを介して、ユーザー操作に基づいてイベントを発生させる場合があります。モバイル デバイスの WebXR でのプライマリ アクションは、画面のタップです。

  1. onSessionStarted の下部に select イベント リスナーを追加します。
this.xrSession.addEventListener("select", this.onSelect);

この例では、スクリーン タップによってヒマワリがレチクルに配置されます。

  1. App クラスに onSelect の実装を作成します。
onSelect = () => {
  if (window.sunflower) {
    const clone = window.sunflower.clone();
    clone.position.copy(this.reticle.position);
    this.scene.add(clone);
  }
}

アプリをテストする

ヒットテストを使用して、お使いのデバイスで目標にできるレチクルを作成しました。画面をタップすると、レチクルが指定した場所にヒマワリを配置できます。

  1. アプリを実行すると、床のサーフェスをトレースするレチクルを表示できます。表示されない場合は、スマートフォンでゆっくりと見回してみてください。
  2. レチクルが表示されたら、タップします。ヒマワリがレクチルの上に配置されます。基盤となる AR プラットフォームが現実世界のサーフェスを適切に検出できるように、多少の移動が必要になる場合があります。ライティングが少なく、サーフェスに特徴がないと、シーン認識の質が低下し、ヒットが見つからない可能性が高くなります。問題が発生した場合は、step-04/app.js コードを確認して、このステップの実例をご覧ください。

5. シャドウを追加する

現実的なシーンを作成するためには、デジタル オブジェクトに対する適切なライティングとシャドウなど、シーンに現実感や没入感を加える要素が必要になります。

ライティングとシャドウは three.js によって処理されます。シャドウを投影するライト、シャドウを受信してレンダリングするマテリアル、シャドウを投影できるメッシュを指定できます。このアプリのシーンには、シャドウを投影するライトと、シャドウのみをレンダリングする平らなサーフェスが含まれています。

  1. three.js WebGLRenderer でシャドウを有効にします。レンダラを作成した後、shadowMap で次の値を設定します。
setupThreeJs() {
  ...
  this.renderer = new THREE.WebGLRenderer(...);
  ...
  this.renderer.shadowMap.enabled = true;
  this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  ...
}

DemoUtils.createLitScene() で作成されたサンプルシーンには、shadowMesh というオブジェクトが含まれています。これは、シャドウのみをレンダリングする平らな水平方向のサーフェスです。このサーフェスの Y 座標の初期値は、10,000 単位です。ヒマワリを配置したら、ヒマワリの影が実世界の地面にレンダリングされるように、shadowMesh を実際のサーフェスと同じ高さに移動します。

  1. onSelect で、clone をシーンに追加した後、シャドウ プレーンの位置を移動するコードを追加します。
onSelect = () => {
  if (window.sunflower) {
    const clone = window.sunflower.clone();
    clone.position.copy(this.reticle.position);
    this.scene.add(clone);

    const shadowMesh = this.scene.children.find(c => c.name === "shadowMesh");
    shadowMesh.position.y = clone.position.y;
  }
}

テスト

ヒマワリを配置すると、シャドウが投影されているのがわかります。問題が発生した場合は、final/app.js コードを確認して、このステップの実例をご覧ください。

6. 参考情報

これで、WebXR を使用した AR に関する Codelab は終了しました。

詳細