7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

N高グループ・N中等部Advent Calendar 2024

Day 14

Meta QuestとThree.jsで現実世界にVRMモデルを召喚してみる

Last updated at Posted at 2024-12-13

はじめに

この記事は、N高グループ・N中等部 Advent Calendar 2024 14日目の記事です。

はじめまして、marilと申します。去年に引き続き、今年もN高グループ・N中等部 Advent Calendarに参加させていただきます。

今回は、Meta QuestとThree.jsを使って、VRMモデルを現実へ呼び出す方法をご紹介していきます。

プロジェクト作成

今回はRemixを例に挙げて紹介していきますが、フレームワークはお好みでどうぞ。

bun create remix vrm-xr

必要なパッケージをインストールします。

bun add three @types/three @pixiv/three-vrm

シーン作成

では、実装に取り掛かっていきます。
まず、Three.jsのシーンを作成し、XR機能を有効にします。

import * as THREE from "three";
import { ARButton, OrbitControls } from "three/examples/jsm/Addons.js";

const setupScene = async () => {
  //WebXRを有効化
  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setAnimationLoop(animate);
  renderer.xr.enabled = true;
  document.body.appendChild(renderer.domElement);

  document.body.appendChild(
    ARButton.createButton(renderer, {
      requiredFeatures: ["plane-detection"],
    })
  );

  //シーンの作成
  const scene = new THREE.Scene();

  //カメラを追加
  const camera = new THREE.PerspectiveCamera(
    35,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  camera.position.set(0.0, 1.4, 7);

  const orbitControls = new OrbitControls(camera, renderer.domElement);
  orbitControls.screenSpacePanning = true;
  orbitControls.target.set(0.0, 0.0, 0.0);
  orbitControls.update();

  //ライトを追加
  const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3);
  light.position.set(0.5, 1, 0.25);
  scene.add(light);

  function animate() {
    renderer.render(scene, camera);
  }
};

export default function Index() {
  useEffect(() => {
    setupScene();
  }, []);

  return <div></div>;
}

これで、XRが有効なThree.jsシーンが作成されます。

VRMモデルの読み込み

あらかじめ、public配下にmodelsフォルダを作成し、そこに読み込みたいvrmモデルを配置しておきます。

VRMモデルを読み込む関数を実装します。

import { VRMUtils, VRMLoaderPlugin } from "@pixiv/three-vrm";
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

const LoadVRM = (path: string): Promise<GLTF> => {
  const loader = new GLTFLoader();
  loader.crossOrigin = "anonymous";
  const helperRoot = new THREE.Group();

  loader.register((parser) => {
    return new VRMLoaderPlugin(parser, {
      helperRoot: helperRoot,
      autoUpdateHumanBones: true,
    });
  });

  return new Promise((resolve, reject) => {
    loader.load(
      path,
      (gltf: GLTF) => {
        VRMUtils.removeUnnecessaryJoints(gltf.scene);

        if (gltf.userData.gltfExtensions?.VRM) {
          //VRM-0.xのモデルかどうかチェックする
          reject(
            "モデルのバージョンに互換性がありません。VRM-1.xのモデルを使用してください。"
          );
        }

        gltf.userData.vrm.scene.traverse((obj) => {
          obj.frustumCulled = false;
        });

        resolve(gltf);
      },
      (progress: { loaded: number; total: number }) =>
        console.log(
          "Loading model...",
          100.0 * (progress.loaded / progress.total),
          "%"
        ),
      (error) => {
        reject(error);
      }
    );
  });
};

setupScene関数に以下を追記します。

const setupScene = async () => {
  //WebXRを有効化
  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setAnimationLoop(animate);
  renderer.xr.enabled = true;
  document.body.appendChild(renderer.domElement);

  document.body.appendChild(
    ARButton.createButton(renderer, {
      requiredFeatures: ["plane-detection"],
    })
  );

  //シーンの作成
  const scene = new THREE.Scene();

  //カメラを追加
  const camera = new THREE.PerspectiveCamera(
    35,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  camera.position.set(0.0, 1.4, 7);

  const orbitControls = new OrbitControls(camera, renderer.domElement);
  orbitControls.screenSpacePanning = true;
  orbitControls.target.set(0.0, 0.0, 0.0);
  orbitControls.update();

  //ライトを追加
  const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3);
  light.position.set(0.5, 1, 0.25);
  scene.add(light);

+ //VRMの読み込み
+ const gltf = await LoadVRM("モデルのパス");
+ const vrm = gltf.userData.vrm;
+ scene.add(vrm.scene);
+ vrm.scene.position.set(0, 0, 0);

+ const clock = new THREE.Clock();

  function animate() {
+   const deltaTime = clock.getDelta();

+   //vrmのanimate
+   if (vrm) vrm.update(deltaTime);

    renderer.render(scene, camera);
  }
};

Questからアクセスしてみる

これで、VRMモデルを現実空間に呼び出す準備が整いました。
実際にMeta Quest3側からアクセスしてみましょう。

今回は、Cloudflare Tunnelを使って一時的にWebサイトを公開します。

Quick Tunnelsのセットアップ方法は、以下の記事が参考になります。

cloudflared tunnel --url localhost:5173

表示されたURLに、Meta Questからアクセスします。

「Start AR」というボタンが表示されていれば、正しくシーンが作成できています。
ボタンをタップして、確認ポップアップの「許可する」をタップします。
VRMモデルが召喚されていれば、成功です!

(初期位置ではたまにモデルが空中に浮遊したり、地面に埋まっていたりすることがあるので、その場合はvrm.scene.position.setでy座標を調節してみてください。)

デバッグ

WebXRアプリを開発していると、エラーなどを確認するため、Quest3で動作しているWebサイトのコンソールを確認したくなることがあるかと思います。

そんなときは、Chromiumブラウザのリモートデバッグ機能を使用することで、PCからQuest上のWebサイトのコンソールを確認することができます。

デバッグの手順は、以下の記事が参考になります。

おわりに

今回は、Meta QuestとThree.jsを使って、VRMモデルを現実へ呼び出す方法をご紹介しました。

このように、Three.jsを使えば簡単にWebXRコンテンツを作成することができるので、ぜひ挑戦してみてください。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?