LoginSignup
45
41

More than 1 year has passed since last update.

AR.js の世界へようこそ! 3歩でわかる お手軽 拡張現実

Posted at

恒例の年末に記事を書くやつ向けの記事です。
今回は AR.js についてご紹介します。

(過去の記事)

この記事でやってみること

  • Step 1 : HTML のみで AR を実現
    • AR.js Studio (Marker based)
    • QR コードの生成
    • 3D モデルの取得
  • Step 2 : エンティティの動的追加
    • AR.js Studio (Location based)
    • 位置座標の取得 (Google Map)
    • 相対位置/座標の角度と距離の対応
  • Step 3 : インタラクションの追加
    • モデルの切替
    • テキスト表示
    • アニメーション

Web AR とは

  • AR(Augmented Reality; 拡張現実)は,現実世界にコンテンツのオーバーレイ(重ねること)を可能にするテクノロジー
  • カメラからの映像にリアルタイムでコンテンツを追加できます
  • Web AR は,Web サイトにアクセスするだけで簡単に AR 体験を提供することができます
  • アプリをダウンロードしたり,専用のデバイスを必要としないためお手軽に利用できます
  • WebXR Device API や WebGL によって,ブラウザ経由でローレベルなハードウェア機能にアクセス可能に
  • 一方,それでもネイティブ AR ほどの複雑な処理は難しいところがあります

AR.js とは

AR の種類

  • ロケーション・ベース と ビジョン・ベース に大別されます

ロケーション・ベース

  • GPS を使った位置情報をトラッキングします
  • e.g. 近くの場所や建物に追加情報を表示します
  • A-Frame 版のみ

ビジョン・ベース

  • カメラが読み取ったオブジェクトをトラッキングします
  • e.g. 名刺,広告,展示物に追加情報を表示します

マーカー・ベース

  • QR コードや特定の画像が黒枠で囲われた専用のマーカーを対象とします
  • カメラによってマーカーが検出されると,コンテンツを表示します

イメージ・ベース

  • NFT (Natural Feature Tracking) ※ Non-Fungible Token ではありません
  • 一定の複雑さを持った画像を対象とします
  • マーカー・ベースと違い,黒枠で囲わなくても OK
  • マーカー・ベースよりも処理が重たい
  • e.g. 恐竜の画像から 3D の恐竜が出てくる

A-Frame

  • VR(仮想現実)体験を構築するための Web フレームワーク
  • HTML だけで簡単にはじめることができます
  • 内部で Three.js を利用
  • Entity Component System パターンを採用
  • A-Frame – Make WebVR
    https://aframe.io/

Entity Component System (ECS)

エンティティ

  • シーン内のすべてのオブジェクトのベースとなるコンテナー・オブジェクト

コンポーネント

  • 付加的な振る舞いや機能を追加するためのデータ・コンテナー
  • エンティティの振る舞いは,実行時にコンポーネントを追加あるいは削除することで変更可能

システム

  • コンポーネントのクラスに対して,グローバル・スコープ,マネジメント,サービスを提供
  • システムがロジックを処理し,コンポーネントがデータ・コンテナーとして機能することで責務を分離

簡単 3 Step で体験してみよう!

Step 0

  • AR.js に行く前に,まずは A-Frame (VR) から見てみましょう!

See the Pen a-frame sample by dsudo (@dsudo) on CodePen.

  • こちら からもご覧いただけます
  • スマホの場合は本体を動かすとオブジェクトの位置が変わります
ソースコード
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
    <title>Hello A-Frame</title>
  </head>

  <body>
    <a-scene>
      <a-sky
        color="#DDEEFF"
      >
      </a-sky>
      <a-box
        color="#4488FF"
        position="0 0 0"
        rotation="0 0 0"
        animation="property: rotation; to: 360 360 0; dur: 4000; easing: linear; loop: true"
      >
      </a-box>
      <a-camera
        position="0 0 2"
      >
      </a-camera>
    </a-scene>
  </body>
</html>
解説

0.ライブラリの読込

<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>

