Unityエディタ拡張WeightSyncProは、異なるメッシュ間でスキンウェイトを転送するためのツールです。開発の初期段階において、特にアニメーションポーズをとったキャラクターにおいて、転送対象となる頂点の検出位置(ギズモ表示)が実際のメッシュ表面からずれるという課題がありました。本記事では、この問題の原因分析と、SkinnedMeshRenderer.BakeMesh()メソッドを用いた解決策、そしてその改善結果について解説します。
問題点:静的な頂点情報によるズレ
初期バージョンのWeightSyncProでは、ウェイト転送のソースおよびターゲットとなるメッシュの頂点情報をSkinnedMeshRenderer.sharedMesh.verticesから取得していました。このプロパティは、メッシュのバインドポーズ(通常はTポーズやAポーズ)における静的な頂点座標を返します。
その結果、キャラクターがアニメーションによってポーズを変えると、sharedMesh.verticesから得られる座標と、実際に画面に表示されているメッシュの表面との間に食い違いが生じます。具体的には、画像のように、ショートデニムを履いたキャラクターでLower Bodyモードで頂点を検出しようとした際、検出ギズモ(緑色の点)が実際のパンツのウエストラインよりもY軸上方向にずれ、腹部の素肌部分やトップスの裾あたりに分布してしまう現象が見られました。これは、ギズモの表示や最近傍頂点の計算が、アニメーション適用後の見た目ではなく、静的なバインドポーズの形状に基づいて行われていたためです。
修正前の状態
画像では、緑色のギズモがパンツの実際の位置よりも全体的に上にずれて分布しています。

解決策:SkinnedMeshRenderer.BakeMesh()の導入
この問題を解決するために、SkinnedMeshRenderer.BakeMesh()メソッドを利用しました。このメソッドは、SkinnedMeshRendererの現在のボーン変形やブレンドシェイプが適用された状態のスナップショットを新しい静的メッシュとしてベイクする機能を提供します。
BakeMesh()を使用することで、アニメーションポーズ適用後の、ユーザーが実際に見ている状態の頂点座標を取得できます。
主な修正点は以下の通りです。
DetectVerticesメソッドの修正: ソースメッシュとターゲットメッシュの頂点座標を取得する際に、sharedMesh.verticesの代わりにBakeMesh()を使用します。
// DetectVerticesメソッド内 Mesh sBakedMesh = new Mesh(); sourceRenderer.BakeMesh(sBakedMesh, true); // useScale=trueでスケールも考慮 Vector3[] sVerts = sBakedMesh.vertices; // アニメーション適用後のソース頂点(ローカル座標) Mesh tBakedMesh = new Mesh(); targetRenderer.BakeMesh(tBakedMesh, true); // useScale=trueでスケールも考慮 targetVerticesCache = tBakedMesh.vertices; // アニメーション適用後のターゲット頂点(ローカル座標)をキャッシュ // ... (以降の処理でsVertsとtargetVerticesCacheをワールド座標に変換して使用) // メソッドの最後にベイクしたメッシュを破棄 if (sBakedMesh != null) DestroyImmediate(sBakedMesh); if (tBakedMesh != null) DestroyImmediate(tBakedMesh);BakeMeshの第二引数useScaleをtrueにすることで、SkinnedMeshRendererのTransformのスケール値も考慮された頂点座標が得られます。取得される頂点座標は、そのSkinnedMeshRendererのローカル座標系です。
ApplyWeightTransferメソッドの修正: ウェイト転送処理時も同様に、最近傍頂点の検索にはBakeMesh()で得られた現在のポーズの頂点座標を使用します。
// ApplyWeightTransferメソッド内 Mesh sBakedMesh = new Mesh(); sourceRenderer.BakeMesh(sBakedMesh, true); Vector3[] sVerts = sBakedMesh.vertices; Mesh tBakedMesh = new Mesh(); targetRenderer.BakeMesh(tBakedMesh, true); Vector3[] tVerts = tBakedMesh.vertices; // またはキャッシュされたtargetVerticesCacheを使用 // ... (ワールド座標に変換して最近傍検索) // メソッドの最後にベイクしたメッシュを破棄 if (sBakedMesh != null) DestroyImmediate(sBakedMesh); if (tBakedMesh != null) DestroyImmediate(tBakedMesh);ギズモ表示の修正: OnSceneGUIメソッド内で検出頂点をギズモ表示する際も、BakeMesh()でキャッシュしたtargetVerticesCache(ローカル座標)をワールド座標に変換して使用します。
// OnSceneGUIメソッド内 Handles.SphereHandleCap(0, targetRenderer.transform.TransformPoint(targetVerticesCache[i]), Quaternion.identity, handleSize, EventType.Repaint);
修正結果
BakeMesh()を導入したことにより、WeightSyncProはキャラクターがどのようなポーズをとっていても、画面上の見た目と一致する正確な頂点位置に基づいて処理を行えるようになりました。
修正後の状態
画像では、緑色のギズモがパンツの形状に沿って正確に分布しており、ウエストラインや裾の位置も実際のメッシュと一致しています。これにより、ユーザーは直感的に転送範囲を確認でき、ウェイト転送の精度も大幅に向上しました。

まとめ
SkinnedMeshRenderer.sharedMesh.verticesは静的なバインドポーズの情報を返すのに対し、SkinnedMeshRenderer.BakeMesh()はアニメーションやブレンドシェイプが適用された現在の見た目に基づいた頂点情報を取得できます。Unityエディタ拡張でスキンメッシュの現在の形状を扱う際には、BakeMesh()の活用が非常に有効です。これにより、WeightSyncProはより正確で信頼性の高いウェイト転送ツールへと改善されました。
なお、BakeMesh()は呼び出し時にCPUでスキニング処理を実行します。頻繁な呼び出しはパフォーマンスに影響を与える可能性があるため、エディタ拡張のようなインタラクティブな操作時や、必要なタイミングに限定して使用するのが適切です。