Open7

React と VRM

HoshiHoshi

環境構築

npx create-next-app@latest . --ts --eslint
yarn add three @react-three/fiber @react-three/drei @pixiv/three-vrm
yarn add -D @types/three
HoshiHoshi

シンプルに VRM を表示

import { Canvas, useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

const SampleModel = () => {
  const gltf = useLoader(GLTFLoader, "/vrms/model.vrm");
  return <primitive object={gltf.scene} />;
};

export default function DemoPage() {
  return (
    <div className="h-screen">
      <Canvas>
        <SampleModel />
      </Canvas>
    </div>
  );
}
HoshiHoshi

VRM 1.0 の仕様を読む

VRM は、現状 0.0 と 1.0 のバージョンがあり、アプリケーションによって扱う

https://vrm.dev/vrm1/index.html

2018 年に発表した 3D アバターファイルフォーマット「VRM」において、 利用が進むにつれて判明した課題やエラーを修正し、 今後必要になると考えられる機能を追加した規格となります。

従来のVRMは今後 VRM 0.x と分類され非推奨となり、今後は VRM 1.0 が正式な規格となる予定です。

VRM 1.0 は、2022年9月にリリースされました。

VRM 1.0 の仕様が公開されているリポジトリである vrm-specification を見てみると、いくつかのドキュメントがある。

specification/
├── 0.0
├── VRMC_materials_hdr_emissiveMultiplier-1.0
├── VRMC_materials_mtoon-1.0
├── VRMC_node_constraint-1.0
├── VRMC_springBone-1.0
├── VRMC_vrm-1.0
└── VRMC_vrm_animation-1.0
HoshiHoshi

VRMC_vrm-1.0/README.ja.md

メモ

概要

  • glTF はシーンを表現するのに対して、VRM は VR アバター向けの人間型モデル一体を表現する

モデル空間

  • VRM では、 VRM モデルを構成する glTF シーンの原点から相対にトランスフォームを観測する「モデル空間」を定義する。これは、 VRM モデルを扱うアプリケーション上のワールド空間とは区別される
  • モデル空間は VRMC_node_constraint 拡張で利用される

形式と拡張子

  • .glb 形式で保存し、.vrm を用いる

座標の単位
glTF に準拠してメートル単位を用いる

JSON Schema

{
  "extensionsUsed": [
    "VRMC_vrm"
  ],
  "extensions": {
    "VRMC_vrm": {
      // VRM extension
      "specVersion": "1.0",
      "humanoid": {},
      "meta": {},
      "firstPerson": {},
      "expression": {},
      "lookAt": {},
    },
    "VRMC_springBone": {},
    "VRMC_node_constraint": {}
  },
  // glTF-2.0
  "materials": [
    "extensions": {
      "VMRC_materials_mtoon": {}
    }
  ],
}

VRMC_vrm.humanoid ノードへのヒューマノイドボーンの割当て (必須)

  • 人型モデルを定義するために、人体の部位を glTF.Node に割り当てる

VRMC_vrm.meta モデル情報(必須)

VRMC_vrm.firstPerson 一人称(オプション)

  • VR を想定した一人称視点の設定を定義

Expression、LookAt、SpringBone、Constraints の適用順序

  1. ヒューマノイドボーンを解決
  2. 頭の位置が決まるため、 LookAt を解決
  • Bone型:leftEye, rightEye ボーンを回転する
  • Expression型:
  1. ExpressionUpdate
  • 喜怒哀楽 コントローラなど外部入力 → Expression ウェイトを設定
  • LipSync → Expression ウェイトを設定
  • AutoBlink → Expression ウェイトを設定
  • Expression 型の LookAt → Expression のウェイトを設定
  1. Expression を Apply
  2. Constraint を解決
  3. SpringBone を解決

VRMC_vrm.expression 表情(オプション)

VRMC_vrm.lookAt 視線制御(オプション)

HoshiHoshi

VRMC_vrm.humanoid.ja.md

extensions.VRMC_vrm.humanoid = {
  "humanBones": {
    "hips": {
      "node": 1 // index of glTF.Node
    },
    "spine": {
      "node": 2
    },
    // 省略
  }
}

ヒューマノイドボーンの一覧

ボーン名前 必須 親ボーン 位置の目安 親ボーンの存在が必須 備考
hips 必須 (root) 股間 - 通常、このボーンだけが移動し、他のボーンは回転だけします
spine 必須 hips 骨盤の上端 -
chest spine 胸郭の下端 - 0.X では必須でした
upperChest chest Yes chest が存在する場合にのみ、このボーンは存在できます
neck upperChest 首の付け根 No 0.X では必須でした

ボーン名前 必須 親ボーン 位置の目安 親ボーンの存在が必須 備考
head 必須 neck 首の上端 No
leftEye head - VRMC_vrm.lookAt 視線制御(オプション)
rightEye head - VRMC_vrm.lookAt 視線制御(オプション)
jaw head -

ボーン名前 必須 親ボーン 位置の目安 親ボーンの存在が必須 備考
leftUpperLeg 必須 hips 脚の付け根 -
leftLowerLeg 必須 leftUpperLeg -
leftFoot 必須 leftLowerLeg 足首 -
leftToes leftFoot 足の指の付け根 -
rightUpperLeg 必須 hips 脚の付け根 -
rightLowerLeg 必須 rightUpperLeg -
rightFoot 必須 rightLowerLeg 足首 -
rightToes rightFoot 足の指の付け根 -

ボーン名前 必須 親ボーン 位置の目安 親ボーンの存在が必須 備考
leftShoulder upperChest No
leftUpperArm 必須 leftShoulder 上腕の付け根 No
leftLowerArm 必須 leftUpperArm -
leftHand 必須 leftLowerArm 手首 -
rightShoulder upperChest No
rightUpperArm 必須 rightShoulder 上腕の付け根 No
rightLowerArm 必須 rightUpperArm -
rightHand 必須 rightLowerArm 手首 -

ボーン名前 必須 親ボーン 位置の目安 親ボーンの存在が必須 備考
leftThumbMetacarpal leftHand -
leftThumbProximal leftThumbMetacarpal Yes
leftThumbDistal leftThumbProximal Yes
leftIndexProximal leftHand -
leftIndexIntermediate leftIndexProximal Yes
leftIndexDistal leftIndexIntermediate Yes
leftMiddleProximal leftHand -
leftMiddleIntermediate leftMiddleProximal Yes
leftMiddleDistal leftMiddleIntermediate Yes
leftRingProximal leftHand -
leftRingIntermediate leftRingProximal Yes
leftRingDistal leftRingIntermediate Yes
leftLittleProximal leftHand -
leftLittleIntermediate leftLittleProximal Yes
leftLittleDistal leftLittleIntermediate Yes
rightThumbMetacarpal rightHand -
rightThumbProximal rightThumbMetacarpal Yes
rightThumbDistal rightThumbProximal Yes
rightIndexProximal rightHand -
rightIndexIntermediate rightIndexProximal Yes
rightIndexDistal rightIndexIntermediate Yes
rightMiddleProximal rightHand -
rightMiddleIntermediate rightMiddleProximal Yes
rightMiddleDistal rightMiddleIntermediate Yes
rightRingProximal rightHand -
rightRingIntermediate rightRingProximal Yes
rightRingDistal rightRingIntermediate Yes
rightLittleProximal rightHand -
rightLittleIntermediate rightLittleProximal Yes
rightLittleDistal rightLittleIntermediate Yes

ヒューマノイドボーンの親子関係

  • root(ヒューマノイドボーンではない。原点)
    • hips
      • spine
        • (chest)
          • (upperChest)
            • (neck)
              • head
                • (leftEye)
                • (rightEye)
                • (jaw)
            • (leftShoulder)
              • leftUpperArm
                • leftLowerArm
                  • leftHand
                    • (fingers) 省略
            • (rightShoulder)
              • rightUpperArm
                • rightLowerArm
                  • rightHand
                    • (fingers) 省略
      • leftUpperLeg
        • leftLowerLeg
          • leftFoot
            • (leftToes)
      • rightUpperLeg
        • rightLowerLeg
          • rightFoot
            • (leftToes)
HoshiHoshi

VRMC_vrm-1.0/meta.ja.md

meta フィールドでは、モデルに関するメタ情報を記述する

  • モデルの名前
  • モデルの作者
  • モデルの利用条件、ライセンス
名前 説明 必須
name string モデルの名前 ✅ Yes
version string モデルのバージョン No
authors string[] モデルの作者名 ✅ Yes
copyrightInformation string モデルの著作権者 No
contactInformation string モデルの作者(代表者)への連絡先 No
references string[] モデルの「親作品」となるようなものがあれば、その情報 No
thirdPartyLicenses string モデルのサードパーティライセンス表記 No
thumbnailImage integer モデルのサムネイルとなる画像へのインデックス No
licenseUrl string このモデルが参照するVRMライセンス文書へのURL ✅ Yes
avatarPermission string このモデルに人格を与えることの許諾範囲 No, 初期値: OnlyAuthor
allowExcessivelyViolentUsage boolean このモデルの過剰な暴力表現を含むコンテンツでの利用を許可するか No, 初期値: false
allowExcessivelySexualUsage boolean このモデルの過剰な性的表現を含むコンテンツでの利用を許可するか No, 初期値: false
commercialUsage string このモデルを利用した商用利用の許可範囲 No, 初期値: personalNonProfit
allowPoliticalOrReligiousUsage boolean このモデルの政治・宗教用途での利用を許可するか No, 初期値: false
allowAntisocialOrHateUsage boolean このモデルの反社会的・憎悪表現を含むコンテンツでの利用を許可するか No, 初期値: false
creditNotation string このモデルのクレジット表記の強制および放棄の指定 No, 初期値: required
allowRedistribution boolean このモデルの再配布を許可するか No, 初期値: false
modification string このモデルの改変の許可範囲 No, 初期値: prohibited
otherLicenseUrl string その他のライセンス条件があれば、そのURL No
HoshiHoshi

VRM に首を振らせる

import { VRM, VRMLoaderPlugin } from "@pixiv/three-vrm";
import { useFrame } from "@react-three/fiber";
import { useEffect, useState } from "react";
import { Object3D } from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

type AgentProps = {};

type BonesState = {
  neck: Object3D | undefined | null;
};

const loader = new GLTFLoader();
loader.register((parser) => new VRMLoaderPlugin(parser));

function Agent(props: AgentProps) {
  // const { scene, camera } = useThree();

  const [bonesState, setBones] = useState<BonesState>();
  const [vrmState, setVrm] = useState<VRM>();

  useEffect(() => {
    loader.load("VRM1_Constraint_Twist_Sample.vrm", (gltf) => {
      const vrm = gltf.userData.vrm as VRM;
      setVrm(vrm);

      const bones = {
        neck: vrm.humanoid.getNormalizedBoneNode("neck"),
      };
      setBones(bones);
    });
  }, []);

  useFrame(({ clock }, delta) => {
    if (vrmState) vrmState.update(delta);

    const t = clock.getElapsedTime();
    if (bonesState?.neck) {
      bonesState.neck.rotation.x = (Math.PI / 4) * Math.sin(t * Math.PI);
    }
  });

  return vrmState && <primitive object={vrmState.scene} />;
}

export default Agent;