個人ブログのようなものです。とくにジャンルはありません。
アシュリー、魔法はよいこになってから!(全3巻)
商品ページ
Amazon
※非収益広告
記事の概要
three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する Part-02
作成日:2025-04-14
最終更新日:2025-04-15
記事の文字数:7753
VRoidWebツール情報技術
本記事のトピック
  • 概要
  • three-vrmのupdate処理をまず呼び出す
  • カメラ目線にする
  • ポーズの変更方法(vroidposeデータから変更する)
  • 表情の変更方法
  • 口の形の変更方法
  • 変なテクスチャが表示されてしまう問題
  • 解像度問題の対応
  • vrm.update処理の中身について
  • サンプルコードはこちら
  • vroidposeにまつわる問題点
three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する Part-02
概要

three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する方法を載せています。
こちらはPart-02となり、Part-01の内容を前提としたものになっています。

今回は以下のコード例を載せています。
Part-01のコードがベースなので、「/* 追加 start */~/* 追加 end */」という形で、追加部分を明記しています。

今回紹介するコード例

  • three-vrmのupdate処理をまず呼び出す
  • カメラ目線にする
  • ポーズの変更方法(vroidposeデータから変更する)
  • 表情の変更方法
  • 口の形の変更方法
  • 変なテクスチャが表示されてしまう問題
  • 解像度問題の対応

また、今回も例によって、実際に動作確認できる(であろう)サンプルコードを下部に載せています。

three-vrmのupdate処理をまず呼び出す

今回行う内容(例えばカメラ目線にする)の中で、three-vrmが用意しているupdate処理を毎フレーム行うことでモデルに反映されるものがあります。

そのため事前準備として、animateメソッドの中でVRoidのアップデート処理を呼び出しておきましょう。

