いなにわうどん

うどんの話に見せかけて技術的な話をしたい(できない)

【WebAR】世界最速、AR で「令和」発表を再現(デモあり)

先日、元号の発表がありました。令和、響きが好きです。万葉集出典というのも趣があります。

元号ビジネスも大隆盛で、4月1日の号外が高額転売されたり、各社こぞって「令和コップ」「令和ファイル」「令和鉛筆」などの記念商品を発売する中、AR令和(拡張現実)がないじゃないか!!と思い立ったので作ってみました。世界最速実装です(たぶん)。

実装要件

ARマーカーにカメラをかざすと「新しい元号は『令和』であります」という音声とともに元号の書が自立する拡張現実を目指します。


元号をあなたの手に

百聞は一見に如かずと言いますので、実際に作動するデモをご用意しました。
iPhone, Android, Webカメラを搭載したPC・タブレットからお試しいただけます。

上記のマーカーを印刷、あるいは画面上に表示させ、もう一台のデバイスから下のリンクにアクセスし、カメラをマーカーに向けると「令和」の書が浮かび上がります。

AR for Reiwa, Japanese Next Era
https://ar-reiwa.netlify.com/
(カメラの使用許可を求めるダイアログが表示されます。古いOSでは動作しない場合があります。iOS端末の場合は音声が流れません。)

実装編

ここからは実際に製作するまでの流れを概説していきます。難易度はそれほど高くないので、プログラミング未経験でも大歓迎です。

1.オブジェクトを作ってみよう

Blenderでささっと3Dモデルを作ります。
「令和」の額縁に注目してみます。計測してみたところ、額縁の縦横の比率はwidth:height=4:5.3 といった具合でした。書のテクスチャについては、以下のサイトから素材を引用させて頂きます。

元号「令和」のベクトルロゴデータをダウンロード|Tamoc
https://tamoc.com/download-meiwa-logo/

後程判明したのですが、差分でブーリアン演算を適応すると、Webで表示した際にただの長方形になってしまうというバグ(?)が判明したため、愚直に長方形を結合して額縁に仕上げました。
Cyclesレンダーではマテリアルの書き出しが出来ないため(下図はCycleレンダーで出力)、標準のBlenderレンダーに設定。マテリアル・UVマップを適当に設定し、objファイルとしてエクスポートします。


2.Webから3Dを見てみよう

A-frameという定石ライブラリを利用します。従来のThree.jsなどはJavaScriptから煩雑な操作を行う必要がありましたが、このA-frameはHTML上にタグを設置するだけで容易にWebGLが扱えるという優れモノです。
aframe.io

CDNからスクリプトを読み込みます。

<!doctype html>
<html>
<head>
	<title>Simulation for Reiwa, Japanese Next Era</title>
	<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
</head>
<body style="margin:0;padding:0">
<a-scene>
	<a-assets>
		<a-asset-item id="frame-obj" src="frame.obj"></a-asset-item>
		<a-asset-item id="frame-mtl" src="frame.mtl"></a-asset-item>
		<a-asset-item id="paper-obj" src="paper.obj"></a-asset-item>
		<a-asset-item id="paper-mtl" src="paper.mtl"></a-asset-item>
	</a-assets>
	<a-obj-model src="#frame-obj" mtl="#frame-mtl" scale="1 1 1" position="0 0 -2" rotation="-10 0 0"></a-obj-model>
	<a-obj-model src="#paper-obj" mtl="#paper-mtl" scale="1 1 1" position="0 0 -2" rotation="-10 0 0"></a-obj-model>
	<a-light type="spot" color="#fff" position="-1 2 10" intensity="0.1"></a-light>
	<a-light type="ambient" color="#fff" position="-1 2 10" intensity="1"></a-light>
	<a-camera position="-4 -3 4" fov="60"></a-camera>
</a-scene>
</body>
</html>

<a-scene> というタグでVR空間を定義します。debugUIEnabled:false; を指定することで、デバッグ用画面を削除しています。まあ、おまじないとして憶えて下さい。(ゴミプログラマーLv.99の思考)

