先日、新元号の発表がありました。令和、響きが好きです。万葉集出典というのも趣があります。
元号ビジネスも大隆盛で、4月1日の号外が高額転売されたり、各社こぞって「令和コップ」「令和ファイル」「令和鉛筆」などの記念商品を発売する中、AR令和(拡張現実)がないじゃないか!!と思い立ったので作ってみました。世界最速実装です(たぶん)。
実装編
ここからは実際に製作するまでの流れを概説していきます。難易度はそれほど高くないので、プログラミング未経験でも大歓迎です。
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
<!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(); }); } });
(サンプルは記事冒頭に掲載したため割愛)