LoginSignup
19
19

More than 3 years have passed since last update.

AR.jsとthree.jsでMarker Based AR

Last updated at Posted at 2020-05-25

AR.jsでMarker Based ARを行う場合、3DライブラリとしてA-Frameとthree.jsのどちらかを選べます。この記事では、three.jsでAR.jsのMarker Based ARを試してみたいと思います。

Marker Based - AR.js Documentation


サンプルとして作成したのは、以下のようにhiroマーカー上に回転するキューブを表示するだけのシンプルなものです。
arjs-crop.gif

コード全文は以下のようになります。このコードはAR.jsのリポジトリ内にあるサンプルを参考にしています。このサンプルを実行するにはdataというディレクトリを作成して、その中にcamera_para.datpatt.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)。また、全画面表示することを想定しているので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.initonResizeの実行を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引数に指定しているので、カメラを動かすためにchangeMatrixModecameraTransformMatrixを指定します。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);
19
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
19