패키지 매니저에 FBX Exporter를 추가하면 사용 가능.
게임오브젝트 FBX로 내보내거나, FBX 파일에 메시가 여러 개 들어있을 때 한 개 씩 분리해야 할 일이있어서 만듬.
using UnityEngine;
using UnityEditor;
using UnityEditor.Formats.Fbx.Exporter;
using System.Collections.Generic;
using System.IO;
using System.Linq;
public class FbxExporterWindow : EditorWindow {
private GameObject tPoseMesh;
private GameObject exportObject;
private Avatar referenceAvatar;
private string exportPath;
private string avatarPath;
private const string exportPathKey = "FBXExporterExportPath";
private const string avatarPathKey = "FBXExporterAvatarPath";
[MenuItem("Window/Tools/FBX Exporter")]
public static void ShowWindow()
{
var w = GetWindow<FbxExporterWindow>(false, "FBX Exporter", true);
}
private void OnEnable()
{
exportPath = Path.Combine(Application.dataPath, "Exports");
exportPath = EditorPrefs.GetString(exportPathKey, exportPath);
avatarPath = Path.Combine(Application.dataPath, "AvatarPaths");
avatarPath = EditorPrefs.GetString(exportPathKey, exportPath);
}
private void OnGUI()
{
GUILayout.Label("Export GameObject to FBX", EditorStyles.boldLabel);
exportObject = (GameObject)EditorGUILayout.ObjectField("Export Object", exportObject, typeof(GameObject), true);
if (GUILayout.Button("Export GameObject to FBX")) {
ExportSelectedGameObject(exportObject);
}
if (GUILayout.Button("Extract and Export Individual Meshes")) {
ExtractAndExportMeshes(exportObject);
}
tPoseMesh = EditorGUILayout.ObjectField("T-Pose Mesh", tPoseMesh, typeof(GameObject), true) as GameObject;
if (GUILayout.Button("Export Combined Mesh And Bones")) {
ExportSkinnedMeshWithBones(tPoseMesh);
}
if (GUILayout.Button("Combine Meshes into T-Pose")) {
CombineMeshes(tPoseMesh);
}
GUILayout.Label("Configure Avatar for Selected Model", EditorStyles.boldLabel);
referenceAvatar = (Avatar)EditorGUILayout.ObjectField("Reference Avatar", referenceAvatar, typeof(Avatar), false);
if (GUILayout.Button("Apply Avatar to Folder")) {
ApplyAvatarToModelsInFolder(referenceAvatar);
}
// exportPath 값이 변경될 때마다 EditorPrefs에 저장합니다.
if (GUI.changed) {
EditorPrefs.SetString(exportPathKey, exportPath);
EditorPrefs.SetString(avatarPathKey, avatarPath);
}
}
private void ExportSelectedGameObject(GameObject obj)
{
if (obj == null) {
EditorUtility.DisplayDialog("Error", "Please assign a GameObject to export.", "OK");
return;
}
string filePath = EditorUtility.SaveFilePanel("Export .fbx file", exportPath, obj.name, "fbx");
if (string.IsNullOrEmpty(filePath)) {
return;
}
// 파일 저장 경로에서 폴더 경로만 추출
string folderPath = Path.GetDirectoryName(filePath);
// 해당 폴더가 존재하지 않으면 생성
if (!AssetDatabase.IsValidFolder(folderPath)) {
Directory.CreateDirectory(folderPath);
AssetDatabase.Refresh();
}
ModelExporter.ExportObject(filePath, obj);
}
private void ExtractAndExportMeshes(GameObject sourceFbx)
{
if (sourceFbx == null) {
EditorUtility.DisplayDialog("Error", "Please assign a GameObject to export.", "OK");
return;
}
var folderPath = EditorUtility.OpenFolderPanel("Choose Export Folder", exportPath, "");
if (string.IsNullOrEmpty(folderPath)) {
return;
}
Transform rootOriginal = sourceFbx.transform.FindDeepChild("Root");
if (rootOriginal == null) {
Debug.LogError("Root bone not found.");
return;
}
foreach (var smr in sourceFbx.GetComponentsInChildren<SkinnedMeshRenderer>(true)) {
var meshName = smr.sharedMesh.name;
var meshGameObject = new GameObject(meshName);
var root = Instantiate(rootOriginal);
root.name = "Root";
root.transform.SetParent(meshGameObject.transform, false);
SkinnedMeshRenderer newSmr = meshGameObject.AddComponent<SkinnedMeshRenderer>();
newSmr.sharedMesh = smr.sharedMesh;
//newSmr.sharedMaterials = smr.sharedMaterials;
List<Transform> bones = new List<Transform>();
foreach (var bone in smr.bones) {
bones.Add(root.FindDeepChild(bone.name));
}
newSmr.bones = bones.ToArray();
newSmr.rootBone = root.FindDeepChild(smr.rootBone.name);
string finalPath = Path.Combine(folderPath, meshName + ".fbx");
ModelExporter.ExportObject(finalPath, meshGameObject);
DestroyImmediate(meshGameObject);
}
AssetDatabase.Refresh();
}
private Mesh[] FindMeshesInSelectedFbx(GameObject sourceFbx)
{
List<Mesh> meshes = new List<Mesh>();
if (sourceFbx != null) {
// MeshFilter 컴포넌트가 있는 경우
MeshFilter[] meshFilters = sourceFbx.GetComponentsInChildren<MeshFilter>();
foreach (MeshFilter mf in meshFilters) {
if (mf.sharedMesh != null) {
meshes.Add(mf.sharedMesh);
}
}
// SkinnedMeshRenderer 컴포넌트가 있는 경우
SkinnedMeshRenderer[] skinnedMeshRenderers = sourceFbx.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (SkinnedMeshRenderer smr in skinnedMeshRenderers) {
if (smr.sharedMesh != null) {
meshes.Add(smr.sharedMesh);
}
}
}
return meshes.ToArray();
}
void ExportSkinnedMeshWithBones(GameObject skinnedMeshObj)
{
// 파일 저장 대화 상자를 통해 내보낼 FBX 파일의 경로를 지정합니다.
string filePath = EditorUtility.SaveFilePanel("Export Skinned Mesh with Bones", exportPath, skinnedMeshObj.name, "fbx");
if (string.IsNullOrEmpty(filePath)) {
Debug.LogError("Export path is empty. Cancelling export.");
return;
}
// 스킨드 메시를 복제합니다.
GameObject clonedSkinnedMeshObj = Instantiate(skinnedMeshObj);
clonedSkinnedMeshObj.name = skinnedMeshObj.name + "_Cloned";
// 본 구조를 복제합니다 (이는 복제할 스킨드 메시에 본이 포함된 경우에만 필요합니다).
Transform[] bones = clonedSkinnedMeshObj.GetComponentInChildren<SkinnedMeshRenderer>().bones;
Transform clonedRootBone = Instantiate(bones[0].root);
clonedRootBone.name = bones[0].root.name + "_Cloned";
// 복제된 스킨드 메시의 본을 복제된 본 구조로 업데이트합니다.
SkinnedMeshRenderer skinnedMeshRenderer = clonedSkinnedMeshObj.GetComponentInChildren<SkinnedMeshRenderer>();
skinnedMeshRenderer.rootBone = clonedRootBone;
skinnedMeshRenderer.bones = clonedRootBone.GetComponentsInChildren<Transform>();
// FBX로 내보냅니다.
var isSuccess = ModelExporter.ExportObject(filePath, clonedSkinnedMeshObj);
if (string.IsNullOrEmpty(isSuccess) == false) {
Debug.Log("Exported skinned mesh with bones to " + filePath);
} else {
Debug.LogError("Failed to export skinned mesh with bones.");
}
// 임시 객체를 삭제합니다.
DestroyImmediate(clonedSkinnedMeshObj);
DestroyImmediate(clonedRootBone.gameObject);
}
private void CombineMeshes(GameObject parentObject)
{
// 메시 필터와 스킨드 메시 렌더러에서 모든 메시를 가져옵니다.
MeshFilter[] meshFilters = parentObject.GetComponentsInChildren<MeshFilter>(true);
SkinnedMeshRenderer[] skinnedMeshRenderers = parentObject.GetComponentsInChildren<SkinnedMeshRenderer>(true);
// 결합 인스턴스의 배열을 초기화합니다.
List<CombineInstance> combine = new List<CombineInstance>();
// MeshFilter 컴포넌트에서 메시를 추가합니다.
foreach (var meshFilter in meshFilters) {
if (meshFilter.sharedMesh != null) {
CombineInstance ci = new CombineInstance();
ci.mesh = meshFilter.sharedMesh;
ci.transform = meshFilter.transform.localToWorldMatrix;
combine.Add(ci);
}
}
// SkinnedMeshRenderer 컴포넌트에서 메시를 추가합니다.
foreach (var skinnedMeshRenderer in skinnedMeshRenderers) {
if (skinnedMeshRenderer.sharedMesh != null) {
CombineInstance ci = new CombineInstance();
ci.mesh = skinnedMeshRenderer.sharedMesh;
ci.transform = skinnedMeshRenderer.transform.localToWorldMatrix;
combine.Add(ci);
}
}
// 새로운 GameObject를 생성하여 결합된 메시를 저장합니다.
GameObject combinedObject = new GameObject("Combined Mesh");
// 메시 필터 및 렌더러를 추가하고 메시를 결합합니다.
MeshFilter combinedMeshFilter = combinedObject.AddComponent<MeshFilter>();
combinedMeshFilter.mesh = new Mesh();
combinedMeshFilter.mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
combinedMeshFilter.mesh.CombineMeshes(combine.ToArray(), true, false);
// 메시 렌더러에 재질을 할당합니다.
MeshRenderer combinedMeshRenderer = combinedObject.AddComponent<MeshRenderer>();
// 예시: 첫 번째 MeshFilter 또는 SkinnedMeshRenderer의 재질을 사용합니다.
if (meshFilters.Length > 0 && meshFilters[0].GetComponent<MeshRenderer>()) {
combinedMeshRenderer.sharedMaterials = meshFilters[0].GetComponent<MeshRenderer>().sharedMaterials;
} else if (skinnedMeshRenderers.Length > 0) {
combinedMeshRenderer.sharedMaterials = skinnedMeshRenderers[0].sharedMaterials;
}
// 씬에 결합된 메시를 활성화합니다.
combinedObject.SetActive(true);
// 디버깅 메시지
Debug.Log($"Combined mesh has {combinedMeshFilter.mesh.vertexCount} vertices.");
AssetDatabase.Refresh();
}
private void ApplyAvatarToModelsInFolder(Avatar avatar)
{
if (avatar == null) {
EditorUtility.DisplayDialog("Error", "Please assign an avatar.", "OK");
return;
}
string folderPath = EditorUtility.OpenFolderPanel("Choose Model Folder", avatarPath, "");
var fullPath = Path.GetFullPath(folderPath);
if (!Directory.Exists(fullPath)) {
EditorUtility.DisplayDialog("Error", "The specified folder path does not exist.", "OK");
return;
}
var modelPaths = Directory.GetFiles(fullPath, "*.fbx", SearchOption.AllDirectories)
.Union(Directory.GetFiles(fullPath, "*.obj", SearchOption.AllDirectories));
foreach (var absoluteModelPath in modelPaths) {
var relativeModelPath = "Assets" + absoluteModelPath.Substring(Application.dataPath.Length).Replace('\\', '/');
// Debugging to see what the final path is
Debug.Log($"Trying to load model importer at path: {relativeModelPath}");
var modelImporter = AssetImporter.GetAtPath(relativeModelPath) as ModelImporter;
if (modelImporter) {
modelImporter.animationType = ModelImporterAnimationType.Human;
modelImporter.avatarSetup = ModelImporterAvatarSetup.CopyFromOther;
modelImporter.sourceAvatar = avatar;
modelImporter.SaveAndReimport();
Debug.Log($"Avatar applied to model: {Path.GetFileName(absoluteModelPath)}");
} else {
Debug.LogError($"Model importer could not be loaded at path: {relativeModelPath}");
}
}
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("Avatar Setup", "Avatar has been applied to all models in the specified folder.", "OK");
}
}
반응형
'Programming > Unity' 카테고리의 다른 글
Unity 프로젝트에서 .md 파일 관리하기: CsprojModifier를 이용한 자동화 솔루션 (0) | 2024.02.12 |
---|---|
newtonsoft Json 추가하기, TypeNameHandling 설정하기 (0) | 2023.08.07 |
Unity Addressable (0) | 2021.12.22 |
GraphicsRaycast (0) | 2019.12.31 |
TextMeshPro 한글 사용 (0) | 2018.09.30 |
유니티 에디터 모드 UI 샘플 메모 (0) | 2018.01.23 |
멀티 접속 테스트 (0) | 2018.01.14 |
유니티 프로젝트 목록 삭제 (0) | 2017.12.14 |