要件
Unity6000.026f1
はじめに
本記事での"Avatar"とはHumanoid型の3Dモデルを使う時にAnimator
に割りついているAvatar要素のことです。
ランタイムで生成する必要があったためメモします。
必要な情報
Avatarを生成するためには以下の関数を実行します。
AvatarBuilder.BuildHumanAvatar(GameObject型の引数, HumanDescription型の引数);
引数は次の2つです。
① GameObject 型の引数:Avatarを生成したい対象のゲームオブジェクト
② HumanDescription型の引数:アバターの情報
①は対象のゲームオブジェクトを指定するだけなので簡単です。
問題は②のHumanDescription型の引数です。
HumanDescription型はUnityのスクリプトリファレンスを見ると
以下の10個の変数から成っており、これらを指定する必要があります。
プロパティ名 | 説明 |
---|---|
armStretch | IK 使用時の腕の長さの引き伸ばし許容量 |
armStretch | IK 使用時の腕の長さの引き伸ばし許容量 |
feetSpacing | ヒューマノイドモデル脚部の最小距離の調整 |
hasTranslationDoF | Degree of Freedom (自由度:DoF) 表現を持つすべてのヒューマンに対して true を返します。デフォルトで false に設定されます。 |
human | Mecanim ボーン名とリグのボーン名の間におけるマッピング |
legStretch | IK 使用のときに許容する脚の伸び幅 |
lowerArmTwist | 回転/ひねりを肘と手首にどの割合で反映するか定義します |
lowerLegTwist | 回転/ひねりを膝や足首にどの割合で反映するか定義します |
skeleton | モデルに含めるボーン Transform のリスト |
upperArmTwist | 回転/ひねりを肩と肘にどの割合で反映するか定義します |
upperLegTwist | 回転/ひねりを脚の付け根おと膝にどの割合で反映するか定義します |
HumanDescriptionの設定
HumanDescriptionを設定するにあたって変数が10個あると言いましたが、
humanとskeleton以外は以下のデフォルト値があるのでその値を使用します。
(デフォルト値はHumanDescriptionの
スクリプトリファレンスの変数ページに書いてあります。)
HumanDescription humanDescription = new HumanDescription { armStretch = 0.05f,//IK(インバースキネマティクス)使用時の腕の長さの引き伸ばし許容量を設定します。0.05fは、腕が5%まで引き伸ばされることを許容することを意味します。 feetSpacing = 0.0f,//ヒューマノイドモデルの脚部の最小距離の調整を設定します。0.0fは、脚部の間隔がデフォルトのままであることを意味します。 hasTranslationDoF = false,//アバターの関節に対して平行移動の自由度を持たせないことを指定します。通常必要ないためfalseに設定します。 human = 設定方法はこの後説明 legStretch = 0.05f,//IK使用時の脚の長さの引き伸ばし許容量を設定します。0.05fは、脚が5%まで引き伸ばされることを許容することを意味します。 lowerArmTwist = 0.5f,//回転/ひねりを肘と手首にどの割合で反映するかを定義します。0.5fは、回転が肘と手首に均等に分配されることを意味します。 lowerLegTwist = 0.5f,//回転/ひねりを膝と足首にどの割合で反映するかを定義します。0.5fは、回転が膝と足首に均等に分配されることを意味します。 skeleton = 設定方法はこの後説明 upperArmTwist = 0.5f,//回転/ひねりを肩と肘にどの割合で反映するかを定義します。0.5fは、回転が肩と肘に均等に分配されることを意味します。 upperLegTwist = 0.5f,//回転/ひねりを脚の付け根と膝にどの割合で反映するかを定義します。0.5fは、回転が脚の付け根と膝に均等に分配されることを意味します。 };
humanとskeletonについて見ていきます。
skeleton
取得がhumanより簡単なskeletonからいきます。
skeletonにはモデルが持つ配下オブジェクト全てのSkeletonBone型の情報を配列で渡します。
SkeletonBone型の情報とは以下の内容になります。
変数名 | 内容 |
---|---|
name | ボーンがマッピングされているトランスフォーム名 |
position | ローカル空間でボーンの T ポーズの位置 |
rotation | ローカル空間でボーンの T ポーズの角度 |
scale | ローカル空間でボーンの T ポーズのスケール |
Tポーズになっているという前提ですが、
Transform型を取得できていればSkeletonBone型の情報は次のスクリプトで取得できます。
※Tポーズでない状態の情報を設定した場合、どのような不具合がでるかは未検証です。
SkeletonBone bone = new SkeletonBone { name = Transform型の変数.name, position = Transform型の変数.localPosition, rotation = Transform型の変数.localRotation, scale = Transform型の変数.localScale };
あとは指定した親ゲームオブジェクトのTrasnform型から
配下全てのTransform型へアクセスすればskeletonの情報は作成できます。
まず、SkeletonBone型のリスト作成と、
配下オブジェクトにアクセスして情報を取得する関数を設置します。
/// <summary> /// HumanDescription.skeleton 用の SkeletonBone 配列を生成 /// </summary> /// <param name="root">スケルトン階層のルート Transform</param> /// <returns>SkeletonBone 配列</returns> private static SkeletonBone[] GenerateSkeleton(Transform root) { // 引数が null の場合はエラー if (root == null) { Debug.LogError("Root Transform is null."); return null; } // スケルトンボーンのリストを作成 List<SkeletonBone> skeletonBones = new List<SkeletonBone>(); // 再帰的にスケルトンを構築 BuildSkeletonRecursive(root, skeletonBones); // スケルトンボーンのリストを配列に変換して返す return skeletonBones.ToArray(); } /// <summary> /// 再帰的に SkeletonBone を構築 /// </summary> /// <param name="current">現在の Transform</param> /// <param name="skeletonBones">構築中の SkeletonBone リスト</param> private static void BuildSkeletonRecursive(Transform current, List<SkeletonBone> skeletonBones) { // SkeletonBone を作成 SkeletonBone bone = new SkeletonBone { name = current.name, position = current.localPosition, rotation = current.localRotation, scale = current.localScale }; // 作成した SkeletonBone をリストに追加 skeletonBones.Add(bone); // 子要素を再帰的に処理 // 子要素がない場合は何もしない // foreachでTransform型に対してループをまわすと子要素に対して処理ができる foreach (Transform child in current) { BuildSkeletonRecursive(child, skeletonBones); } }
そして、関数GenerateSkeleton に親ゲームオブジェクトのTrasnform型の情報を
渡して実行することで取得可能です。
// ターゲットのオブジェクトのTransformからモデルのTransformの配列を作成 SkeletonBone[] skeletonBones = GenerateSkeleton(親ゲームオブジェクトのTrasnform型);
以上がskeletonの取得方法になります。
human
humanにはHumanBone型の情報を配列で渡します。
HumanBone型の情報とは以下の内容になります。
変数名 | 内容 |
---|---|
boneName | Mecanim のヒューマノイド型ボーンがマッピングされたボーン名 |
humanName | モデルにおけるボーンのマッピング元となる Mecanim のヒューマノイド型ボーン名 |
limit | ボーンにおける割り込みの可動域を返す |
説明欄にマッピングという言葉が出てきて困惑しますね。
HumanBone型は簡単に言えば、
"Unityが定義しているボーン名 humanName "に対する
"3Dモデルが持つボーン名 boneName ”の対応情報です。
Unity上にて3Dモデル配下のAvatarを選択し「アバターを設定」を押すと表示される情報を
作るためのものといえばわかりやすいでしょうか。
設定可能なボーン情報は以下の55種類です。
(最後のLastBoneは飾りみたいなものなのでカウント対象外です。)
そのうち、登録が必須のボーン情報は赤文字の15種類になります。
HumanBone型の humanName に対しては
下表の"ボーン名"のいずれかを指定することになります。
番号 | ボーン名 |
---|---|
1 | Hips |
2 | LeftUpperLeg |
3 | RightUpperLeg |
4 | LeftLowerLeg |
5 | RightLowerLeg |
6 | LeftFoot |
7 | RightFoot |
8 | Spine |
9 | Chest |
10 | UpperChest |
11 | Neck |
12 | Head |
13 | LeftShoulder |
14 | RightShoulder |
15 | LeftUpperArm |
16 | RightUpperArm |
17 | LeftLowerArm |
18 | RightLowerArm |
19 | LeftHand |
20 | RightHand |
21 | LeftToes |
22 | RightToes |
23 | LeftEye |
24 | RightEye |
25 | Jaw |
26 | LeftThumbProximal |
27 | LeftThumbIntermediate |
28 | LeftThumbDistal |
29 | LeftIndexProximal |
30 | LeftIndexIntermediate |
31 | LeftIndexDistal |
32 | LeftMiddleProximal |
33 | LeftMiddleIntermediate |
34 | LeftMiddleDistal |
35 | LeftRingProximal |
36 | LeftRingIntermediate |
37 | LeftRingDistal |
38 | LeftLittleProximal |
39 | LeftLittleIntermediate |
40 | LeftLittleDistal |
41 | RightThumbProximal |
42 | RightThumbIntermediate |
43 | RightThumbDistal |
44 | RightIndexProximal |
45 | RightIndexIntermediate |
46 | RightIndexDistal |
47 | RightMiddleProximal |
48 | RightMiddleIntermediate |
49 | RightMiddleDistal |
50 | RightRingProximal |
51 | RightRingIntermediate |
52 | RightRingDistal |
53 | RightLittleProximal |
54 | RightLittleIntermediate |
55 | RightLittleDistal |
56 | LastBone |
話は少しそれますが、アバター情報をよくみると必須ボーンと任意設定ボーンでは
アイコンが異なっていることがわかります。
設定必須のボーン アイコン
任意設定のボーン アイコン
ではプログラムで
"Unityが定義しているボーン名 humanName "に対する
"3Dモデルが持つボーン名 boneName ”の対応情報を設定します。
繰り返しで処理するためDictionary型を使用して
humanName と boneName の対応関係を書きます。
Dictionary<string, string> boneNameMap = new Dictionary<string, string> { ["Hips"] = 対応するボーン名, ["LeftUpperLeg"] = 対応するボーン名, ["RightUpperLeg"] = 対応するボーン名, ["LeftLowerLeg"] = 対応するボーン名, ["RightLowerLeg"] = 対応するボーン名, ["LeftFoot"] = 対応するボーン名, ["RightFoot"] = 対応するボーン名, ["Spine"] = 対応するボーン名, ["Head"] = 対応するボーン名, ["LeftUpperArm"] = 対応するボーン名, ["RightUpperArm"] =対応するボーン名, ["LeftLowerArm"] = 対応するボーン名, ["RightLowerArm"] = 対応するボーン名, ["LeftHand"] = 対応するボーン名, ["RightHand"] = 対応するボーン名, };
具体例としてmixamoからダウンロードできる
"Hip Hop Dancing"というモデルデータで設定してみます。
このモデルのアバター情報は以下です。
必須ボーンの"Hips"や"LeftUpperLeg"に対応するボーン名を
インスペクターから把握して先ほどのプログラムを作ります。
例えば、必須ボーン"Hips"に対応しているボーン名は
"mixamorig:Hips"であることが把握できます。
そのため"Hips"に対応する名前は"mixamorig:Hips"と書きます。
Dictionary<string, string> boneNameMap = new Dictionary<string, string> { ["Hips"] = "mixamorig:Hips", ・ ・ 省略 ・ ・ };
今回のモデルでは必須ボーンに対応するボーン名は以下になりました。
// ボーン名マッピングの情報 Dictionary<string, string> boneNameMap = new Dictionary<string, string> { ["Hips"] = "mixamorig:Hips", ["LeftUpperLeg"] = "mixamorig:LeftUpLeg", ["RightUpperLeg"] = "mixamorig:RightUpLeg", ["LeftLowerLeg"] = "mixamorig:LeftLeg", ["RightLowerLeg"] = "mixamorig:RightLeg", ["LeftFoot"] = "mixamorig:LeftFoot", ["RightFoot"] = "mixamorig:RightFoot", ["Spine"] = "mixamorig:Spine", ["Head"] = "mixamorig:Head", ["LeftUpperArm"] = "mixamorig:LeftArm", ["RightUpperArm"] = "mixamorig:RightArm", ["LeftLowerArm"] = "mixamorig:LeftForeArm", ["RightLowerArm"] = "mixamorig:RightForeArm", ["LeftHand"] = "mixamorig:LeftHand", ["RightHand"] = "mixamorig:RightHand", };
このDictionary型の情報から以下のプログラムでHumanBone型の配列を作ります。
/// <summary> /// ボーン名マッピング情報から HumanBone 配列を生成 /// </summary> /// <param name="boneNameMap">ボーン名マッピング情報</param> /// <returns></returns> private HumanBone[] CreateHumanBones(Dictionary<string, string> boneNameMap) { // HumanBone のリストを作成 List<HumanBone> humanBones = new List<HumanBone>(); // マッピング情報を元に HumanBone を作成 foreach (var pair in boneNameMap) { // HumanBone を作成 HumanBone humanBone = new HumanBone { humanName = pair.Key, boneName = pair.Value, limit = new HumanLimit { useDefaultValues = true } }; // HumanBone をリストに追加 humanBones.Add(humanBone); } // HumanBone のリストを配列に変換して返す return humanBones.ToArray(); }
上記プログラムの中でHumanBone型を作成する部分は以下です。
こちらをDictionary型の要素分繰り返しています。
HumanBone型の中のlimitはHumanLimit型になります。
こちらはデフォルト値を使用する書き方で書きました。
// HumanBone を作成 HumanBone humanBone = new HumanBone { humanName = pair.Key, boneName = pair.Value, limit = new HumanLimit { useDefaultValues = true } // デフォルト値を使用 };
あとは、関数CreateHumanBones に
ボーン名マッピング情報が入ったDictionary型の情報を渡して実行することで取得可能です。
// ボーン名マッピングの情報からHumanBoneのリストを作成 HumanBone[] humanBones = CreateHumanBones(Dictionary型のボーンマッピング情報);
以上がhumanの取得方法になります。
プログラム全文
mixamoからダウンロードできる"Hip Hop Dancing"の
モデルデータを使用する前提のプログラムです。
using System.Collections.Generic; using UnityEngine; public class GenerateAvatarTest : MonoBehaviour { //アバターを生成したい対象のゲームオブジェクト [SerializeField] private GameObject _targetModel; //アバターを生成する対象のアニメーターコンポーネント private Animator _animator; void Start() { // アニメーターコンポーネントを取得 _animator = _targetModel.GetComponent<Animator>(); // アバターを生成 GenerateAvatar(); } /// <summary> /// アバターを生成 /// </summary> private void GenerateAvatar() { //最終的にAvatarBuilder.BuildHumanAvatar (引数1 GameObject型, 引数2 HumanDescription型);を使用してアバターを生成する //引数1はアバターを生成したいゲームオブジェクト //引数2はアバターの構造を定義するHumanDescription構造体 // ボーン名マッピングの情報 Dictionary<string, string> boneNameMap = new Dictionary<string, string> { ["Hips"] = "mixamorig:Hips", ["LeftUpperLeg"] = "mixamorig:LeftUpLeg", ["RightUpperLeg"] = "mixamorig:RightUpLeg", ["LeftLowerLeg"] = "mixamorig:LeftLeg", ["RightLowerLeg"] = "mixamorig:RightLeg", ["LeftFoot"] = "mixamorig:LeftFoot", ["RightFoot"] = "mixamorig:RightFoot", ["Spine"] = "mixamorig:Spine", ["Head"] = "mixamorig:Head", ["LeftUpperArm"] = "mixamorig:LeftArm", ["RightUpperArm"] = "mixamorig:RightArm", ["LeftLowerArm"] = "mixamorig:LeftForeArm", ["RightLowerArm"] = "mixamorig:RightForeArm", ["LeftHand"] = "mixamorig:LeftHand", ["RightHand"] = "mixamorig:RightHand", }; // ボーン名マッピングの情報からHumanBoneのリストを作成 HumanBone[] humanBones = CreateHumanBones(boneNameMap); // ターゲットのオブジェクトのTransformからモデルのTransformの配列を作成 SkeletonBone[] skeletonBones = GenerateSkeleton(_targetModel.transform); HumanDescription humanDescription = new HumanDescription { armStretch = 0.05f,//IK(インバースキネマティクス)使用時の腕の長さの引き伸ばし許容量を設定します。0.05fは、腕が5%まで引き伸ばされることを許容することを意味します。 feetSpacing = 0.0f,//ヒューマノイドモデルの脚部の最小距離の調整を設定します。0.0fは、脚部の間隔がデフォルトのままであることを意味します。 hasTranslationDoF = false,//アバターの関節に対して平行移動の自由度を持たせないことを指定します。通常必要ないためfalseに設定します。 human = humanBones, legStretch = 0.05f,//IK使用時の脚の長さの引き伸ばし許容量を設定します。0.05fは、脚が5%まで引き伸ばされることを許容することを意味します。 lowerArmTwist = 0.5f,//回転/ひねりを肘と手首にどの割合で反映するかを定義します。0.5fは、回転が肘と手首に均等に分配されることを意味します。 lowerLegTwist = 0.5f,//回転/ひねりを膝と足首にどの割合で反映するかを定義します。0.5fは、回転が膝と足首に均等に分配されることを意味します。 skeleton = skeletonBones, upperArmTwist = 0.5f,//回転/ひねりを肩と肘にどの割合で反映するかを定義します。0.5fは、回転が肩と肘に均等に分配されることを意味します。 upperLegTwist = 0.5f,//回転/ひねりを脚の付け根と膝にどの割合で反映するかを定義します。0.5fは、回転が脚の付け根と膝に均等に分配されることを意味します。 }; // アバターを生成 var avatar = AvatarBuilder.BuildHumanAvatar (_targetModel, humanDescription); //確認 if (avatar.isValid && avatar.isHuman) { _animator.avatar = avatar; Debug.Log("Avatar created successfully!"); } else { Debug.LogError("Avatar creation failed."); } } /// <summary> /// ボーン名マッピング情報から HumanBone 配列を生成 /// </summary> /// <param name="boneNameMap">ボーン名マッピング情報</param> /// <returns></returns> private HumanBone[] CreateHumanBones(Dictionary<string, string> boneNameMap) { // HumanBone のリストを作成 List<HumanBone> humanBones = new List<HumanBone>(); // マッピング情報を元に HumanBone を作成 foreach (var pair in boneNameMap) { // HumanBone を作成 HumanBone humanBone = new HumanBone { humanName = pair.Key, boneName = pair.Value, limit = new HumanLimit { useDefaultValues = true } // デフォルト値を使用 }; // HumanBone をリストに追加 humanBones.Add(humanBone); } // HumanBone のリストを配列に変換して返す return humanBones.ToArray(); } /// <summary> /// HumanDescription.skeleton 用の SkeletonBone 配列を生成 /// </summary> /// <param name="root">スケルトン階層のルート Transform</param> /// <returns>SkeletonBone 配列</returns> private static SkeletonBone[] GenerateSkeleton(Transform root) { // 引数が null の場合はエラー if (root == null) { Debug.LogError("Root Transform is null."); return null; } // スケルトンボーンのリストを作成 List<SkeletonBone> skeletonBones = new List<SkeletonBone>(); // 再帰的にスケルトンを構築 BuildSkeletonRecursive(root, skeletonBones); // スケルトンボーンのリストを配列に変換して返す return skeletonBones.ToArray(); } /// <summary> /// 再帰的に SkeletonBone を構築 /// </summary> /// <param name="current">現在の Transform</param> /// <param name="skeletonBones">構築中の SkeletonBone リスト</param> private static void BuildSkeletonRecursive(Transform current, List<SkeletonBone> skeletonBones) { // SkeletonBone を作成 SkeletonBone bone = new SkeletonBone { name = current.name, position = current.localPosition, rotation = current.localRotation, scale = current.localScale }; // 作成した SkeletonBone をリストに追加 skeletonBones.Add(bone); // 子要素を再帰的に処理 // 子要素がない場合は何もしない // foreachでTransform型に対してループをまわすと子要素に対して処理ができる foreach (Transform child in current) { BuildSkeletonRecursive(child, skeletonBones); } } }
実行
実際に動かします。
モデルをヒエラルキー上に配置すると
Animatorコンポーネントの"Avatar"はすでに設定されているので予めを削除しておきます。
作成した"GenerateAvatarTest"スクリプトを割り当てて"Target Model"を指定します。
実行するとAvatarが生成されたことが確認できます。
ボーンマッピング情報作成方法が肝
今回、必須ボーンに対するボーン名は事前に把握できている状態でしたが、
ランタイムでインポートしたモデルは対応するボーン名が事前にわかりません。
このような場合はボーンマッピング情報をどのようなアルゴリズムで決定するのかが
正常なAvatar生成に関して肝になってきます。
Avatar生成が成功していてもHipsに対応するボーンを足のボーンに設定しまったとしたら
アニメーションした時におかしな動きになるのが想像できると思います。
Unity上ではUnityが勝手に対応するボーンを判定して設定してくれますが、
そのアルゴリズムは公開されていないようなので
自分でアルゴリズムを見出す必要があるようです。
おわりに
アバター生成に必要な必須のボーン情報はスクリプトリファレンスの
HumanTrait.RequiredBoneの例文を実行することで確認することができます。
Unityバージョンで結果が異なる可能性があるためAvatar生成前に
必須ボーン情報が存在するかを確認すると良さそうですね。