続いて、<a-assets> で、先程出力した3Dオブジェクトをインポートしています。reiwa.objが実際のモデル、reiwa.mtlはマテリアル(装飾)情報です。読み込んだオブジェクトは <a-obj-model src="#id" mtl="#id"> で表示させることが可能です。

<a-camera> はカメラを、<a-light> はライティングを表します。a-lightのtypeには spot-light(スポットライト)、ambient-light(環境光)をそれぞれ設定しています。

下記リンクからサンプルをお試しいただけます。
画面をドラッグすることで視線をぐりぐりと動かすことが可能です。

Simulation for Reiwa, Japanese Next Era
https://ar-reiwa.netlify.com/3d.html

3.マーカーに重ねてみよう

Web上で拡張現実を実現する AR.js なるライブラリが存在しています。
こちらもCDNから読み込みます。最新版を読み込むのがベターかも。

続いて以下のサイトより、WebAR用のマーカーを作成します。

AR.js Marker Training
https://jeromeetienne.github.io/AR.js/three.js/examples/marker-training/examples/generator.html

Helveticaで組んだ「2019」をアップロードし、pattファイル、pngファイルの双方をダウンロードします。pngファイルはマーカーとして使用するための画像ファイルであり、pattファイルはAR.jsが認識すべきマーカーを示したファイルです。

<!doctype html>
<html>
<head>
	<title>AR for Reiwa, Japanese Next Era.</title>
	<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
	<script src="https://cdn.rawgit.com/jeromeetienne/AR.js/1.6.2/aframe/build/aframe-ar.js"></script>
</head>
<body style="margin:0;padding:0">
<a-scene embedded arjs="debugUIEnabled:false;">
	(中略)
	<a-marker  type="pattern" url="pattern-marker.patt">
		<a-obj-model src="#reiwa-obj" mtl="#reiwa-mtl" scale="1 1 1" position="0 0 -2" rotation="-10 0 0"></a-obj-model>
		<a-obj-model src="#paper-obj" mtl="#paper-mtl" scale="1 1 1" position="0 0 -2" rotation="-10 0 0"></a-obj-model>
	</a-marker>
	(中略)
	<a-camera></a-camera>
</a-scene>
</body>
</html>

おっ、<a-marker> などという輩が出てきました。これがマーカーを認識するためのタグでして、この中にオブジェクトを入れることで、マーカー上に重ねて表示することが可能となります。a-camera は画面と連動して動くため、positionの指定は不要です。

4.元号を動かしてみよう

「新しい元号は―」という菅官房長官のお言葉とともに元号ハンドパワーで自立させてみます。

元号発表の動画から音声を抽出して不要部分を削除し、10秒程度に圧縮したmp3ファイルを用意しました。また、<a-obj-model> という要素をa-obj-modelに内包させることで、アニメーションを設定しています。attribute="rotation" でアニメーションの種類を回転に、delay="3000" で3000ミリ秒(=3病)遅延、from="90 0 0" to="-10 0 0" でx軸に90度→-10度の傾き、といった具合に指定します。

<body style="margin:0;padding:0">
<a-scene embedded arjs="debugUIEnabled:false;">
	(中略)
	<a-marker type="pattern" url="pattern-marker.patt" registerd-events>
		<a-obj-model src="#reiwa-obj" mtl="#reiwa-mtl" scale="1 1 1" position="0 -3 0" rotation="90 0 0">
			<a-animation attribute="rotation" delay="3000" from="90 0 0" to="-10 0 0"
				direction="normal" dur="2000"   repeat="0" easing="ease" begin="marker-found" end="marker-lost"></a-animation>
			<a-animation attribute="position" delay="3000" from="0 -3 0" to="0 0 -2"
				direction="normal" dur="2000" repeat="0" easing="ease" begin="marker-found" end="marker-lost"></a-animation>
		</a-obj-model>
		<a-obj-model src="#paper-obj" mtl="#paper-mtl" scale="1 1 1" position="0 -3 -2" rotation="90 0 0">
			<a-animation attribute="rotation" delay="3000" from="90 0 0" to="-10 0 0"
				direction="normal" dur="2000" repeat="0" easing="ease" begin="marker-found" end="marker-lost"></a-animation>
			<a-animation attribute="position" delay="3000" from="0 -3 0" to="0 0 -2"
				direction="normal" dur="2000" repeat="0" easing="ease" begin="marker-found" end="marker-lost"></a-animation>
		</a-obj-model>
	</a-marker>
	(中略)
	<a-sound src="reiwa.mp3" preload position="-1 -1 3" begin="marker-found" end="marker-lost"></a-sound>
