Blender で パーフェクトシンク用のシェイプキー(Shape Key)を追加して、Unity で VRM0 のブレンドシェイプに設定する手順について説明する。VRM1 に関しては、「パーフェクトシンク対応(VRM1)」という記事を参照してほしい。
前提
VRoid で作成したモデルは想定していない。
パーフェクトシンクとは
パーフェクトシンクは、ARKit[1] で取得出来る BlendShapeLocation[2] 全52点を VRM の同名 BlendShapeClip に適用させる設定です。(中略)
パーフェクトシンクについて
パーフェクトシンクを使う事で、実際のユーザーの表情を直接アバターに適用する事が出来ます。これによって、非常に表情豊かな表現をすることが出来ます。(中略)
「パーフェクトシンク」はARKitの特徴を利用してモーフィングする挙動を指す造語です
- Apple 社が開発した iOS デバイスで動作する AR フレームワーク、API
- 検出された表情における顔の部位の位置(動き)を係数として記述したもの
スクリプト①
import bpy
def add_shape_keys():
keys = [
"Basis",
"BrowDownLeft",
"BrowDownRight",
"BrowInnerUp",
"BrowOuterUpLeft",
"BrowOuterUpRight",
"CheekPuff",
"CheekSquintLeft",
"CheekSquintRight",
"EyeBlinkLeft",
"EyeBlinkRight",
"EyeLookDownLeft",
"EyeLookDownRight",
"EyeLookInLeft",
"EyeLookInRight",
"EyeLookOutLeft",
"EyeLookOutRight",
"EyeLookUpLeft",
"EyeLookUpRight",
"EyeSquintLeft",
"EyeSquintRight",
"EyeWideLeft",
"EyeWideRight",
"JawForward",
"JawLeft",
"JawOpen",
"JawRight",
"MouthClose",
"MouthDimpleLeft",
"MouthDimpleRight",
"MouthFrownLeft",
"MouthFrownRight",
"MouthFunnel",
"MouthLeft",
"MouthLowerDownLeft",
"MouthLowerDownRight",
"MouthPressLeft",
"MouthPressRight",
"MouthPucker",
"MouthRight",
"MouthRollLower",
"MouthRollUpper",
"MouthShrugLower",
"MouthShrugUpper",
"MouthSmileLeft",
"MouthSmileRight",
"MouthStretchLeft",
"MouthStretchRight",
"MouthUpperUpLeft",
"MouthUpperUpRight",
"NoseSneerLeft",
"NoseSneerRight",
"TongueOut"
]
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
for key in keys:
bpy.context.active_object.shape_key_add(name=key)
add_shape_keys()
空のシェイプキーを追加する
- ワークスペースを Scripting に変更する
- シェイプキーを追加したいオブジェクトを選択する
- 上記のスクリプト①をコピペする
- スクリプトを実行する

VRChat や VRM で使用する空のシェイプキーも追加する場合は、「VRChat と VRM で使用する空のシェイプキーを追加する」という記事を参考にしてほしい。
表情を作成する
はいぬっかさんの以下の記事を参考にして、Blender で表情を作成する。
パーフェクトシンク対応モデルをつくろう! iPhone顔トラッキング用 52BlendShapes制作メモ – はいぬっかメモ (hatenablog.com)
ブレンドシェイプを手動で設定したくない
Unity にて、52 個の BlendShapeClip を作成して、その全てに Weight をセットする作業は、非常に時間がかかるので手動では決してやりたくない。スクリプトを用いて設定する。

