本プロジェクトはVue向けのVRMアバター表示コンポーネントを開発したことに関する資料です。
NPM: https://www.npmjs.com/package/vue-vrm
Github: https://github.com/logue/vue-vrm
デモ: https://logue.dev/vue-vrm
1. ビルドスタック:なぜRsbuildなのか
2. ライセンスの防波堤:ArrayBuffer設計
VRMやVRMAを使う上で見落としがちなのは、アセットのライセンスの問題です。
例えばVRoid Studioでアバターを作るときに、BoothやEtsy、ニコニ・コモンズで配布/販売されているアセットやアバターを使っていたとします。
これを画像のsrcのようにURL指定で受け取ったらどういうことになるでしょうか?
ブラウザの開発バーのネットワークタブを見ると、どこから何のファイルを取得したかのログが残ります。
たとえ、そのアバターが自分に著作権があっても、そこで使用している第三者のアセットを使用している場合、多くのアセットは再配布を禁じているので、VRMやVRMAを画像と同じような感覚で扱うと、ブラウザのコンソールログからアバターのURLが漏れて、意図せずとも再配布している状態になってしまいます。
VRMという規格自体にもメタ情報でライセンスを縛る仕組みがありますが、glTFという規格のサブセットという性質上、紳士協定に近く簡単に中身が見えてしまいます。
つまるところ、VRMアバターやVRMAをURL指定で読み込むという実装は、簡単にダウンロードできる状態ということになり、もしもそこに第三者のアセットが含まれていた場合、ライセンス違反になります。
そこで、Amazon S3やCloudflare R2のようなオブジェクトストレージにVRMやVRMAファイルを置き、Cloudflare Workersのようなワーカー使ってAPI経由でデータをArrayBuffer形式で受け取る実装を想定しています。
こうすることで、通信はオブジェクトストレージとAPI間のみになり、ブラウザに流れてくる情報はArrayBufferとなるので、開発バーに通信ログが残らず安全にVRMやVRMAを使用することができます。
3. 実装のキモ
まず、このVueコンポーネントは、VRMアバターの表示に特化したものであるという点です。主に小規模フォーラムのユーザのアイコン代わりの3Dアバター、音声AIチャットの依代としての運用を想定しています。HTML文法に近いVueの手軽さと組み合わせて、タグを埋めこむだけでアバターが表示されるようにするというのがコンセプトです。
three.jsはcanvasに描画する性質上、画面のサイズに追従しません。画面サイズが変わったときに、アスペクト比を保持しつつcanvasタグをリサイズさせ、再描画する処理は自力で書く必要があります。このためのイベント処理は、ResizeObserverを使っています。一昔前まではsetTimeout関数でスレッドを回して監視していましたが、この関数を使うとリサイズされたタイミングでイベントが発火するので処理が軽くなります。
また、アスペクト比を入れるプロップ(デフォルトでは3:4)も用意しているため、ウィンドウのサイズを変えてもアバターが途切れるという現象を予め起きないようにしています。画面サイズ追従はレスポンシブデザインの基本ですしね。
これは00年代のマビノギの公式掲示板から着想を得ましたが、アバターにインタラクトできるプロップも用意しました。ズーム、パン(カメラの上下左右移動)に加えて、3D空間におけるカメラの3軸回転――すなわち、ピッチ(首を縦に振る)、ヨー(左右を見回す)、ロール(首をかしげる)、チルト(首を傾ける) といった基本操作を、それぞれオプションで直感的に設定できるようにしています。
4. 実際の使用例
まず、本コンポーネントとthreejs、vrm関連のライブラリをインストールします。
pnpm add vue-vrm vue three @pixiv/three-vrm @pixiv/three-vrm-animation
Vue側のコードは下記のように記述します。このコードではファイルフォームにvrmファイルを入れると、そのアバターがTポーズで表示されます。
<script setup lang="ts">
import { ref } from 'vue';
import { VrmCanvas } from 'vue-vrm';
const modelData = ref<ArrayBuffer | null>(null);
async function onModelFile(event: Event): Promise<void> {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
modelData.value = await file.arrayBuffer();
}
</script>
<template>
<div>
<input type="file" accept=".vrm,.glb" @change="onModelFile" />
<VrmCanvas :model-data="modelData" :show-grid="true" />
</div>
</template>
より詳しい情報は、vue-vrmの日本語公式ドキュメントを見てください。
5. バックエンドの防壁:Cloudflare Workersによるバイナリ配信
第2章で述べた「ArrayBuffer設計」を成立させるためには、アセット(VRM)を安全に隠すバックエンド(API)が必要です。
「Cloudflare Workersとかややこしそう」「インフラの設定面倒くさい」と思うかもしれませんが、やることはシンプルです。Amazon S3やCloudflare R2にアバターを格納し、Workersを経由させてブラウザに「生のバイナリ(Streaming)」として流し込むだけです。
これによって、フロントエンドのネットワークタブにはAPIのURLしか残らず、アセットの直リンク流出(意図せぬ再配布)を100%遮断します。
以下に、Wrangler(Wrangler v3以降)で即座にデプロイできる最小限のWorkerコード(TypeScript)を置いておきます。
// JSDocはAIへの指示書であり、未来の自分への仕様書である。
interface Env {
BUCKET: R2Bucket; // あなたのR2バケット
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.pathname.slice(1); // URLからファイル名を取得
// 1. レガシーな世界からの不正アクセスを弾く最低限のバリデーション
if (!key.endsWith('.vrm') && !key.endsWith('.vrma')) {
return new Response('Invalid Asset Type', { status: 400 });
}
const object = await env.BUCKET.get(key);
if (!object) {
return new Response('Asset Not Found', { status: 404 });
}
// 2. フロントエンドのArrayBuffer通信を許可するCORSヘッダーのコンパイル
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('Access-Control-Allow-Origin', '[https://your-frontend-domain.com](https://your-frontend-domain.com)');
headers.set('Access-Control-Allow-Methods', 'GET');
// 3. バイナリをストリーミングとして安全に出力
return new Response(object.body, { headers });
},
};
このあたりの実装はかなりややこしいので、下記ページのドキュメントを読みつつ、生成AIを活用して作業してください。
- https://github.com/logue/vue-vrm/blob/master/examples/cloudflare-workers.md
- https://github.com/logue/vue-vrm/blob/master/examples/vroid-hub-api.md
6. 今後の展望
現在、VRMファイルを読み込みアバターをVueコンポーネントで表示し、そこにVRMA形式のアニメを反映するところと、カメラの位置や、ライティングの設定など最小限のことしか実装していません。
three.jsのインスタンスや、@pixiv/vrmのインスタンスへ直接アクセスできる設計にしていますが、まだまだ発展途上です。今後のIssueなどの様子を見て実装していきたいと思います。
7. おわりに:単に行儀の良いコードならAIでも書ける
このRsbuild×Vue3×VRMという尖ったスタックを組んでいる最中、生成AIにコードのアシストを頼んでみたところ、AIはRsbuildのピュアな美しさが理解できず、途中で脳死のViteを混ぜてきたり、RslintがあるのにESLintをインポートしようとしてハングアップ(コンパイルエラー)を起こしました。
AIは「世の中の凡庸なコピペコード(What)」を真似ることは得意ですが、「なぜこの依存関係を引き剥がし、アセットのライセンスを守るためにArrayBufferで縛るのか」という、人間の生々しいコンテキスト(Why)を理解した設計はできません。
これは、フリーランス市場で「技術選定の立場」を求めている人間に対して、「Reactの経験年数は?」と聞いてきたり、「フリーランスがダメなら正社員になれば?」と的外れなバグを吐き出す、どこぞの仲介エージェントの処理能力の低さに酷似しています。
単に行儀が良く、誰の感情も逆撫でしない代わりに、脆弱性だらけでパッと見で意図のわからない負債コードを量産する時代は、もう終わりにしましょう。
本ライブラリ(vue-vrm)が、あなたのアバターを、美しく安全にインターネットへデプロイするための堅牢な防波堤になれば幸いです。