</a-scene>
</body>

とここでご注目いただきたいのがa-animationとa-soundの属性。begin="marker-found" end="marker-lost" という代物が設定されています。
これはなんぞや?というお話です。

アニメーションとARの動機

無指定の場合、アニメーションは基本的にページの読み込みが完了した時点でスタートします。ARマーカーを認識したタイミングでアニメーションが発動するように改良を加えます。

マーカーの認識して初めてアニメーションが作動し音声が流れるよう、begin, end属性を通じて、アニメーションのタイミングを指定しています。marker-found, maker-lost というイベントをjsから発火させてあげることで、アニメーションの再生/停止を制御することが可能になるわけです。

a-marker要素に registed-events という属性を設定し、AFRAME.registerComponent から インストラクタを指定することでマーカーへのイベントハンドラの設定が可能になります。マーカーを認識した際、マーカーを見失った際にそれぞれ makerFound, makerLost というイベントが発火するため、DOMでa-obj-model要素を取得し、addEventListener(JavaScriptでよく使うよね!!)にコールバック関数を指定することで、イベントを発生させています。
同様に a-audio タグを取得し、components.sound.play[stop]Sound() 関数を実行することで、音声の再生/停止の制御も可能となります。

AFRAME.registerComponent("registerd-events", {
	init : function() {
		var marker = this.el;
		var obj_model = document.getElementsByTagName("a-obj-model");

		marker.addEventListener("markerFound", function() {
			for (var i = 0; i < obj_model.length; i++)
				obj_model[i].emit("marker-found");
			var sound = document.getElementsByTagName("a-sound")[0];
			sound.components.sound.playSound();
		});
		marker.addEventListener("markerLost", function() {
			for (var i = 0; i < obj_model.length; i++)
				obj_model[i].emit("marker-lost");
			var sound = document.getElementsByTagName("a-sound")[0];
			sound.components.sound.stopSound();
		});
	}
});

(サンプルは記事冒頭に掲載したため割愛)

5.公開してみよう

セキュリティの観点上、localhosthttps以外でのWebRTCの使用は制限されているため、セキュアな環境で動作される必要があります。
今回は、無料で使用可能なNetlifyという超・高機能ホスティングサービスを利用しました。
www.netlify.com

詳細は以下の記事に解りやすくまとまっています。
qiita.com

開発者としてあるまじく私はGitHubを使ったことがないので、ドロップアンドドロップでファイルを転送します。

<!-- 祝☆完成!-->

結びにかえて

ARを試している間に5回ほどPCがフリーズしてしまいました。そろそろMacBookが欲しいです。

さて個人的な話ですが、ARに挑戦するのは実は二回目であります。2年半前にARやりたいなーと思った際はAR.jsなどという便利なライブラリは存在せず……というよりiPhoneSafariがまだカメラの制御に対応していませんでした。Web開発の進展に感銘を受けた次第です。AR.jsはまだまだ実装途中といった段階で、認識が不正確だったり表示が乱れたりと課題もあります。今後の発展に期待したいと思います。

え、ARなんか使い道ないんじゃないの――って?ほら、あれですよ。年賀状

「AR元号」年賀状、来るぞ

毎年少しずつ姿を潜めゆく年賀状。最近の年賀状業界ではARがアツいらしく、各社がこぞって「スマホをかざすと動く」年賀状を発売しています。スマホ上で見るならLINEでええやんなどという指摘はさておき、個人的な直感ですが今年は「AR元号」、確実にブーム到来と踏んでおります。街中でそのような商品を見かけた際には、これ俺が最初に実装したんやでwwとイキってみる所存です。

年賀状ビジネスを手掛ける写真館の皆様方、導入の程ご検討されてみては如何でしょうか。


――数十年後の元号改正の際はどのような発表手法になるのでしょうか。私はAR元号に、一票。