Add : 아바타 로컬로테이션 변조 툴 1차 업데이트

This commit is contained in:
KINDNICK 2025-07-26 10:31:01 +09:00
parent a114b8cce5
commit 61763d81d0
9 changed files with 890 additions and 4 deletions

Binary file not shown.

BIN
Assets/Motion/T-Pose_20250726_024111.json (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d52d79965c0c87f4dbc7d4ea99597abe
guid: 963c54edbba1b4b45bed1b3a96506b08
TextScriptImporter:
externalObjects: {}
userData:

BIN
Assets/Scenes/PoseRotationBaker.unity (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4aa63adf1196d0743ac835945bd69261
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,373 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System;
namespace Streamingle.Editor
{
public class PoseRecorderWindow : EditorWindow
{
private GameObject selectedAvatar;
private Animator avatarAnimator;
private string poseName = "NewPose";
private string savePath = "Assets/Recordings/";
// 휴먼본 본들 (Animator HumanBodyBones 사용)
private readonly HumanBodyBones[] humanBodyBones = {
HumanBodyBones.Hips,
HumanBodyBones.Spine,
HumanBodyBones.Chest,
HumanBodyBones.UpperChest,
HumanBodyBones.Neck,
HumanBodyBones.Head,
HumanBodyBones.LeftShoulder,
HumanBodyBones.LeftUpperArm,
HumanBodyBones.LeftLowerArm,
HumanBodyBones.LeftHand,
HumanBodyBones.RightShoulder,
HumanBodyBones.RightUpperArm,
HumanBodyBones.RightLowerArm,
HumanBodyBones.RightHand,
HumanBodyBones.LeftUpperLeg,
HumanBodyBones.LeftLowerLeg,
HumanBodyBones.LeftFoot,
HumanBodyBones.LeftToes,
HumanBodyBones.RightUpperLeg,
HumanBodyBones.RightLowerLeg,
HumanBodyBones.RightFoot,
HumanBodyBones.RightToes
};
// 손가락 본들
private readonly HumanBodyBones[] fingerBones = {
// 왼손 손가락
HumanBodyBones.LeftThumbProximal,
HumanBodyBones.LeftThumbIntermediate,
HumanBodyBones.LeftThumbDistal,
HumanBodyBones.LeftIndexProximal,
HumanBodyBones.LeftIndexIntermediate,
HumanBodyBones.LeftIndexDistal,
HumanBodyBones.LeftMiddleProximal,
HumanBodyBones.LeftMiddleIntermediate,
HumanBodyBones.LeftMiddleDistal,
HumanBodyBones.LeftRingProximal,
HumanBodyBones.LeftRingIntermediate,
HumanBodyBones.LeftRingDistal,
HumanBodyBones.LeftLittleProximal,
HumanBodyBones.LeftLittleIntermediate,
HumanBodyBones.LeftLittleDistal,
// 오른손 손가락
HumanBodyBones.RightThumbProximal,
HumanBodyBones.RightThumbIntermediate,
HumanBodyBones.RightThumbDistal,
HumanBodyBones.RightIndexProximal,
HumanBodyBones.RightIndexIntermediate,
HumanBodyBones.RightIndexDistal,
HumanBodyBones.RightMiddleProximal,
HumanBodyBones.RightMiddleIntermediate,
HumanBodyBones.RightMiddleDistal,
HumanBodyBones.RightRingProximal,
HumanBodyBones.RightRingIntermediate,
HumanBodyBones.RightRingDistal,
HumanBodyBones.RightLittleProximal,
HumanBodyBones.RightLittleIntermediate,
HumanBodyBones.RightLittleDistal
};
[MenuItem("Tools/Pose Recorder")]
public static void ShowWindow()
{
GetWindow<PoseRecorderWindow>("포즈 기록기");
}
private void OnEnable()
{
// 선택된 GameObject가 아바타인지 확인
if (Selection.activeGameObject != null)
{
selectedAvatar = Selection.activeGameObject;
avatarAnimator = selectedAvatar.GetComponent<Animator>();
if (avatarAnimator == null)
{
avatarAnimator = selectedAvatar.GetComponentInChildren<Animator>();
}
}
}
private Vector2 scrollPosition;
private void OnGUI()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
GUILayout.Label("아바타 포즈 기록기", EditorStyles.boldLabel);
EditorGUILayout.Space();
// 아바타 선택
EditorGUILayout.BeginHorizontal();
GUILayout.Label("아바타:", GUILayout.Width(60));
selectedAvatar = (GameObject)EditorGUILayout.ObjectField(selectedAvatar, typeof(GameObject), true);
EditorGUILayout.EndHorizontal();
if (selectedAvatar != null)
{
avatarAnimator = selectedAvatar.GetComponent<Animator>();
if (avatarAnimator == null)
{
avatarAnimator = selectedAvatar.GetComponentInChildren<Animator>();
}
if (avatarAnimator == null)
{
EditorGUILayout.HelpBox("선택된 오브젝트에 Animator가 없습니다!", MessageType.Error);
EditorGUILayout.EndScrollView();
return;
}
EditorGUILayout.HelpBox($"아바타: {selectedAvatar.name}", MessageType.Info);
}
else
{
EditorGUILayout.HelpBox("아바타를 선택해주세요.", MessageType.Warning);
}
EditorGUILayout.Space();
// 포즈 이름 입력
EditorGUILayout.BeginHorizontal();
GUILayout.Label("포즈 이름:", GUILayout.Width(60));
poseName = EditorGUILayout.TextField(poseName);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
// 저장 경로 설정
EditorGUILayout.BeginHorizontal();
GUILayout.Label("저장 경로:", GUILayout.Width(60));
savePath = EditorGUILayout.TextField(savePath);
if (GUILayout.Button("폴더 선택", GUILayout.Width(80)))
{
string newPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", savePath, "");
if (!string.IsNullOrEmpty(newPath))
{
// Assets 폴더 내부로 경로 변환
if (newPath.StartsWith(Application.dataPath))
{
savePath = "Assets" + newPath.Substring(Application.dataPath.Length) + "/";
}
else
{
savePath = "Assets/Recordings/";
}
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
// 버튼들
EditorGUI.BeginDisabledGroup(selectedAvatar == null || avatarAnimator == null);
if (GUILayout.Button("현재 포즈 기록 (전체)", GUILayout.Height(30)))
{
RecordCurrentPose(true);
}
if (GUILayout.Button("현재 포즈 기록 (본체만)", GUILayout.Height(30)))
{
RecordCurrentPose(false);
}
EditorGUILayout.Space();
if (GUILayout.Button("T-Pose 기록", GUILayout.Height(30)))
{
RecordTPose();
}
if (GUILayout.Button("A-Pose 기록", GUILayout.Height(30)))
{
RecordAPose();
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
// 정보 표시
if (selectedAvatar != null && avatarAnimator != null)
{
EditorGUILayout.LabelField("기록 가능한 본들:", EditorStyles.boldLabel);
// 본체 본들
EditorGUILayout.LabelField("본체 본들:", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
int foundBodyBones = 0;
foreach (HumanBodyBones humanBone in humanBodyBones)
{
Transform bone = avatarAnimator.GetBoneTransform(humanBone);
if (bone != null)
{
EditorGUILayout.LabelField($"✓ {humanBone}");
foundBodyBones++;
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.LabelField($"본체 본: {foundBodyBones}개");
EditorGUILayout.Space();
// 손가락 본들
EditorGUILayout.LabelField("손가락 본들:", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
int foundFingerBones = 0;
foreach (HumanBodyBones fingerBone in fingerBones)
{
Transform bone = avatarAnimator.GetBoneTransform(fingerBone);
if (bone != null)
{
EditorGUILayout.LabelField($"✓ {fingerBone}");
foundFingerBones++;
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.LabelField($"손가락 본: {foundFingerBones}개");
EditorGUILayout.Space();
EditorGUILayout.LabelField($"총 {foundBodyBones + foundFingerBones}개 본 발견", EditorStyles.boldLabel);
}
EditorGUILayout.EndScrollView();
}
private void RecordCurrentPose(bool includeFingers = true)
{
if (selectedAvatar == null || avatarAnimator == null)
{
EditorUtility.DisplayDialog("오류", "아바타를 선택해주세요.", "확인");
return;
}
PoseData poseData = new PoseData();
poseData.avatarName = selectedAvatar.name;
poseData.poseName = poseName;
poseData.recordedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
int recordedBones = 0;
// 본체 본들 기록
foreach (HumanBodyBones humanBone in humanBodyBones)
{
Transform bone = avatarAnimator.GetBoneTransform(humanBone);
if (bone != null)
{
BonePoseData boneData = new BonePoseData();
boneData.boneName = humanBone.ToString();
boneData.localPosition = bone.localPosition;
boneData.localRotation = bone.localEulerAngles;
boneData.localScale = bone.localScale;
poseData.bonePoses.Add(boneData);
recordedBones++;
}
}
// 손가락 본들 기록 (옵션)
if (includeFingers)
{
int fingerBonesCount = 0;
foreach (HumanBodyBones fingerBone in fingerBones)
{
Transform bone = avatarAnimator.GetBoneTransform(fingerBone);
if (bone != null)
{
BonePoseData boneData = new BonePoseData();
boneData.boneName = fingerBone.ToString();
boneData.localPosition = bone.localPosition;
boneData.localRotation = bone.localEulerAngles;
boneData.localScale = bone.localScale;
poseData.bonePoses.Add(boneData);
recordedBones++;
fingerBonesCount++;
}
}
if (fingerBonesCount > 0)
{
UnityEngine.Debug.Log($"손가락 본 {fingerBonesCount}개 기록됨");
}
}
if (recordedBones > 0)
{
SavePoseData(poseData);
string message = includeFingers ?
$"포즈가 기록되었습니다!\n본체 본: {recordedBones - fingerBones.Length}개\n손가락 본: {fingerBones.Length}개\n총 {recordedBones}개 본" :
$"포즈가 기록되었습니다!\n본체 본: {recordedBones}개";
EditorUtility.DisplayDialog("성공", message, "확인");
}
else
{
EditorUtility.DisplayDialog("오류", "기록할 본을 찾을 수 없습니다.", "확인");
}
}
private void RecordTPose()
{
poseName = "T-Pose";
RecordCurrentPose(true); // 손가락 포함
}
private void RecordAPose()
{
poseName = "A-Pose";
RecordCurrentPose(true); // 손가락 포함
}
private void SavePoseData(PoseData poseData)
{
// 저장 폴더 생성
if (!Directory.Exists(savePath))
{
Directory.CreateDirectory(savePath);
}
// 파일명 생성
string fileName = $"{poseData.poseName}_{DateTime.Now:yyyyMMdd_HHmmss}.json";
string filePath = Path.Combine(savePath, fileName);
// JSON으로 저장
string json = JsonUtility.ToJson(poseData, true);
File.WriteAllText(filePath, json);
// Unity 에디터에서 파일 새로고침
AssetDatabase.Refresh();
UnityEngine.Debug.Log($"포즈가 저장되었습니다: {filePath}");
}
}
[System.Serializable]
public class BonePoseData
{
public string boneName;
public Vector3 localPosition;
public Vector3 localRotation;
public Vector3 localScale;
}
[System.Serializable]
public class PoseData
{
public string avatarName;
public string poseName;
public string recordedTime;
public List<BonePoseData> bonePoses = new List<BonePoseData>();
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 03b437e4b8ab0fa4191196f14605c61c

View File

@ -0,0 +1,499 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System;
namespace Streamingle.Editor
{
public class PoseRotationBaker : EditorWindow
{
private GameObject selectedAvatar;
private Animator avatarAnimator;
private string jsonFilePath = "";
private PoseData loadedPoseData;
private Vector2 scrollPosition;
// 베이크 옵션
private bool applyToAllBones = true;
private bool applyToSpecificBones = false;
private List<string> specificBones = new List<string>();
// 미리보기 옵션
private bool showBoneInfo = true;
[MenuItem("Tools/Pose Rotation Baker")]
public static void ShowWindow()
{
GetWindow<PoseRotationBaker>("포즈 로테이션 베이커");
}
private void OnEnable()
{
if (Selection.activeGameObject != null)
{
selectedAvatar = Selection.activeGameObject;
avatarAnimator = selectedAvatar.GetComponent<Animator>();
if (avatarAnimator == null)
{
avatarAnimator = selectedAvatar.GetComponentInChildren<Animator>();
}
}
}
private void OnGUI()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
GUILayout.Label("포즈 로테이션 베이커", EditorStyles.boldLabel);
EditorGUILayout.Space();
// 아바타 선택
EditorGUILayout.BeginHorizontal();
GUILayout.Label("아바타:", GUILayout.Width(60));
selectedAvatar = (GameObject)EditorGUILayout.ObjectField(selectedAvatar, typeof(GameObject), true);
EditorGUILayout.EndHorizontal();
if (selectedAvatar != null)
{
avatarAnimator = selectedAvatar.GetComponent<Animator>();
if (avatarAnimator == null)
{
avatarAnimator = selectedAvatar.GetComponentInChildren<Animator>();
}
if (avatarAnimator == null)
{
EditorGUILayout.HelpBox("선택된 오브젝트에 Animator가 없습니다!", MessageType.Error);
EditorGUILayout.EndScrollView();
return;
}
EditorGUILayout.HelpBox($"아바타: {selectedAvatar.name}", MessageType.Info);
}
else
{
EditorGUILayout.HelpBox("아바타를 선택해주세요.", MessageType.Warning);
}
EditorGUILayout.Space();
// JSON 파일 로드
EditorGUILayout.LabelField("포즈 데이터 로드:", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
GUILayout.Label("JSON 파일:", GUILayout.Width(60));
jsonFilePath = EditorGUILayout.TextField(jsonFilePath);
if (GUILayout.Button("파일 선택", GUILayout.Width(80)))
{
string path = EditorUtility.OpenFilePanel("포즈 JSON 파일 선택", "", "json");
if (!string.IsNullOrEmpty(path))
{
jsonFilePath = path;
LoadPoseData();
}
}
EditorGUILayout.EndHorizontal();
if (loadedPoseData != null)
{
EditorGUILayout.HelpBox($"로드된 포즈: {loadedPoseData.poseName}\n아바타: {loadedPoseData.avatarName}\n본 개수: {loadedPoseData.bonePoses.Count}개", MessageType.Info);
}
EditorGUILayout.Space();
// 베이크 옵션
EditorGUILayout.LabelField("베이크 옵션:", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
applyToAllBones = EditorGUILayout.Toggle("모든 본에 적용", applyToAllBones);
applyToSpecificBones = EditorGUILayout.Toggle("특정 본만 적용", applyToSpecificBones);
if (applyToSpecificBones)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("적용할 본들:");
EditorGUILayout.BeginVertical("box");
if (loadedPoseData != null)
{
foreach (var bonePose in loadedPoseData.bonePoses)
{
bool isSelected = specificBones.Contains(bonePose.boneName);
bool newSelection = EditorGUILayout.Toggle(bonePose.boneName, isSelected);
if (newSelection != isSelected)
{
if (newSelection)
{
specificBones.Add(bonePose.boneName);
}
else
{
specificBones.Remove(bonePose.boneName);
}
}
}
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
// 미리보기 옵션
EditorGUILayout.LabelField("미리보기 옵션:", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
showBoneInfo = EditorGUILayout.Toggle("본 정보 표시", showBoneInfo);
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
// 버튼들
EditorGUI.BeginDisabledGroup(selectedAvatar == null || avatarAnimator == null || loadedPoseData == null);
if (GUILayout.Button("본 회전 베이크 (메쉬 고정)", GUILayout.Height(30)))
{
BakeWithRiggingData();
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
// 정보 표시
if (loadedPoseData != null && showBoneInfo)
{
EditorGUILayout.LabelField("포즈 데이터 정보:", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
foreach (var bonePose in loadedPoseData.bonePoses)
{
Transform bone = FindBoneByName(selectedAvatar, bonePose.boneName);
if (bone != null)
{
EditorGUILayout.LabelField($"✓ {bonePose.boneName}");
EditorGUILayout.LabelField($" 회전: {bonePose.localRotation}");
}
else
{
EditorGUILayout.LabelField($"✗ {bonePose.boneName} (찾을 수 없음)");
}
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndScrollView();
}
private void LoadPoseData()
{
if (string.IsNullOrEmpty(jsonFilePath) || !File.Exists(jsonFilePath))
{
EditorUtility.DisplayDialog("오류", "JSON 파일을 찾을 수 없습니다.", "확인");
return;
}
try
{
string json = File.ReadAllText(jsonFilePath);
loadedPoseData = JsonUtility.FromJson<PoseData>(json);
if (loadedPoseData != null && loadedPoseData.bonePoses.Count > 0)
{
UnityEngine.Debug.Log($"포즈 데이터 로드 완료: {loadedPoseData.poseName}, {loadedPoseData.bonePoses.Count}개 본");
}
else
{
EditorUtility.DisplayDialog("오류", "유효하지 않은 포즈 데이터입니다.", "확인");
loadedPoseData = null;
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("오류", $"파일 로드 중 오류 발생: {e.Message}", "확인");
loadedPoseData = null;
}
}
private Transform FindBoneByName(GameObject targetAvatar, string boneName)
{
if (targetAvatar == null) return null;
// HumanBodyBones enum으로 찾기
if (System.Enum.TryParse(boneName, out HumanBodyBones humanBone))
{
Animator targetAnimator = targetAvatar.GetComponent<Animator>();
if (targetAnimator == null)
{
targetAnimator = targetAvatar.GetComponentInChildren<Animator>();
}
return targetAnimator?.GetBoneTransform(humanBone);
}
// 이름으로 직접 찾기
Transform bone = targetAvatar.transform.Find(boneName);
if (bone == null)
{
bone = FindChildRecursively(targetAvatar.transform, boneName);
}
return bone;
}
private Transform FindChildRecursively(Transform parent, string childName)
{
foreach (Transform child in parent)
{
if (child.name == childName)
return child;
Transform result = FindChildRecursively(child, childName);
if (result != null)
return result;
}
return null;
}
// 본 회전 베이크 (메쉬 고정)
private void BakeWithRiggingData()
{
if (loadedPoseData == null || selectedAvatar == null)
{
EditorUtility.DisplayDialog("오류", "포즈 데이터와 아바타를 선택해주세요.", "확인");
return;
}
if (!EditorUtility.DisplayDialog("본 회전 베이크",
"본의 로테이션을 변경하되 메쉬는 원래 모양을 유지합니다.\n\n" +
"본은 회전하지만 스킨메쉬렌더러는 변형되지 않습니다.", "베이크 실행", "취소"))
return;
try
{
// 1단계: 아바타 복사본 생성
GameObject originalAvatar = selectedAvatar;
GameObject copiedAvatar = Instantiate(originalAvatar);
copiedAvatar.name = originalAvatar.name + "_Baked_" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
// 복사본을 씬에 추가
copiedAvatar.transform.SetParent(originalAvatar.transform.parent);
copiedAvatar.transform.position = originalAvatar.transform.position;
copiedAvatar.transform.rotation = originalAvatar.transform.rotation;
copiedAvatar.transform.localScale = originalAvatar.transform.localScale;
// 2단계: 모든 스킨메쉬렌더러 찾기
SkinnedMeshRenderer[] allSkinnedMeshes = copiedAvatar.GetComponentsInChildren<SkinnedMeshRenderer>();
if (allSkinnedMeshes.Length == 0)
{
EditorUtility.DisplayDialog("오류", "스킨메쉬렌더러를 찾을 수 없습니다.", "확인");
DestroyImmediate(copiedAvatar);
return;
}
UnityEngine.Debug.Log($"복사본 아바타: {copiedAvatar.name}, 스킨메쉬 개수: {allSkinnedMeshes.Length}개");
// 각 스킨메쉬렌더러별 메쉬 복사본 생성
Dictionary<SkinnedMeshRenderer, Mesh> copiedMeshes = new Dictionary<SkinnedMeshRenderer, Mesh>();
Dictionary<SkinnedMeshRenderer, Matrix4x4[]> storedBindPoses = new Dictionary<SkinnedMeshRenderer, Matrix4x4[]>();
Dictionary<SkinnedMeshRenderer, Transform[]> storedBones = new Dictionary<SkinnedMeshRenderer, Transform[]>();
foreach (SkinnedMeshRenderer skinnedMesh in allSkinnedMeshes)
{
UnityEngine.Debug.Log($"스킨메쉬 처리 중: {skinnedMesh.name}");
// 메쉬 복사본 생성
Mesh originalMesh = skinnedMesh.sharedMesh;
Mesh copiedMesh = Instantiate(originalMesh);
copiedMesh.name = originalMesh.name + "_Baked_" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
// 복사된 메쉬를 스킨메쉬렌더러에 할당
skinnedMesh.sharedMesh = copiedMesh;
copiedMeshes[skinnedMesh] = copiedMesh;
// 원래 바인드포즈와 본 정보 저장
Matrix4x4[] bindPoses = copiedMesh.bindposes;
Transform[] bones = skinnedMesh.bones;
storedBindPoses[skinnedMesh] = bindPoses;
storedBones[skinnedMesh] = bones;
UnityEngine.Debug.Log($"메쉬 복사 완료: {copiedMesh.name}, 바인드포즈: {bindPoses.Length}개, 본: {bones.Length}개");
}
// 4단계: 본의 원래 상태 기록
Dictionary<Transform, Transform> originalParents = new Dictionary<Transform, Transform>();
Dictionary<Transform, Vector3> originalBoneRotations = new Dictionary<Transform, Vector3>();
List<Transform> allBones = GetAllBones(copiedAvatar);
UnityEngine.Debug.Log($"본 상태 기록 시작: {allBones.Count}개 본");
foreach (Transform bone in allBones)
{
originalParents[bone] = bone.parent;
originalBoneRotations[bone] = bone.localEulerAngles;
bone.SetParent(copiedAvatar.transform); // 모든 본을 아바타의 직접 자식으로
}
// 5단계: JSON 데이터로 본 로테이션 적용
int appliedBones = 0;
foreach (var bonePose in loadedPoseData.bonePoses)
{
if (applyToSpecificBones && !specificBones.Contains(bonePose.boneName))
continue;
Transform bone = FindBoneByName(copiedAvatar, bonePose.boneName);
if (bone != null)
{
UnityEngine.Debug.Log($"본 로테이션 적용: {bonePose.boneName} - {bone.localEulerAngles} → {bonePose.localRotation}");
bone.localEulerAngles = bonePose.localRotation;
appliedBones++;
}
}
// 5.5단계: 본 변형을 메쉬에 강제 반영 (바인드포즈 보존)
UnityEngine.Debug.Log("본 변형을 메쉬에 강제 반영 시작");
foreach (SkinnedMeshRenderer skinnedMesh in allSkinnedMeshes)
{
// 1단계: 원본 메쉬의 바인드포즈와 본 가중치 백업
Mesh originalMesh = copiedMeshes[skinnedMesh];
Matrix4x4[] originalBindPoses = originalMesh.bindposes;
BoneWeight[] originalBoneWeights = originalMesh.boneWeights;
Transform[] originalBones = storedBones[skinnedMesh];
Matrix4x4[] originalStoredBindPoses = storedBindPoses[skinnedMesh];
UnityEngine.Debug.Log($"바인드포즈 백업 완료: {skinnedMesh.name}, 바인드포즈: {originalBindPoses.Length}개, 본가중치: {originalBoneWeights.Length}개");
// 2단계: BakeMesh로 본 변형 반영
Mesh tempMesh = new Mesh();
skinnedMesh.BakeMesh(tempMesh);
UnityEngine.Debug.Log($"BakeMesh 완료: {skinnedMesh.name}, 버텍스: {tempMesh.vertices.Length}개");
// 3단계: 바인드포즈 조정을 통한 메쉬 고정 (X축 -90도 보정 포함)
Matrix4x4[] newBindPoses = new Matrix4x4[originalBindPoses.Length];
// X축 -90도 회전 보정 행렬
Matrix4x4 correctionMatrix = Matrix4x4.Rotate(Quaternion.Euler(90f, 0f, 0f));
// 각 본의 회전 변화량을 바인드포즈에 반영
for (int boneIndex = 0; boneIndex < originalBones.Length; boneIndex++)
{
Transform bone = originalBones[boneIndex];
Matrix4x4 originalBindPose = originalStoredBindPoses[boneIndex];
// 현재 본의 Transform의 역행렬에 X축 -90도 보정 적용
Matrix4x4 currentBoneTransform = bone.localToWorldMatrix;
Matrix4x4 newBindPose = (correctionMatrix * currentBoneTransform).inverse;
newBindPoses[boneIndex] = newBindPose;
UnityEngine.Debug.Log($"바인드포즈 조정: {bone.name} (X축 -90도 보정 적용)");
}
// 원래 버텍스와 조정된 바인드포즈 사용
tempMesh.vertices = originalMesh.vertices;
tempMesh.bindposes = newBindPoses;
tempMesh.boneWeights = originalBoneWeights;
// 블렌드쉐입 데이터 보존
if (originalMesh.blendShapeCount > 0)
{
UnityEngine.Debug.Log($"블렌드쉐입 복사 시작: {originalMesh.blendShapeCount}개");
for (int i = 0; i < originalMesh.blendShapeCount; i++)
{
string shapeName = originalMesh.GetBlendShapeName(i);
int frameCount = originalMesh.GetBlendShapeFrameCount(i);
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
{
float frameWeight = originalMesh.GetBlendShapeFrameWeight(i, frameIndex);
Vector3[] deltaVertices = new Vector3[originalMesh.vertexCount];
Vector3[] deltaNormals = new Vector3[originalMesh.vertexCount];
Vector3[] deltaTangents = new Vector3[originalMesh.vertexCount];
originalMesh.GetBlendShapeFrameVertices(i, frameIndex, deltaVertices, deltaNormals, deltaTangents);
tempMesh.AddBlendShapeFrame(shapeName, frameWeight, deltaVertices, deltaNormals, deltaTangents);
}
}
UnityEngine.Debug.Log($"블렌드쉐입 복사 완료: {tempMesh.blendShapeCount}개");
}
// 4단계: SkinnedMeshRenderer에 조정된 메쉬 적용
tempMesh.RecalculateNormals();
tempMesh.RecalculateBounds();
// 조정된 메쉬를 SkinnedMeshRenderer에 적용
skinnedMesh.sharedMesh = tempMesh;
UnityEngine.Debug.Log($"메쉬 고정 완료: {skinnedMesh.name}, 바인드포즈 조정됨");
}
// 6단계: 완료 (5.5단계에서 모든 작업 완료)
UnityEngine.Debug.Log("모든 스킨메쉬 처리 완료");
// 7단계: 원래 계층 구조 복원
UnityEngine.Debug.Log("원래 계층 구조 복원 시작");
foreach (Transform bone in allBones)
{
if (originalParents.ContainsKey(bone))
{
bone.SetParent(originalParents[bone]);
}
}
EditorUtility.SetDirty(copiedAvatar);
EditorUtility.DisplayDialog("베이크 완료",
$"본 회전 베이크가 완료되었습니다!\n" +
$"적용된 본: {appliedBones}개\n" +
$"바인드포즈 조정된 메쉬: {allSkinnedMeshes.Length}개\n\n" +
$"복사본 아바타가 생성되었습니다: {copiedAvatar.name}\n\n" +
"본은 회전했지만 바인드포즈 조정으로 메쉬는 변형되지 않습니다.", "확인");
UnityEngine.Debug.Log($"본 회전 베이크 완료: {appliedBones}개 본");
// 생성된 아바타를 선택
Selection.activeGameObject = copiedAvatar;
}
catch (Exception e)
{
EditorUtility.DisplayDialog("오류", $"베이크 중 오류가 발생했습니다: {e.Message}", "확인");
UnityEngine.Debug.LogError($"베이크 오류: {e.Message}");
}
}
// 모든 본을 가져오는 헬퍼 메서드
private List<Transform> GetAllBones(GameObject avatar)
{
List<Transform> bones = new List<Transform>();
Transform[] allTransforms = avatar.GetComponentsInChildren<Transform>();
foreach (Transform t in allTransforms)
{
if (t != avatar.transform) // 아바타 루트는 제외
{
bones.Add(t);
}
}
return bones;
}
}
// 리깅 데이터 백업을 위한 컴포넌트
[System.Serializable]
public class RiggingDataBackup : MonoBehaviour
{
public BoneWeight[] boneWeights;
public Matrix4x4[] bindPoses;
public Transform[] originalBones;
public Vector3[] originalVertices;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 150464f3f9d1e5c49a568fbcada754b7