fix ; 리타겟팅 스크립트 최적화

This commit is contained in:
qsxft258@gmail.com 2025-12-02 18:35:08 +09:00
parent a2e080414f
commit 420301c441
3 changed files with 78 additions and 53 deletions

View File

@ -57,6 +57,10 @@ namespace KindRetargeting
private HumanPose sourcePose;
private HumanPose targetPose;
// 최적화: 프레임당 한 번만 GetHumanPose 호출하기 위한 플래그
private bool isSourcePoseCachedThisFrame = false;
private bool isTargetPoseCachedThisFrame = false;
// 본별 회전 오프셋을 저장하는 딕셔너리
private Dictionary<HumanBodyBones, Quaternion> rotationOffsets = new Dictionary<HumanBodyBones, Quaternion>();
@ -184,8 +188,9 @@ namespace KindRetargeting
}
}
// 각 손가락 관절별로 필터 버퍼를 관리하는 Dictionary 추가
private Dictionary<int, Queue<float>> fingerFilterBuffers = new Dictionary<int, Queue<float>>();
// 각 손가락 관절별로 필터 버퍼를 관리하는 Dictionary 추가 (GC 최적화: 배열 재사용)
private Dictionary<int, float[]> fingerFilterBuffers = new Dictionary<int, float[]>();
private Dictionary<int, int> fingerFilterIndices = new Dictionary<int, int>();
// CopyFingerPoseByMuscle에서 사용할 본 Transform 저장용 (메모리 재사용)
private Dictionary<HumanBodyBones, (Vector3 position, Quaternion rotation)> savedBoneTransforms =
@ -487,6 +492,30 @@ namespace KindRetargeting
}
}
/// <summary>
/// 소스 포즈를 캐싱하여 반환합니다. 프레임당 한 번만 GetHumanPose 호출.
/// </summary>
private void EnsureSourcePoseCached()
{
if (!isSourcePoseCachedThisFrame && sourcePoseHandler != null)
{
sourcePoseHandler.GetHumanPose(ref sourcePose);
isSourcePoseCachedThisFrame = true;
}
}
/// <summary>
/// 타겟 포즈를 캐싱하여 반환합니다. 프레임당 한 번만 GetHumanPose 호출.
/// </summary>
private void EnsureTargetPoseCached()
{
if (!isTargetPoseCachedThisFrame && targetPoseHandler != null)
{
targetPoseHandler.GetHumanPose(ref targetPose);
isTargetPoseCachedThisFrame = true;
}
}
/// <summary>
/// 원본과 대상 아바타의 각 본 간 회전 오프셋을 계산하여 저장합니다.
/// </summary>
@ -1144,8 +1173,8 @@ namespace KindRetargeting
{
int stretchedMuscleIndex = muscleIndices.stretchedMuscle;
// 소스 아바타의 현재 머슬 값 가져오기
sourcePoseHandler.GetHumanPose(ref sourcePose);
// 소스 아바타의 현재 머슬 값 가져오기 (최적화: 캐싱된 포즈 사용)
EnsureSourcePoseCached();
float currentMuscle = sourcePose.muscles[stretchedMuscleIndex];
// 캘리브레이션 데이터가 있으면 정규화
@ -1295,6 +1324,10 @@ namespace KindRetargeting
/// </summary>
void Update()
{
// 최적화: 프레임 시작 시 포즈 캐시 플래그 리셋
isSourcePoseCachedThisFrame = false;
isTargetPoseCachedThisFrame = false;
// 포즈 복사 및 동기화
CopyPoseToTarget();
@ -1342,9 +1375,9 @@ namespace KindRetargeting
}
}
// 2. 머슬 데이터 업데이트
sourcePoseHandler.GetHumanPose(ref sourcePose);
targetPoseHandler.GetHumanPose(ref targetPose);
// 2. 머슬 데이터 업데이트 (최적화: 캐싱된 포즈 사용)
EnsureSourcePoseCached();
EnsureTargetPoseCached();
for (int i = 0; i < 40; i++)
{
@ -1388,37 +1421,38 @@ namespace KindRetargeting
private float ApplyFilter(float value, int fingerIndex)
{
// 해당 손가락 관절의 필터 버퍼가 없으면 생성
if (!fingerFilterBuffers.ContainsKey(fingerIndex))
// 해당 손가락 관절의 필터 버퍼가 없으면 생성 (GC 최적화: 배열 재사용)
if (!fingerFilterBuffers.TryGetValue(fingerIndex, out float[] buffer))
{
fingerFilterBuffers[fingerIndex] = new Queue<float>();
buffer = new float[filterBufferSize];
fingerFilterBuffers[fingerIndex] = buffer;
fingerFilterIndices[fingerIndex] = 0;
// 초기값으로 버퍼를 채움
for (int i = 0; i < filterBufferSize; i++)
{
fingerFilterBuffers[fingerIndex].Enqueue(value);
buffer[i] = value;
}
}
var buffer = fingerFilterBuffers[fingerIndex];
// 현재 인덱스 가져오기
int currentIndex = fingerFilterIndices[fingerIndex];
// 가장 오래된 값 제거
if (buffer.Count >= filterBufferSize)
{
buffer.Dequeue();
}
// 새 값을 현재 위치에 덮어쓰기 (순환 버퍼)
buffer[currentIndex] = value;
// 새 값 추가
buffer.Enqueue(value);
// 다음 인덱스로 이동 (순환)
fingerFilterIndices[fingerIndex] = (currentIndex + 1) % filterBufferSize;
// 가중 평균 계산
// 가중 평균 계산 (배열 직접 순회, ToArray 제거)
float sum = 0f;
float weight = 1f;
float totalWeight = 0f;
float[] values = buffer.ToArray();
for (int i = 0; i < values.Length; i++)
// 가장 오래된 값부터 순회 (currentIndex+1이 가장 오래된 값)
for (int i = 0; i < filterBufferSize; i++)
{
sum += values[i] * weight;
int idx = (currentIndex + 1 + i) % filterBufferSize;
sum += buffer[idx] * weight;
totalWeight += weight;
weight *= 1.5f; // 최근 값에 더 높은 가중치 부여
}
@ -1495,8 +1529,8 @@ namespace KindRetargeting
}
}
// 2. 타겟 포즈 가져오기
targetPoseHandler.GetHumanPose(ref targetPose);
// 2. 타겟 포즈 가져오기 (최적화: 캐싱된 포즈 사용)
EnsureTargetPoseCached();
// 3. 각 손가락 본에 대해 정규화된 값을 머슬에 적용
for (int boneIndex = 24; boneIndex <= 53; boneIndex++)

