恒例の年末に記事を書くやつ向けの記事です。
今回は AR.js についてご紹介します。
(過去の記事)
- 2019 - Three.js
- 2020 - Google Earth Studio
- 2021 - Deck.gl
この記事でやってみること
- 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 とは
- Web AR のための軽量ライブラリ
- マーカー認識/トラッキングに JSARToolKit5 を使用します
- 拡張コンテンツの表示に Three.js または A-Frame を使用します
- オープンソース
- AR.js リポジトリ内は MIT License
- artoolkit5-js は LGPLv3 License
- AR.js Documentation
https://ar-js-org.github.io/AR.js-Docs/ - GitHub(現在のバージョン:v3)
https://github.com/AR-js-org/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)
- 主に 3D ゲーム開発に使用されるソフトウェア・アーキテクチャ・パターン
- 継承よりコンポジションの原則に従うことで,より柔軟にエンティティを定義
- Entity-Component-System
https://aframe.io/docs/1.3.0/introduction/entity-component-system.html
エンティティ
- シーン内のすべてのオブジェクトのベースとなるコンテナー・オブジェクト
コンポーネント
- 付加的な振る舞いや機能を追加するためのデータ・コンテナー
- エンティティの振る舞いは,実行時にコンポーネントを追加あるいは削除することで変更可能
システム
- コンポーネントのクラスに対して,グローバル・スコープ,マネジメント,サービスを提供
- システムがロジックを処理し,コンポーネントがデータ・コンテナーとして機能することで責務を分離
簡単 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.コンポーネントの追加
Step 1 : HTML のみで AR を実現
- それではいよいよ AR.js いってみましょう!
- なんと A-Frame 版では HTML のみで実現できます
- さらに AR.js Studio を使って GUI で作成することが可能です
- とっても簡単ですね
つくるもの
- マーカー・ベース AR
- QR コードを読み込んだら Web アプリへ
- QR コードのマーカーを認識してコンテンツが表示されます
— dsudo (@_dsudo) December 6, 2022
1.QR コードの作成
- URL から QR コードを作成します
-
QR Code Generator
- 公開しようとしている URL を入力する
- ダウンロード をクリックする
- しばらく待つ(ダイアログは無視して OK)
2.3D モデルの取得
- 3D モデルのフリー素材を探します
- 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 にホスティングすることもできます
- ロケーション・ベースとマーカー・ベースに対応しています
- Marker-based を選択する
- Start building をクリックする
- 1 の Upload image をクリックし,作成したマーカーファイルを選択します
- Download marker をクリックし,マーカーを取得します
- 2 の Upload file をクリックし,作成した gltf の入った zip を選択します
- ファイルが適切にアップロードできていればコンテンツが表示されます
- 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 を指定します
- glTF 形式のモデル(
Step 1 の動作確認
※ マーカーを表示するデバイスと WebAR 画面を表示するデバイスが必要です
- マーカー表示
- QR Code Marker を表示します
- WebAR アプリ
- QR コードから URL ジャンプします
(もしくは こちら にアクセス) - カメラの利用を許可します
- カメラをマーカーに向けるとコンテンツが表示されます
- 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 による現在地との位置関係によってコンテンツが表示されます
— dsudo (@_dsudo) December 6, 2022
1.位置座標の取得
- コンテンツを配置する座標を取得します
- 参考
- 緯度と経度の座標で位置を確認または検索する
https://support.google.com/maps/answer/18539
- 緯度と経度の座標で位置を確認または検索する
PC
- Google マップを開きます
- 地図上の目的の場所を右クリックします
- ポップアップウィンドウに緯度と経度表示されます
- 緯度と経度を左クリックすると座標がクリップボードへコピーされます
Android
- Google マップを開きます
- 赤いピンがドロップされるまで地図上でラベルの付いていない場所を長押しします
- 検索ボックスで座標を確認できます
iPhone / iPad
- Google マップを開きます
- 赤いピンがドロップされるまで地図上でラベルの付いていない場所を長押しします
- 下部で “指定した地点” をタップして座標を確認できます
2.静的な HTML の生成(ロケーション・ベース)
- まずはひな形と動的に生成しようとするエンティティを用意します
- ソースコード
AR.js Studio
- AR.js Studio で HTML を生成します
- 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() で生成します
- 公式のサンプルでは setAttribute で設定していましたが
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
- 参考
- 緯度・経度の 1度はどれくらいの長さがあるのか
https://www.wingfield.gr.jp/archives/9721
- 緯度・経度の 1度はどれくらいの長さがあるのか
現在位置を渡して実行
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 の拡張
- ボタンをクリックするとモデルが切り替わります
— dsudo (@_dsudo) December 6, 2022
1.アセットの追加
- モデルに切り替えるために別の 3D 素材も入手します
- Step 1 の手順でそれぞれ FBX から glTF に変換します
使用するモデル
要素の追加
-
a-assets
にa-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>
- 適当な CSS も追加します
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-entity
にanimation
コンポーネントを追加します(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 を設定した場合は無限
- property
完成!
Step3 の動作確認
- WebAR アプリページにアクセスします
- カメラを許可します
- 位置情報を許可します
- ボタンをクリックするとモデルが切り替わります
おまけ:テキスト表示
- テキストを追加してみます
- ソースコード
— dsudo (@_dsudo) December 6, 2022
- 画面を開くとコンテンツが配置されます
- その場所から移動すると元の場所付近にコンテンツが置かれたままになります(わりとズレます)
ライブラリ
- コンテンツを常にカメラに向けるためのライブラリを追加します
<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 体験ができて楽しかったです
- イーブイちゃんかわいい!!
参考
- ARとは?ロケーションベースとビジョンベースの開発事例と合わせて紹介
https://relipasoft.com/blog/ar-locationbased/ - AR Over the Web Browser Using AR.js, A-Frame, & WebXR
https://www.youtube.com/watch?v=wPJ6LHtV844 - Build your Location-Based Augmented Reality Web App
https://medium.com/chialab-open-source/build-your-location-based-augmented-reality-web-app-c2442e716564 - Blender + AR.js + A-FrameでWebARを作る方法
https://zenn.dev/aknecco/articles/05b2c38724f66d - AR.js + A-frame触ってみた
https://qiita.com/miya-watanabe/items/f833d648ef878c878c8e
-
多重度おかしいですが,すべての位置に同じモデルを使う想定なので... ↩