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:
parent
1621dbbe0e
commit
c15a9b5ce0
@ -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>
|
||||
|
||||
BIN
Assets/Resources/StreamingleDashboard/dashboard_script.txt
(Stored with Git LFS)
BIN
Assets/Resources/StreamingleDashboard/dashboard_script.txt
(Stored with Git LFS)
Binary file not shown.
@ -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();
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d017e8fb3cc30d5448512160bfea29f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
x
Reference in New Issue
Block a user