View File

@ -89,15 +89,8 @@ namespace KindRetargeting
if (sourceBone != null && targetBone != null)
{
Quaternion offset = Quaternion.Inverse(sourceBone.rotation) * targetBone.rotation;
if (rotationOffsets.ContainsKey(bone))
{
rotationOffsets[bone] = offset;
}
else
{
rotationOffsets.Add(bone, offset);
}
// 최적화: ContainsKey + 인덱서 이중 조회 대신 인덱서 직접 사용
rotationOffsets[bone] = offset;
}
}
@ -111,11 +104,8 @@ namespace KindRetargeting
/// <returns>회전 오프셋 (없으면 Identity)</returns>
public Quaternion GetRotationOffset(HumanBodyBones bone)
{
if (rotationOffsets.ContainsKey(bone))
{
return rotationOffsets[bone];
}
return Quaternion.identity;
// 최적화: TryGetValue로 단일 조회
return rotationOffsets.TryGetValue(bone, out Quaternion offset) ? offset : Quaternion.identity;
}
/// <summary>
@ -126,11 +116,10 @@ namespace KindRetargeting
/// <returns>오프셋이 적용된 회전값</returns>
public Quaternion ApplyRotationOffset(HumanBodyBones bone, Quaternion originalRotation)
{
if (rotationOffsets.ContainsKey(bone))
{
return originalRotation * rotationOffsets[bone];
}
return originalRotation;
// 최적화: TryGetValue로 단일 조회
return rotationOffsets.TryGetValue(bone, out Quaternion offset)
? originalRotation * offset
: originalRotation;
}
#endregion

View File

@ -1,8 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using System.Linq;
namespace KindRetargeting
{
@ -33,29 +29,35 @@ namespace KindRetargeting
private Transform rightShoulder;
private Transform leftUpperArm;
private Transform rightUpperArm;
// 최적화: 팔꿈치(LowerArm) Transform 캐싱 추가
private Transform leftLowerArm;
private Transform rightLowerArm;
private void Start()
{
retargetingScript = GetComponent<CustomRetargetingScript>();
leftShoulder = retargetingScript.targetAnimator.GetBoneTransform(HumanBodyBones.LeftShoulder);
rightShoulder = retargetingScript.targetAnimator.GetBoneTransform(HumanBodyBones.RightShoulder);
leftUpperArm = retargetingScript.targetAnimator.GetBoneTransform(HumanBodyBones.LeftUpperArm);
rightUpperArm = retargetingScript.targetAnimator.GetBoneTransform(HumanBodyBones.RightUpperArm);
// 최적화: 팔꿈치 Transform도 Start에서 캐싱
leftLowerArm = retargetingScript.targetAnimator.GetBoneTransform(HumanBodyBones.LeftLowerArm);
rightLowerArm = retargetingScript.targetAnimator.GetBoneTransform(HumanBodyBones.RightLowerArm);
}
private void Update()
{
// 왼쪽 어깨 보정
Vector3 leftElbowPos = retargetingScript.targetAnimator.GetBoneTransform(HumanBodyBones.LeftLowerArm).position;
// 왼쪽 어깨 보정 (최적화: 캐싱된 Transform 사용)
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 = retargetingScript.targetAnimator.GetBoneTransform(HumanBodyBones.RightLowerArm).position;
// 오른쪽 어깨 보정 (최적화: 캐싱된 Transform 사용)
Vector3 rightElbowPos = rightLowerArm.position;
float rightHeightDiff = rightElbowPos.y - rightShoulder.position.y;
float rightRawBlend = Mathf.Clamp01(
Mathf.InverseLerp(minHeightDifference, maxHeightDifference, rightHeightDiff) * blendStrength