Remove : ShoulderCorrectionFunction 제거, 어깨 증폭으로 대체

- ShoulderCorrectionFunction.cs 삭제
- CustomRetargetingScript, 에디터, 리모트 컨트롤러, 대시보드에서 참조 전부 제거
- OptitrackSkeletonAnimator_Mingle에 shoulderAmplify 추가 (기본값 2x)
  - rest pose 대비 delta를 SlerpUnclamped로 증폭
  - 상완 역보정으로 손 위치 보존
- 소스 스켈레톤 단에서 어깨 과장 → 리타게팅 IK가 팔 자동 보정

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
user 2026-03-29 02:44:14 +09:00
parent 1621dbbe0e
commit c15a9b5ce0
8 changed files with 40 additions and 206 deletions

View File

@ -39,6 +39,11 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
[HideInInspector]
public FilterStrength filterStrength = FilterStrength.Medium;
[Header("어깨 증폭")]
[Tooltip("어깨 회전을 증폭합니다. 1 = 원본, 2 = 2배. 하위 체인(상완)은 자동 역보정되어 손 위치가 유지됩니다.")]
[Range(0f, 10f)]
public float shoulderAmplify = 2f;
[HideInInspector] public float filterMinCutoff = 3.0f;
[HideInInspector] public float filterBeta = 1.5f;
[HideInInspector] public float filterMaxCutoff = 15.0f;
@ -423,6 +428,39 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
}
}
// ── 어깨 증폭 + 상완 역보정 (1.0이면 스킵) ──
if (Mathf.Abs(shoulderAmplify - 1f) > 0.001f)
{
AmplifyShoulderWithCompensation("LShoulder", "LUArm");
AmplifyShoulderWithCompensation("RShoulder", "RUArm");
}
}
/// <summary>
/// 어깨 회전을 rest pose 대비 증폭하고, 상완에서 추가 회전분을 상쇄하여 손 위치를 보존합니다.
/// </summary>
private void AmplifyShoulderWithCompensation(string shoulderName, string upperArmName)
{
Transform shoulder = GetMappedTransform(shoulderName);
Transform upperArm = GetMappedTransform(upperArmName);
if (shoulder == null || upperArm == null) return;
// rest pose 대비 델타 추출 → 증폭
Quaternion restRot = GetRestLocalRotation(shoulderName);
Quaternion currentRot = shoulder.localRotation;
Quaternion delta = Quaternion.Inverse(restRot) * currentRot;
Quaternion amplifiedDelta = Quaternion.SlerpUnclamped(Quaternion.identity, delta, shoulderAmplify);
Quaternion amplifiedRot = restRot * amplifiedDelta;
// 추가된 회전량
Quaternion extraRotation = Quaternion.Inverse(currentRot) * amplifiedRot;
// 어깨에 증폭 적용
shoulder.localRotation = amplifiedRot;
// 상완에서 추가분 상쇄 (손 위치 보존)
upperArm.localRotation = Quaternion.Inverse(extraRotation) * upperArm.localRotation;
}
/// <summary>

Binary file not shown.

View File

@ -106,9 +106,6 @@ namespace KindRetargeting
[HideInInspector] public Vector3 tPoseHeadForward = Vector3.forward;
[HideInInspector] public Vector3 tPoseHeadUp = Vector3.up;
[Header("어깨 보정")]
[SerializeField] public ShoulderCorrectionFunction shoulderCorrection = new ShoulderCorrectionFunction();
[Header("프랍 부착")]
[SerializeField] public PropLocationController propLocation = new PropLocationController();
@ -263,10 +260,6 @@ namespace KindRetargeting
// OptiTrack 스파인 분배 초기화
InitializeOptiTrackSpineDistribution();
// 어깨 보정 모듈 초기화
if (targetAnimator != null)
shoulderCorrection.Initialize(targetAnimator);
// 프랍 부착 모듈 초기화
if (targetAnimator != null)
propLocation.Initialize(targetAnimator);
@ -738,7 +731,6 @@ namespace KindRetargeting
// 손가락은 SyncBoneRotations에서 함께 처리됨 (lastBoneIndex=55)
fingerShaped.OnUpdate();
shoulderCorrection.OnUpdate();
limbWeight.OnUpdate();
ikSolver.OnUpdate();

View File