1.シーンの追加

  • a-scene
    • シーンはグローバル・ルート・オブジェクト
    • すべてのエンティティはシーン内に含まれます

2.エンティティの追加

  • a-entity
    • エンティティはプレースホルダー(入れ物)オブジェクト
    • コンポーネントによって,外観,動作,機能を付与できます

プリミティブ

特定のケースを実現するための組み込みのオブジェクト

  • a-sky
    • 背景色または 360° 画像をシーンに追加します
  • a-box
    • 立方体/直方体の形状を作成します
  • a-camera
    • ユーザーの視点を決定します
    • カメラエンティティの位置と回転を変更することで,ビューポートを変更できます

3.コンポーネントの追加

  • animation
    • エンティティにアニメーションを追加します
  • embedded
    • 既存の Web ページのレイアウト内へ埋め込みやすくします

Step 1 : HTML のみで AR を実現

  • それではいよいよ AR.js いってみましょう!
  • なんと A-Frame 版では HTML のみで実現できます
  • さらに AR.js Studio を使って GUI で作成することが可能です
  • とっても簡単ですね

つくるもの

  • マーカー・ベース AR
  • QR コードを読み込んだら Web アプリへ
  • QR コードのマーカーを認識してコンテンツが表示されます

1.QR コードの作成

  • URL から QR コードを作成します
  • QR Code Generator
    • 公開しようとしている URL を入力する
    • ダウンロード をクリックする
    • しばらく待つ(ダイアログは無視して OK)

2.3D モデルの取得

  • 3D モデルのフリー素材を探します
  • Free3D.com
Free3d.com

3D モデルの形式変換

  • 3D モデルの形式を WebAR 向けの glTF に変換します
  • 今回は元データが FBX でしたので,FBX から glTF に変換します
  • FBX2glTF を使います
    • Releases から実行ファイルを取得します
    • 以下のコマンドを実行します
      $ FBX2glTF /path/to/model.fbx
      
    • xxxx_out というディレクトリができるので zip します
      • glTF 以外の関連ファイルも必要なため固めておきます

glTF (GL Transmission Format)

  • JSON によって 3D モデルやシーンを表現するフォーマット
  • WebGL などを利用するアプリケーションの処理を効率化することが目的

3.HTML の生成

  • AR.js Studio で HTML を生成します

AR.js Studio

  • オープンソースの WebAR 作成プラットフォーム
  • Web 画面からの操作だけで WebAR アプリケーションが作成できます
  • そのまま GitHub にホスティングすることもできます
  • ロケーション・ベースとマーカー・ベースに対応しています
AR.js Studio
  • Marker-based を選択する
  • Start building をクリックする
AR.js Studio marker-based-01
  • 1 の Upload image をクリックし,作成したマーカーファイルを選択します
  • Download marker をクリックし,マーカーを取得します
AR.js Studio marker-based-02
  • 2 の Upload file をクリックし,作成した gltf の入った zip を選択します
  • ファイルが適切にアップロードできていればコンテンツが表示されます
AR.js Studio marker-based-03
  • 3 の Download package をクリックします

完成!

$ tree ar
ar
├── assets
│   ├── asset.gltf
│   └── marker.patt
└── index.html

1 directory, 3 files
  • A-Frame のバージョンが古いので最新化します
    • 1.0.4 -> 1.3.0

A-Frame の要素

  • a-assets
    • パフォーマンス向上のためのアセット管理システム
    • アセットを一か所に配置する
    • アセットをプリロードおよびキャッシュできる
      • ※ デバッグ時はキャッシュが効いて混乱する原因にもなる
  • gltf-model
    • glTF 形式のモデル(.gltf または .glb)を扱うコンポーネント
    • URL または a-asset-item 要素の id を指定します

Step 1 の動作確認

※ マーカーを表示するデバイスと WebAR 画面を表示するデバイスが必要です

  1. マーカー表示
  2. WebAR アプリ
    • QR コードから URL ジャンプします
      (もしくは こちら にアクセス)
    • カメラの利用を許可します
    • カメラをマーカーに向けるとコンテンツが表示されます

