見出し画像

WebXR Device API と three.js を使ったスマホWebAR

※この記事は2023年11月27日に弊社運営の技術情報サイト「ギャップロ」に掲載した記事です。

はじめに

2023年も「PlayStation VR2」の発売に始まり、Apple社初となるXRデバイス「Apple Vision Pro」の発表や、Meta社の「Meta Quest 3」の発売等々、XR業界は話題に事欠かなかったのではないのでしょうか。
自分は普段はWebのフロントエンドエンジニアをやっており、XR界隈についてはニワカ程度の知識しかないのですが、今回は「WebXR Device API」を使ってその一端に触れてみたいと思います。

WebXR Device API とは

WebXR Device API は、Web上からのXR(AR・VR)デバイスへのアクセスを提供するAPIです。
仕様の策定が進んでおり、2023年10月現在はW3C勧告候補草案となっています。
対応ブラウザについては以下のような状況になっており、Safariに関してはVision Pro向けのSafariでのみ一部が試験的に対応予定のようです。


https://immersiveweb.dev/#supporttable

WebXR Device API と three.js を使ったARコンテンツの実装

ということで、今回はこの WebXR Device API を使って簡単なスマホ向けARコンテンツを実装したい思うのですが、せっかくなので以前の記事で作成したVRMモデルのプレビュー用デモサイトに以下の機能を追加する形で実装してみます。

  1. デバイスとブラウザがWebARをサポートしているかを判定し、サポートしている場合ARボタンを表示

  2. ARボタンを押下するとARモードが起動

  3. カメラで写した現実世界の映像上で位置を指定し、その位置にVRMモデルをARとして表示

デモサイトについてや three.js と three-vrm を使ったVRMモデルの表示については、下の記事を参照してみてください。

ここからは WebXR Device API と three.js を使って、上記の機能を実装する上でコアとなる部分に絞って書いていきます。

1. WebARサポートの判定

まずは、デバイスとブラウザが WebXR Device API をサポートしているかの判定を実装します。
デバイスやブラウザが WebXR Device API をサポートしている場合、navigatorオブジェクトにxrプロパティが提供されます。
このxrプロパティが返すXRSystemオブジェクトを使用して WebXR Device API にアクセスすることができます。

また、WebXR Device API は

  • immersive-vr

  • immersive-ar

  • inline

の3つのモードのセッションを使用できるのですが、今回はimmersive-arモードを使用するためこのモードがサポートされているかどうかもチェックします。
これはXRSystemオブジェクトのisSessionSupported()メソッドでチェックできます。

(async () => {
  // WebXR Device APIとimmersive-arモードのサポートをチェック
  const isArSupported =
    navigator.xr && (await navigator.xr.isSessionSupported('immersive-ar'))

  if (isArSupported) {
    // サポートしている場合の処理
  } else {
    // サポートしていない場合の処理
  }
})()

2.1. three.js の準備

ARセッションを開始してARの描画を行う前に three.js でARの描画を行うための準備をします。

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
  70,
  window.innerWidth / window.innerHeight,
  0.01,
  20,
)
const light = new THREE.DirectionalLight(0xffffff)
light.position.set(1, 1, 1).normalize()
scene.add(light)
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(window.devicePixelRatio)
// XRのレンダリングを有効化
renderer.xr.enabled = true
renderer.xr.setReferenceSpaceType('local')

// controllerの設定
const controller = renderer.xr.getController(0)
controller.addEventListener('select', onSelect)
scene.add(controller)

// ヒットテスト用のreticleを用意
const reticle = new THREE.Mesh(
  new THREE.RingGeometry(0.15, 0.2, 32).rotateX(-Math.PI / 2),
  new THREE.MeshBasicMaterial(),
)
reticle.matrixAutoUpdate = false
reticle.visible = false
scene.add(reticle)

単に3D空間にVRMモデルを表示していた前回と大きく異なるのは

// XRのレンダリングを有効化
renderer.xr.enabled = true
renderer.xr.setReferenceSpaceType('local')

の部分で、XRのレンダリングを有効化し、参照空間のタイプを設定しています。
また、

// controllerの設定
const controller = renderer.xr.getController(0)
controller.addEventListener('select', onSelect)

の部分でコントローラーを取得して、イベントハンドラを設定しています。
今回だとスマホの画面のタップ時に相当するイベントに対し処理を設定しています。
処理の内容については後述します。

2.2. ARセッションの開始

