リアルなアバターの表情に、Quest Proのフェイストラッキングを反映させる

30.9K Views

February 18, 23

スライド概要

character creator 4で作成したリアルなアバターに、Quest Proのフェイストラッキング・アイトラッキングをフィードバックする方法を説明しています。 - Quest link接続 PC版 -

profile-image

XR(VR/AR)のエバンジェリスト。XR技術で世界を変えていきましょう。 https://majimajiwaroze.connpass.com/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

リアルなアバター(CC4)の表情に QUEST PROやIPHONEの フェイストラッキングを反映させる。 -QUEST LINKでのPC版-

2.

大久保 聡 Mail [email protected] Twitter @followapp

3.

目標1:Quest Proのフェイストラッキングをリアルなアバターに反映させる。 表情のBlendshapeが 設定されている3Dモデル 表情の値 (Fece Expression)

4.

Quest Proのフェイストラッキングとアイトラッキングのセンサー位置。

5.

目標2:iPhoneのフェイストラッキングをリアルなアバターに反映させる。 表情のBlendshapeが 設定されている3Dモデル OSC プロトコル? Face Capture 表情の値

6.

CC4でキャラ作成

7.

キャラクター作成ツールを使い、表情のあるリアルなアバターを作成する。

8.

テンプレートキャラのケビンを読み込み。

9.

しかしBlendshapeを変更してもあごが開かない。これはBlendshapeとBoneの両方で あごと目を動かしていることが原因のよう。

10.

全てBlend Shapeで表情を作るように変更する。 お手本となる形状を作成する。例は、Jaw_Openで口が完全に開いた状態にする。

11.

口が開いた状態を、お手本としてその状態の3DモデルをObjファイルとしてExportする。

12.

お手本のObjファイルの形状をもとに、Blend Shapeを作成する。

13.

目の向き、あごのうごきの12個についてお手本を作成し、Blend Shapeとして設定する。

14.

ボーンを使わないBlend Shape版を、カスタムプロファイルとして保存しておく。 これは、他のモデルにも適用できる。

15.

Unity用に完成した3Dモデルを、FBXファイル形式でExportする。

16.

Unity用にMeshのみExportする。 服の下などの隠れている部分のメッシュは削除する設定。

17.

UNITY URPプロジェクト作成

18.

UnityのURPプロジェクト作成。 特にURPじゃないとダメではないですが、今回はURPで作成しています。

19.

CC4からExportしたファイルの取り込み。

20.

CC4のキャラをシーンに配置する・・・・が

21.

CC4のUnity Toolsでマテリアルを設定する必要がある。 CC4のUnity Toolsをインストールするため、ダウンロードもとのURLをコピーする。 (URP/HDRPなどUnity環境に合わせて選択) 参考(https://soupday.github.io/cc_unity_tools/installation.html)

22.

コピーしたGitのURLを使い、CC4のUnity ToolsをPackage Managerから インストール。 https://github.com/soupday/cc_unity_tools_URP.git

23.

メニューからImport Charactersを選択する。

24.

設定を行い、マテリアルのビルドを実行する。 テッセレーションを行うと、ポリゴンの分割数を増やし てより滑らかな曲面を作成できたり、分割された頂点を テクスチャを参照して直接盛り上げたり(ディスプレー スメントマッピング)出来ます。

25.

いい感じになります。

26.

Animation preview playerで、表情・アニメーションのテストができます。

27.

配下のGame Objectの眉やヒゲなど、それぞれににBlend Shapeが設定されています。

28.

3点トラッキングから アバターポーズの設定 IKでHUMANOIDを動かす

29.

XR Plugin/Oculus XR Plugin/Oculus Integrationのインポート。

30.

OVR Camera Rigをシーンに配置。 Floor Levelの設定を行う。

31.

Final IKのインポート。 モデルにVR IKコンポーネントを追加する。自動でボーンのリ ファレンスがセットされるのでそのまま使う。

32.

VR IKコンポーネントの頭と両手のターゲット(Head/Left Arm/Right Arm)にOVR Camera RigのAnchorを指定します。 ※各Anchorの配下に空のGame Objectを配置し、ターゲットの方向や位置の補正を行い ますが、ここでは説明を省略します。

33.

フェイストラッキングの設定

34.

OVR Camera Rigのコンポーネントで、OVR Managerの設定からFace Tracking/Eye Trackingの設定を利用可能に設定します。

35.

WindowsのOculusアプリを起動し、ベータ機能の設定で視線と表情をQuset Linkで利用 可能に設定します。

36.

Oculus Integrationのコンポーネントで顔の表情を取得するOVR Face Expressionsを CC_Base Bodyに追加します。 CC_Base_Body フェイストラッキングの値を取得する。

37.
[beta]
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の対応付けをスクリプタブル
オブジェクトで作成。

38.

Projectで右クリック、4QuestProという名前で対応表を作成。

39.
[beta]
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);
}
}
}

}

}

}
}

