Blender で パーフェクトシンク用のシェイプキー(Shape Key)を追加して、Unity で VRM1 のブレンドシェイプに設定する手順について説明する。VRM0 に関しては、「パーフェクトシンク対応(VRM0)」という記事を参照してほしい。
前提
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)
スクリプト②
using System.IO;
using UnityEditor;
using UnityEngine;
namespace UniVRM10
{
public class AddPerfectSyncExpression : MonoBehaviour
{
// AssetsフォルダのVRMプレハブをセットしてもらう
public GameObject vrmAsset;
// 縦の三点リーダーから関数を実行してもらう
[ContextMenu("AddPerfectSyncExpression")]
public void AddBlendShapeClip()
{
// パスを特定する
var vrmAssetPath = AssetDatabase.GetAssetPath(vrmAsset);
var vrmAssetName = vrmAsset.name + ".vrm";
var clipAssetFolderName = vrmAsset.name + ".vrm1.Assets";
var clipAssetFolderPath = vrmAssetPath.Replace(vrmAssetName, clipAssetFolderName);
if (!Directory.Exists(clipAssetFolderPath)){
Directory.CreateDirectory(clipAssetFolderPath);
}
// vrmのinstanceを変数にセットする
var instance = AssetDatabase.LoadAssetAtPath<Vrm10Instance>(vrmAssetPath);
// BlendShapeがあるmeshを特定する
Mesh mesh = null;
var meshName = "";
int blendShapeCount = 0;
foreach (var transform in instance.transform.GetChildren())
{
var skinnedMeshRenderer = transform.GetComponent<SkinnedMeshRenderer>();
if (skinnedMeshRenderer != null)
{
blendShapeCount = skinnedMeshRenderer.sharedMesh.blendShapeCount;
if (blendShapeCount != 0)
{
mesh = skinnedMeshRenderer.sharedMesh;
meshName = mesh.name.Replace(".baked", ""); // Replace必要?
break;
}
}
}
// clipのassetを作成して、instanceに追加する
for (int i = 0; i < blendShapeCount; i++) {
name = mesh.GetBlendShapeName(i);
var clipPath = clipAssetFolderPath + "/" + name + ".asset";
if (!File.Exists(clipPath))
{
var key = ExpressionKey.CreateCustom(name);
var clip = ScriptableObject.CreateInstance<VRM10Expression>();
clip.Prefab = vrmAsset;
clip.IsBinary = false;
clip.name = name;
AssetDatabase.CreateAsset(clip, clipPath);
instance.Vrm.Expression.AddClip(key.Preset, clip);
}
}
// blendShapeと同名のpresetのclipがあれば、weightを設定する
foreach (var clip in instance.Vrm.Expression.Clips)
{
var idx = mesh.GetBlendShapeIndex(clip.Clip.name);
if (idx != -1)
{
clip.Clip.Prefab = vrmAsset;
var MTB = new MorphTargetBinding
{
Index = idx,
RelativePath = meshName,
Weight = 1.0f
};
clip.Clip.MorphTargetBindings = null;
clip.Clip.MorphTargetBindings = new MorphTargetBinding[] { MTB };
}
}
}
}
}
Expression をスクリプトで設定する
Unity にて、3D モデルを Export VRM した後、以下の手順を実施する。
- Assets フォルダの適当な場所に、AddPerfectSyncExpression という名前の C# Script を作成する
(Project タブで右クリック > Create > C# Script) - スクリプトをダブルクリックしてエディタで開き、上記のスクリプトをコピペして上書き保存する
- GameObject を新規に作成する(Hierarchy タブで右クリック > Create Empty)
- GameObject にスクリプト②をアタッチする
- VRM のプレハブをスクリプトの変数にセットする(Project タブからドラッグ&ドロップ)
- 関数(AddPerfectSyncExpression)を実行する


「Extract Meta And Expressions …」をクリックする。

_vrm1_ の inspector にて、Expression を表示して、Clip が追加されたことを確認する。

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