From 420301c4410c9780c83fa87c695188c7f4e923f1 Mon Sep 17 00:00:00 2001 From: "qsxft258@gmail.com" Date: Tue, 2 Dec 2025 18:35:08 +0900 Subject: [PATCH] =?UTF-8?q?fix=20;=20=EB=A6=AC=ED=83=80=EA=B2=9F=ED=8C=85?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomRetargetingScript.cs | 84 +++++++++++++------ .../Scripts/KindRetargeting/OffsetTransfer.cs | 27 ++---- .../ShoulderCorrectionFunction.cs | 20 +++-- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs index 502720b8..49520577 100644 --- a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs +++ b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs @@ -57,6 +57,10 @@ namespace KindRetargeting private HumanPose sourcePose; private HumanPose targetPose; + // 최적화: 프레임당 한 번만 GetHumanPose 호출하기 위한 플래그 + private bool isSourcePoseCachedThisFrame = false; + private bool isTargetPoseCachedThisFrame = false; + // 본별 회전 오프셋을 저장하는 딕셔너리 private Dictionary rotationOffsets = new Dictionary(); @@ -184,8 +188,9 @@ namespace KindRetargeting } } - // 각 손가락 관절별로 필터 버퍼를 관리하는 Dictionary 추가 - private Dictionary> fingerFilterBuffers = new Dictionary>(); + // 각 손가락 관절별로 필터 버퍼를 관리하는 Dictionary 추가 (GC 최적화: 배열 재사용) + private Dictionary fingerFilterBuffers = new Dictionary(); + private Dictionary fingerFilterIndices = new Dictionary(); // CopyFingerPoseByMuscle에서 사용할 본 Transform 저장용 (메모리 재사용) private Dictionary savedBoneTransforms = @@ -487,6 +492,30 @@ namespace KindRetargeting } } + /// + /// 소스 포즈를 캐싱하여 반환합니다. 프레임당 한 번만 GetHumanPose 호출. + /// + private void EnsureSourcePoseCached() + { + if (!isSourcePoseCachedThisFrame && sourcePoseHandler != null) + { + sourcePoseHandler.GetHumanPose(ref sourcePose); + isSourcePoseCachedThisFrame = true; + } + } + + /// + /// 타겟 포즈를 캐싱하여 반환합니다. 프레임당 한 번만 GetHumanPose 호출. + /// + private void EnsureTargetPoseCached() + { + if (!isTargetPoseCachedThisFrame && targetPoseHandler != null) + { + targetPoseHandler.GetHumanPose(ref targetPose); + isTargetPoseCachedThisFrame = true; + } + } + /// /// 원본과 대상 아바타의 각 본 간 회전 오프셋을 계산하여 저장합니다. /// @@ -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 /// 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(); + 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++) diff --git a/Assets/Scripts/KindRetargeting/OffsetTransfer.cs b/Assets/Scripts/KindRetargeting/OffsetTransfer.cs index 762d963b..09177035 100644 --- a/Assets/Scripts/KindRetargeting/OffsetTransfer.cs +++ b/Assets/Scripts/KindRetargeting/OffsetTransfer.cs @@ -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 /// 회전 오프셋 (없으면 Identity) 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; } /// @@ -126,11 +116,10 @@ namespace KindRetargeting /// 오프셋이 적용된 회전값 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 diff --git a/Assets/Scripts/KindRetargeting/ShoulderCorrectionFunction.cs b/Assets/Scripts/KindRetargeting/ShoulderCorrectionFunction.cs index 7fda08a2..daa61e9e 100644 --- a/Assets/Scripts/KindRetargeting/ShoulderCorrectionFunction.cs +++ b/Assets/Scripts/KindRetargeting/ShoulderCorrectionFunction.cs @@ -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(); - + 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