【Nuxt3×Three.js×AR.js】ARクリスマスカードを作りたい!
2024-12-19
azblob://2024/12/18/eyecatch/2024-12-19-nuxt-ar-christmas-card-000.jpg

本記事はFIXER Advent Calendar 2024( FIXER Advent Calendar 2024 ~ ルーキー編 〜)12月19日の記事です。

玉置です。

師走ですね。学生の頃この時期になると「今年こそThree.jsでクリスマスカードを作るぞ〜!」と言っていたのを思い出しました。

"今年こそ"と言っている時点でこれまで一度も作っていなかったわけなんですが......

ですが今年こそ、Three.jsを使ってクリスマスカードを作ったので簡単にThree.jsの使い方をまとめていきたいと思います!

概要

Three.jsとAR.jsを使ってARマーカーからクリスマスっぽいメッセージやオブジェクトを表示するWebアプリを作っていきます。

バージョン

  • Nuxt.js -> 3.14.1592
  • Three.js -> 0.171.0
  • AR.js -> 2.2.2

また、TypeScriptでThree.jsを使うために `types/three` と `ar-js-org/ar.js-threejs` を使う必要があるので入れておきます。

いざ、実装

まずは普通のNuxtプロジェクトを作成して必要なファイルを作成していきます。

XML├── README.md
├── assets
│   └── fonts
├── components
│   └── ARScene.vue
├── layouts
│   └── default.vue
├── node_modules
├── nuxt.config.ts
├── package-lock.json
├── package.json
├── pages
│   └── index.vue
├── public
│   ├── data
│   │   ├── Courgette_Regular.json
│   │   ├── camera_para.dat
│   │   └── patt.hiro
│   ├── favicon.ico
│   └── robots.txt
├── server
│   └── tsconfig.json
└── tsconfig.json

TypeScriptでAR.jsを使うためのライブラリがあるのでサンプルコードのexample-ts/basic.tsを参考に作りました。

https://github.com/AR-js-org/AR.js-threejs

では、コンポーネントから説明していきます。

components

ARScene.vue

HTML<template>
  <div ref="arContainer"></div>
</template>

普通のテンプレート部です。

TypeScript<script setup lang="ts">
import * as THREE from "three";
import { THREEx, ARjs } from "@ar-js-org/ar.js-threejs";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader.js";
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";

THREExとARjsはTypeScriptでAR.jsを使うためのライブラリar-js-org/ar.js-threejsからのインポートです。

後はThree.jsで文字を表示するためにFontLoaderとTextGeometryをインポートします。

TypeScriptconst arContainer = ref<HTMLElement | null>(null);
let renderer: THREE.WebGLRenderer;
let scene: THREE.Scene;
let camera: THREE.PerspectiveCamera;
let arToolkitSource: any;
let arToolkitContext: any;
let arMarkerControls: any;
let mesh: THREE.Mesh;
let torusMesh: THREE.Mesh;
const onRenderFcts: ((delta: number, now: number) => void)[] = [];
const objectParam = {
  x: 0,
  y: -1,
  z: 0,
};

変数の宣言

TypeScriptconst initThree = () => {
  THREEx.ArToolkitContext.baseURL = "./";
  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setClearColor(new THREE.Color("lightgrey"), 0);
  renderer.setSize(640, 480);
  renderer.domElement.style.position = "absolute";
  renderer.domElement.style.top = "0px";
  renderer.domElement.style.left = "0px";
  arContainer.value?.appendChild(renderer.domElement);
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera();
  scene.add(camera);
};

Three.jsの初期化。

TypeScriptconst initAR = () => {
  // カメラを設定
  arToolkitSource = new THREEx.ArToolkitSource({
    sourceType: "webcam",
    sourceWidth: window.innerWidth > window.innerHeight ? 640 : 480,
    sourceHeight: window.innerWidth > window.innerHeight ? 480 : 640,
  });
  arToolkitSource.init(
    () => {
      arToolkitSource.domElement.addEventListener("canplay", initARContext);
      setTimeout(onResize, 2000);
    },
    () => {}
  );
  window.addEventListener("resize", onResize);
};

AR.jsの初期化。

TypeScriptconst initARContext = () => {
  arToolkitContext = new THREEx.ArToolkitContext({
    cameraParametersUrl:
      THREEx.ArToolkitContext.baseURL + "./data/camera_para.dat",
    detectionMode: "mono",
  });
  arToolkitContext.init(() => {
    camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix());
    arToolkitContext.arController.orientation = getSourceOrientation();
    arToolkitContext.arController.options.orientation = getSourceOrientation();
  });
  arMarkerControls = new THREEx.ArMarkerControls(arToolkitContext, camera, {
    type: "pattern",
    patternUrl: THREEx.ArToolkitContext.baseURL + "./data/patt.hiro",
    changeMatrixMode: "cameraTransformMatrix",
  });
  scene.visible = false;
};
const onResize = () => {
  arToolkitSource.onResizeElement();
  arToolkitSource.copyElementSizeTo(renderer.domElement);
  if (arToolkitContext.arController !== null) {
    arToolkitSource.copyElementSizeTo(arToolkitContext.arController.canvas);
  }
};
const getSourceOrientation = (): string => {
  if (!arToolkitSource) return "";
  return arToolkitSource.domElement.videoWidth >
    arToolkitSource.domElement.videoHeight
    ? "landscape"
    : "portrait";
};