@ -62,9 +62,6 @@ namespace KindRetargeting
// ── 머리 회전 오프셋 ──
root.Add(BuildHeadRotationSection());
// ── 어깨 보정 (ShoulderCorrection) ──
root.Add(BuildShoulderSection());
// ── 사지 가중치 (LimbWeight) ──
root.Add(BuildLimbWeightSection());
@ -185,33 +182,6 @@ namespace KindRetargeting
return foldout;
}
// ========== 어깨 보정 ==========
private VisualElement BuildShoulderSection()
{
var foldout = new Foldout { text = "어깨 보정 (ShoulderCorrection)", value = false };
var strength = new Slider("블렌드 강도", 0f, 5f) { showInputField = true };
strength.BindProperty(serializedObject.FindProperty("shoulderCorrection.blendStrength"));
foldout.Add(strength);
var maxBlend = new Slider("최대 블렌드", 0f, 1f) { showInputField = true };
maxBlend.BindProperty(serializedObject.FindProperty("shoulderCorrection.maxShoulderBlend"));
foldout.Add(maxBlend);
foldout.Add(new PropertyField(serializedObject.FindProperty("shoulderCorrection.reverseLeftRotation"), "왼쪽 회전 반전"));
foldout.Add(new PropertyField(serializedObject.FindProperty("shoulderCorrection.reverseRightRotation"), "오른쪽 회전 반전"));
foldout.Add(BuildMinMaxRange("높이 차이 범위",
serializedObject.FindProperty("shoulderCorrection.minHeightDifference"),
serializedObject.FindProperty("shoulderCorrection.maxHeightDifference"),
-0.5f, 2f));
foldout.Add(new PropertyField(serializedObject.FindProperty("shoulderCorrection.shoulderCorrectionCurve"), "보정 커브"));
return foldout;
}
// ========== 사지 가중치 ==========
private VisualElement BuildLimbWeightSection()

View File

@ -224,9 +224,6 @@ public class RetargetingControlWindow : EditorWindow
footContainer.Bind(so);
panel.Add(footFoldout);
// 어깨 보정
panel.Add(BuildShoulderSection(script, so));
// 접지 설정
// 손가락 제어 설정
@ -729,37 +726,6 @@ public class RetargetingControlWindow : EditorWindow
return box;
}
// ========== Shoulder Correction ==========
private VisualElement BuildShoulderSection(CustomRetargetingScript script, SerializedObject so)
{
var foldout = new Foldout { text = "어깨 보정", value = false };
var container = new VisualElement();
var strength = new Slider("블렌드 강도", 0f, 5f) { showInputField = true };
strength.BindProperty(so.FindProperty("shoulderCorrection.blendStrength"));
container.Add(strength);
var maxBlend = new Slider("최대 블렌드", 0f, 1f) { showInputField = true };
maxBlend.BindProperty(so.FindProperty("shoulderCorrection.maxShoulderBlend"));
container.Add(maxBlend);
container.Add(new PropertyField(so.FindProperty("shoulderCorrection.reverseLeftRotation"), "왼쪽 회전 반전"));
container.Add(new PropertyField(so.FindProperty("shoulderCorrection.reverseRightRotation"), "오른쪽 회전 반전"));
container.Add(BuildMinMaxRange("높이 차이 범위",
so.FindProperty("shoulderCorrection.minHeightDifference"),
so.FindProperty("shoulderCorrection.maxHeightDifference"),
-0.5f, 2f, so));
container.Add(new PropertyField(so.FindProperty("shoulderCorrection.shoulderCorrectionCurve"), "보정 커브"));
container.Bind(so);
foldout.Add(container);
return foldout;
}
// ========== Helpers ==========
private VisualElement BuildMinMaxRange(string label, SerializedProperty minProp, SerializedProperty maxProp, float limitMin, float limitMax, SerializedObject so)

View File

@ -280,14 +280,6 @@ namespace KindRetargeting.Remote
{ "enableLeftArmIK", script.limbWeight.enableLeftArmIK },
{ "enableRightArmIK", script.limbWeight.enableRightArmIK },
// ShoulderCorrection 데이터
{ "shoulderBlendStrength", script.shoulderCorrection.blendStrength },
{ "shoulderMaxBlend", script.shoulderCorrection.maxShoulderBlend },
{ "shoulderMaxHeightDiff", script.shoulderCorrection.maxHeightDifference },
{ "shoulderMinHeightDiff", script.shoulderCorrection.minHeightDifference },
{ "shoulderReverseLeft", script.shoulderCorrection.reverseLeftRotation },
{ "shoulderReverseRight", script.shoulderCorrection.reverseRightRotation },
// FingerShapedController 데이터
{ "handPoseEnabled", script.fingerShaped.enabled },
{ "leftHandEnabled", script.fingerShaped.leftHandEnabled },
@ -420,26 +412,6 @@ namespace KindRetargeting.Remote
script.limbWeight.enableRightArmIK = value > 0.5f;
break;
// ShoulderCorrection 속성
case "shoulderBlendStrength":
script.shoulderCorrection.blendStrength = value;
break;
case "shoulderMaxBlend":
script.shoulderCorrection.maxShoulderBlend = value;
break;
case "shoulderMaxHeightDiff":
script.shoulderCorrection.maxHeightDifference = value;
break;
case "shoulderMinHeightDiff":
script.shoulderCorrection.minHeightDifference = value;
break;
case "shoulderReverseLeft":
script.shoulderCorrection.reverseLeftRotation = value > 0.5f;
break;
case "shoulderReverseRight":
script.shoulderCorrection.reverseRightRotation = value > 0.5f;
break;
// FingerShapedController 속성
case "handPoseEnabled":
script.fingerShaped.enabled = value > 0.5f;

