👀 はじめに
この記事はAR Advent Calendar 2020の24日目の記事です。
こんにちは。フリーランスのウェブフロントエンドエンジニア、@ki_230 です。
学生時代はARの研究をしていて、ガラケーと車窓を使ったARをつくったりしてました。
ただ、この作品はARといっても、マーカーも無ければ風景を認識しているわけでもなく、ただただプラ板にウェブコンテンツ(Flash lite)を映して現実の景色とオーバーレイさせているだけでした。デバイスがガラケーだったこともあり、当時はそれが限界だったのです。
時は過ぎて、2020年。
AR技術の進歩は凄まじく、簡単なARコンテンツであれば、WebARとしてブラウザのみで体験できるようになりました。
いや。10年前でもFlashを使えばWebARは手軽に実装できた(ただしiPhoneでは動かない)わけなので、とりわけ新しい技術ではなく、WebAR自体は、むしろ枯れた技術と言えるかもしれません。
しかし、個人的には、マーカー型のWebARにはまだまだ可能性があると感じているので、マーカーの状態を検知するWebARコンテンツのモックを何本か作り、そのポテンシャルを確認してみました。本記事ではサンプルコードとともに紹介します。
A-Frameとは
今回紹介するモックはすべてA-Frameで実装しています。
A-FrameはThree.jsをラップした、WebVR、WebAR用のフレームワークです。
もともとVR・ARコンテンツはUnityを使って実装していたのですが、最終的にWebアプリとして書き出すのであれば、はじめからWebで実装してしまえと思い、最近使い始めました。
A-FrameでARコンテンツをつくる
WebARを実装できることでおなじみのライブラリ、AR.js。こちらを使ってWebARを実現します。
使い方ですが、AR.jsにはA-Frame用のスクリプトがあるので、それを読み込むだけでOKです。
リポジトリをみると、
- aframe-ar-location-only.js
- aframe-ar-nft.js
- aframe-ar.js
の3種類が用意されているのですが、今回はaframe-ar.jsを使います。
マーカーではなく画像をトラッキングしたい場合はaframe-ar-nft.jsを使いましょう。
読み込み方は、
<script src="https://raw.githack.com/AR-js-org/AR.js/3.3.1/aframe/build/aframe-ar.js"></script>
と、バージョン(3.3.1)を指定して読み込むか、バージョンを指定せずに、
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>
と読み込むかですが、今回は3.3.1で固定します。
また、A-FrameとAR.jsを使えばスマホからでも観覧可能なWebARコンテンツが簡単に作成できるのですが、今回は時間短縮のためにPC版のChromeのみをターゲットにしたデモを作りました。
ARの機能自体はスマホでも動くのですが、音を再生するデモがいくつかあり、スマホだと「音の再生がユーザーきっかけじゃないといけない」という制約に引っかかるからです。
こちらの制約は数行スクリプトを書けば簡単に回避できるのですが、本質ではないコードが紛れ込むとわかりにくくなるので、思い切ってPC版Chromeのみを対象としています。
マーカーをつくる
こちらのサイトに画像をアップすると、マーカーを作成することができます。
マーカーのデータは .pattファイル としてダウンロードできるので、それを読み込んで使いましょう。
<a-marker type='pattern' url='./pattern.patt'></a-marker>
という感じで.pattファイルのパスを指定してあげればOKです。
この記事内のデモでは、基本的にこちらの画像をマーカーとして使っています。
ミニマルなWebARの例
ソースコード
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>box</title> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no" /> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> <script src="https://raw.githack.com/AR-js-org/AR.js/3.3.1/aframe/build/aframe-ar.js"></script> </head> <body> <a-scene embedded arjs="debugUIEnabled: false" device-orientation-permission-ui="enabled: false" vr-mode-ui="enabled: false" > <a-marker type='pattern' url='./pattern.patt'> <a-box></a-box> </a-marker> <a-entity camera></a-entity> </a-scene> </body> </html>
マーカーの上に立方体を表示するだけのWebARであればこれだけで実装できます。
非常にお手軽です。
前置きが長くなりましたが、ここから本題に入ります。
早速、マーカーの状態を検知していきましょう。
👀 マーカーが認識されたことを検知する
A-Frameでマーカーが認識されたことを検知するためには、
AFRAME.registerComponent('marker', { init: function () { const marker = this.el; marker.addEventListener('markerFound', function () { console.log('!'); }); } });
という感じでマーカーのmarkerFoundイベントに対してコールバック関数を設定します。
DEMO
隠し通路を発見した感じを体験できるARを作りました。 pic.twitter.com/CHDLdtH0va
— 君塚史高 (@ki_230) 2020年12月16日
👉 https://develop.kimizuka.org/ar-kaidan/secret/
敢えてマーカーを隠しておき、マーカー発見時に音を再生するように設定しています。
ソースコード
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>kakushi</title> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no" /> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> <script src="https://raw.githack.com/AR-js-org/AR.js/3.3.1/aframe/build/aframe-ar.js"></script> <style> * { margin: 0; padding: 0; } body { overflow: hidden; cursor: none; } </style> </head> <body> <script> let audio; AFRAME.registerComponent('marker', { init: function () { const marker = this.el; marker.addEventListener('markerFound', function () { if (audio) { audio.currentTime = 0; audio.play(); } }); marker.addEventListener('markerLost', function () { audio && audio.pause(); }); } }); window.onload = () => { audio = document.getElementById('audio'); }; </script> <a-scene embedded arjs="debugUIEnabled: false" device-orientation-permission-ui="enabled: false" vr-mode-ui="enabled: false" > <a-assets> <audio id="audio" src="audio.mp3"></audio> <img id="img"src="img.png" /> </a-assets> <a-marker marker type='pattern' url='./pattern.patt'> <a-plane rotation="-90 0 0" src="#img" ></a-plane> </a-marker> <a-entity camera></a-entity> </a-scene> </body> </html>
👀 マーカーの認識が切れたことを検知する
逆に、A-Frameでマーカーの認識が切れたことをキャッチするためには、
AFRAME.registerComponent('marker', { init: function () { const marker = this.el; marker.addEventListener('markerLost', function () { console.log('!'); }); } });
という感じでマーカーのmarkerLostイベントに対してコールバック関数を設定します。
DEMO
ルパンっぽいARを作りました。名付けて「エーアールパン」です。 pic.twitter.com/eHVkOjQq0h
— 君塚史高 (@ki_230) 2020年11月12日
👉 https://develop.kimizuka.org/arupin/
マーカーの上に人が乗っかると認識が切れるので、そのタイミングで音を再生しつつ、画面を赤く点滅させています。
ソースコード
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>arupin</title> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no" /> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> <script src="https://raw.githack.com/AR-js-org/AR.js/3.3.1/aframe/build/aframe-ar-nft.js"></script> <style> * { margin: 0; padding: 0; } body { overflow: hidden; cursor: none; } .red { position: absolute; top: 0; bottom: 0; left: 0; right: 0; background: rgba(255, 0, 0, .4); opacity: 0; } .red.show { animation: blink .4s ease-in-out infinite; } @keyframes blink { 0% { opacity: 0; } 100% { opacity: 1; } } </style> </head> <body> <script> let red; let audio; AFRAME.registerComponent('marker', { init: function () { const marker = this.el; marker.addEventListener('markerFound', function () { red && red.classList.remove('show'); audio && audio.pause(); }); marker.addEventListener('markerLost', function () { red && red.classList.add('show'); if (audio) { audio.currentTime = 0; audio.play(); } }); } }); window.onload = () => { red = document.querySelector('.red'); audio = document.getElementById('audio'); }; </script> <a-scene embedded arjs="debugUIEnabled: false" device-orientation-permission-ui="enabled: false" vr-mode-ui="enabled: false" > <a-assets> <audio id="audio" src="buzzer.mp3"></audio> </a-assets> <a-marker marker type='pattern' url='./pattern.patt'> <a-plane color="#000" width="10" height="10" position="0 0 0" rotation="-90 0 0" material="opacity: .0" ></a-plane> <a-cylinder color="#f00" position="0 2 0" height="10" radius=".02" rotation="90 0 -30" material="opacity: .4" ></a-cylinder> <a-cylinder color="#f00" position="1 1 1" height="10" radius=".02" rotation="-5 0 30" material="opacity: .4" ></a-cylinder> <a-cylinder color="#f00" position="0 0 0" height="20" radius=".02" rotation="-45 0 0" material="opacity: .4" ></a-cylinder> <a-cylinder color="#f00" position="0 0 -3" height="10" radius=".02" rotation="80 0 90" material="opacity: .4" ></a-cylinder> <a-cylinder color="#f00" position="0 1 -2" height="10" radius=".02" rotation="-80 0 90" material="opacity: .4" ></a-cylinder> <a-cylinder color="#f00" position="-1 0 -1" height="10" radius=".02" rotation="-5 0 -30" material="opacity: .4" ></a-cylinder> <a-cylinder color="#f00" position="1 0 2" height="20" radius=".02" rotation="-25 0 0" material="opacity: .4" ></a-cylinder> <a-cylinder color="#f00" position="2 0 0" height="20" radius=".02" rotation="20 0 -125" material="opacity: .4" ></a-cylinder> <a-cylinder color="#f00" position="-1 0 1.5" height="20" radius=".02" rotation="-5 0 -5" material="opacity: .4" ></a-cylinder> </a-marker> <a-entity camera></a-entity> </a-scene> <div class="red"></div> </body> </html>
思ったこと
基本的にARはマーカーを認識する技術なので、切れたときにイベントを設定すると一風変わった体験が作れる可能性を感じています。
エアギター用のTシャツを作りました。マーカーを認識するとギターが現れて、マーカーの認識が切れると音が出ます。 pic.twitter.com/qlBozEPvJv
— 君塚史高 (@ki_230) 2020年12月1日
マーカー内の文字が流れていくARをつくりました。名付けて「ARマーキー」です。 pic.twitter.com/E2GSlNukSd
— 君塚史高 (@ki_230) 2020年12月17日
3秒以上、上に物を乗せっぱなしにすると爆発音がするマーカーを作りました。ARの認識が切れた秒数をカウントしてます。 pic.twitter.com/kNJjqM59jb
— 君塚史高 (@ki_230) 2020年11月11日
👀 マーカーの移動を検知する
カメラを固定にしておくことが前提になりますが、前のフレームのマーカーのポジションとの比較を行うことで、マーカーが移動したか否かを判定することができます。
簡単な動体検知程度なら耐えうる精度がでます。
DEMO
ARマーカーの動体検知。 pic.twitter.com/J2QFS7lE1J
— 君塚史高 (@ki_230) 2020年11月17日
👉 https://develop.kimizuka.org/ar-motion-detection/position.html
マーカーが止まっているときに「STOP」、移動しているときに「MOVE」と表示されます。
ソースコード
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>motion-detection</title> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no" /> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> <script src="https://raw.githack.com/AR-js-org/AR.js/3.3.1/aframe/build/aframe-ar.js"></script> <style> * { margin: 0; padding: 0; } body { overflow: hidden; cursor: none; } </style> </head> <body> <script> let lastDelta = 0; let lastPos; AFRAME.registerComponent('marker', { tick: function(delta) { if (delta - lastDelta < 100) { return; } const marker = this.el; const text = document.querySelector('a-text'); text.setAttribute('color', 'red'); text.setAttribute('value', 'STOP'); const pos = marker.getAttribute('position'); if (lastPos) { const diffPosX = pos.x - lastPos.x; const diffPosY = pos.y - lastPos.y; const diffPosZ = pos.z - lastPos.z; const diffPos = Math.sqrt(Math.pow(diffPosX * 10, 2) + Math.pow(diffPosY * 10, 2) + Math.pow(diffPosZ * 10, 2)); if (diffPos > .1) { text.setAttribute('color', 'blue'); text.setAttribute('value', 'MOVE'); } } lastDelta = delta; lastPos = Object.assign({}, pos); } }); </script> <a-scene embedded arjs="debugUIEnabled: false" device-orientation-permission-ui="enabled: false" vr-mode-ui="enabled: false" > <a-marker marker type='pattern' url='./pattern.patt'> <a-box wireframe="true" ></a-box> <a-text align="center" position="0 1 0" value="STOP" ></a-text> </a-marker> <a-entity camera></a-entity> </a-scene> </body> </html>
思ったこと
マーカーが移動した時のみアニメーションするデモを作ってみたのですが、なかなかユニークなものに仕上がった気がします。
ただ、通常のARはユーザーがスマホを持って体験するものなのでカメラが定点にならないので注意が必要です。
「動いた時に3Dモデルが足踏みするARマーカー」を取り付けることで、楽しく掃除ができるフローリングモップを作りました。 pic.twitter.com/LyIPTWiBlz
— 君塚史高 (@ki_230) 2020年11月17日
👀 マーカーの回転を検知する
DEMO
ARマーカーの回転検知。 pic.twitter.com/W6ukDJorN8
— 君塚史高 (@ki_230) 2020年11月19日
👉 https://develop.kimizuka.org/ar-motion-detection/rotation.html
マーカーが止まっているときに「STOP」、回転しているときに「ROTATE」と表示されます。
ソースコード
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>motion-detection</title> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, shrink-to-fit=no" /> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> <script src="https://raw.githack.com/AR-js-org/AR.js/3.3.1/aframe/build/aframe-ar.js"></script> <style> * { margin: 0; padding: 0; } body { overflow: hidden; cursor: none; } </style> </head> <body> <script> let lastDelta = 0; let lastRot; AFRAME.registerComponent('marker', { tick: function(delta) { if (delta - lastDelta < 100) { return; } const marker = this.el; const text = document.querySelector('a-text'); text.setAttribute('color', 'red'); text.setAttribute('value', 'STOP'); const rot = marker.getAttribute('rotation'); if (lastRot) { const diffRotX = rot.x - lastRot.x; const diffRotY = rot.y - lastRot.y; const diffRotZ = rot.z - lastRot.z; const diffRot = Math.abs(diffRotX) + Math.abs(diffRotY) + Math.abs(diffRotZ); if (diffRot > 3) { text.setAttribute('color', 'green'); text.setAttribute('value', 'ROTATE'); } } lastDelta = delta; lastRot = Object.assign({}, rot); } }); </script> <a-scene embedded arjs="debugUIEnabled: false" device-orientation-permission-ui="enabled: false" vr-mode-ui="enabled: false" > <a-marker marker type='pattern' url='./pattern.patt'> <a-box wireframe="true" ></a-box> <a-text align="center" position="0 1 0" value="STOP" ></a-text> </a-marker> <a-entity camera></a-entity> </a-scene> </body> </html>
思ったこと
閾値次第ですが、移動の検知よりもシビアでした。
回転させたときに音を出すことでスクラッチ感を出すARレコードを作ってみました。
ARマーカーの回転を検知することで、スクラッチできるARレコードを作りました。移動時には音が鳴らず、回転時のみ音が鳴ります。 pic.twitter.com/dwr5KEe7GB
— 君塚史高 (@ki_230) 2020年11月26日
👀 おわりに
いかがだったでしょうか。
個人的にはマーカー型ARにもまだまだ可能性があるような気がしてなりません。
今年マーカーの状態検知以外にも、
プロジェクション × AR
ARにプロジェクションで影をつけることで、存在感のあるARを作りました。 pic.twitter.com/iAm9tRBPEj
— 君塚史高 (@ki_230) 2020年11月10日
音声認識 × AR
ARと音声認識を使って、頭の上に吹き出しを浮かべてみました。吹き出しの形状は音量に応じて変化させています。 pic.twitter.com/q5zzXZuWbh
— 君塚史高 (@ki_230) 2020年12月14日
スマートスピーカー × AR
ARを使ってGoogleHomeに表情をつけてみました。 pic.twitter.com/HvxzM4tayw
— 君塚史高 (@ki_230) 2020年12月23日
などを試しました。
来年も引き続き試行錯誤していこうと思います。