これまで、 A-Frameを使ったWebAR を実装して来たり、 AR.js + Three.js + Next.jsを使ったWebAR を実装してきたりしました。
blog.kimizuka.org
blog.kimizuka.org
今回は極限までシンプルに、必要最低限のライブラリ(AR.js + Three.js)のみを使って、画面いっぱいをカメラプレビューとしつつ、マーカーの上に立方体を表示するだけの超シンプルなWebARを実装してみます。
方針としては「とにかくシンプルに」を心がけるのですが、スマートフォンでもいい感じに動作させることは必須項目としています。
DEMO
ソースコード(全文)
index.html
<html> <head> <title>three.js + ar.js</title> <meta name="viewport" content="width=device-width, viewport-fit=cover, shrink-to-fit=no" /> <style> * { margin: 0; padding: 0; } .wrapper { position: relative; overflow: hidden; } </style> </head> <body> <div class="wrapper"> <canvas></canvas> </div> <!--❶ three.jsとAR.jsを読み込む--> <script src="https://unpkg.com/three@0.127.0/build/three.min.js"></script> <script src="https://raw.githack.com/AR-js-org/AR.js/3.3.3/three.js/build/ar.js"></script> <script async> const renderer = new THREE.WebGLRenderer({ canvas: document.querySelector('canvas'), antialias: true, alpha: true }); const camera = new THREE.PerspectiveCamera(); const scene = new THREE.Scene(); const markerRoot = new THREE.Group(); const arToolkitContext = new THREEx.ArToolkitContext({ cameraParametersUrl: './camera.dat', // ❷ camera.datを読み込む detectionMode: 'mono' }); const arToolkitSource = new THREEx.ArToolkitSource({ sourceType: 'webcam' }); const arMarkerControl = new THREEx.ArMarkerControls(arToolkitContext, markerRoot, { type: 'pattern', patternUrl: 'pattern.patt' // ❸ pattern.pattを読み込む }); renderer.setSize(window.innerWidth, window.innerHeight); window.addEventListener('resize', handleResize, { passive: true }); arToolkitContext.init(() => { camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix()); }); arToolkitSource.init(() => { document.querySelector('.wrapper').appendChild(arToolkitSource.domElement); // ❹ videoタグを.wrapper配下に移動させる setTimeout(handleResize, 400); // ❺ リサイズイベントを一度発火させる }); scene.add(markerRoot); const cube = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial() ); cube.position.set(0, .5, 0); markerRoot.add(cube); renderer.setAnimationLoop((delta) => { if (arToolkitSource.ready) { arToolkitContext.update(arToolkitSource.domElement); } renderer.render(scene, camera); }); function handleResize() { if (arToolkitSource.ready) { arToolkitSource.onResize(); arToolkitSource.copySizeTo(renderer.domElement); } renderer.setPixelRatio(window.devicePixelRatio); } </script> </body> </html>
行数にあまり意味はないですが、100行程度のHTMLファイルで実現できました。
解説
❶ Three.js・AR.jsのバージョン
現時点でThree.jsの最新がr149、AR.jsの最新が3.4.4なので、当初は両方最新で実装し、PC版のGoogle Chromeでは問題なく動作することを確認しました。
が。Android版のGoogle Chrome、iOS版のmobile Safariで、カメラプレビューを画面いっぱいにした場合、ARの位置が合わない(canvasの大きさが意図したものより巨大になるため)という現象が解決できなかったため、最終的にはThree.jsのr127、AR.jsの3.3.3を使用することにした次第です。
❸ pattern.pattの入手先
こちらのサイト に画像をアップすると、マーカーの作成とダウンロードができます。
今回はこちらの画像をマーカーとして使っていますが、hiroにしておけばよかったと後悔しています。
家にプリンタがないので差し替えを諦めました。
❹ videoタグを.wrapper配下に移動させる理由
AR.js + Three.js + Next.jsで実装した際 にも同様の処理を入れたのですが、
- AR.jsがbody直下にvideo要素を追加する
- AR.jsがvideo要素の親(body要素)にoverflow: hiddenを掛けないとスクロールが発生してしまう
- スマートフォンのブラウザではbodyにoverflow: hiddenが効かない
が原因なので、overflow:hiddenを掛けた要素配下にvideoを移動させています。
❺ リサイズイベントを一度発火させる理由
めちゃめちゃ気持ち悪いのですが、サイズ合わせの為にarToolkitSourceの初期化のタイミングから400ms秒後にリサイズイベントを発火させています。
ここをいい感じに処理できないか、イベントを探したり、ドキュメントを読んだりしたのですが、 公式のAR.js + Three.jsのexample でもsetTimeout(しかも2000ms)を使っているので諦めました。
流石に2000msは待ちすぎなので、短くしながら検証していった結果、400msに落ち着いた次第です。
と、今回はこんな感じになりました。
が、 こちらのサイト の このデモページ では、video要素はbody直下のままだし、setTimeoutを使わずにリサイズイベントを発火させてるんですよね。
stemkoski.github.io
stemkoski.github.io
ソースコードを読むとTHREE.CubeGeometry(現在はTHREE.BoxGeometry)が使われていて、相当古いThree.js(おそらくr67以前)が使われてることから、これをそのまま使うのは気が進まないのですが、まだまだシンプルにできる余地があると捉え、引き続き調査していこうと思います。