AR.jsでMarker Based ARを行う場合、3DライブラリとしてA-Frameとthree.jsのどちらかを選べます。この記事では、three.jsでAR.jsのMarker Based ARを試してみたいと思います。
Marker Based - AR.js Documentation
サンプルとして作成したのは、以下のようにhiroマーカー上に回転するキューブを表示するだけのシンプルなものです。
コード全文は以下のようになります。このコードはAR.jsのリポジトリ内にあるサンプルを参考にしています。このサンプルを実行するにはdata
というディレクトリを作成して、その中にcamera_para.datとpatt.hiroを配置する必要があります。
<!DOCTYPE html>
<html>
<head>
<title>Marker Based AR with AR.js and Three.js</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/3.1.0/three.js/build/ar.js"></script>
</head>
<body>
<script>
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setClearColor(new THREE.Color(), 0);
renderer.setSize(640, 480);
renderer.domElement.style.position = 'absolute';
renderer.domElement.style.top = '0px';
renderer.domElement.style.left = '0px';
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.visible = false;
const camera = new THREE.Camera();
scene.add(camera);
const arToolkitSource = new THREEx.ArToolkitSource({
sourceType: 'webcam'
});
arToolkitSource.init(() => {
setTimeout(() => {
onResize();
}, 2000);
});
addEventListener('resize', () => {
onResize();
});
function onResize() {
arToolkitSource.onResizeElement();
arToolkitSource.copyElementSizeTo(renderer.domElement);
if (arToolkitContext.arController !== null) {
arToolkitSource.copyElementSizeTo(arToolkitContext.arController.canvas);
}
};
const arToolkitContext = new THREEx.ArToolkitContext({
cameraParametersUrl: 'data/camera_para.dat',
detectionMode: 'mono'
});
arToolkitContext.init(() => {
camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix());
});
const arMarkerControls = new THREEx.ArMarkerControls(arToolkitContext, camera, {
type: 'pattern',
patternUrl: 'data/patt.hiro',
changeMatrixMode: 'cameraTransformMatrix'
});
const mesh = new THREE.Mesh(
new THREE.CubeGeometry(1, 1, 1),
new THREE.MeshNormalMaterial(),
);
mesh.position.y = 1.0;
scene.add(mesh);
const clock = new THREE.Clock();
requestAnimationFrame(function animate(){
requestAnimationFrame(animate);
if (arToolkitSource.ready) {
arToolkitContext.update(arToolkitSource.domElement);
scene.visible = camera.visible;
}
const delta = clock.getDelta();
mesh.rotation.x += delta * 1.0;
mesh.rotation.y += delta * 1.5;
renderer.render(scene, camera);
});
</script>
</body>
</html>
コードについて順に解説していきます。
まず、AR.jsとthree.jsをインポートします。AR.jsではDocumentationに書いてあるように、使用する機能(Marker BasedまたはImage Tracking)と3Dライブラリ(A-Frameまたはthree.js)ごとに異なるビルドが提供されているので注意してください。ここでは、2020年5月25日現在において最新バージョンであるAR.js 3.1.0と、CDNで取得できるうちの最新バージョンであるthree.js r110を使用しています。
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/3.1.0/three.js/build/ar.js"></script>
次にスクリプトを見ていきます。
最初にTHREE.WebGLRenderer
を作成します。AR.jsでは3D描画を行うcanvas要素の背面にカメラから取得した映像を描画するHTML要素を配置しています。3Dオブジェクトが存在しない箇所では背景のHTML要素が見えるようにするためにalpha: true
を設定して、setClearColor
の第2引数を0
にする必要があります(参考: [javascript - Transparent background with three.js - Stack Overflow]
(https://stackoverflow.com/questions/20495302/transparent-background-with-three-js))。また、全画面表示することを想定しているのでstyleでマージンが生じないようにします。
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setClearColor(new THREE.Color(), 0);
renderer.setSize(640, 480);
renderer.domElement.style.position = 'absolute';
renderer.domElement.style.top = '0px';
renderer.domElement.style.left = '0px';
document.body.appendChild(renderer.domElement);
次にシーンとカメラを作成します。 マーカーを検出するまではシーン内のオブジェクトが表示される必要がないので、scene.visible
の初期値はfalse
にしています。カメラのパラメータはデバイスのカメラのパラメータに合わせるために後から書き換えられるので、THREE.PerspectiveCamera
などではなく基底クラスのTHREE.Camera
を使用しています。
const scene = new THREE.Scene();
scene.visible = false;
const camera = new THREE.Camera();
scene.add(camera);
THREEx.ArToolkitSource
を作成して、解析対象とする画像ソースを取得するようにします。webcam
を指定してデバイスのカメラを使うようにするのが一般的だと思いますが、静止画(image
)や動画(video
)を使用することも可能です。
const arToolkitSource = new THREEx.ArToolkitSource({
sourceType: 'webcam'
});
画像ソースの準備が完了したら、3Dを描画するcanvas要素と背景のカメラ映像を表示するHTML要素が画面いっぱいに表示されるように、サイズ調整を行います。arToolkitSource.onResizeElementで、カメラ映像のHTML要素のサイズを調整し、arToolkitSource.copyElementSizeToでcanvasも同じ大きさになるようにしています。arToolkitSource.init
でonResize
の実行を2秒ほど遅らせている理由はよくわかりませんが、参考にしたサンプルでは意図的にこの処理を入れているようなので、ここでも入れておきました。
arToolkitSource.init(() => {
setTimeout(() => {
onResize();
}, 2000);
});
addEventListener('resize', () => {
onResize();
});
function onResize() {
arToolkitSource.onResizeElement();
arToolkitSource.copyElementSizeTo(renderer.domElement);
if (arToolkitContext.arController !== null) {
arToolkitSource.copyElementSizeTo(arToolkitContext.arController.canvas);
}
};
THREEx.ARToolkitContext
の作成を行います。cameraParametersUrl
にカメラのキャリブレーションに必要なデータを格納するファイルのパスを指定します。ここで指定したファイルの情報をもとにカメラのプロジェクション行列を設定します。detectionMode
についてはよくわかってないですが、Marker Based ARの場合はモノクローム(mono
)でいいのかなという気がします。
const arToolkitContext = new THREEx.ArToolkitContext({
cameraParametersUrl: 'data/camera_para.dat',
detectionMode: 'mono'
});
arToolkitContext.init(() => {
camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix());
});
THREEx.ArMarkerControls
が一つのマーカーに対応しています。THREEx.ArMakerControls
の第2引数はマーカーを発見したときに、そのマーカーと整合性をあわせるために位置や回転を操作するObject3Dを指定します。
今回はcamera
を第2引数に指定しているので、カメラを動かすためにchangeMatrixMode
でcameraTransformMatrix
を指定します。changeMatrixMode
にはmodelViewMatrix
という値を設定することも可能です。 modelViewMatrix
を使用する方法はこの記事の最後に書きました。
const arMarkerControls = new THREEx.ArMarkerControls(arToolkitContext, camera, {
type: 'pattern',
patternUrl: 'data/patt.hiro',
changeMatrixMode: 'cameraTransformMatrix'
});
マーカーが発見されたときに表示するキューブを作成して、シーンに追加しておきます。
const mesh = new THREE.Mesh(
new THREE.CubeGeometry(1, 1, 1),
new THREE.MeshNormalMaterial(),
);
mesh.position.y = 1.0;
scene.add(mesh);
最後にレンダリングループを回します。マーカーが見つかったかどうかに応じてTHREEx.ArMakerControls
の第2引数で指定したオブジェクト(ここではcamera
のこと)のvisible
が操作されます。その値をシーンのvisible
に反映することでマーカーが発見されたときにキューブが描画されるようにしています。
const clock = new THREE.Clock();
requestAnimationFrame(function animate(){
requestAnimationFrame(animate);
if (arToolkitSource.ready) {
arToolkitContext.update(arToolkitSource.domElement);
scene.visible = camera.visible;
}
const delta = clock.getDelta();
mesh.rotation.x += delta * 1.0;
mesh.rotation.y += delta * 1.5;
renderer.render(scene, camera);
});
ソースコードについて一通り解説しました。
最後にTHREEx.ArMarkerControls
で指定するchangeMatrixMode
のもう一つの値であるmodelViewMatrix
についても見ていきたいと思います。modelViewMatrix
を指定する場合、ArMarkerControls
の第2引数は以下のようにカメラではなくなります。ここでは階層構造を表したいだけなのでTHREE.Group
を使用しています。一度に複数のマーカーを使用する場合には、個々のマーカーに対してvisibleのオンオフを変更できるので、こちらを使用したほうがいいと思います。
const marker = new THREE.Group();
scene.add(marker);
const arMarkerControls = new THREEx.ArMarkerControls(arToolkitContext, marker, {
type: 'pattern',
patternUrl: 'data/patt.hiro',
changeMatrixMode: 'modelViewMatrix'
});
const mesh = new THREE.Mesh(
new THREE.CubeGeometry(1, 1, 1),
new THREE.MeshNormalMaterial(),
);
mesh.position.y = 1.0;
marker.add(mesh);