Fix : 페이셜 고도화
This commit is contained in:
parent
76906a6380
commit
e1d9e2f88c
@ -35,7 +35,7 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
[Header("Data Filtering")]
|
||||
[Tooltip("데이터 스무딩/필터링 활성화")]
|
||||
public bool enableFiltering = true;
|
||||
[Tooltip("스무딩 강도 (0=필터없음, 1=최대 스무딩)")]
|
||||
[Tooltip("스무딩 강도 (0=필터없음, 1=최대 스무딩). 프레임레이트 독립적으로 동작")]
|
||||
[Range(0f, 0.95f)]
|
||||
public float smoothingFactor = 0.5f;
|
||||
[Tooltip("프레임 간 최대 허용 변화량 (BlendShape, 0~100 스케일)")]
|
||||
@ -44,6 +44,12 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
[Tooltip("프레임 간 최대 허용 회전 변화량 (도)")]
|
||||
[Range(1f, 90f)]
|
||||
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>();
|
||||
@ -51,6 +57,26 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
private Vector3 prevHeadPosition = Vector3.zero;
|
||||
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 readonly char[] splitEquals = new char[] { '=' };
|
||||
@ -561,33 +587,87 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <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>
|
||||
float FilterBlendShapeValue(string name, float rawValue)
|
||||
{
|
||||
if (prevBlendShapeValues.TryGetValue(name, out float prevValue))
|
||||
{
|
||||
float delta = Mathf.Abs(rawValue - prevValue);
|
||||
float diff = rawValue - prevValue;
|
||||
float delta = Mathf.Abs(diff);
|
||||
|
||||
// 스파이크 감지: 변화량이 임계값 초과 시 이전 값 유지
|
||||
if (delta > maxBlendShapeDelta)
|
||||
// 빠르게 변하는 BlendShape는 임계값을 높여줌
|
||||
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;
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
// 첫 프레임은 그대로 저장
|
||||
// 첫 프레임
|
||||
prevBlendShapeValues[name] = rawValue;
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 본 회전 필터링: 스파이크 제거 + EMA 스무딩
|
||||
/// 본 회전 필터링: 연속 스파이크 판별 + 프레임독립 EMA
|
||||
/// </summary>
|
||||
Vector3 FilterBoneRotation(string boneName, Vector3 rawRotation)
|
||||
{
|
||||
@ -595,14 +675,30 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
{
|
||||
float delta = Vector3.Distance(rawRotation, prevRot);
|
||||
|
||||
// 스파이크 감지
|
||||
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;
|
||||
}
|
||||
|
||||
// EMA 스무딩
|
||||
Vector3 smoothed = Vector3.Lerp(rawRotation, prevRot, smoothingFactor);
|
||||
Vector3 clamped = Vector3.MoveTowards(prevRot, rawRotation, maxRotationDelta);
|
||||
prevBoneRotations[boneName] = clamped;
|
||||
return clamped;
|
||||
}
|
||||
|
||||
boneSpikeCount[boneName] = 0;
|
||||
|
||||
float alpha = GetFrameIndependentSmoothing();
|
||||
Vector3 smoothed = Vector3.Lerp(rawRotation, prevRot, alpha);
|
||||
prevBoneRotations[boneName] = smoothed;
|
||||
return smoothed;
|
||||
}
|
||||
@ -618,15 +714,18 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
{
|
||||
if (hasFirstFrame)
|
||||
{
|
||||
float maxPosDelta = maxRotationDelta * 0.01f;
|
||||
float delta = Vector3.Distance(rawPos, prevHeadPosition);
|
||||
|
||||
// 위치 스파이크 감지 (0.1 단위 기준)
|
||||
if (delta > maxRotationDelta * 0.01f)
|
||||
if (delta > maxPosDelta)
|
||||
{
|
||||
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;
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user