【Three.js×AR.js】マーカーありWebARを実装する

本記事ではThree.jsを使ったARコンテンツの実装シリーズ第一弾として、Three.js×AR.jsでマーカーベースARを実装していきます。

▼Three.jsのAR開発記事 第二弾
マーカーなし(マーカーレス)ARはこちらをご覧ください

1. ARマーカーの用意

まずはARマーカーを用意していきましょう。
AR.jsではプリセット(デフォルト)のマーカーか、オリジナルのマーカーを使用するか選択できます。

プリセットのマーカーは以下の「Hiro」マーカーと「Kanji」マーカーの2種類。
こちらを使用する場合はこの章を読み飛ばしてください。

引用: A-Frame

オリジナルの画像でマーカーを用意したい場合は、専用のマーカーファイルである.pattファイルを作る必要があります。

こちらのマーカージェネレーターで、.pattファイルを作成します。
AR.js Marker Training

 スクリーンショット: AR.js Marker Training

左の「UPLOAD」を選択し好きな画像を選択。
また、お好みで「UPLOAD」ボタンの下にあるPattern Ratio(枠の太さ)、Image size(画像サイズ)、Border color(枠の色)を調節してください。

※Pattern Ratioを変更した場合は、後ほどコード側で指定する必要があるため数値を覚えておきましょう。

調節が終わったら「DOWNLOAD MARKER」を選択して.pattファイルを出力します。
実際にかざして使う画像データは「DOWNLOAD IMAGE」を選択すると出力できます。

筆者は以下のようなマーカーを作成。🐄

アイコン素材: icooon-mono

※マーカーの認識精度:
使用する画像によって認識精度の安定/不安定が変わります。試してみて、認識が安定しない場合は画像を変更してマーカーを作ってみるのがいいかもしれません。
詳しくはこちらの記事もご覧ください。

2. シーンの作成

マーカーが準備できたら早速シーンを用意していきます。
まずはthree.jsとar.jsのcdnを用意します。

▼index.html

<head>
  <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>

Three.jsシーン作成

次にThree.jsのシーンを作成しつつ、threex-artoolkitを使用してARの処理を書いていきます。

※threex-artoolkitは、AR.jsがベースとしているartoolkitをThree.jsで簡単に扱えるようにする拡張機能です。

▼script.js

let w;
let h;
let canvas;
let scene;
let camera;
let renderer;
let object;

let arToolkitSource;
let arToolkitContext;

const init = () => {
  w = window.innerWidth;
  h = window.innerHeight;
  canvas = document.getElementById("canvas");
  setScene();
  setCamera();
  setObject();
  setArToolkit();
  setRenderer();
};

const setScene = () => {
  scene = new THREE.Scene();
  scene.visible = false;
};

const setCamera = () => {
  camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 30);
  scene.add(camera);
};

const setArToolkit = () => {
  arToolkitSource = new THREEx.ArToolkitSource({
    sourceType: "webcam",
  });

  arToolkitSource.init(() => {
    setTimeout(() => {
      onResize();
    }, 2000);
  });

  arToolkitContext = new THREEx.ArToolkitContext({
    cameraParametersUrl:
      THREEx.ArToolkitContext.baseURL + "../data/data/camera_para.dat",
    detectionMode: "mono",
    // ※1 作ったマーカーのPattern Ratioを入れる
    patternRatio: 0.8,
  });

  arToolkitContext.init(
    (onCompleted = () => {
      camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix());
    })
  );

  let onRenderFcts = [];
  onRenderFcts.push(() => {
    if (arToolkitSource.ready === false) return;
    arToolkitContext.update(arToolkitSource.domElement);
    scene.visible = camera.visible;
  });

  const markerControls = new THREEx.ArMarkerControls(arToolkitContext, camera, {
    type: "pattern",
    // ※2 マーカーのpattファイルのパスを入れる
    patternUrl: "/static/patt/marker.patt",
    changeMatrixMode: "cameraTransformMatrix",
  });
};

const setObject = () => {
  const geometry = new THREE.BoxGeometry(1, 1, 1);
  const material = new THREE.MeshNormalMaterial();
  object = new THREE.Mesh(geometry, material);
  object.position.set(0, 0, 0);
  scene.add(object);
};

const setRenderer = () => {
  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true,
    canvas: canvas,
  });
  renderer.setClearColor(0x000000, 0);
  renderer.setSize(w, h);
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setAnimationLoop(() => {
    render();
  });
};

const render = () => {
  if (arToolkitSource.ready) {
    arToolkitContext.update(arToolkitSource.domElement);
    scene.visible = camera.visible;
  }
  renderer.render(scene, camera);
};

const onResize = () => {
  arToolkitSource.onResizeElement();
  arToolkitSource.copyElementSizeTo(renderer.domElement);
  if (arToolkitContext.arController !== null) {
    arToolkitSource.copyElementSizeTo(arToolkitContext.arController.canvas);
  }
};

window.addEventListener("resize", () => {
  onResize();
});

window.onload = () => {
  init();
};

※1の箇所では、前章でオリジナルマーカーのpatternRatioを変更している場合は、patternRatioの数値を必要に応じて変更します。
patternRatioを変更していない/プリセットのマーカーを使用している場合は、patternRatio自体の記述を削除してしまってOKです。

※2の箇所ではマーカーの.pattファイルのパスを記入します。
プリセットのHiro/Kanjiマーカーを使用する場合は以下のようにします。

▼Hiroマーカー

patternUrl: THREEx.ArToolkitContext.baseURL + "../data/data/patt.hiro"

▼Kanjiマーカー

patternUrl: THREEx.ArToolkitContext.baseURL + "../data/data/patt.kanji"

無事に表示されました。

検証

うまく表示されない場合は、※1のpatternRatioの数値が合っているかをご確認ください。

また、マーカーが正常に認識できているか確認したい場合は、markerControlsを定義した後に以下のイベントを入れてみましょう。

markerControls.addEventListener("markerFound", () => {
  // マーカーが見つかっている時は毎秒呼ばれる
  console.log("marker found");
});

このmarkerFoundイベントはカメラ映像からマーカーが検出されている間は毎秒呼ばれるため、そもそもマーカーが検出されているか、マーカーは検出できているがオブジェクトの描画がうまくいっていないか…を検証できます。

パラメーター

その他、threex-arttoolkitを構成する3つのクラスArToolkitSourceArToolkitContextArMarkerControlsは、渡すパラメーターによって検出の仕方などの微調整ができます。

詳しくはAR.jsのドキュメントをご覧ください。

まとめ

今回はARマーカーを使用したThree.js×AR.jsの基本的な実装をご紹介しました。
次回はマーカーなしの実装についての記事を書きたいと思っております、お楽しみに。