はじめに

こんにちは。空間コンピューティンググループの松岡です。

今回ブラウザでできるARコンテンツとして、平面を検知して目の前にアバターを表示するデモを作りました。

(※2025/10/02追記:デモの公開は終了しました)

以前同じようにWebARを使ってアバターを表示するという記事をFintanに公開しており、そのときはUnityで制作しましたが、今回はReactで制作しています!

解説

主な使用ライブラリとバージョンは以下です

   react: 18
   next: 14.2.3
   react-three/fiber: 8.16.8
   react-three/xr: 5.7.1
   react-three/drei: 9.106.0
   three: 0.165.0
   pixiv/three-vrm: 2.1.2

メインのライブラリとして、React Three FiberというReactで3Dモデルなど3次元空間を扱えるライブラリを使っています。

React Three Fiberは Three.js を React でラップしたライブラリです。(以降R3Fと呼びます)

Three.jsはWebGLを使って3Dグラフィックスをレンダリングするためのライブラリです。

R3FはReactのコンポーネントモデルと統合され、Three.jsの扱いが簡単になるため、開発が直感的で効率的に行えます。

またWebARを実装するためにreact-xrというライブラリを使いました。

 

以下で、このデモの主な要素であるアバターの読み込みと、平面検知をしてARでアバターを表示する部分について解説します。

想定の読者はReactの基礎を理解している方です。

アバターの読み込み

アバターはVRM形式のものを使っています。これをAWSのS3というリモート環境に置き、URLを参照して表示しています。

VRMを扱うにはpixiv/three-vrmライブラリを使います。

主な参考コードはこちらです。基本的にはgltfファイルをロードする方法と同じです。

ローカルに置いているVRMファイルを読み込む際はavatarPath部分にローカルのパスを書き、S3に置いているVRMファイルを読み込む場合はS3のオブジェクトURLを書きます。

注意点としてS3に置いているVRMファイルを読み込む場合はこちらの記事にあるように、VRMファイルを置いてあるS3のバケットの設定でCross-Origin Resource Sharing (CORS)を有効にする必要があります。

S3のアクセス許可のCross-Origin Resource Sharing (CORS)に以下のように書くと有効になります。

[ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET" ], "AllowedOrigins": [ "*" ], "ExposeHeaders": [], "MaxAgeSeconds": 3000 } ]

ARで平面検知

平面を検知して、その場所にARオブジェクトとして先ほどロードしたVRMアバターを表示しています。

基本的にはuseHitTestというWebXRのAPIを使います。

今回の実装はこちらのコードを参考にして、平面を検知したときに表示される白い円(配置マーカー)をタップしたら、その場所にARオブジェクトが固定で表示されるという挙動にしています。

また追加の工夫として、Meta Quest 3では配置マーカーを選択するにはコントローラーからレイのようなものが出ていたほうが選択しやすいと思ったため、react-xrのControllers コンポーネントを追加しました。これにより、Meta Quest 3でコントローラーとレイが表示され配置マーカーを選択しやすくなりました。

おまけ dreiのHtmlについて

おまけとしてXRならではのつまずきポイントを紹介します。

R3Fを使う際に便利なコンポーネントやユーティリティを提供するライブラリに、dreiというものがあります。

dreiにはHtmlコンポーネントというものがあり、3次元空間にHTMLコンテンツを埋め込むことができます。

CSSなども使えて便利なので、アバターの頭上にあるネームプレートのようなちょっとした2Dデザインを3次元空間の中に表示するために使っていました。

しかしスマホでの全画面ARモード時やMeta Quest 3の没入モードでARをしたとき、Htmlコンポーネントで作ったものは表示されませんでした。

調べたところ、以下の理由で表示されないようです。

「DreiのHtmlコンポーネントはVR/ARでは動作しません。HtmlコンポーネントはThree.jsのシーン内ではなく、Canvasの上にDOMノードを追加することで動作するため、VRセッション中には3Dシーン内でレンダリングされているものだけが見えるため」

https://github.com/pmndrs/react-xr/issues/21より引用&日本語翻訳

代わりに「html2canvasなどのライブラリを使用して HTML をキャンバスにレンダリングし、そのキャンバスを平面上のテクスチャとして使用することで、テクスチャを動的にレンダリング」することでHTML要素を使うこともできるようです。(同上のissueより引用&日本語翻訳)

しかし今回はHtmlコンポーネントを使うのをやめて、同じdreiライブラリの中のPlaneコンポーネントTextコンポーネントで作り直すことにしました。

Htmlコンポーネントを使ったコード↓


  const pillStyle: CSSProperties = {
    backgroundColor: "rgba(0, 0, 0, 0.5)", // 半透明のグレー
    borderRadius: "50px", // ピル型
    padding: "8px 20px",
    display: "inline-block",
    color: "white",
    transform: "scale(0.2)",
  };

//中略

<Html style={pillStyle} transform>
{name}
</Html>

 

Plane+Textを使ったコード↓

      <group ref={ref} position={[position.x, position.y, position.z]}>
        <Text
          color="white"
          anchorX="center"
          anchorY="middle"
          fontSize={0.08}
          fontWeight="bold"
        >
          {name}
        </Text>
        <Plane args={[0.8, 0.15]} position={[0, 0, -0.001]}>
          <meshBasicMaterial
            attach="material"
            map={texture}
            transparent={true}
          />
        </Plane>
      </group>

 

Planeのマテリアルに以下のようなpngテクスチャを設定しました。

これで見た目はhtmlタグで作っていた時と同じようになりました。

そして無事、没入型のARモードでもネームプレートが表示されました。

最後に

R3Fやreact-xrライブラリは日本語の参考記事が少なく、シンプルなサンプルも見つからないことがあるため、この記事が制作の助けになれば幸いです。