コンテンツの向き

  • マーカーが地面と並行のときにコンテンツが垂直に表示されます
  • PC 画面などでマーカーが垂直になっているとコンテンツを上から観た状態になります
  • マーカーが垂直の状態でコンテンツを正面に向けたい場合は rotation を指定します

トラブルシューティング

  • 「このサイトは権限を要求できません」と表示される場合
    • “他のアプリの上に重ねて表示アプリ” を OFF にする
    • 参考:Chromeブラウザ「このサイトは権限を要求できません」を解消する方法
      https://www.m-totsu.com/205/

AR.js Studio を使わない場合のマーカーの作成

  • AR.js Marker Training
    • UPLOAD をクリックして,QR コードのファイルを選択します
    • DOWNLOAD MARKER をクリックします
    • DOWNLOAD IMAGE をクリックします

Step 2 : エンティティの動的追加

  • 次はシーンにエンティティを動的に追加してみましょう!

つくるもの

  • ロケーション・ベース
  • 特定の位置にコンテンツを配置します
  • GPS による現在地との位置関係によってコンテンツが表示されます

1.位置座標の取得

PC

  • Google マップを開きます
  • 地図上の目的の場所を右クリックします
  • ポップアップウィンドウに緯度と経度表示されます
  • 緯度と経度を左クリックすると座標がクリップボードへコピーされます

Android

  • Google マップを開きます
  • 赤いピンがドロップされるまで地図上でラベルの付いていない場所を長押しします
  • 検索ボックスで座標を確認できます

iPhone / iPad

  • Google マップを開きます
  • 赤いピンがドロップされるまで地図上でラベルの付いていない場所を長押しします
  • 下部で “指定した地点” をタップして座標を確認できます

2.静的な HTML の生成(ロケーション・ベース)

  • まずはひな形と動的に生成しようとするエンティティを用意します
  • ソースコード

AR.js Studio

AR.js Studio location-based-01
  • Location-based を選択します
  • 先ほどのマーカーの代わりに座標を入力します

調整

  • look-at はいったん要らないので削除します

AR.js の要素

  • gps-camera
    • ロケーション AR を有効にするコンポーネント
    • camera エンティティに追加します
    • カメラの位置と回転からユーザーがデバイスをどこに向けているかを判断してくれます
    • 回転イベントを処理するために,付随して rotation-reader コンポーネントも追加します
  • gps-entity-place
    • エンティティに対して GPS トラッキングを可能にするためのコンポーネント
    • 特定のワールド座標を割り当ててくれます
    • ユーザーから離れているほどエンティティは小さく見えます
      • 遠すぎると全く見えません
  • Location Based
    https://ar-js-org.github.io/AR.js-Docs/location-based/

3.エンティティの動的追加

  • 先ほど生成した HTML を改造して,エンティティを動的に追加します
  • ソースコード

エンティティの削除

  • あとで動的に追加するので HTML の a-scene から a-entity は削除しておきます

JavaScript 追加

  • app.js を作成して body に script タグを追加します

エンティティを生成する関数

  • 座標,モデルの URL,スケールを受け取って a-entity を生成する関数を作成します
    • 公式のサンプルでは setAttribute で設定していましたが
      なぜか setAttribute が効かないので事前にすべて文字列で組み立てておいて
      document.createRange().createContextualFragment() で生成します
const createEntity = ({ location: { latitude, longitude }, model, scale: [x, y, z] }) => {
  const $entity = document.createRange().createContextualFragment(`
    <a-entity
      gltf-model="${model}"
      scale="${x} ${y} ${z}"
      gps-entity-place="latitude: ${latitude}; longitude: ${longitude};"
    ></a-entity>
  `)

  $entity.addEventListener(
    'loaded',
    () => window.dispatchEvent(new CustomEvent('gps-entity-place-loaded')),
  );

  return $entity;
};

シーンへ追加する関数

  • a-entity 要素を作成して a-scene 要素に追加します