function animate() { requestAnimationFrame(animate); /* 追加 start */ // VRoid自動アップデート const deltaTime = 1 / 60; // 本当は毎フレームごとに計算した方が良いと思うけど、概ね1/60秒で動くと思うので vrm.update(deltaTime); // これが大切 /* 追加 end */ renderer.render(scene, camera); }

最後の方でこのvrm.updateについて簡単にまとめますが、色んな自動計算を行ってくれているようです。

また、この追加とともにvrmの初期化のあたりで、以下を入れておいてください。
これも後述しますが、これをしないと後の手順で行う「ポーズの変更」がVRoidに反映されないはずです

// VRMモデルを設定してシーンに追加 vrm = gltf.userData.vrm; scene.add(vrm.scene); /* 追加 start */ // 姿勢はvroidposeに従って制御するので、自動制御はOFFにする vrm.humanoid.autoUpdateHumanBones = false; /* 追加 end */
カメラ目線にする

VRoidをカメラ目線にします。
より正しく言えば、vrmの初期化時にVRoidの注視点をカメラに設定します。

// カメラ設定を反映 controls.update(); /* 追加 start */ // VRMをカメラ目線にする vrm.lookAt.target = camera; vrm.lookAt.autoUpdate = true; // 初期値だとあまりカメラに目を向けないようになっているので、補正する vrm.lookAt.applier.rangeMapHorizontalInner.outputScale = 20; vrm.lookAt.applier.rangeMapHorizontalOuter.outputScale = 20; vrm.lookAt.applier.rangeMapVerticalDown.outputScale = 20; vrm.lookAt.applier.rangeMapVerticalUp.outputScale = 20; /* 追加 end */ animate();

行数で言うとたった6行で完了(後は前項のvrm.updateを呼び出す必要あり)。

最初の2行が「VRoidがカメラを見る」(VRoidの眼球をカメラに向ける)というものになります。まぁそのままですね。

最後の4行ですが、デフォルト設定だとそんなにカメラを見てくれなくて、微妙に違う場所を見ている感じになってしまいます(一旦この行を消して試してみると分かりやすいです)。

そのため、カメラに目を向ける補正値を大きくすることでカメラ目線になるようにします。あまりに大きい値にすると今度は行き過ぎてしまうので丁度いい値を探すと良いと思います。

ポーズの変更方法(vroidposeデータから変更する)

VRoidのポーズを変更します。
手動で設定するのも良いですが、vroidposeファイルを使用するのが楽だと思うのでその方法で書きます。

/* 追加 start */ // ポーズデータを読み込んだら、それをvrmに適用する document.getElementById("poseDataInput").addEventListener('change', (event)=>{ const file = event.target.files[0]; if(file && vrm){ // ファイルを読み込む const reader = new FileReader(); reader.onload = () => { const poseData = JSON.parse(reader.result); const bonePose = poseData.BoneDefinition; // ボーンごとに保持する for (const boneName in bonePose) { const lowerBoneName = boneName.charAt(0).toLowerCase() + boneName.slice(1); // ポーズデータとVRMボーンの名前を同じにするために、ローワーキャメルにする // vrmにポーズを適用 const vrmBone = vrm.humanoid.getRawBoneNode(lowerBoneName); if(vrmBone){ const quat = bonePose[boneName]; vrmBone.quaternion.set(quat.x, quat.y, -quat.z, -quat.w); } } }; reader.readAsText(file); } }); /* 追加 end */

html側でもファイルを読み込めるようにする

<input type="file" id="poseDataInput" accept=".vroidpose">

ファイルを読み込む処理が入っているので若干行数多めですが、肝心のポーズを変更する処理は「vrmBone.quaternion.set(quat.x, quat.y, -quat.z, -quat.w)」の一行のみです。

もしこの処理を入れても、上手くいかないようであれば最初のvrm.update処理の中で記述した「vrm.humanoid.autoUpdateHumanBones = false;」と言うのを忘れているのではないかと思います。

ところでthree-vrmで読み込んだvroidモデル情報には「rawBone」と「normalizedBone」という二つのボーン情報が入っています。
・rawBone:threejsが持っている大元のボーン情報。本処理で書き換えてるのはこっち。
・normalizedBone:three-vrmjsによってvrm用に正規化されたボーン情報。

vrm.updateメソッドを呼び出すと、このnormalizedBoneからrawBoneに自動的にいい感じにボーンを設定してくれます。

なので逆に言うと、vrm.updateメソッドを使ってしまうとrawBoneを書き換えても勝手にnormalizedBone情報からrawBone側を勝手に書き換えてしまい、元のポーズに戻ってしまいます。「vrm.humanoid.autoUpdateHumanBones = false;」とすることで、その書き換えをさせないようにしています。

本処理ではrawBoneを使っていますが、normalizedBoneを使うことで、vrm側の設定違い(軸やポジションの違い)を補正してrawBone側に設定してくれるようです。なので、normalizedBoneを使う方が無難ではあるかと思います。

normalizedBoneを使いたい場合は上のコードの「vrm.humanoid.getRawBoneNode(lowerBoneName)」を「vrm.humanoid.getNormalizedBoneNode(lowerBoneName)」としたうえで、「vrm.humanoid.autoUpdateHumanBones = false;」を消せば良いです。

また、「手の形」は反映されません。これは元のvroidposeデータを見れば分かると思いますが、手だけはボーンではなくテンプレートを指定して設定されているためです。テンプレートを手のボーンに変換しないとVRoidには反映できないかなと思います。

表情の変更方法

表情変更も簡単です。

/* 追加 start */ // 表情変更 document.getElementById("expressionOn").addEventListener('click', (event)=>{ if(!vrm) return; vrm.expressionManager.setValue('relaxed', 1.0); }); document.getElementById("expressionOff").addEventListener('click', (event)=>{ if(!vrm) return; vrm.expressionManager.setValue('relaxed', 0.0); }); /* 追加 end */

html側で切り替えできるようにする

<input type="button" id="expressionOn" value="笑う"> <input type="button" id="expressionOff" value="笑わない">

実質「vrm.expressionManager.setValue('relaxed', 1.0);」の一行のみです。

setValueの引数に、「表情」と「変更の度合い」を設定すれば良くて、もし怒りと悲しみを0.5ずつ設定したいときは「setValue('sad', 0.5)」「setValue('angry', 0.5)」にすれば良いだけです。

ちなみにVRoidStudioで作ったモデルであれば、以下の表情が設定できると思います。
・relaxed
・sad
・angry
・happy
・Surprised
・neutral

口の形の変更方法

口の形の変え方はほぼ表情変更と同じです。

/* 追加 start */ // 口の開け閉め document.getElementById("mouthOpen").addEventListener('click', (event)=>{ if(!vrm) return; vrm.expressionManager.setValue('aa', 1.0); }); document.getElementById("mouthClose").addEventListener('click', (event)=>{ if(!vrm) return; vrm.expressionManager.setValue('aa', 0.0); }); /* 追加 end */

html側で切り替えできるようにする

<input type="button" id="mouthOpen" value="口を開ける"> <input type="button" id="mouthClose" value="口を閉じる">

実質「vrm.expressionManager.setValue('aa', 1.0);」の一行のみです。

表情変更とほぼ同じですね。

ちなみにVRoidStudioで作ったモデルであれば、以下の口の形が設定できると思います。
・aa
・ih
・ou
・ee
・oh

変なテクスチャが表示されてしまう問題

Part-01では、モデルの首の下や背中部分に不自然に黒いテクスチャが出現していたかと思います。

これというのは実は服のテクスチャに、「ほぼ透明に近いけど透明ではない部分」があり、それが不透明度1.00(最大)として表示されてしまっていたためです。

しかし、恐らく現状のコードであれば多分透明になっていると思います。

一番最初に行った「vrm.update」メソッドがこの部分をいい感じにしてくれているようで、それによって透明になっているようです。
なので、現状対応不要なのですが、vrm.updateメソッドの処理をすべて行うとは限らない(後述)ので、一応個別に設定したい場合の方法を書きます。

// VRMモデルを設定してシーンに追加 vrm = gltf.userData.vrm; scene.add(vrm.scene); /* 追加 start */ Object.values(vrm.materials).forEach((val) => { if(val?.uniforms?.alphaTest?.value !== undefined){ val.uniforms.alphaTest.value = 0.02; // これ以上の不透明度のテクスチャは不透明になる。初期値は0。 } }); /* 追加 end */

テクスチャは複数あるので、ループで回す必要はありますが、肝心の個別設定は「val.uniforms.alphaTest.value = 0.02;」の一行のみで設定できます。

このalphaTest.valueという値が閾値になっており、これ以上の不透明度のテクスチャは不透明度1.00(最大)として表示されるようです。初期値は0なので、ごくわずかでもテクスチャに不透明な場所があるとそれが不透明度1.00(最大)として表示されてしまうということです。

vrm.updateメソッド内で行われているテクスチャ関連の自動更新処理がこのalphaTestの値をいい感じに変更してくれるようなので、vrm.updateメソッドをきちんと呼んでいればこの問題は多分起こらないと思います。

解像度問題の対応

スマホなどの高解像度の端末で表示すると、綺麗にcanvasが表示されず若干崩れて表示されることがあります。

まぁ原因は解像度が高いから、とそれ以上でもそれ以下でも無いですが、解像度の設定も容易にできます。

// canvasの用意 renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize( canvasWidth, canvasHeight ); /* 追加 start */ renderer.setPixelRatio(window.devicePixelRatio); // 解像度の高い端末でも綺麗に表示されるようにする /* 追加 end */ document.body.appendChild(renderer.domElement);

window.devicePixelRatioの値が、Android端末だと大体3.5倍くらいなのですが、割と顕著に処理が重くなります。もちろん元のcanvasサイズが大きければ更に重くなります。

実際にはwindow.devicePixelRatioの値をそのまま設定するのではなく、「Math.min(window.devicePixelRatio, 2.0)」などとすることで、上限値を設けた方が良いと思います。

vrm.update処理の中身について

何度も話に出てくる「vrm.update」メソッドについて、簡単に書いておきます。

このメソッドですがざっくり以下のことをやっているようです(更に深堀りすると複雑になるので、更に細かく見たい場合は御自身でご確認ください)。

// normalizedBoneから、rawBoneを更新する if(this.autoUpdateHumanBones){ this._normalizedHumanBones.update(); } // モデルの目を注視点(現在の設定ではカメラ)に向ける if(this.lookAt){ this.lookAt.update(t); } // モデルの表情を更新 if(this.expressionManager){ this.expressionManager.update(); } // ボーンの連動関係があれば更新する if (this.nodeConstraintManager) { this.nodeConstraintManager.update(); } // モデルのボーンを更新 if (this.springBoneManager) { this.springBoneManager.update(e); } // モデルのマテリアルを更新 if (this.materials) { this.materials.forEach(n => { if (n.update) { n.update(e); } }); }

今回の所で言うと、this.lookAt.updateメソッドはかなり関係します。カメラ目線にする際、カメラをターゲットにするだけで良かったですが、それはこのメソッドのおかげです。

コード量を減らすためにvrm.updateメソッドはきちんと利用するべき(というかそうしないならthree-vrmを使う意味が……)だとは思いますが、毎フレーム呼ばれている処理なので不要な処理はなるべく呼びたくないという人もいると思います。そういう方はvrm.updateメソッドの中身を見て個別に呼び出すのが良いかと思います。

サンプルコードはこちら

サンプルコードはPart-01と同じ場所(GitHub)にあります。

vroidposeにまつわる問題点

vroidposeデータはVRoidStudio用のポーズデータですが、前述の通り「手の形」はそのままではボーンに反映されない問題があります。
こちらは対応がまだできるのですが、「SpineControlPointDeltaPosition」という独自の設定をボーンに反映するのが難しいです。

何か良い対応方法が思いつけば記事にするかもしれません。

コメントログ
※コメントは最新50件が表示されます
コメント投稿




画面下部の「コンタクト」からも連絡可能です。
D.C.Ⅱ.S.S.~ダ・カーポⅡセカンドシーズン~(TV番組)
商品ページ
Amazon
※非収益広告
管理人作品宣伝
ぱらぱら工房
Webサイト / 最終更新:2025-03-26
jpegやpngなどの静止画画像ファイルを複数枚結合して、GIFやAPNG(アニメーシ…jpegやpngなどの静止画画像ファイルを複数枚結合して、GIFやAPNG(アニメーション付きPNG)を作成するだけのWebツールです。

HPで閲覧する支援特典
利用素材等の詳細情報
VRoidポーズ集-Part03
3Dモデル / 最終更新:2024-12-03
VRoidのポーズデータ(vroidpose)集です。 写真とかによくありそうなポーズ…VRoidのポーズデータ(vroidpose)集です。 写真とかによくありそうなポーズが中心に入っています。

Boothで閲覧する
利用素材等の詳細情報
作品一覧はこちら
関連ページ
three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する Part-02
概要 three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する方…
three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する Part-01
概要 three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する方…
GIF / APNG(アニメーション付きPNG)ファイル解析ページ
ファイル読込・操作 以下に調べたいファイルを読み込ませてください。 ファイル情報 カラーパレットを…
gifler.js仕様メモ
本ページの趣旨 「gifler.js」という、gifアニメーションをcanvasに簡単に表示できる…
普通の文章をホラーっぽく変換
テキスト:ホラー変換 変換する度に結果が変わります 変換回数: 変換する 変換結果 変換する…
【プログラミング】実例で分かるかもしれない再帰処理
本ページは以下動画の台本を書き起こしたものです 解説の趣旨・方向性 皆さん、こんばんは今回はプログ…
管理人について
「ふじみ むい」と言います ひょんなことから肉体を得たのでその肉体を使って活動をしています。 とい…
SNSツイート一元化対応(Twitter・Misskey・Mastodon・Bluesky)-公開
概要 SNSツイートを一元化するためのツールを作成しています(古い記事ですが、こちらのページで紹介…
VRoidを使う前に絶対に表情はいじった方が良いと思うという話
デフォルトのVRoidの表情はすごいVRoidっぽい VRoidStudioでは「楽しい」「悲しい…
Twitter:【日替わり】一問一答自己紹介テーマ
Twitter:【日替わり】一問一答自己紹介テーマ 日替わりで簡単な質問が表示されます。 毎日つぶ…
管理人ツイート
本サイトのタグ一覧
NovelAIR18VRoidWebサイト作成Webツールととモノ。アークナイツアークナイツ-ステージ攻略日記アズールレーンアズールレーン-日記ウマ娘ギャラリーゲームデビラビローグプログラミングホラーポケットタウン怪談気ままな日記情報技術情報技術-WebAPI知的財産権統合戦略白夜極光本サイトについて魔王スライム様がんばる!漫画
人気記事
メイド・オブ・ザ・デッド-攻略お助け情報
ネタバレ注意! 本ページは『メイド・オブ・ザ・デッド』の情報を記録しているものです。 攻略の参考に…
989.6480 pt
ポケットタウン_パズル一覧
グレーのピースの数 (Number of gray pieces):検索グレーピースの数を入力して、…
789.6056 pt
剣と魔法と学園モノ。2G - パーティ編成確認ツール
ツール概要 ととモノ。2Gのパーティ編成を考える際に使うツールです。 あくまでストーリークリアまで…
145.8605 pt
アークナイツ-昇進2率ランキング
アークナイツのTier表を作る際の備忘録です こちらのページで、昇進2率を基にTier表を作ろうと…
89.6533 pt
アークナイツ-常設商品-理性換算
概要 "常設商品でお得な商品はどれか"というのを理性に換算して一覧化したものとなります。 絶対的に…
88.3600 pt
ロックマンエグゼ3-バグのかけら必要数まとめ-
バグのかけら必要数 必要数 これぐらいあれば足りるはず。 コレクト要素に関わる部分だけなら、ギガチ…
76.4211 pt
アークナイツ:統合戦略#5「サルカズの炉辺奇談」-「心打つ鍵鞭」攻略お助け情報
概要 統合戦略#5「サルカズの炉辺奇談」の公式サイトからできる「心打つ鍵鞭」についての、攻略お助け…
63.1579 pt
アークナイツ-5周年商品-理性換算
概要 "「5周年商品」および「新常設商品」でお得な商品はどれか"というのを理性に換算して一覧化した…
48.0000 pt
最新記事
three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する Part-02
概要 three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する方…
three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する Part-01
概要 three.js・three-vrm.jsを使用して、VRoidをWebページ上に表示する方…
スペシャルサンクス
順不同、敬称略。 サイト構築系 全般 ・さくらインターネット:レンタルサーバ ・GitHub:ソー…
本サイトについて
本サイトの概要 概要 個人ブログのようなものです。とくにジャンルはありません。 本サイト内の情報に…
タイム・リープ<上> あしたはきのう (電撃文庫)
商品ページ
Amazon
※収益広告