ARコンテキストを初期化。

TypeScriptconst addObjects = () => {
  const fontLoader = new FontLoader();
  const textGroup = new THREE.Group();
  fontLoader.load("/data/Courgette_Regular.json", (font) => {
    console.log("loaded font!!");
    const textGeometry = new TextGeometry("Marry Christmas", {
      font: font,
      size: 1,
      height: 0.2,
      curveSegments: 12,
      bevelEnabled: true,
      bevelThickness: 0.1,
      bevelSize: 0.02,
      bevelOffset: 0,
      bevelSegments: 5,
    });
    textGeometry.center();
    // メインのテキストマテリアル
    const textMaterial = new THREE.MeshBasicMaterial({
      color: new THREE.Color(1, 1, 1), // 白色
    });
    // アウトライン用のマテリアル
    const outlineMaterial = new THREE.MeshBasicMaterial({
      color: new THREE.Color(0, 0, 0), // 黒色
      side: THREE.BackSide, // 裏面を表示
    });
    // メインのテキストメッシュ
    const textMesh = new THREE.Mesh(textGeometry, textMaterial);
    // アウトライン用のメッシュ
    const outlineMesh = new THREE.Mesh(textGeometry, outlineMaterial);
    outlineMesh.scale.multiplyScalar(1.05); // アウトラインを少し大きくする
    // テキストグループを作成
    textGroup.add(textMesh);
    textGroup.add(outlineMesh);
    // グループ全体を回転
    textGroup.rotation.x = -Math.PI / 4; // 45度回転(X軸周り)
    textGroup.position.z = 1;
    scene.add(textGroup);
  });
};

シーンに3Dオブジェクトを追加します。

今回はMerry Christmas!と表示するのでTextGeometryを作成します。

TextGeometryで使うfontですが、fontLoaderではjsonを使うので事前にttfをjson変換しておきます。

TypeScriptonMounted(() => {
  initThree();
  initAR();
  addObjects();
  onRenderFcts.push(() => {
    renderer.render(scene, camera);
  });
  
  window.addEventListener("markerFound", (e) => {
    console.log("marker found!", e);
  });
});
onUnmounted(() => {
  window.removeEventListener("resize", onResize);
});
</script>

コンポーネントのマウント時に初期化処理と3Dオブジェクトの追加を行います。

 CSRでないとAR.jsが動作しないのでマウント後に色々処理します。

次はpagesです。

pages

index.vue

HTML<template>
  <ClientOnly>
    <ARScene />
  </ClientOnly>
</template>

<script setup lang="ts"></script>

Nuxt3ではClientOnlyを使うとコンポーネントをクライアントサイドだけでレンダリングできます。

今回は1ページしかないのでこれだけです。

最後はpublicです。

public

publicディレクトリにさらにdataを作成してその中に

camera_para.datARマーカーのpattファイルフォントを入れておきます。

camera_para.dat

https://github.com/AR-js-org/AR.js-threejs

参考にしたリポジトリから拝借しました。

pattファイル

https://jeromeetienne.github.io/AR.js/three.js/examples/marker-training…

AR.jsで使えるマーカーを作成するツールがあるのでこちらを使ってオリジナルのマーカーを作成したのですが、pattファイルの中身が全て0でうまく生成できていなかったので泣く泣くデフォルトのhiroを使います;;

マーカーはこちらです。

マーカーのhiro.png
サンプルコードのリポジトリから泣く泣く拝借しました;;

実際に動かしてみると......!

PCに表示したマーカーで動作確認

マーカーを認識してちゃんと表示されています!

これをクリスマスカードにして......

クリスマスカード

読み込ませてみます。

クリスマスカードにしてみた表示

ちゃんと表示されました!

まとめ

今回はNuxtでThree.jsとAR.jsを使ったWeb ARを作ってみました。

AR.jsのCSRのところだったり、TypeScriptで書くための調査で時間を使ってしまったのでただ文字を表示するだけとなってしまいましたが、時間があればもっと凝った3Dを用意してみたかったです。

今回のプロジェクトは公開しているのでpublicで説明したマーカーを使って遊んでみてください!

※カメラが動作します

https://nuxt-three-christmas.vercel.app/

簡単な3DやARを表示させるところまでは意外と簡単にできるので興味を持ったらぜひ試してみてください!

以上〜