XRSystemオブジェクトのrequestSession()メソッドを使用してXRSessionを開始します。
今回はimmersive-arモードの指定に加え、オプションとして以下の機能を有効化します。

  • hit-test: 現実世界のジオメトリに対してヒットテストを実行するための機能

  • dom-overlay: XRセッション中にテキストやコントロール要素などの DOM要素をオーバーレイ表示するための機能

async function arButtonClick() {
  const options = {
    requiredFeatures: ['hit-test'],
    optionalFeatures: ['dom-overlay'],
    domOverlay: { root: overlayElem },
  }
  const xrSession = await navigator.xr!.requestSession('immersive-ar', options)
  await renderer.xr.setSession(xrSession)
}

options.domOverlay.rootにはARセッション中にオーバーレイ表示したいUIのHTMLElementを指定しています。

以上を指定してrequestSession()をすることで、カメラの使用許可を求めるダイアログが表示され、ユーザーがこれに同意することでARセッションが開始されます。

また、ここで three.js のレンダラに対して開始したセッションを渡しています。

await renderer.xr.setSession(xrSession)

今回はARボタンを押下した際にARが起動するようにするので、ボタン押下時のイベントハンドラにこれらの処理を設定しています。

3.1. レンダリング

フレーム毎に実行されるレンダリング処理を書きます。
この処理の中では three.js のレンダラのレンダリング実行以外にヒットテストの結果を取得して、ヒット位置にサークル(reticle)を描画するという処理を行っています。

let hitTestSource: THREE.XRHitTestSource | null = null

function animate() {
  // レンダリング処理をループ
  renderer.setAnimationLoop(render)
}

async function render(timestamp: number, frame?: THREE.XRFrame) {
  if (!frame) return

  const referenceSpace = renderer.xr.getReferenceSpace()
  const session = renderer.xr.getSession()
  if (!hitTestSource && referenceSpace && session) {
    const space = await session.requestReferenceSpace('viewer')
    // ヒットテストソースの設定
    hitTestSource = await session.requestHitTestSource({
      space,
    })

    session.addEventListener('end', () => {
      hitTestSource = null
    })
  }
  if (hitTestSource) {
    // ヒットテスト結果の取得
    const hitTestResults = frame.getHitTestResults(hitTestSource)
    if (hitTestResults.length && referenceSpace) {
      const hit = hitTestResults[0]
      // ヒット位置にreticleを表示
      reticle.visible = true
      reticle.matrix.fromArray(hit.getPose(referenceSpace)!.transform.matrix)
    } else {
      reticle.visible = false
    }
  }

  renderer.render(scene, camera)
}

animate()

ここまでで、AR起動後カメラで写した現実世界の映像上で位置を指定するというところまで実装できました。

3.2. VRMモデルの表示

最後に、2.1.でcontrollerに対して設定したスマホの画面タップ時のイベントハンドラの中身を書いていきます。
three-vrm でVRMモデルをロードし、サークルの位置を反映、シーンに追加するだけです。

async function onSelect() {
  if (reticle.visible) {
    // VRMモデルをロード
    const loader = new GLTFLoader()
    const gltf = await loader.loadAsync(`/models/AliciaSolid.vrm`)
    VRMUtils.removeUnnecessaryJoints(gltf.scene)
    const vrm = await VRM.from(gltf)
    // reticleの位置をVRMモデルに反映
    reticle.matrix.decompose(
      vrm.scene.position,
      vrm.scene.quaternion,
      vrm.scene.scale,
    )
    vrm.scene.rotation.y = camera.rotation.y + Math.PI
    vrm.lookAt!.target = camera
    // シーンに追加
    scene.add(vrm.scene)
    reticle.visible = false
  }
}

これで、画面タップでサークルの位置にVRMモデルを表示させるという機能も実装できました。

以上で全ての機能の実装完了です。
実際にデモサイトに組み込んでの一通りの動作はこのような感じになりました。

★クリックして動画を見る★

まとめ

WebXR Device API と three.js を使ってスマホ向けのWebARコンテンツを実装することができました。
肝となるARセッションのセットアップやヒットテストについては、three.js 公式のサンプルコードも見つつで比較的簡単に実装できたと思います。
また実際にWebARに触れてみて、端末とブラウザさえ対応していればURLにアクセスするだけでこういったコンテンツが楽しめる手軽さは、WebARの大きな利点かなと感じました。
今後のiOS端末の対応状況の如何が WebXR Device API の可用性に大きく関わってくると思うので、引き続き動向を追っていけたらなと思います。
最後に、今回の記事中で機能追加したデモサイトのURLも掲載しておくので、対応端末をお持ちの方は良ければ実際に触ってみてください。
https://vrm-viewer-48655.web.app/

この記事が気に入ったらサポートをしてみませんか?