40.

CC4の顔に関連したBlendShapeが設定されている、SkinnedMeshRendererを配列 に設定していく。さきほど作成した対応表もセット。

41.

動作イメージ https://twitter.com/i/status/1619676321675902977

42.

UNITY FECE CAPTURE フェイストラッキングをIPHONEから行う

43.

iPhoneアプリのUnity Face Captureをインストール。 https://apps.apple.com/jp/app/unity-face-capture/id1544159771

44.

Unityプロジェクトを作成。 特にHDRPじゃないとダメではないですが、今回はHDRPで作成しています。

45.

Package ManagerでLive Captureをインポート。

46.

空のGameObjectを作成、Okubo_Rootにリネーム 配下に、モデルのプレハブを配置してAnimatorのコントローラーをNoneに設定。 Okubo_Rootをプレハブ化しておく。

47.

Okubo_RootにArKit Face Actorを追加。

48.

Projectウィンドウで右クリックして、Live CaptureのMapperを作成。

49.

作成したFace Mapperを選択し、さきほど作成したRootのプレハブをRig Prfabaに設定 する。

50.

Add Rendererを押下して、顔の表情を持つRendererの設定を追加する。

51.

Blend Shapeで自動でうまく割り当たらない部分を、手動で設定していく。

52.

Evaluatorを作成して割り当てると、反映の強弱の設定などが可能。

53.

頭は、首のボーンで動かすようにHead Rotationに首のボーンをセットする。

54.

作成が完了したFace Mapperを、ARKit Face Actorにセットする。

55.

Hierarchyウィンドウで右クリックして、Live CaptureのTake Recorderを作成。

56.

Capture Devicesの+を押下して、ARKit Face Deviceを追加する。 HierarchyウィンドウのTake Recorder配下にNew FaceDeviceが追加される。

57.

New FaceDeviceを選択し、ActorにHierarchyのARKit Face Actorを設定する。

58.

Windowメニューから、Live CaptureのConnectionsを開く。 初回、Configure Firewallのボタンが表示されるので、そのボタンを押下する。

59.

iPhoneでFace Captureを起動。 UnityのStartボタンを押下する。表情が反映されたらOKです。

60.

つながらない場合、Defenderに阻まれている可能性あり。 Unity Face Captureの通信用に、Defenderに穴をあけます。

61.

Live Captureですが、ここで悲しいお知らせです。 これ、Editorでしか動作しません・・・ ビルドして使うことができません。

62.

EXEで動かすためには、少し手を加える必要があります。 ¥Library¥PackageCache¥[email protected]¥Runtime の中身を、Assetsフォルダに移動します。

63.

\Runtime\CompanionApp\CompanionAppServer.csを開き、 CompanionAppServerのスコープをPublicに変更します。

64.

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); } } }

65.

CompanionAppServerという名前で空のGameObjectを作成します。 そのGameObjectに先ほど作成したクラスを追加します。

66.

ビルドして、iPhoneのFace Captureから接続できることを確認します。 Defenderに穴をあけるのをお忘れなく。

67.

CHARACTER CREATOR4 HEADSHOT PLUGIN 写真からキャラクター作成

68.

正面の顔写真1枚から、3Dモデルを作成する恐ろしいPlugin。 横顔は自分で手直しする必要あり。

69.

ハゲができます、そこそこいい感じ。 メガネは外した写真で、髪もまゆや目にかからない写真が良い。 (無料版だとファイル保存できません。)

70.

付録:対応表 QUEST PRO / IPHONE