const renderPlace = ({ location }) => {
  const $scene = document.querySelector('a-scene');
  const $entity = createEntity({
    location,
    model: '#asset-eevee',
    scale: ['0.5', '0.5', '0.5'],
  });
  $scene.appendChild($entity);
};

位置情報を取得する関数

  • 任意の静的な位置座標を取れる関数を作成します
const staticLoadPlaces = () => [
  {
    location: {
      latitude:   35.658581,
      longitude: 139.745433,
    },
  },
];

位置情報を元に基づいたエンティティの追加

  • navigator.geolocation.getCurrentPosition で現在位置が取得できたら
    staticLoadPlaces で位置情報を取得して renderPlace を呼びます
window.onload = () => (
  navigator.geolocation.getCurrentPosition(
    position => {
      console.log('success', position);
      staticLoadPlaces().forEach(renderPlace);
    },
  )
);

4.現在位置に基づくエンティティの配置

  • 位置情報を静的に実装してしまうとその場所に行かない限り使えません
  • 続いて現在位置に基づいてエンティティを配置してみましょう

相対位置を返す関数

  • 引数の位置座標から 北西,南西,北東,南東 に移動させます
const createPlaces = ({ latitude, longitude }) => [
  [-1, -1],
  [-1,  1],
  [ 1, -1],
  [ 1,  1],
].map(([cy,  cx]) => ({
  location: {
    latitude : latitude  + 0.000009 * cy,
    longitude: longitude + 0.000011 * cx,
  },
}));
座標の角度と距離の対応
  • 緯度 35~36 のときの 1m のおおよその角(度)
    • 緯度 0.000009
    • 経度 0.000011
  • 参考

現在位置を渡して実行

navigator.geolocation.getCurrentPosition(
  position => createPlaces(position.coords).forEach(renderPlace)
);

完成!

Step2 の動作確認

  • WebAR アプリページにアクセスします
  • カメラを許可します
  • 位置情報を許可します
  • 辺りを見渡すとコンテンツが表示されているはずです

コンテンツのブレの軽減

  • 「イーブイちゃんが震えていて寒そう」ということでブレ対策
  • カメラにコンポーネントを追加します
<a-camera
  gps-camera 
  arjs-look-controls='smoothingFactor: 0.1'
  rotation-reader
></a-camera>

Step 3 : インタラクションの追加

  • 最後のステップでは,インタラクションを追加して,モデル(エンティティ)を動的に変更してみます

つくるもの

  • Step 2 の拡張
  • ボタンをクリックするとモデルが切り替わります

1.アセットの追加

  • モデルに切り替えるために別の 3D 素材も入手します
  • Step 1 の手順でそれぞれ FBX から glTF に変換します

使用するモデル

要素の追加

  • a-assetsa-asset-item を追加します
<a-assets>
  <a-asset-item
    id="asset-eevee"
    src="./assets/eevee.gltf"
  ></a-asset-item>
  <a-asset-item
    id="asset-jolteon"
    src="./assets/jolteon.gltf"
  ></a-asset-item>
  <a-asset-item
    id="asset-vaporeon"
    src="./assets/vaporeon.gltf"
  ></a-asset-item>
  <a-asset-item
    id="asset-flareon"
    src="./assets/flareon.gltf"
  ></a-asset-item>
</a-assets>

2.ボタンと表示領域の追加

  • インタラクションのためのボタンと現在の情報を表示するための領域を追加します
<div class="centered">
  <div class="info">...</div>
</div>
<div class="centered command">
  <button class="btn" data-action="change"></button>
</div>

3.クリックイベント処理の追加

  • ボタンクリックでモデルが切り替わるようにします

モデル情報リスト

  • モデルごとの情報をリストにしておきます
const models = [
  {
    url: '#asset-eevee',
    scale: ['0.5', '0.5', '0.5'],
    info: 'Eevee',
  },
  ...
];
  • url
    • モデルのパス
    • 今回は a-assets に追加済みなので a-asset-item の ID
    • 直接モデルの URL でも可
  • scale
    • モデルのスケール
    • 本来はモデルの方を修正しておくのが筋!?
  • info
    • モデルの情報
    • 画面に表示します