スクリプト②
using System.IO;
using UnityEditor;
using UnityEngine;
namespace VRM
{
public class AddPerfectSyncBlendShapeClip : MonoBehaviour
{
// AssetsフォルダのVRMプレハブをセットしてもらう
public GameObject VRMPrefab;
// 縦の三点リーダーから関数を実行してもらう
[ContextMenu("AddPerfectSyncBlendShapeClip")]
public void AddBlendShapeClip()
{
// パスを特定する
var vrmPrefabPath = AssetDatabase.GetAssetPath(VRMPrefab);
var ext = ".prefab";
var blendShapePath = vrmPrefabPath.Replace(ext, ".BlendShapes/BlendShape.asset");
// アバターをロードする
var blendShapeAvatar = AssetDatabase.LoadAssetAtPath<BlendShapeAvatar>(blendShapePath);
// BlendShapeがあるメッシュを特定する
Mesh mesh;
int blendShapeCount;
int transformCount = VRMPrefab.transform.childCount;
for (int i = 0; i < transformCount; i++)
{
var transform = VRMPrefab.transform.GetChild(i);
var skinnedMeshRenderer = transform.GetComponent<SkinnedMeshRenderer>();
if (skinnedMeshRenderer != null)
{
blendShapeCount = skinnedMeshRenderer.sharedMesh.blendShapeCount;
if (blendShapeCount != 0)
{
mesh = skinnedMeshRenderer.sharedMesh;
// Clipを追加してゆく
for (int j = 0; j < blendShapeCount; j++)
{
var name = mesh.GetBlendShapeName(j);
var assetName = name + ".asset";
var clipPath = blendShapePath.Replace("BlendShape.asset", assetName);
var DefaultClipPath = blendShapePath.Replace(".asset", "." + assetName);
if (!File.Exists(clipPath) && !File.Exists(DefaultClipPath))
{
var clip = ScriptableObject.CreateInstance<BlendShapeClip>();
clip.name = name;
clip.BlendShapeName = name;
clip.Preset = BlendShapePreset.Unknown;
AssetDatabase.CreateAsset(clip, clipPath);
if (blendShapeAvatar.GetClip(name) == null)
{
blendShapeAvatar.Clips.Add(clip);
}
}
}
// Weightを設定してゆく
var proxy = VRMPrefab.GetComponent<VRMBlendShapeProxy>();
var meshName = mesh.name.Replace(".baked", "");
for (int j = 0; j < blendShapeCount; j++)
{
for (int k = 0; k < proxy.BlendShapeAvatar.Clips.Count; k++)
{
var clip = proxy.BlendShapeAvatar.Clips[k];
if (mesh.GetBlendShapeName(j) == clip.BlendShapeName)
{
var value = new BlendShapeBinding
{
RelativePath = meshName,
Index = j,
Weight = 100.0f
};
clip.Values = null;
clip.Values = new BlendShapeBinding[1] { value };
}
}
}
}
}
}
}
}
}
ブレンドシェイプをスクリプトで設定する
- Assets フォルダの適当な場所に、AddPerfectSyncBlendShapeClip という名前の C# Script を作成する
(Project タブで右クリック > Create > C# Script) - スクリプトをダブルクリックしてエディタで開き、上記のスクリプト②をコピペして、上書き保存する
(Unity にアタッチする必要がある場合もある) - GameObject を新規に作成する(Hierarchy タブで右クリック > Create Empty)
- GameObeject にスクリプトをドラッグ&ドロップでアタッチする
- VRM のプレハブをスクリプトの変数にセットする(Project タブからドラッグ&ドロップ)
- 関数(AddPerfectSyncBlendShapeClip)を実行する


BlendShapeProxy にパーフェクトシンク用の BlendShape が追加されたことを確認する。

最後に、作成したスクリプトとゲームオブジェクトを削除する。
ドキュメント
Apple Developer – Arkit / ARFaceanchor / BlendShapeLocationARKit
Unity Documentation – Enum ARKitBlendShapeLocation | ARKit Face Tracking | 1.0.14 (unity3d.com)
所感
- VRoid で作成したモデルは、頂点データが同じであるため、自動でパーフェクトシンクを設定してくれるツールが存在する。フルスクラッチで作成したモデルは、52 個の表情を手動で作成するしかない。骨の折れる作業だ。
- スクリプトには必要最低限のコードしか書いていないので、適宜修正して使ってほしい。
- パーフェクトシンクという名前は大げさだと思う。