Fix : 페이셜 스크립트 최적화
This commit is contained in:
parent
431c4dcd03
commit
becd49ca45
@ -26,9 +26,51 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
|
||||
private UdpClient udp;
|
||||
private Thread thread;
|
||||
private volatile bool isThreadRunning = false; // 스레드 실행 상태 플래그
|
||||
private string messageString = "";
|
||||
private string lastProcessedMessage = ""; // 이전 메시지 저장용
|
||||
public int LOCAL_PORT = 49983;
|
||||
|
||||
// 성능 최적화를 위한 캐시
|
||||
private Dictionary<string, List<BlendShapeMapping>> blendShapeCache;
|
||||
private readonly char[] splitEquals = new char[] { '=' };
|
||||
private readonly char[] splitPipe = new char[] { '|' };
|
||||
private readonly char[] splitAnd = new char[] { '&' };
|
||||
private readonly char[] splitDash = new char[] { '-' };
|
||||
private readonly char[] splitHash = new char[] { '#' };
|
||||
private readonly char[] splitComma = new char[] { ',' };
|
||||
|
||||
// Mirror mode용 정적 매핑 테이블
|
||||
private static readonly Dictionary<string, string> EyeMirrorMap = new Dictionary<string, string>() {
|
||||
{"eyelookupleft", "EyeLookUpRight"},
|
||||
{"eyelookupright", "EyeLookUpLeft"},
|
||||
{"eyelookdownleft", "EyeLookDownRight"},
|
||||
{"eyelookdownright", "EyeLookDownLeft"},
|
||||
{"eyelookinleft", "EyeLookInRight"},
|
||||
{"eyelookinright", "EyeLookInLeft"},
|
||||
{"eyelookoutleft", "EyeLookOutRight"},
|
||||
{"eyelookoutright", "EyeLookOutLeft"},
|
||||
{"eyewideleft", "EyeWideRight"},
|
||||
{"eyewideright", "EyeWideLeft"},
|
||||
{"eyesquintleft", "EyeSquintRight"},
|
||||
{"eyesquintright", "EyeSquintLeft"},
|
||||
{"eyeblinkleft", "EyeBlinkRight"},
|
||||
{"eyeblinkright", "EyeBlinkLeft"}
|
||||
};
|
||||
|
||||
// BlendShape 매핑 정보를 저장하는 구조체
|
||||
private struct BlendShapeMapping
|
||||
{
|
||||
public SkinnedMeshRenderer renderer;
|
||||
public int index;
|
||||
|
||||
public BlendShapeMapping(SkinnedMeshRenderer r, int i)
|
||||
{
|
||||
renderer = r;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Start is called
|
||||
void StartFunction()
|
||||
{
|
||||
@ -38,6 +80,9 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
|
||||
FindGameObjectsInsideUnitySettings();
|
||||
|
||||
// BlendShape 인덱스 캐싱 초기화
|
||||
InitializeBlendShapeCache();
|
||||
|
||||
//Send to iOS
|
||||
if (gameStartWithConnect == true)
|
||||
{
|
||||
@ -54,6 +99,49 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
StartFunction();
|
||||
}
|
||||
|
||||
// BlendShape 인덱스를 미리 캐싱하여 매번 검색하지 않도록 함
|
||||
void InitializeBlendShapeCache()
|
||||
{
|
||||
blendShapeCache = new Dictionary<string, List<BlendShapeMapping>>();
|
||||
|
||||
if (faceMeshRenderers == null) return;
|
||||
|
||||
foreach (var meshRenderer in faceMeshRenderers)
|
||||
{
|
||||
if (meshRenderer == null || meshRenderer.sharedMesh == null) continue;
|
||||
|
||||
for (int i = 0; i < meshRenderer.sharedMesh.blendShapeCount; i++)
|
||||
{
|
||||
string shapeName = meshRenderer.sharedMesh.GetBlendShapeName(i);
|
||||
string normalizedName = shapeName.ToLowerInvariant();
|
||||
|
||||
// 여러 변형 이름들을 모두 캐싱
|
||||
AddToCache(normalizedName, meshRenderer, i);
|
||||
|
||||
// _L, _R 변환 버전도 캐싱
|
||||
if (shapeName.Contains("_L"))
|
||||
{
|
||||
string leftVariant = shapeName.Replace("_L", "Left");
|
||||
AddToCache(leftVariant.ToLowerInvariant(), meshRenderer, i);
|
||||
}
|
||||
else if (shapeName.Contains("_R"))
|
||||
{
|
||||
string rightVariant = shapeName.Replace("_R", "Right");
|
||||
AddToCache(rightVariant.ToLowerInvariant(), meshRenderer, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddToCache(string key, SkinnedMeshRenderer renderer, int index)
|
||||
{
|
||||
if (!blendShapeCache.ContainsKey(key))
|
||||
{
|
||||
blendShapeCache[key] = new List<BlendShapeMapping>();
|
||||
}
|
||||
blendShapeCache[key].Add(new BlendShapeMapping(renderer, index));
|
||||
}
|
||||
|
||||
void CreateUdpServer()
|
||||
{
|
||||
try
|
||||
@ -61,7 +149,9 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
udp = new UdpClient(LOCAL_PORT);
|
||||
udp.Client.ReceiveTimeout = 5;
|
||||
|
||||
isThreadRunning = true;
|
||||
thread = new Thread(new ThreadStart(ThreadMethod));
|
||||
thread.IsBackground = true; // 백그라운드 스레드로 설정
|
||||
thread.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -121,37 +211,41 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
try
|
||||
// 메시지가 변경되었을 때만 처리 (성능 최적화)
|
||||
if (!string.IsNullOrEmpty(messageString) && messageString != lastProcessedMessage)
|
||||
{
|
||||
SetAnimation_inside_Unity_settings();
|
||||
try
|
||||
{
|
||||
SetAnimation_inside_Unity_settings();
|
||||
lastProcessedMessage = messageString;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[iFacialMocap] Animation 처리 중 오류: {e.Message}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
|
||||
//BlendShapeの設定
|
||||
//set blendshapes
|
||||
//set blendshapes (캐시 사용으로 최적화)
|
||||
void SetBlendShapeWeightFromStrArray(string[] strArray2)
|
||||
{
|
||||
string mappedShapeName = strArray2[0].Replace("_R", "Right").Replace("_L", "Left");
|
||||
if (blendShapeCache == null) return;
|
||||
|
||||
string shapeName = strArray2[0];
|
||||
float weight = float.Parse(strArray2[1], CultureInfo.InvariantCulture);
|
||||
|
||||
if (faceMeshRenderers != null)
|
||||
// 정규화된 이름으로 캐시 검색
|
||||
string normalizedName = NormalizeBlendShapeName(shapeName).ToLowerInvariant();
|
||||
|
||||
if (blendShapeCache.TryGetValue(normalizedName, out List<BlendShapeMapping> mappings))
|
||||
{
|
||||
foreach (var meshRenderer in faceMeshRenderers)
|
||||
// 캐시에서 찾은 모든 매핑에 대해 weight 설정
|
||||
foreach (var mapping in mappings)
|
||||
{
|
||||
if (meshRenderer != null && meshRenderer.sharedMesh != null)
|
||||
if (mapping.renderer != null)
|
||||
{
|
||||
// 대소문자 구분 없이 블렌드쉐입 찾기
|
||||
for (int i = 0; i < meshRenderer.sharedMesh.blendShapeCount; i++)
|
||||
{
|
||||
string blendShapeName = meshRenderer.sharedMesh.GetBlendShapeName(i);
|
||||
if (string.Equals(blendShapeName, mappedShapeName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
meshRenderer.SetBlendShapeWeight(i, weight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mapping.renderer.SetBlendShapeWeight(mapping.index, weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -176,26 +270,29 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
}
|
||||
|
||||
//BlendShapeとボーンの回転の設定
|
||||
//set blendshapes & bone rotation
|
||||
//set blendshapes & bone rotation (최적화 버전)
|
||||
void SetAnimation_inside_Unity_settings()
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] strArray1 = messageString.Split('=');
|
||||
string[] strArray1 = messageString.Split(splitEquals, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (strArray1.Length >= 2)
|
||||
{
|
||||
//blendShapes
|
||||
foreach (string message in strArray1[0].Split('|'))
|
||||
string[] blendShapeMessages = strArray1[0].Split(splitPipe, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (string message in blendShapeMessages)
|
||||
{
|
||||
string[] strArray2 = new string[3];
|
||||
if (string.IsNullOrEmpty(message)) continue;
|
||||
|
||||
string[] strArray2;
|
||||
if (message.Contains("&"))
|
||||
{
|
||||
strArray2 = message.Split('&');
|
||||
strArray2 = message.Split(splitAnd, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
else
|
||||
{
|
||||
strArray2 = message.Split('-');
|
||||
strArray2 = message.Split(splitDash, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
if (strArray2.Length == 2)
|
||||
@ -206,27 +303,12 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
{
|
||||
string originalShapeName = strArray2[0];
|
||||
string shapeNameLower = originalShapeName.ToLowerInvariant();
|
||||
// eye 블렌드쉐입 매핑 (key는 소문자, value는 Unity 카멜케이스)
|
||||
Dictionary<string, string> eyeMirrorMap = new Dictionary<string, string>() {
|
||||
{"eyelookupleft", "EyeLookUpRight"},
|
||||
{"eyelookupright", "EyeLookUpLeft"},
|
||||
{"eyelookdownleft", "EyeLookDownRight"},
|
||||
{"eyelookdownright", "EyeLookDownLeft"},
|
||||
{"eyelookinleft", "EyeLookInRight"},
|
||||
{"eyelookinright", "EyeLookInLeft"},
|
||||
{"eyelookoutleft", "EyeLookOutRight"},
|
||||
{"eyelookoutright", "EyeLookOutLeft"},
|
||||
{"eyewideleft", "EyeWideRight"},
|
||||
{"eyewideright", "EyeWideLeft"},
|
||||
{"eyesquintleft", "EyeSquintRight"},
|
||||
{"eyesquintright", "EyeSquintLeft"},
|
||||
{"eyeblinkleft", "EyeBlinkRight"},
|
||||
{"eyeblinkright", "EyeBlinkLeft"}
|
||||
};
|
||||
|
||||
// 정적 Dictionary 사용
|
||||
string mirroredName = originalShapeName;
|
||||
if (eyeMirrorMap.ContainsKey(shapeNameLower))
|
||||
if (EyeMirrorMap.TryGetValue(shapeNameLower, out string mappedName))
|
||||
{
|
||||
mirroredName = eyeMirrorMap[shapeNameLower];
|
||||
mirroredName = mappedName;
|
||||
}
|
||||
else if (originalShapeName.Contains("Right"))
|
||||
{
|
||||
@ -242,72 +324,65 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string message in strArray1[1].Split('|'))
|
||||
// 본 회전 처리
|
||||
string[] boneMessages = strArray1[1].Split(splitPipe, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (string message in boneMessages)
|
||||
{
|
||||
string[] strArray2 = message.Split('#');
|
||||
if (string.IsNullOrEmpty(message)) continue;
|
||||
|
||||
string[] strArray2 = message.Split(splitHash, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (strArray2.Length == 2)
|
||||
{
|
||||
string[] commaList = strArray2[1].Split(',');
|
||||
if (strArray2[0] == "head" && headBone != null)
|
||||
string[] commaList = strArray2[1].Split(splitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 파싱 한 번만 수행
|
||||
if (commaList.Length < 3) continue;
|
||||
|
||||
float x = float.Parse(commaList[0], CultureInfo.InvariantCulture);
|
||||
float y = float.Parse(commaList[1], CultureInfo.InvariantCulture);
|
||||
float z = float.Parse(commaList[2], CultureInfo.InvariantCulture);
|
||||
|
||||
if (mirrorMode)
|
||||
{
|
||||
float x = float.Parse(commaList[0], CultureInfo.InvariantCulture);
|
||||
float y = float.Parse(commaList[1], CultureInfo.InvariantCulture);
|
||||
float z = float.Parse(commaList[2], CultureInfo.InvariantCulture);
|
||||
|
||||
if (mirrorMode)
|
||||
{
|
||||
y = -y; // Y축 회전 반전
|
||||
}
|
||||
|
||||
headBone.localRotation = Quaternion.Euler(x, y, -z);
|
||||
|
||||
if (headPositionObject != null)
|
||||
{
|
||||
float posX = -float.Parse(commaList[3], CultureInfo.InvariantCulture);
|
||||
float posY = float.Parse(commaList[4], CultureInfo.InvariantCulture);
|
||||
float posZ = float.Parse(commaList[5], CultureInfo.InvariantCulture);
|
||||
|
||||
if (mirrorMode)
|
||||
y = -y; // Y축 회전 반전
|
||||
}
|
||||
|
||||
switch (strArray2[0])
|
||||
{
|
||||
case "head" when headBone != null:
|
||||
headBone.localRotation = Quaternion.Euler(x, y, -z);
|
||||
|
||||
if (headPositionObject != null && commaList.Length >= 6)
|
||||
{
|
||||
posX = -posX; // X축 위치 반전
|
||||
float posX = -float.Parse(commaList[3], CultureInfo.InvariantCulture);
|
||||
float posY = float.Parse(commaList[4], CultureInfo.InvariantCulture);
|
||||
float posZ = float.Parse(commaList[5], CultureInfo.InvariantCulture);
|
||||
|
||||
if (mirrorMode)
|
||||
{
|
||||
posX = -posX; // X축 위치 반전
|
||||
}
|
||||
|
||||
headPositionObject.localPosition = new Vector3(posX, posY, posZ);
|
||||
}
|
||||
|
||||
headPositionObject.localPosition = new Vector3(posX, posY, posZ);
|
||||
}
|
||||
}
|
||||
else if (strArray2[0] == "rightEye" && rightEyeBone != null)
|
||||
{
|
||||
float x = float.Parse(commaList[0], CultureInfo.InvariantCulture);
|
||||
float y = float.Parse(commaList[1], CultureInfo.InvariantCulture);
|
||||
float z = float.Parse(commaList[2], CultureInfo.InvariantCulture);
|
||||
|
||||
if (mirrorMode)
|
||||
{
|
||||
y = -y; // Y축 회전 반전
|
||||
}
|
||||
|
||||
rightEyeBone.localRotation = Quaternion.Euler(x, y, z);
|
||||
}
|
||||
else if (strArray2[0] == "leftEye" && leftEyeBone != null)
|
||||
{
|
||||
float x = float.Parse(commaList[0], CultureInfo.InvariantCulture);
|
||||
float y = float.Parse(commaList[1], CultureInfo.InvariantCulture);
|
||||
float z = float.Parse(commaList[2], CultureInfo.InvariantCulture);
|
||||
|
||||
if (mirrorMode)
|
||||
{
|
||||
y = -y; // Y축 회전 반전
|
||||
}
|
||||
|
||||
leftEyeBone.localRotation = Quaternion.Euler(x, y, z);
|
||||
break;
|
||||
|
||||
case "rightEye" when rightEyeBone != null:
|
||||
rightEyeBone.localRotation = Quaternion.Euler(x, y, z);
|
||||
break;
|
||||
|
||||
case "leftEye" when leftEyeBone != null:
|
||||
leftEyeBone.localRotation = Quaternion.Euler(x, y, z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[iFacialMocap] Animation 설정 중 오류: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,20 +393,24 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
|
||||
void ThreadMethod()
|
||||
{
|
||||
//Process once every 5ms
|
||||
long next = DateTime.Now.Ticks + 50000;
|
||||
long now;
|
||||
|
||||
while (true)
|
||||
while (isThreadRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPEndPoint remoteEP = null;
|
||||
byte[] data = udp.Receive(ref remoteEP);
|
||||
messageString = Encoding.ASCII.GetString(data);
|
||||
|
||||
// 데이터를 받았을 때만 업데이트
|
||||
if (data != null && data.Length > 0)
|
||||
{
|
||||
messageString = Encoding.ASCII.GetString(data);
|
||||
}
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
// 스레드 종료 중이면 로그 생략
|
||||
if (!isThreadRunning) break;
|
||||
|
||||
if (e.SocketErrorCode != SocketError.TimedOut)
|
||||
{
|
||||
Debug.LogError($"[iFacialMocap] 데이터 수신 오류: {e.Message}");
|
||||
@ -339,15 +418,15 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 스레드 종료 중이면 로그 생략
|
||||
if (!isThreadRunning) break;
|
||||
|
||||
Debug.LogError($"[iFacialMocap] 예상치 못한 오류: {e.Message}");
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
now = DateTime.Now.Ticks;
|
||||
}
|
||||
while (now < next);
|
||||
next += 50000;
|
||||
// CPU를 양보하는 Sleep 사용 (5ms 대기)
|
||||
// Busy waiting 대신 Thread.Sleep으로 CPU 사용률 감소
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,8 +447,9 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
{
|
||||
OnApplicationQuit();
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[iFacialMocap] OnDisable 중 오류: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,8 +469,28 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
{
|
||||
StopStreaming_iOS_App();
|
||||
}
|
||||
udp.Dispose();
|
||||
thread.Abort();
|
||||
|
||||
// 안전한 스레드 종료
|
||||
isThreadRunning = false;
|
||||
|
||||
// UDP 종료
|
||||
if (udp != null)
|
||||
{
|
||||
udp.Close();
|
||||
udp.Dispose();
|
||||
}
|
||||
|
||||
// 스레드가 종료될 때까지 대기 (최대 100ms)
|
||||
if (thread != null && thread.IsAlive)
|
||||
{
|
||||
thread.Join(100);
|
||||
|
||||
// 그래도 종료되지 않으면 강제 종료
|
||||
if (thread.IsAlive)
|
||||
{
|
||||
thread.Abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasBlendShapes(SkinnedMeshRenderer skin)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user