Fix : 페이셜 노이즈 제거 기능 추가
This commit is contained in:
parent
1b5530240f
commit
76906a6380
@ -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<string, float> prevBlendShapeValues = new Dictionary<string, float>();
|
||||
private Dictionary<string, Vector3> prevBoneRotations = new Dictionary<string, Vector3>();
|
||||
private Vector3 prevHeadPosition = Vector3.zero;
|
||||
private bool hasFirstFrame = false;
|
||||
|
||||
// 성능 최적화를 위한 캐시
|
||||
private Dictionary<string, List<BlendShapeMapping>> 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<BlendShapeMapping> 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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BlendShape 값 필터링: 스파이크 제거 + EMA 스무딩
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 본 회전 필터링: 스파이크 제거 + EMA 스무딩
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 머리 위치 필터링
|
||||
/// </summary>
|
||||
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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user