View File

@ -1,93 +0,0 @@
using UnityEngine;
namespace KindRetargeting
{
[System.Serializable]
public class ShoulderCorrectionFunction
{
[Header("설정")]
[Range(0f, 5f)]
public float blendStrength = 2f;
[Range(0f, 1f)]
public float maxShoulderBlend = 0.7f;
public bool reverseLeftRotation = false;
public bool reverseRightRotation = false;
[Header("높이 제한 설정")]
public float maxHeightDifference = 0.8f;
public float minHeightDifference = -0.1f;
[Header("보정 커브 설정")]
public AnimationCurve shoulderCorrectionCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
private float leftBlendWeight = 0f;
private float rightBlendWeight = 0f;
private Transform leftShoulder;
private Transform rightShoulder;
private Transform leftUpperArm;
private Transform rightUpperArm;
private Transform leftLowerArm;
private Transform rightLowerArm;
public void Initialize(Animator targetAnimator)
{
leftShoulder = targetAnimator.GetBoneTransform(HumanBodyBones.LeftShoulder);
rightShoulder = targetAnimator.GetBoneTransform(HumanBodyBones.RightShoulder);
leftUpperArm = targetAnimator.GetBoneTransform(HumanBodyBones.LeftUpperArm);
rightUpperArm = targetAnimator.GetBoneTransform(HumanBodyBones.RightUpperArm);
leftLowerArm = targetAnimator.GetBoneTransform(HumanBodyBones.LeftLowerArm);
rightLowerArm = targetAnimator.GetBoneTransform(HumanBodyBones.RightLowerArm);
}
public void OnUpdate()
{
if (leftShoulder == null || rightShoulder == null) return;
// 왼쪽 어깨 보정
Vector3 leftElbowPos = leftLowerArm.position;
float leftHeightDiff = leftElbowPos.y - leftShoulder.position.y;
float leftRawBlend = Mathf.Clamp01(
Mathf.InverseLerp(minHeightDifference, maxHeightDifference, leftHeightDiff) * blendStrength
);
leftBlendWeight = shoulderCorrectionCurve.Evaluate(leftRawBlend) * maxShoulderBlend;
// 오른쪽 어깨 보정
Vector3 rightElbowPos = rightLowerArm.position;
float rightHeightDiff = rightElbowPos.y - rightShoulder.position.y;
float rightRawBlend = Mathf.Clamp01(
Mathf.InverseLerp(minHeightDifference, maxHeightDifference, rightHeightDiff) * blendStrength
);
rightBlendWeight = shoulderCorrectionCurve.Evaluate(rightRawBlend) * maxShoulderBlend;
// 어깨와 윗팔 회전 보정 적용
if (leftBlendWeight > 0.01f)
{
Quaternion currentWorldShoulderRot = leftShoulder.rotation;
Quaternion currentWorldArmRot = leftUpperArm.rotation;
Vector3 shoulderToArm = (leftUpperArm.position - leftShoulder.position).normalized;
Quaternion targetRotation = Quaternion.FromToRotation(leftShoulder.forward,
reverseLeftRotation ? shoulderToArm : -shoulderToArm);
Quaternion targetWorldShoulderRot = targetRotation * currentWorldShoulderRot;
leftShoulder.rotation = Quaternion.Lerp(currentWorldShoulderRot, targetWorldShoulderRot, leftBlendWeight);
leftUpperArm.rotation = currentWorldArmRot;
}
if (rightBlendWeight > 0.01f)
{
Quaternion currentWorldShoulderRot = rightShoulder.rotation;
Quaternion currentWorldArmRot = rightUpperArm.rotation;
Vector3 shoulderToArm = (rightUpperArm.position - rightShoulder.position).normalized;
Quaternion targetRotation = Quaternion.FromToRotation(rightShoulder.forward,
reverseRightRotation ? -shoulderToArm : shoulderToArm);
Quaternion targetWorldShoulderRot = targetRotation * currentWorldShoulderRot;
rightShoulder.rotation = Quaternion.Lerp(currentWorldShoulderRot, targetWorldShoulderRot, rightBlendWeight);
rightUpperArm.rotation = currentWorldArmRot;
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: d017e8fb3cc30d5448512160bfea29f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: