Fix : 페이셜 고도화
This commit is contained in:
parent
76906a6380
commit
e1d9e2f88c
@ -35,7 +35,7 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
|||||||
[Header("Data Filtering")]
|
[Header("Data Filtering")]
|
||||||
[Tooltip("데이터 스무딩/필터링 활성화")]
|
[Tooltip("데이터 스무딩/필터링 활성화")]
|
||||||
public bool enableFiltering = true;
|
public bool enableFiltering = true;
|
||||||
[Tooltip("스무딩 강도 (0=필터없음, 1=최대 스무딩)")]
|
[Tooltip("스무딩 강도 (0=필터없음, 1=최대 스무딩). 프레임레이트 독립적으로 동작")]
|
||||||
[Range(0f, 0.95f)]
|
[Range(0f, 0.95f)]
|
||||||
public float smoothingFactor = 0.5f;
|
public float smoothingFactor = 0.5f;
|
||||||
[Tooltip("프레임 간 최대 허용 변화량 (BlendShape, 0~100 스케일)")]
|
[Tooltip("프레임 간 최대 허용 변화량 (BlendShape, 0~100 스케일)")]
|
||||||
@ -44,6 +44,12 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
|||||||
[Tooltip("프레임 간 최대 허용 회전 변화량 (도)")]
|
[Tooltip("프레임 간 최대 허용 회전 변화량 (도)")]
|
||||||
[Range(1f, 90f)]
|
[Range(1f, 90f)]
|
||||||
public float maxRotationDelta = 25f;
|
public float maxRotationDelta = 25f;
|
||||||
|
[Tooltip("눈 깜빡임 등 빠른 BlendShape의 임계값 배수")]
|
||||||
|
[Range(1f, 3f)]
|
||||||
|
public float fastBlendShapeMultiplier = 2.0f;
|
||||||
|
[Tooltip("스파이크 판정 전 허용할 연속 프레임 수 (연속이면 실제 움직임으로 판단)")]
|
||||||
|
[Range(1, 5)]
|
||||||
|
public int spikeToleranceFrames = 2;
|
||||||
|
|
||||||
// 필터링용 이전 값 저장
|
// 필터링용 이전 값 저장
|
||||||
private Dictionary<string, float> prevBlendShapeValues = new Dictionary<string, float>();
|
private Dictionary<string, float> prevBlendShapeValues = new Dictionary<string, float>();
|
||||||
@ -51,6 +57,26 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
|||||||
private Vector3 prevHeadPosition = Vector3.zero;
|
private Vector3 prevHeadPosition = Vector3.zero;
|
||||||
private bool hasFirstFrame = false;
|
private bool hasFirstFrame = false;
|
||||||
|
|
||||||
|
// 연속 스파이크 추적 (같은 방향으로 연속이면 실제 움직임)
|
||||||
|
private Dictionary<string, int> blendShapeSpikeCount = new Dictionary<string, int>();
|
||||||
|
private Dictionary<string, float> blendShapeSpikeDirection = new Dictionary<string, float>();
|
||||||
|
private Dictionary<string, int> boneSpikeCount = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
// 빠르게 변하는 BlendShape 목록 (눈 깜빡임, 입 등)
|
||||||
|
private static readonly HashSet<string> FastBlendShapes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
"eyeblinkleft", "eyeblinkright",
|
||||||
|
"eyesquintleft", "eyesquintright",
|
||||||
|
"eyewideleft", "eyewideright",
|
||||||
|
"jawopen", "mouthclose",
|
||||||
|
"mouthfunnel", "mouthpucker",
|
||||||
|
"mouthsmileright", "mouthsmileleft",
|
||||||
|
"mouthfrownright", "mouthfrownleft",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 프레임레이트 독립 스무딩을 위한 기준 FPS
|
||||||
|
private const float ReferenceFPS = 60f;
|
||||||
|
|
||||||
// 성능 최적화를 위한 캐시
|
// 성능 최적화를 위한 캐시
|
||||||
private Dictionary<string, List<BlendShapeMapping>> blendShapeCache;
|
private Dictionary<string, List<BlendShapeMapping>> blendShapeCache;
|
||||||
private readonly char[] splitEquals = new char[] { '=' };
|
private readonly char[] splitEquals = new char[] { '=' };
|
||||||
@ -561,33 +587,87 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// BlendShape 값 필터링: 스파이크 제거 + EMA 스무딩
|
/// 프레임레이트 독립적 EMA 계수 계산
|
||||||
|
/// </summary>
|
||||||
|
float GetFrameIndependentSmoothing()
|
||||||
|
{
|
||||||
|
float dt = Time.deltaTime;
|
||||||
|
if (dt <= 0f) return smoothingFactor;
|
||||||
|
// 기준 60fps에서의 smoothingFactor를 현재 dt에 맞게 보정
|
||||||
|
return 1f - Mathf.Pow(1f - smoothingFactor, dt * ReferenceFPS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BlendShape 값 필터링: 연속 스파이크 판별 + 카테고리별 임계값 + 프레임독립 EMA
|
||||||
/// </summary>
|
/// </summary>
|
||||||
float FilterBlendShapeValue(string name, float rawValue)
|
float FilterBlendShapeValue(string name, float rawValue)
|
||||||
{
|
{
|
||||||
if (prevBlendShapeValues.TryGetValue(name, out float prevValue))
|
if (prevBlendShapeValues.TryGetValue(name, out float prevValue))
|
||||||
{
|
{
|
||||||
float delta = Mathf.Abs(rawValue - prevValue);
|
float diff = rawValue - prevValue;
|
||||||
|
float delta = Mathf.Abs(diff);
|
||||||
|
|
||||||
// 스파이크 감지: 변화량이 임계값 초과 시 이전 값 유지
|
// 빠르게 변하는 BlendShape는 임계값을 높여줌
|
||||||
if (delta > maxBlendShapeDelta)
|
float threshold = maxBlendShapeDelta;
|
||||||
|
if (FastBlendShapes.Contains(name))
|
||||||
{
|
{
|
||||||
return prevValue;
|
threshold *= fastBlendShapeMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
// EMA 스무딩 적용
|
// 스파이크 감지
|
||||||
float smoothed = Mathf.Lerp(rawValue, prevValue, smoothingFactor);
|
if (delta > threshold)
|
||||||
|
{
|
||||||
|
// 연속 스파이크 추적: 같은 방향이면 카운트 증가
|
||||||
|
float prevDir = 0f;
|
||||||
|
blendShapeSpikeDirection.TryGetValue(name, out prevDir);
|
||||||
|
bool sameDirection = (diff > 0 && prevDir > 0) || (diff < 0 && prevDir < 0);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
blendShapeSpikeCount.TryGetValue(name, out count);
|
||||||
|
|
||||||
|
if (sameDirection)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blendShapeSpikeCount[name] = count;
|
||||||
|
blendShapeSpikeDirection[name] = diff;
|
||||||
|
|
||||||
|
// 연속 프레임 이상 같은 방향이면 실제 움직임으로 판단 → 통과
|
||||||
|
if (count >= spikeToleranceFrames)
|
||||||
|
{
|
||||||
|
blendShapeSpikeCount[name] = 0;
|
||||||
|
prevBlendShapeValues[name] = rawValue;
|
||||||
|
return rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단발성 스파이크 → 허용량만큼만 이동
|
||||||
|
float clamped = prevValue + Mathf.Clamp(diff, -threshold, threshold);
|
||||||
|
prevBlendShapeValues[name] = clamped;
|
||||||
|
return clamped;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 정상 범위 → 스파이크 카운터 리셋
|
||||||
|
blendShapeSpikeCount[name] = 0;
|
||||||
|
|
||||||
|
// 프레임레이트 독립 EMA 스무딩
|
||||||
|
float alpha = GetFrameIndependentSmoothing();
|
||||||
|
float smoothed = Mathf.Lerp(rawValue, prevValue, alpha);
|
||||||
prevBlendShapeValues[name] = smoothed;
|
prevBlendShapeValues[name] = smoothed;
|
||||||
return smoothed;
|
return smoothed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 첫 프레임은 그대로 저장
|
// 첫 프레임
|
||||||
prevBlendShapeValues[name] = rawValue;
|
prevBlendShapeValues[name] = rawValue;
|
||||||
return rawValue;
|
return rawValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 본 회전 필터링: 스파이크 제거 + EMA 스무딩
|
/// 본 회전 필터링: 연속 스파이크 판별 + 프레임독립 EMA
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Vector3 FilterBoneRotation(string boneName, Vector3 rawRotation)
|
Vector3 FilterBoneRotation(string boneName, Vector3 rawRotation)
|
||||||
{
|
{
|
||||||
@ -595,14 +675,30 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
|||||||
{
|
{
|
||||||
float delta = Vector3.Distance(rawRotation, prevRot);
|
float delta = Vector3.Distance(rawRotation, prevRot);
|
||||||
|
|
||||||
// 스파이크 감지
|
|
||||||
if (delta > maxRotationDelta)
|
if (delta > maxRotationDelta)
|
||||||
{
|
{
|
||||||
return prevRot;
|
int count = 0;
|
||||||
|
boneSpikeCount.TryGetValue(boneName, out count);
|
||||||
|
count++;
|
||||||
|
boneSpikeCount[boneName] = count;
|
||||||
|
|
||||||
|
// 연속이면 실제 움직임
|
||||||
|
if (count >= spikeToleranceFrames)
|
||||||
|
{
|
||||||
|
boneSpikeCount[boneName] = 0;
|
||||||
|
prevBoneRotations[boneName] = rawRotation;
|
||||||
|
return rawRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 clamped = Vector3.MoveTowards(prevRot, rawRotation, maxRotationDelta);
|
||||||
|
prevBoneRotations[boneName] = clamped;
|
||||||
|
return clamped;
|
||||||
}
|
}
|
||||||
|
|
||||||
// EMA 스무딩
|
boneSpikeCount[boneName] = 0;
|
||||||
Vector3 smoothed = Vector3.Lerp(rawRotation, prevRot, smoothingFactor);
|
|
||||||
|
float alpha = GetFrameIndependentSmoothing();
|
||||||
|
Vector3 smoothed = Vector3.Lerp(rawRotation, prevRot, alpha);
|
||||||
prevBoneRotations[boneName] = smoothed;
|
prevBoneRotations[boneName] = smoothed;
|
||||||
return smoothed;
|
return smoothed;
|
||||||
}
|
}
|
||||||
@ -618,15 +714,18 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (hasFirstFrame)
|
if (hasFirstFrame)
|
||||||
{
|
{
|
||||||
|
float maxPosDelta = maxRotationDelta * 0.01f;
|
||||||
float delta = Vector3.Distance(rawPos, prevHeadPosition);
|
float delta = Vector3.Distance(rawPos, prevHeadPosition);
|
||||||
|
|
||||||
// 위치 스파이크 감지 (0.1 단위 기준)
|
if (delta > maxPosDelta)
|
||||||
if (delta > maxRotationDelta * 0.01f)
|
|
||||||
{
|
{
|
||||||
return prevHeadPosition;
|
Vector3 clamped = Vector3.MoveTowards(prevHeadPosition, rawPos, maxPosDelta);
|
||||||
|
prevHeadPosition = clamped;
|
||||||
|
return clamped;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 smoothed = Vector3.Lerp(rawPos, prevHeadPosition, smoothingFactor);
|
float alpha = GetFrameIndependentSmoothing();
|
||||||
|
Vector3 smoothed = Vector3.Lerp(rawPos, prevHeadPosition, alpha);
|
||||||
prevHeadPosition = smoothed;
|
prevHeadPosition = smoothed;
|
||||||
return smoothed;
|
return smoothed;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user