コンテンツ配置処理の修正

  • 現在のカウント値によってどのモデルを使うかを選択します
    models[count % models.length]
    
  • モデルの info を表示領域の innerText に設定します 1
  • エンティティの変更はいったん removeChild で削除して,あらたに生成したエンティティを appendChild で追加します(前述のとおり setAttribute が効かないため)
  • エンティティはイベントリスナー内で引くために id を設定しておきます
  • イベントリスナーは一回実行されたらリスナー登録を解除して renderPlace を再帰的に呼び出します(その結果もう一度イベントリスナーが登録されます)
const renderPlace = ({ location }, count) => {
  const { url, scale, info } = models[count % models.length];

  const $info = document.querySelector('.info');
  $info.innerText = info;

  const $scene = document.querySelector('a-scene');
  const entityId = `e-${Math.floor(Math.random() * 100000)}`;
  const $entity = createEntity({ id: entityId, location, model: url, scale });
  $scene.appendChild($entity);

  const $button = document.querySelector('button[data-action="change"]');
  const buttonClickEventListener = () => {
    $button.removeEventListener('click', buttonClickEventListener, false);
    $scene.removeChild(document.getElementById(entityId));
    renderPlace({ location }, count + 1);
  }
  $button.addEventListener('click', buttonClickEventListener);
};
  • 初回の renderPlace を count: 0 で呼び出せば OK
  • ボタンクリックされるたびに count が増加し,モデルが切り替わります
navigator.geolocation.getCurrentPosition(
  position => createPlaces(position.coords)
    .forEach(place => renderPlace(place, 0))
);

4.アニメーションの追加

  • a-entityanimation コンポーネントを追加します(Step 0 の box と同じ)
animation="property: rotation; to: 0 360 0; dur: 4000; easing: linear; loop: true"
  • animation
    • property
      • アニメーションのためのコンポーネントを指定
      • rotation もコンポーネントの一種
        • "${x} ${y} ${z}" で各軸に対する回転角を指定
        • e.g.
          <a-entity rotation="45 90 180"></a-entity>
          
    • from
      • アニメーション開始時のプロパティの値
      • デフォルトはエンティティの現在の値
    • to
      • アニメーション終了時のプロパティの値
    • dur
      • アニメーションサイクルの開始から終了までの時間(ミリ秒)
    • easing
      • アニメーションの動き方(加速度の変化)
      • 詳細 easings
    • loop
      • アニメーションを繰り返す回数
      • true を設定した場合は無限

完成!

Step3 の動作確認

  • WebAR アプリページにアクセスします
  • カメラを許可します
  • 位置情報を許可します
  • ボタンをクリックするとモデルが切り替わります

おまけ:テキスト表示

  • 画面を開くとコンテンツが配置されます
  • その場所から移動すると元の場所付近にコンテンツが置かれたままになります(わりとズレます)

ライブラリ

  • コンテンツを常にカメラに向けるためのライブラリを追加します
<script src="https://unpkg.com/aframe-look-at-component@0.8.0/dist/aframe-look-at-component.min.js"></script>

テキストを生成する関数

  • エンティティの生成関数と同じ感じで a-text を生成する関数を用意します
  • look-at="[gps-camera]" 属性を付与します
const createText = ({ id, location: { latitude, longitude }, value }) => {
  const $text = document.createRange().createContextualFragment(`
    <a-text
      id="${id}"
      value="${value}"
      look-at="[gps-camera]"
      scale="0.5 0.5 0.5"
      gps-entity-place="latitude: ${latitude}; longitude: ${longitude};"
    ></a-text>
  `);

  $text.addEventListener(
    'loaded',
    () => window.dispatchEvent(new CustomEvent('gps-entity-place-loaded')),
  );

  return $text;
};
  • entity と同じタイミングで text も appendChild / removeChild します
  • model の info を value に渡せば OK です

おしまい

  • お手軽に AR 体験ができて楽しかったです
  • イーブイちゃんかわいい!!

参考

  1. 多重度おかしいですが,すべての位置に同じモデルを使う想定なので...

45
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
41