30.9K Views
February 18, 23
スライド概要
character creator 4で作成したリアルなアバターに、Quest Proのフェイストラッキング・アイトラッキングをフィードバックする方法を説明しています。 - Quest link接続 PC版 -
リアルなアバター(CC4)の表情に QUEST PROやIPHONEの フェイストラッキングを反映させる。 -QUEST LINKでのPC版-
大久保 聡 Mail [email protected] Twitter @followapp
目標1:Quest Proのフェイストラッキングをリアルなアバターに反映させる。 表情のBlendshapeが 設定されている3Dモデル 表情の値 (Fece Expression)
Quest Proのフェイストラッキングとアイトラッキングのセンサー位置。
目標2:iPhoneのフェイストラッキングをリアルなアバターに反映させる。 表情のBlendshapeが 設定されている3Dモデル OSC プロトコル? Face Capture 表情の値
CC4でキャラ作成
キャラクター作成ツールを使い、表情のあるリアルなアバターを作成する。
テンプレートキャラのケビンを読み込み。
しかしBlendshapeを変更してもあごが開かない。これはBlendshapeとBoneの両方で あごと目を動かしていることが原因のよう。
全てBlend Shapeで表情を作るように変更する。 お手本となる形状を作成する。例は、Jaw_Openで口が完全に開いた状態にする。
口が開いた状態を、お手本としてその状態の3DモデルをObjファイルとしてExportする。
お手本のObjファイルの形状をもとに、Blend Shapeを作成する。
目の向き、あごのうごきの12個についてお手本を作成し、Blend Shapeとして設定する。
ボーンを使わないBlend Shape版を、カスタムプロファイルとして保存しておく。 これは、他のモデルにも適用できる。
Unity用に完成した3Dモデルを、FBXファイル形式でExportする。
Unity用にMeshのみExportする。 服の下などの隠れている部分のメッシュは削除する設定。
UNITY URPプロジェクト作成
UnityのURPプロジェクト作成。 特にURPじゃないとダメではないですが、今回はURPで作成しています。
CC4からExportしたファイルの取り込み。
CC4のキャラをシーンに配置する・・・・が
CC4のUnity Toolsでマテリアルを設定する必要がある。 CC4のUnity Toolsをインストールするため、ダウンロードもとのURLをコピーする。 (URP/HDRPなどUnity環境に合わせて選択) 参考(https://soupday.github.io/cc_unity_tools/installation.html)
コピーしたGitのURLを使い、CC4のUnity ToolsをPackage Managerから インストール。 https://github.com/soupday/cc_unity_tools_URP.git
メニューからImport Charactersを選択する。
設定を行い、マテリアルのビルドを実行する。 テッセレーションを行うと、ポリゴンの分割数を増やし てより滑らかな曲面を作成できたり、分割された頂点を テクスチャを参照して直接盛り上げたり(ディスプレー スメントマッピング)出来ます。
いい感じになります。
Animation preview playerで、表情・アニメーションのテストができます。
配下のGame Objectの眉やヒゲなど、それぞれににBlend Shapeが設定されています。
3点トラッキングから アバターポーズの設定 IKでHUMANOIDを動かす
XR Plugin/Oculus XR Plugin/Oculus Integrationのインポート。
OVR Camera Rigをシーンに配置。 Floor Levelの設定を行う。
Final IKのインポート。 モデルにVR IKコンポーネントを追加する。自動でボーンのリ ファレンスがセットされるのでそのまま使う。
VR IKコンポーネントの頭と両手のターゲット(Head/Left Arm/Right Arm)にOVR Camera RigのAnchorを指定します。 ※各Anchorの配下に空のGame Objectを配置し、ターゲットの方向や位置の補正を行い ますが、ここでは説明を省略します。
フェイストラッキングの設定
OVR Camera Rigのコンポーネントで、OVR Managerの設定からFace Tracking/Eye Trackingの設定を利用可能に設定します。
WindowsのOculusアプリを起動し、ベータ機能の設定で視線と表情をQuset Linkで利用 可能に設定します。
Oculus Integrationのコンポーネントで顔の表情を取得するOVR Face Expressionsを CC_Base Bodyに追加します。 CC_Base_Body フェイストラッキングの値を取得する。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.SerializableAttribute]
public class CorrespondenceTable
{
public string blendShapeName;
public OVRFaceExpressions.FaceExpression faceExpression;
[Range(0f, 2f)]
public float rate;
[CreateAssetMenu(menuName = "FaceTrackingScriptable/Create Correspondence Table")]
public class BlendShapeMappingTable : ScriptableObject
{
public CorrespondenceTable[] correspondenceTable;
private void Awake()
{
// 対応表 { CC4 , Quest Pro}
correspondenceTable = new CorrespondenceTable[] {
new CorrespondenceTable( "Brow_Drop_L", OVRFaceExpressions.FaceExpression.BrowLowererL, 1f ),
new CorrespondenceTable( "Brow_Drop_R", OVRFaceExpressions.FaceExpression.BrowLowererR, 1f ),
new CorrespondenceTable( "Cheek_Puff_L", OVRFaceExpressions.FaceExpression.CheekPuffL, 1f ),
new CorrespondenceTable( "Cheek_Puff_R", OVRFaceExpressions.FaceExpression.CheekPuffR, 1f ),
new CorrespondenceTable( "Cheek_Raise_L", OVRFaceExpressions.FaceExpression.CheekRaiserL, 1f ),
new CorrespondenceTable( "Cheek_Raise_R", OVRFaceExpressions.FaceExpression.CheekRaiserR, 1f ),
new CorrespondenceTable( "Cheek_Suck_L", OVRFaceExpressions.FaceExpression.CheekSuckL, 1f ),
new CorrespondenceTable( "Cheek_Suck_R", OVRFaceExpressions.FaceExpression.CheekSuckR, 1f ),
new CorrespondenceTable( "Mouth_Shrug_Upper", OVRFaceExpressions.FaceExpression.ChinRaiserT, 1f ),
new CorrespondenceTable( "Mouth_Shrug_Lower", OVRFaceExpressions.FaceExpression.ChinRaiserB, 1f ),
new CorrespondenceTable( "Mouth_Dimple_L", OVRFaceExpressions.FaceExpression.DimplerL, 1f ),
new CorrespondenceTable( "Mouth_Dimple_R", OVRFaceExpressions.FaceExpression.DimplerR, 1f ),
new CorrespondenceTable( "Eye_Blink_L", OVRFaceExpressions.FaceExpression.EyesClosedL, 1f ),
new CorrespondenceTable( "Eye_Blink_R", OVRFaceExpressions.FaceExpression.EyesClosedR, 1f ),
new CorrespondenceTable( "Eye_L_Look_Down", OVRFaceExpressions.FaceExpression.EyesLookDownL, 1f ),
new CorrespondenceTable( "Eye_R_Look_Down", OVRFaceExpressions.FaceExpression.EyesLookDownR, 1f ),
new CorrespondenceTable( "Eye_L_Look_L", OVRFaceExpressions.FaceExpression.EyesLookLeftL, 1f ),
new CorrespondenceTable( "Eye_R_Look_L", OVRFaceExpressions.FaceExpression.EyesLookLeftR, 1f ),
new CorrespondenceTable( "Eye_L_Look_R", OVRFaceExpressions.FaceExpression.EyesLookRightL, 1f ),
new CorrespondenceTable( "Eye_R_Look_R", OVRFaceExpressions.FaceExpression.EyesLookRightR, 1f ),
new CorrespondenceTable( "Eye_L_Look_Up", OVRFaceExpressions.FaceExpression.EyesLookUpL, 1f ),
new CorrespondenceTable( "Eye_R_Look_Up", OVRFaceExpressions.FaceExpression.EyesLookUpR, 1f ),
new CorrespondenceTable( "Brow_Raise_Inner_L", OVRFaceExpressions.FaceExpression.InnerBrowRaiserL, 1f ),
new CorrespondenceTable( "Brow_Raise_Inner_R", OVRFaceExpressions.FaceExpression.InnerBrowRaiserR, 1f ),
new CorrespondenceTable( "Jaw_Open", OVRFaceExpressions.FaceExpression.JawDrop, 0.8f ),
new CorrespondenceTable( "Jaw_L", OVRFaceExpressions.FaceExpression.JawSidewaysLeft, 0.8f ),
new CorrespondenceTable( "Jaw_R", OVRFaceExpressions.FaceExpression.JawSidewaysRight, 0.8f ),
new CorrespondenceTable( "Jaw_Forward", OVRFaceExpressions.FaceExpression.JawThrust, 0.8f ),
new CorrespondenceTable( "Eye_Squint_L", OVRFaceExpressions.FaceExpression.LidTightenerL, 1f ),
new CorrespondenceTable( "Eye_Squint_R", OVRFaceExpressions.FaceExpression.LidTightenerR, 1f ),
new CorrespondenceTable( "Mouth_Frown_L", OVRFaceExpressions.FaceExpression.LipCornerDepressorL, 1f ),
new CorrespondenceTable( "Mouth_Frown_R", OVRFaceExpressions.FaceExpression.LipCornerDepressorR, 1f ),
new CorrespondenceTable( "Mouth_Smile_L", OVRFaceExpressions.FaceExpression.LipCornerPullerL, 1f ),
new CorrespondenceTable( "Mouth_Smile_R", OVRFaceExpressions.FaceExpression.LipCornerPullerR, 1f ),
new CorrespondenceTable( "Mouth_Funnel_Down_L", OVRFaceExpressions.FaceExpression.LipFunnelerLB, 1f ),
new CorrespondenceTable( "Mouth_Funnel_Up_L", OVRFaceExpressions.FaceExpression.LipFunnelerLT, 1f ),
new CorrespondenceTable( "Mouth_Funnel_Down_R", OVRFaceExpressions.FaceExpression.LipFunnelerRB, 1f ),
new CorrespondenceTable( "Mouth_Funnel_Up_R", OVRFaceExpressions.FaceExpression.LipFunnelerRT, 1f ),
new CorrespondenceTable( "Mouth_Press_L", OVRFaceExpressions.FaceExpression.LipPressorL, 1f ),
new CorrespondenceTable( "Mouth_Press_R", OVRFaceExpressions.FaceExpression.LipPressorR, 1f ),
new CorrespondenceTable( "Mouth_Pucker_Up_L", OVRFaceExpressions.FaceExpression.LipPuckerL, 1f ),
new CorrespondenceTable( "Mouth_Pucker_Down_L", OVRFaceExpressions.FaceExpression.LipPuckerL, 1f ),
new CorrespondenceTable( "Mouth_Pucker_Up_R", OVRFaceExpressions.FaceExpression.LipPuckerR, 1f ),
new CorrespondenceTable( "Mouth_Pucker_Down_R", OVRFaceExpressions.FaceExpression.LipPuckerR, 1f ),
new CorrespondenceTable( "Mouth_Stretch_L", OVRFaceExpressions.FaceExpression.LipStretcherL, 1f ),
new CorrespondenceTable( "Mouth_Stretch_R", OVRFaceExpressions.FaceExpression.LipStretcherR, 1f ),
new CorrespondenceTable( "Mouth_Roll_In_Lower_L", OVRFaceExpressions.FaceExpression.LipSuckLB, 1f ),
new CorrespondenceTable( "Mouth_Roll_In_Upper_L", OVRFaceExpressions.FaceExpression.LipSuckLT, 1f ),
new CorrespondenceTable( "Mouth_Roll_In_Lower_R", OVRFaceExpressions.FaceExpression.LipSuckRB, 1f ),
new CorrespondenceTable( "Mouth_Roll_In_Upper_R", OVRFaceExpressions.FaceExpression.LipSuckRT, 1f ),
new CorrespondenceTable( "Mouth_Tighten_L", OVRFaceExpressions.FaceExpression.LipTightenerL, 1f ),
new CorrespondenceTable( "Mouth_Tighten_R", OVRFaceExpressions.FaceExpression.LipTightenerR, 1f ),
new CorrespondenceTable( "Mouth_Close", OVRFaceExpressions.FaceExpression.LipsToward, 1f ),
new CorrespondenceTable( "Mouth_Down_Lower_L", OVRFaceExpressions.FaceExpression.LowerLipDepressorL, 1f ),
new CorrespondenceTable( "Mouth_Down_Lower_R", OVRFaceExpressions.FaceExpression.LowerLipDepressorR, 1f ),
new CorrespondenceTable( "Mouth_L", OVRFaceExpressions.FaceExpression.MouthLeft, 1f ),
new CorrespondenceTable( "Mouth_R", OVRFaceExpressions.FaceExpression.MouthRight, 1f ),
new CorrespondenceTable( "Nose_Sneer_L", OVRFaceExpressions.FaceExpression.NoseWrinklerL, 1f ),
new CorrespondenceTable( "Nose_Sneer_R", OVRFaceExpressions.FaceExpression.NoseWrinklerR, 1f ),
new CorrespondenceTable( "Brow_Raise_Outer_L", OVRFaceExpressions.FaceExpression.OuterBrowRaiserL, 1f ),
new CorrespondenceTable( "Brow_Raise_Outer_R", OVRFaceExpressions.FaceExpression.OuterBrowRaiserR, 1f ),
new CorrespondenceTable( "Eye_Wide_L", OVRFaceExpressions.FaceExpression.UpperLidRaiserL, 1f ),
new CorrespondenceTable( "Eye_Wide_R", OVRFaceExpressions.FaceExpression.UpperLidRaiserR, 1f ),
new CorrespondenceTable( "Mouth_Up_Upper_L", OVRFaceExpressions.FaceExpression.UpperLipRaiserL, 1f ),
new CorrespondenceTable( "Mouth_Up_Upper_R", OVRFaceExpressions.FaceExpression.UpperLipRaiserR, 1f )
};
}
public CorrespondenceTable(string blendShapeName, OVRFaceExpressions.FaceExpression faceExpression, float rate)
{
this.faceExpression = faceExpression;
this.blendShapeName = blendShapeName;
this.rate = rate;
}
}
}
CC4とOVRExpressionsの対応付けをスクリプタブル
オブジェクトで作成。
Projectで右クリック、4QuestProという名前で対応表を作成。
OVRExpressionsから、BlendShapeに値をセットする。
複数のブレンドシェープに対し、Expressionsから取得した値をセットし表情を
変える。
using
using
using
using
using
System;
System.Collections;
System.Collections.Generic;
UnityEngine;
static BlendShapeMappingTable;
// 表情の変更
void Update()
{
if (faceExpressions == null) return;
if (faceExpressions.FaceTrackingEnabled && faceExpressions.ValidExpressions)
{
Dictionary<String, float> trackingData = new Dictionary<string, float>();
foreach (var(id, val) in mapping)
{
trackingData.Add(id, 100f * faceExpressions[val.faceExpression] * val.rate);
}
[RequireComponent(typeof(OVRFaceExpressions))]
public class FaceExpressionsController : MonoBehaviour
{
private OVRFaceExpressions faceExpressions;
[SerializeField]
private SkinnedMeshRenderer[] meshRenderers;
int meshRendererId = 0;
foreach (var dictionary in blendShapeDictionaries)
{
foreach (var (id, val) in trackingData)
{
int shapeID;
if(dictionary.TryGetValue(id, out shapeID))
{
meshRenderers[meshRendererId].SetBlendShapeWeight(shapeID, val);
}
}
meshRendererId++;
}
// 対応表 { CC4 , Quest Pro}
public BlendShapeMappingTable correspondenceTables;
private Dictionary<string, CorrespondenceTable> mapping;
List<Dictionary<String, int>> blendShapeDictionaries = new List<Dictionary<string, int>>();
void Start()
{
faceExpressions = GetComponent<OVRFaceExpressions>();
// Blendshapeの名前で検索できるようにDictionary化
mapping = new Dictionary<string, CorrespondenceTable>();
foreach (CorrespondenceTable correspondenceTable in correspondenceTables.correspondenceTable)
{
mapping.Add(correspondenceTable.blendShapeName, correspondenceTable);
}
// 該当のBlendshapeを検索し保持
foreach (SkinnedMeshRenderer meshRenderer in meshRenderers)
{
blendShapeDictionaries.Add(new Dictionary<string, int>());
for (int index = 0; index < meshRenderer.sharedMesh.blendShapeCount; index++)
{
string blendShapeName = meshRenderer.sharedMesh.GetBlendShapeName(index);
if (mapping.ContainsKey(blendShapeName))
{
blendShapeDictionaries[blendShapeDictionaries.Count - 1].Add(blendShapeName, index);
}
}
}
}
}
}
}
CC4の顔に関連したBlendShapeが設定されている、SkinnedMeshRendererを配列 に設定していく。さきほど作成した対応表もセット。
動作イメージ https://twitter.com/i/status/1619676321675902977
UNITY FECE CAPTURE フェイストラッキングをIPHONEから行う
iPhoneアプリのUnity Face Captureをインストール。 https://apps.apple.com/jp/app/unity-face-capture/id1544159771
Unityプロジェクトを作成。 特にHDRPじゃないとダメではないですが、今回はHDRPで作成しています。
Package ManagerでLive Captureをインポート。
空のGameObjectを作成、Okubo_Rootにリネーム 配下に、モデルのプレハブを配置してAnimatorのコントローラーをNoneに設定。 Okubo_Rootをプレハブ化しておく。
Okubo_RootにArKit Face Actorを追加。
Projectウィンドウで右クリックして、Live CaptureのMapperを作成。
作成したFace Mapperを選択し、さきほど作成したRootのプレハブをRig Prfabaに設定 する。
Add Rendererを押下して、顔の表情を持つRendererの設定を追加する。
Blend Shapeで自動でうまく割り当たらない部分を、手動で設定していく。
Evaluatorを作成して割り当てると、反映の強弱の設定などが可能。
頭は、首のボーンで動かすようにHead Rotationに首のボーンをセットする。
作成が完了したFace Mapperを、ARKit Face Actorにセットする。
Hierarchyウィンドウで右クリックして、Live CaptureのTake Recorderを作成。
Capture Devicesの+を押下して、ARKit Face Deviceを追加する。 HierarchyウィンドウのTake Recorder配下にNew FaceDeviceが追加される。
New FaceDeviceを選択し、ActorにHierarchyのARKit Face Actorを設定する。
Windowメニューから、Live CaptureのConnectionsを開く。 初回、Configure Firewallのボタンが表示されるので、そのボタンを押下する。
iPhoneでFace Captureを起動。 UnityのStartボタンを押下する。表情が反映されたらOKです。
つながらない場合、Defenderに阻まれている可能性あり。 Unity Face Captureの通信用に、Defenderに穴をあけます。
Live Captureですが、ここで悲しいお知らせです。 これ、Editorでしか動作しません・・・ ビルドして使うことができません。
EXEで動かすためには、少し手を加える必要があります。 ¥Library¥PackageCache¥[email protected]¥Runtime の中身を、Assetsフォルダに移動します。
\Runtime\CompanionApp\CompanionAppServer.csを開き、 CompanionAppServerのスコープをPublicに変更します。
CompanionAppServer のインスタンス化して、起動させるクラスを作成します。 using UnityEngine; using Unity.LiveCapture.CompanionApp; using Unity.LiveCapture; public class CompanionAppServerStarter : MonoBehaviour { CompanionAppServer appServer; void Start() { var servMag = ServerManager.Instance; appServer = (CompanionAppServer)servMag.CreateServer(typeof(CompanionAppServer)); appServer.AutoStartOnPlay = false; appServer.Port = 9000; appServer.StartServer(); } private void Update() { appServer.OnUpdate(); } private void OnDestroy() { if (appServer) { ServerManager.Instance.DestroyServer(appServer); } } }
CompanionAppServerという名前で空のGameObjectを作成します。 そのGameObjectに先ほど作成したクラスを追加します。
ビルドして、iPhoneのFace Captureから接続できることを確認します。 Defenderに穴をあけるのをお忘れなく。
CHARACTER CREATOR4 HEADSHOT PLUGIN 写真からキャラクター作成
正面の顔写真1枚から、3Dモデルを作成する恐ろしいPlugin。 横顔は自分で手直しする必要あり。
ハゲができます、そこそこいい感じ。 メガネは外した写真で、髪もまゆや目にかからない写真が良い。 (無料版だとファイル保存できません。)
付録:対応表 QUEST PRO / IPHONE