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]
|
[HideInInspector]
|
||||||
public FilterStrength filterStrength = FilterStrength.Medium;
|
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 filterMinCutoff = 3.0f;
|
||||||
[HideInInspector] public float filterBeta = 1.5f;
|
[HideInInspector] public float filterBeta = 1.5f;
|
||||||
[HideInInspector] public float filterMaxCutoff = 15.0f;
|
[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>
|
/// <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 tPoseHeadForward = Vector3.forward;
|
||||||
[HideInInspector] public Vector3 tPoseHeadUp = Vector3.up;
|
[HideInInspector] public Vector3 tPoseHeadUp = Vector3.up;
|
||||||
|
|
||||||
[Header("어깨 보정")]
|
|
||||||
[SerializeField] public ShoulderCorrectionFunction shoulderCorrection = new ShoulderCorrectionFunction();
|
|
||||||
|
|
||||||
[Header("프랍 부착")]
|
[Header("프랍 부착")]
|
||||||
[SerializeField] public PropLocationController propLocation = new PropLocationController();
|
[SerializeField] public PropLocationController propLocation = new PropLocationController();
|
||||||
|
|
||||||
@ -263,10 +260,6 @@ namespace KindRetargeting
|
|||||||
// OptiTrack 스파인 분배 초기화
|
// OptiTrack 스파인 분배 초기화
|
||||||
InitializeOptiTrackSpineDistribution();
|
InitializeOptiTrackSpineDistribution();
|
||||||
|
|
||||||
// 어깨 보정 모듈 초기화
|
|
||||||
if (targetAnimator != null)
|
|
||||||
shoulderCorrection.Initialize(targetAnimator);
|
|
||||||
|
|
||||||
// 프랍 부착 모듈 초기화
|
// 프랍 부착 모듈 초기화
|
||||||
if (targetAnimator != null)
|
if (targetAnimator != null)
|
||||||
propLocation.Initialize(targetAnimator);
|
propLocation.Initialize(targetAnimator);
|
||||||
@ -738,7 +731,6 @@ namespace KindRetargeting
|
|||||||
// 손가락은 SyncBoneRotations에서 함께 처리됨 (lastBoneIndex=55)
|
// 손가락은 SyncBoneRotations에서 함께 처리됨 (lastBoneIndex=55)
|
||||||
|
|
||||||
fingerShaped.OnUpdate();
|
fingerShaped.OnUpdate();
|
||||||
shoulderCorrection.OnUpdate();
|
|
||||||
limbWeight.OnUpdate();
|
limbWeight.OnUpdate();
|
||||||
ikSolver.OnUpdate();
|
ikSolver.OnUpdate();
|
||||||
|
|
||||||
|
|||||||
@ -62,9 +62,6 @@ namespace KindRetargeting
|
|||||||
// ── 머리 회전 오프셋 ──
|
// ── 머리 회전 오프셋 ──
|
||||||
root.Add(BuildHeadRotationSection());
|
root.Add(BuildHeadRotationSection());
|
||||||
|
|
||||||
// ── 어깨 보정 (ShoulderCorrection) ──
|
|
||||||
root.Add(BuildShoulderSection());
|
|
||||||
|
|
||||||
// ── 사지 가중치 (LimbWeight) ──
|
// ── 사지 가중치 (LimbWeight) ──
|
||||||
root.Add(BuildLimbWeightSection());
|
root.Add(BuildLimbWeightSection());
|
||||||
|
|
||||||
@ -185,33 +182,6 @@ namespace KindRetargeting
|
|||||||
return foldout;
|
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()
|
private VisualElement BuildLimbWeightSection()
|
||||||
|
|||||||
@ -224,9 +224,6 @@ public class RetargetingControlWindow : EditorWindow
|
|||||||
footContainer.Bind(so);
|
footContainer.Bind(so);
|
||||||
panel.Add(footFoldout);
|
panel.Add(footFoldout);
|
||||||
|
|
||||||
// 어깨 보정
|
|
||||||
panel.Add(BuildShoulderSection(script, so));
|
|
||||||
|
|
||||||
// 접지 설정
|
// 접지 설정
|
||||||
|
|
||||||
// 손가락 제어 설정
|
// 손가락 제어 설정
|
||||||
@ -729,37 +726,6 @@ public class RetargetingControlWindow : EditorWindow
|
|||||||
return box;
|
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 ==========
|
// ========== Helpers ==========
|
||||||
|
|
||||||
private VisualElement BuildMinMaxRange(string label, SerializedProperty minProp, SerializedProperty maxProp, float limitMin, float limitMax, SerializedObject so)
|
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 },
|
{ "enableLeftArmIK", script.limbWeight.enableLeftArmIK },
|
||||||
{ "enableRightArmIK", script.limbWeight.enableRightArmIK },
|
{ "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 데이터
|
// FingerShapedController 데이터
|
||||||
{ "handPoseEnabled", script.fingerShaped.enabled },
|
{ "handPoseEnabled", script.fingerShaped.enabled },
|
||||||
{ "leftHandEnabled", script.fingerShaped.leftHandEnabled },
|
{ "leftHandEnabled", script.fingerShaped.leftHandEnabled },
|
||||||
@ -420,26 +412,6 @@ namespace KindRetargeting.Remote
|
|||||||
script.limbWeight.enableRightArmIK = value > 0.5f;
|
script.limbWeight.enableRightArmIK = value > 0.5f;
|
||||||
break;
|
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 속성
|
// FingerShapedController 속성
|
||||||
case "handPoseEnabled":
|
case "handPoseEnabled":
|
||||||
script.fingerShaped.enabled = value > 0.5f;
|
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