From 76906a63802d50b5cb23d0593976e8f38e9682b7 Mon Sep 17 00:00:00 2001 From: "qsxft258@gmail.com" Date: Sun, 1 Feb 2026 12:19:34 +0900 Subject: [PATCH] =?UTF-8?q?Fix=20:=20=ED=8E=98=EC=9D=B4=EC=85=9C=20?= =?UTF-8?q?=EB=85=B8=EC=9D=B4=EC=A6=88=20=EC=A0=9C=EA=B1=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tyRecieve_FACEMOTION3D_and_iFacialMocap.cs | 126 +++++++++++++++++- 1 file changed, 120 insertions(+), 6 deletions(-) diff --git a/Assets/External/Ifacialmocap/UnityRecieve_FACEMOTION3D_and_iFacialMocap.cs b/Assets/External/Ifacialmocap/UnityRecieve_FACEMOTION3D_and_iFacialMocap.cs index 1d7c0992..a4221fca 100644 --- a/Assets/External/Ifacialmocap/UnityRecieve_FACEMOTION3D_and_iFacialMocap.cs +++ b/Assets/External/Ifacialmocap/UnityRecieve_FACEMOTION3D_and_iFacialMocap.cs @@ -31,6 +31,26 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour private string lastProcessedMessage = ""; // 이전 메시지 저장용 public int LOCAL_PORT = 49983; + // 데이터 필터링 설정 + [Header("Data Filtering")] + [Tooltip("데이터 스무딩/필터링 활성화")] + public bool enableFiltering = true; + [Tooltip("스무딩 강도 (0=필터없음, 1=최대 스무딩)")] + [Range(0f, 0.95f)] + public float smoothingFactor = 0.5f; + [Tooltip("프레임 간 최대 허용 변화량 (BlendShape, 0~100 스케일)")] + [Range(1f, 100f)] + public float maxBlendShapeDelta = 30f; + [Tooltip("프레임 간 최대 허용 회전 변화량 (도)")] + [Range(1f, 90f)] + public float maxRotationDelta = 25f; + + // 필터링용 이전 값 저장 + private Dictionary prevBlendShapeValues = new Dictionary(); + private Dictionary prevBoneRotations = new Dictionary(); + private Vector3 prevHeadPosition = Vector3.zero; + private bool hasFirstFrame = false; + // 성능 최적화를 위한 캐시 private Dictionary> blendShapeCache; private readonly char[] splitEquals = new char[] { '=' }; @@ -238,9 +258,14 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour // 정규화된 이름으로 캐시 검색 string normalizedName = NormalizeBlendShapeName(shapeName).ToLowerInvariant(); + // 필터링 적용 + if (enableFiltering) + { + weight = FilterBlendShapeValue(normalizedName, weight); + } + if (blendShapeCache.TryGetValue(normalizedName, out List mappings)) { - // 캐시에서 찾은 모든 매핑에 대해 weight 설정 foreach (var mapping in mappings) { if (mapping.renderer != null) @@ -348,10 +373,18 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour y = -y; // Y축 회전 반전 } + Vector3 rotation = new Vector3(x, y, z); + + // 본 회전 필터링 적용 + if (enableFiltering) + { + rotation = FilterBoneRotation(strArray2[0], rotation); + } + switch (strArray2[0]) { case "head" when headBone != null: - headBone.localRotation = Quaternion.Euler(x, y, -z); + headBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, -rotation.z); if (headPositionObject != null && commaList.Length >= 6) { @@ -361,19 +394,24 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour if (mirrorMode) { - posX = -posX; // X축 위치 반전 + posX = -posX; } - headPositionObject.localPosition = new Vector3(posX, posY, posZ); + Vector3 newPos = new Vector3(posX, posY, posZ); + if (enableFiltering) + { + newPos = FilterHeadPosition(newPos); + } + headPositionObject.localPosition = newPos; } break; case "rightEye" when rightEyeBone != null: - rightEyeBone.localRotation = Quaternion.Euler(x, y, z); + rightEyeBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, rotation.z); break; case "leftEye" when leftEyeBone != null: - leftEyeBone.localRotation = Quaternion.Euler(x, y, z); + leftEyeBone.localRotation = Quaternion.Euler(rotation.x, rotation.y, rotation.z); break; } } @@ -522,6 +560,82 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour } } + /// + /// BlendShape 값 필터링: 스파이크 제거 + EMA 스무딩 + /// + float FilterBlendShapeValue(string name, float rawValue) + { + if (prevBlendShapeValues.TryGetValue(name, out float prevValue)) + { + float delta = Mathf.Abs(rawValue - prevValue); + + // 스파이크 감지: 변화량이 임계값 초과 시 이전 값 유지 + if (delta > maxBlendShapeDelta) + { + return prevValue; + } + + // EMA 스무딩 적용 + float smoothed = Mathf.Lerp(rawValue, prevValue, smoothingFactor); + prevBlendShapeValues[name] = smoothed; + return smoothed; + } + + // 첫 프레임은 그대로 저장 + prevBlendShapeValues[name] = rawValue; + return rawValue; + } + + /// + /// 본 회전 필터링: 스파이크 제거 + EMA 스무딩 + /// + Vector3 FilterBoneRotation(string boneName, Vector3 rawRotation) + { + if (prevBoneRotations.TryGetValue(boneName, out Vector3 prevRot)) + { + float delta = Vector3.Distance(rawRotation, prevRot); + + // 스파이크 감지 + if (delta > maxRotationDelta) + { + return prevRot; + } + + // EMA 스무딩 + Vector3 smoothed = Vector3.Lerp(rawRotation, prevRot, smoothingFactor); + prevBoneRotations[boneName] = smoothed; + return smoothed; + } + + prevBoneRotations[boneName] = rawRotation; + return rawRotation; + } + + /// + /// 머리 위치 필터링 + /// + Vector3 FilterHeadPosition(Vector3 rawPos) + { + if (hasFirstFrame) + { + float delta = Vector3.Distance(rawPos, prevHeadPosition); + + // 위치 스파이크 감지 (0.1 단위 기준) + if (delta > maxRotationDelta * 0.01f) + { + return prevHeadPosition; + } + + Vector3 smoothed = Vector3.Lerp(rawPos, prevHeadPosition, smoothingFactor); + prevHeadPosition = smoothed; + return smoothed; + } + + hasFirstFrame = true; + prevHeadPosition = rawPos; + return rawPos; + } + private bool HasBlendShapes(SkinnedMeshRenderer skin) { if (!skin.sharedMesh)