GLTFImporter 추가 #8
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(dir:*)",
|
||||
"Bash(powershell -Command:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.GLTF;
|
||||
|
||||
/// <summary>
|
||||
/// 샘플 장면 드라이버: 버튼 클릭으로 SHI BlockDetail 모달을 생성/표시하고,
|
||||
@@ -24,6 +25,18 @@ public class ShiPopupSample : MonoBehaviour
|
||||
|
||||
[SerializeField]
|
||||
private Button openNWModalButton;
|
||||
|
||||
[SerializeField]
|
||||
private Button loadGLTFMultiLODButton;
|
||||
|
||||
[SerializeField]
|
||||
private Button loadGLTFButton;
|
||||
|
||||
[SerializeField]
|
||||
private Transform lodRoot;
|
||||
|
||||
[SerializeField]
|
||||
private Transform lodRoot2;
|
||||
|
||||
private ISOPModal? isopModal;
|
||||
private NWModal? nwModal;
|
||||
@@ -65,6 +78,30 @@ public class ShiPopupSample : MonoBehaviour
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
if(loadGLTFMultiLODButton != null)
|
||||
{
|
||||
loadGLTFMultiLODButton.onClick.AddListener(() =>
|
||||
{
|
||||
string sa = Application.streamingAssetsPath;
|
||||
GLTFImporter.ImportWithLOD(new List<string> {
|
||||
Path.Combine(sa, "model_lod0.glb"),
|
||||
Path.Combine(sa, "model_lod1.glb"),
|
||||
Path.Combine(sa, "model_lod2.glb"),
|
||||
Path.Combine(sa, "model_lod3.glb"),
|
||||
}, lodRoot).Forget();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if(loadGLTFButton != null)
|
||||
{
|
||||
loadGLTFButton.onClick.AddListener(() =>
|
||||
{
|
||||
string sa = Application.streamingAssetsPath;
|
||||
GLTFImporter.ImportFromFile(Path.Combine(sa, "model_with_lod.glb"), lodRoot2).Forget();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +124,8 @@ public class ShiPopupSample : MonoBehaviour
|
||||
|
||||
Debug.Log($"Loaded blockDetailModal:{isopModal}");
|
||||
await isopModal.LoadData(new List<string> {
|
||||
Path.Combine(sa, "B11TC.glb"),
|
||||
Path.Combine(sa, "B16VC.glb"),
|
||||
Path.Combine(sa, "model_with_lod.glb"),
|
||||
Path.Combine(sa, "model_with_lod2.glb"),
|
||||
Path.Combine(sa, "E11VC.glb"),
|
||||
Path.Combine(sa, "E41UC.glb"),
|
||||
Path.Combine(sa, "M11UC.glb"),
|
||||
|
||||
@@ -119,6 +119,68 @@ NavMeshSettings:
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &279219350
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 279219351}
|
||||
m_Layer: 0
|
||||
m_Name: LODRoot
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &279219351
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 279219350}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &519133180
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 519133181}
|
||||
m_Layer: 0
|
||||
m_Name: LODRoot2
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &519133181
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519133180}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &701037396
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -289,6 +351,10 @@ MonoBehaviour:
|
||||
nwModalPrefab: {fileID: 4549899540058300928, guid: 5295f7b9a5b84ae4c8230c52ebdabef2, type: 3}
|
||||
openISOPModalButton: {fileID: 1154598113}
|
||||
openNWModalButton: {fileID: 2055194488}
|
||||
loadGLTFMultiLODButton: {fileID: 1610110302}
|
||||
loadGLTFButton: {fileID: 1860861870}
|
||||
lodRoot: {fileID: 279219351}
|
||||
lodRoot2: {fileID: 519133181}
|
||||
--- !u!4 &717482005
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -304,6 +370,142 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &860110276
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 860110277}
|
||||
- component: {fileID: 860110279}
|
||||
- component: {fileID: 860110278}
|
||||
m_Layer: 5
|
||||
m_Name: Text (TMP)
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &860110277
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 860110276}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1610110301}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &860110278
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 860110276}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: loadGLTFMultiLOD
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4281479730
|
||||
m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 14
|
||||
m_fontSizeBase: 14
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!222 &860110279
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 860110276}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1154598111
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -425,6 +627,142 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1154598111}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1299547236
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1299547237}
|
||||
- component: {fileID: 1299547239}
|
||||
- component: {fileID: 1299547238}
|
||||
m_Layer: 5
|
||||
m_Name: Text (TMP)
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1299547237
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1299547236}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1860861869}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1299547238
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1299547236}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: loadGLTFButton
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4281479730
|
||||
m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 14
|
||||
m_fontSizeBase: 14
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_TextWrappingMode: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_ActiveFontFeatures: 6e72656b
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_EmojiFallbackSupport: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!222 &1299547239
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1299547236}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1304768789
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -521,6 +859,8 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 1154598112}
|
||||
- {fileID: 2055194487}
|
||||
- {fileID: 1610110301}
|
||||
- {fileID: 1860861869}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
@@ -800,6 +1140,127 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1520929354}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1610110300
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1610110301}
|
||||
- component: {fileID: 1610110304}
|
||||
- component: {fileID: 1610110303}
|
||||
- component: {fileID: 1610110302}
|
||||
m_Layer: 5
|
||||
m_Name: loadGLTFMultiLODButton
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1610110301
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1610110300}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 860110277}
|
||||
m_Father: {fileID: 1304768793}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: -86.8}
|
||||
m_SizeDelta: {x: 160, y: 30}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1610110302
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1610110300}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 1610110303}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &1610110303
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1610110300}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!222 &1610110304
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1610110300}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1788944821
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -879,6 +1340,127 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1860861868
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1860861869}
|
||||
- component: {fileID: 1860861872}
|
||||
- component: {fileID: 1860861871}
|
||||
- component: {fileID: 1860861870}
|
||||
m_Layer: 5
|
||||
m_Name: loadGLTFButton
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1860861869
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1860861868}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1299547237}
|
||||
m_Father: {fileID: 1304768793}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: -125.6}
|
||||
m_SizeDelta: {x: 160, y: 30}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1860861870
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1860861868}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 1860861871}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &1860861871
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1860861868}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!222 &1860861872
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1860861868}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &1977662939
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1127,6 +1709,8 @@ SceneRoots:
|
||||
m_Roots:
|
||||
- {fileID: 701037399}
|
||||
- {fileID: 1977662941}
|
||||
- {fileID: 717482005}
|
||||
- {fileID: 1304768793}
|
||||
- {fileID: 1788944824}
|
||||
- {fileID: 717482005}
|
||||
- {fileID: 279219351}
|
||||
- {fileID: 519133181}
|
||||
|
||||
@@ -191,7 +191,8 @@ namespace SHI.Modal.ISOP
|
||||
|
||||
Debug.Log($"ISOPModelView.LoadModelAsync: Loading {path}");
|
||||
var gltf = new GltfImport();
|
||||
var success = await gltf.Load(path, new ImportSettings(), ct);
|
||||
var importSettings = new ImportSettings();
|
||||
var success = await gltf.Load(path, importSettings, ct);
|
||||
if (!success)
|
||||
{
|
||||
Debug.LogError($"glTFast Load failed: {path}");
|
||||
@@ -425,7 +426,7 @@ namespace SHI.Modal.ISOP
|
||||
if (_idToObject.TryGetValue(id, out var go))
|
||||
{
|
||||
Debug.Log($"Exporting object: {go.name}");
|
||||
UVC.GLTF.GLTFExporter.ExportNodeByExplorer(go);
|
||||
UVC.GLTF.Export.GLTFExporter.ExportNodeByExplorer(go);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -427,7 +427,7 @@ namespace SHI.Modal.NW
|
||||
{
|
||||
if (_idToObject.TryGetValue(id, out var go))
|
||||
{
|
||||
UVC.GLTF.GLTFExporter.ExportNodeByExplorer(go);
|
||||
UVC.GLTF.Export.GLTFExporter.ExportNodeByExplorer(go);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
8
Assets/Scripts/UVC/GLTF/Export.meta
Normal file
8
Assets/Scripts/UVC/GLTF/Export.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5f5aa5fcd3aa0a4eb2a746675279e9c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -27,7 +27,7 @@ using GltfPrimitive = GLTFast.Schema.MeshPrimitive;
|
||||
using GltfTexture = GLTFast.Schema.Texture;
|
||||
using GltfTextureInfo = GLTFast.Schema.TextureInfo;
|
||||
|
||||
namespace UVC.GLTF
|
||||
namespace UVC.GLTF.Export
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
137
Assets/Scripts/UVC/GLTF/GLTFImporter.cs
Normal file
137
Assets/Scripts/UVC/GLTF/GLTFImporter.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
#nullable enable
|
||||
using GLTFast;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UVC.GLTF
|
||||
{
|
||||
public static class GLTFImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// glTF/glb 파일을 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="filePath">파일 경로</param>
|
||||
/// <param name="parentTransform">부모 Transform</param>
|
||||
/// <param name="useMsftLod">MSFT_lod 확장을 LODGroup으로 변환할지 여부</param>
|
||||
public static async UniTask<GameObject?> ImportFromFile(string filePath, Transform parentTransform)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return null;
|
||||
|
||||
Debug.Log($"ImportFromFile {filePath}");
|
||||
var gltf = new GltfImport();
|
||||
var importSettings = new ImportSettings();
|
||||
|
||||
var success = await gltf.Load(filePath, importSettings);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Debug.LogError($"glTFast Load failed: {filePath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// MSFT_lod 사용 시 커스텀 인스턴시에이터 사용
|
||||
GameObjectInstantiator instantiator;
|
||||
// if (useMsftLod)
|
||||
// {
|
||||
// // glb/gltf 파일에서 MSFT_lod 정보 파싱
|
||||
// var lodInfos = MsftLodParser.ParseFromFile(filePath);
|
||||
// Debug.Log($"ImportFromFile: Found {lodInfos.Count} LOD definitions in {filePath}");
|
||||
|
||||
// var lodInstantiator = new LODGameObjectInstantiator(gltf, parentTransform);
|
||||
// lodInstantiator.SetLodInfo(lodInfos);
|
||||
// instantiator = lodInstantiator;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
instantiator = new GameObjectInstantiator(gltf, parentTransform);
|
||||
// }
|
||||
|
||||
var sceneOk = await gltf.InstantiateMainSceneAsync(instantiator);
|
||||
if (!sceneOk)
|
||||
{
|
||||
Debug.LogError($"InstantiateMainSceneAsync failed: {filePath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return instantiator.SceneTransform != null ? instantiator.SceneTransform.gameObject : null;
|
||||
}
|
||||
|
||||
public static async UniTask<GameObject?> ImportWithLOD(List<string> paths, Transform parentTransform)
|
||||
{
|
||||
if (paths == null || paths.Count == 0) return null;
|
||||
|
||||
// 1. 부모 객체 생성 (여기에 LODGroup 컴포넌트가 붙습니다)
|
||||
GameObject rootObject = new GameObject("Loaded_LOD_Model");
|
||||
rootObject.transform.position = Vector3.zero;
|
||||
|
||||
bool successAll = true;
|
||||
List<GameObject> loadedObjects = new List<GameObject>();
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) continue;
|
||||
|
||||
Debug.Log($"ImportFromWithLOD Loading {path}");
|
||||
var gltf = new GltfImport();
|
||||
var importSettings = new ImportSettings();
|
||||
var success = await gltf.Load(path, importSettings);
|
||||
if (!success)
|
||||
{
|
||||
Debug.LogError($"glTFast Load failed: {path}");
|
||||
successAll = false;
|
||||
break;
|
||||
}
|
||||
//path 이름만
|
||||
string lodName = System.IO.Path.GetFileNameWithoutExtension(path);
|
||||
GameObject gameObject = new GameObject(lodName);
|
||||
gameObject.transform.SetParent(rootObject.transform, false);
|
||||
var instantiator = new GameObjectInstantiator(gltf, gameObject.transform);
|
||||
var sceneOk = await gltf.InstantiateMainSceneAsync(instantiator);
|
||||
if (!sceneOk)
|
||||
{
|
||||
Debug.LogError($"InstantiateMainSceneAsync failed: {path}");
|
||||
successAll = false;
|
||||
break;
|
||||
}
|
||||
if(instantiator.SceneTransform != null){
|
||||
loadedObjects.Add(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (successAll)
|
||||
{
|
||||
// 3. LODGroup 설정
|
||||
var lodGroup = rootObject.AddComponent<LODGroup>();
|
||||
LOD[] lods = new LOD[loadedObjects.Count];
|
||||
float[] lodScreenPercentages = new float[] { 0.5f, 0.25f, 0.1f, 0f}; // 예시로 LOD 전환 비율 설정
|
||||
for (int i = 0; i < loadedObjects.Count; i++)
|
||||
{
|
||||
Renderer? renderer = loadedObjects[i].GetComponent<Renderer>();
|
||||
if(renderer == null)
|
||||
{
|
||||
renderer = loadedObjects[i].GetComponentInChildren<Renderer>();
|
||||
}
|
||||
|
||||
float screenRelativeTransitionHeight = lodScreenPercentages[Mathf.Min(i, lodScreenPercentages.Length - 1)]; // LOD 전환 비율 설정 사용
|
||||
Debug.Log($"{loadedObjects[i].name} LOD {i}: screenRelativeTransitionHeight={screenRelativeTransitionHeight}");
|
||||
lods[i] = new LOD(screenRelativeTransitionHeight, new Renderer[]{renderer!});
|
||||
}
|
||||
lodGroup.SetLODs(lods);
|
||||
lodGroup.RecalculateBounds(); // 바운딩 박스 재계산 (필수)
|
||||
rootObject.transform.SetParent(parentTransform, false);
|
||||
return rootObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameObject.Destroy(rootObject);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
7
Assets/Scripts/UVC/GLTF/GLTFImporter.cs.meta
Normal file
7
Assets/Scripts/UVC/GLTF/GLTFImporter.cs.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9737b1961c24c584fa071c051a55b85b
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
227
Assets/Scripts/UVC/GLTF/LODGameObjectInstantiator.cs
Normal file
227
Assets/Scripts/UVC/GLTF/LODGameObjectInstantiator.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
#nullable enable
|
||||
using GLTFast;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace UVC.GLTF
|
||||
{
|
||||
/// <summary>
|
||||
/// GameObjectInstantiator를 확장하여 MSFT_lod 확장을 LODGroup으로 변환합니다.
|
||||
/// 제대로 동작 않함. 수정 필요.
|
||||
/// </summary>
|
||||
public class LODGameObjectInstantiator : GameObjectInstantiator
|
||||
{
|
||||
// LOD 정보를 저장 (부모 노드 인덱스 -> LOD 노드 인덱스 리스트)
|
||||
private readonly Dictionary<uint, List<uint>> m_LodNodes = new();
|
||||
|
||||
// MSFT_screencoverage 값 저장
|
||||
private readonly Dictionary<uint, float[]> m_ScreenCoverages = new();
|
||||
|
||||
public LODGameObjectInstantiator(IGltfReadable gltf, Transform? parent)
|
||||
: base(gltf, parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 외부에서 파싱된 LOD 정보를 설정합니다.
|
||||
/// </summary>
|
||||
public void SetLodInfo(List<MsftLodParser.LodInfo> lodInfos)
|
||||
{
|
||||
m_LodNodes.Clear();
|
||||
m_ScreenCoverages.Clear();
|
||||
|
||||
foreach (var info in lodInfos)
|
||||
{
|
||||
m_LodNodes[(uint)info.NodeIndex] = info.LodNodeIds.Select(id => (uint)id).ToList();
|
||||
|
||||
if (info.ScreenCoverages != null)
|
||||
{
|
||||
m_ScreenCoverages[(uint)info.NodeIndex] = info.ScreenCoverages;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"LODGameObjectInstantiator: Set {m_LodNodes.Count} LOD nodes");
|
||||
}
|
||||
|
||||
public override void EndScene(uint[] rootNodeIndices)
|
||||
{
|
||||
base.EndScene(rootNodeIndices);
|
||||
|
||||
// LODGroup 적용
|
||||
ApplyLodGroups();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MSFT_lod 정보를 기반으로 LODGroup을 생성합니다.
|
||||
/// </summary>
|
||||
private void ApplyLodGroups()
|
||||
{
|
||||
if (m_LodNodes.Count == 0)
|
||||
{
|
||||
Debug.Log("LODGameObjectInstantiator: No LOD nodes to apply");
|
||||
return;
|
||||
}
|
||||
|
||||
// m_Nodes는 protected이므로 직접 접근 가능
|
||||
if (m_Nodes == null)
|
||||
{
|
||||
Debug.LogWarning("LODGameObjectInstantiator: m_Nodes is null");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"LODGameObjectInstantiator: m_Nodes count = {m_Nodes.Count}, LOD definitions = {m_LodNodes.Count}");
|
||||
|
||||
foreach (var kvp in m_LodNodes)
|
||||
{
|
||||
uint parentNodeIndex = kvp.Key;
|
||||
List<uint> lodNodeIndices = kvp.Value;
|
||||
|
||||
if (!m_Nodes.TryGetValue(parentNodeIndex, out var parentGo))
|
||||
{
|
||||
Debug.LogWarning($"LODGameObjectInstantiator: Parent node {parentNodeIndex} not found in m_Nodes");
|
||||
continue;
|
||||
}
|
||||
|
||||
// LOD 레벨 수집 (LOD0 = parent, LOD1+ = lodNodeIndices)
|
||||
var allLodObjects = new List<GameObject> { parentGo };
|
||||
foreach (var lodIndex in lodNodeIndices)
|
||||
{
|
||||
if (m_Nodes.TryGetValue(lodIndex, out var lodGo))
|
||||
{
|
||||
allLodObjects.Add(lodGo);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"LODGameObjectInstantiator: LOD node {lodIndex} not found in m_Nodes");
|
||||
}
|
||||
}
|
||||
|
||||
if (allLodObjects.Count < 2)
|
||||
{
|
||||
Debug.LogWarning($"LODGameObjectInstantiator: Not enough LOD objects for node {parentNodeIndex}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 먼저 각 LOD 객체의 렌더러를 수집 (부모 이동 전에!)
|
||||
// 각 LOD는 다른 LOD 객체들을 제외하고 렌더러를 수집
|
||||
var lodRenderers = new List<Renderer[]>();
|
||||
for (int i = 0; i < allLodObjects.Count; i++)
|
||||
{
|
||||
var lodObject = allLodObjects[i];
|
||||
|
||||
// 이 LOD 객체를 제외한 다른 LOD 객체들을 제외 목록에 추가
|
||||
var excludeSet = new HashSet<GameObject>();
|
||||
for (int j = 0; j < allLodObjects.Count; j++)
|
||||
{
|
||||
if (i != j)
|
||||
{
|
||||
excludeSet.Add(allLodObjects[j]);
|
||||
}
|
||||
}
|
||||
|
||||
var renderers = GetRenderersExcluding(lodObject, excludeSet);
|
||||
lodRenderers.Add(renderers);
|
||||
}
|
||||
|
||||
// MSFT_screencoverage 값 사용 또는 기본값 계산
|
||||
float[] screenCoverages;
|
||||
if (m_ScreenCoverages.TryGetValue(parentNodeIndex, out var coverage) && coverage.Length >= allLodObjects.Count)
|
||||
{
|
||||
screenCoverages = coverage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 기본 screen coverage 계산 (균등 분배)
|
||||
screenCoverages = CalculateDefaultScreenCoverages(allLodObjects.Count);
|
||||
}
|
||||
|
||||
// LOD1+ 객체들을 LOD0의 자식으로 이동
|
||||
for (int i = 1; i < allLodObjects.Count; i++)
|
||||
{
|
||||
var lodObject = allLodObjects[i];
|
||||
if (lodObject.transform.parent != parentGo.transform)
|
||||
{
|
||||
// 월드 위치/회전 유지하면서 부모 변경
|
||||
lodObject.transform.SetParent(parentGo.transform, true);
|
||||
}
|
||||
}
|
||||
|
||||
// LODGroup 생성 및 설정
|
||||
var lodGroup = parentGo.AddComponent<LODGroup>();
|
||||
var lods = new LOD[allLodObjects.Count];
|
||||
|
||||
for (int i = 0; i < allLodObjects.Count; i++)
|
||||
{
|
||||
float screenRelativeHeight = screenCoverages[i];
|
||||
lods[i] = new LOD(screenRelativeHeight, lodRenderers[i]);
|
||||
|
||||
Debug.Log($" LOD{i}: {lodRenderers[i].Length} renderers, screenHeight={screenRelativeHeight}");
|
||||
}
|
||||
|
||||
lodGroup.SetLODs(lods);
|
||||
lodGroup.RecalculateBounds();
|
||||
|
||||
Debug.Log($"LODGameObjectInstantiator: Applied LODGroup to '{parentGo.name}' with {allLodObjects.Count} LOD levels");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GameObject와 자식들의 렌더러를 수집합니다.
|
||||
/// excludeGameObjects에 포함된 GameObject와 그 자식들은 제외합니다.
|
||||
/// </summary>
|
||||
private Renderer[] GetRenderersExcluding(GameObject go, HashSet<GameObject> excludeGameObjects)
|
||||
{
|
||||
var renderers = new List<Renderer>();
|
||||
CollectRenderersRecursive(go.transform, renderers, excludeGameObjects);
|
||||
return renderers.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 재귀적으로 렌더러를 수집하되, 제외 목록에 있는 GameObject는 건너뜁니다.
|
||||
/// </summary>
|
||||
private void CollectRenderersRecursive(Transform current, List<Renderer> renderers, HashSet<GameObject> excludeGameObjects)
|
||||
{
|
||||
// 현재 객체가 제외 목록에 있으면 자식도 모두 건너뜀
|
||||
if (excludeGameObjects.Contains(current.gameObject))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 현재 객체의 렌더러 추가
|
||||
var renderer = current.GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
renderers.Add(renderer);
|
||||
}
|
||||
|
||||
// 자식들 재귀 탐색
|
||||
foreach (Transform child in current)
|
||||
{
|
||||
CollectRenderersRecursive(child, renderers, excludeGameObjects);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기본 screen coverage 값을 계산합니다.
|
||||
/// </summary>
|
||||
private float[] CalculateDefaultScreenCoverages(int lodCount)
|
||||
{
|
||||
// 일반적인 LOD 전환 비율
|
||||
// LOD0: 0.5 (50% 이상일 때)
|
||||
// LOD1: 0.25 (25% 이상일 때)
|
||||
// LOD2: 0.1 (10% 이상일 때)
|
||||
// LOD3: 0 (0% 이상일 때)
|
||||
// 등등...
|
||||
|
||||
float[] lodScreenPercentages = new float[] { 0.5f, 0.25f, 0.1f, 0f};
|
||||
|
||||
var coverages = new float[lodCount];
|
||||
for (int i = 0; i < lodCount; i++)
|
||||
{
|
||||
coverages[i] = lodScreenPercentages[Mathf.Min(i, lodScreenPercentages.Length - 1)];
|
||||
}
|
||||
return coverages;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5d3912849cbc414093b6265df85581d
|
||||
232
Assets/Scripts/UVC/GLTF/MsftLodParser.cs
Normal file
232
Assets/Scripts/UVC/GLTF/MsftLodParser.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.GLTF
|
||||
{
|
||||
/// <summary>
|
||||
/// glTF/glb 파일에서 MSFT_lod 확장 정보를 파싱합니다.
|
||||
/// GLTFast가 MSFT_lod를 지원하지 않으므로 직접 JSON을 파싱합니다.
|
||||
/// </summary>
|
||||
public static class MsftLodParser
|
||||
{
|
||||
/// <summary>
|
||||
/// LOD 정보를 담는 클래스
|
||||
/// </summary>
|
||||
public class LodInfo
|
||||
{
|
||||
public int NodeIndex { get; set; }
|
||||
public int[] LodNodeIds { get; set; } = Array.Empty<int>();
|
||||
public float[]? ScreenCoverages { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// glb 또는 gltf 파일에서 MSFT_lod 정보를 파싱합니다.
|
||||
/// </summary>
|
||||
public static List<LodInfo> ParseFromFile(string filePath)
|
||||
{
|
||||
var result = new List<LodInfo>();
|
||||
|
||||
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string json;
|
||||
string extension = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
|
||||
if (extension == ".glb")
|
||||
{
|
||||
json = ExtractJsonFromGlb(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
json = File.ReadAllText(filePath);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return ParseMsftLodFromJson(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"MsftLodParser: Failed to parse {filePath}: {ex.Message}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// glb 파일에서 JSON 청크를 추출합니다.
|
||||
/// </summary>
|
||||
private static string ExtractJsonFromGlb(string filePath)
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
// GLB 헤더: magic(4) + version(4) + length(4) = 12 bytes
|
||||
uint magic = reader.ReadUInt32();
|
||||
if (magic != 0x46546C67) // "glTF" in little-endian
|
||||
{
|
||||
Debug.LogError("MsftLodParser: Invalid GLB magic number");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
uint version = reader.ReadUInt32();
|
||||
uint length = reader.ReadUInt32();
|
||||
|
||||
// JSON 청크: chunkLength(4) + chunkType(4) + chunkData
|
||||
uint jsonChunkLength = reader.ReadUInt32();
|
||||
uint jsonChunkType = reader.ReadUInt32();
|
||||
|
||||
if (jsonChunkType != 0x4E4F534A) // "JSON" in little-endian
|
||||
{
|
||||
Debug.LogError("MsftLodParser: First chunk is not JSON");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
byte[] jsonBytes = reader.ReadBytes((int)jsonChunkLength);
|
||||
return Encoding.UTF8.GetString(jsonBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON 문자열에서 MSFT_lod 정보를 파싱합니다.
|
||||
/// Unity의 JsonUtility는 중첩 객체를 잘 처리하지 못하므로 수동 파싱합니다.
|
||||
/// </summary>
|
||||
private static List<LodInfo> ParseMsftLodFromJson(string json)
|
||||
{
|
||||
var result = new List<LodInfo>();
|
||||
|
||||
// "nodes" 배열 찾기
|
||||
int nodesStart = json.IndexOf("\"nodes\"");
|
||||
if (nodesStart < 0) return result;
|
||||
|
||||
int nodesArrayStart = json.IndexOf('[', nodesStart);
|
||||
if (nodesArrayStart < 0) return result;
|
||||
|
||||
// 노드 배열의 끝 찾기 (괄호 매칭)
|
||||
int nodesArrayEnd = FindMatchingBracket(json, nodesArrayStart, '[', ']');
|
||||
if (nodesArrayEnd < 0) return result;
|
||||
|
||||
string nodesJson = json.Substring(nodesArrayStart, nodesArrayEnd - nodesArrayStart + 1);
|
||||
|
||||
// 각 노드를 개별적으로 파싱
|
||||
int nodeIndex = 0;
|
||||
int searchPos = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// 다음 노드 객체 찾기
|
||||
int nodeStart = nodesJson.IndexOf('{', searchPos);
|
||||
if (nodeStart < 0) break;
|
||||
|
||||
int nodeEnd = FindMatchingBracket(nodesJson, nodeStart, '{', '}');
|
||||
if (nodeEnd < 0) break;
|
||||
|
||||
string nodeJson = nodesJson.Substring(nodeStart, nodeEnd - nodeStart + 1);
|
||||
|
||||
// MSFT_lod 확장 확인
|
||||
int msftLodStart = nodeJson.IndexOf("\"MSFT_lod\"");
|
||||
if (msftLodStart >= 0)
|
||||
{
|
||||
var lodInfo = ParseLodInfoFromNode(nodeJson, nodeIndex);
|
||||
if (lodInfo != null)
|
||||
{
|
||||
result.Add(lodInfo);
|
||||
}
|
||||
}
|
||||
|
||||
searchPos = nodeEnd + 1;
|
||||
nodeIndex++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드 JSON에서 LOD 정보를 파싱합니다.
|
||||
/// </summary>
|
||||
private static LodInfo? ParseLodInfoFromNode(string nodeJson, int nodeIndex)
|
||||
{
|
||||
// "ids" 배열 찾기
|
||||
int idsStart = nodeJson.IndexOf("\"ids\"");
|
||||
if (idsStart < 0) return null;
|
||||
|
||||
int idsArrayStart = nodeJson.IndexOf('[', idsStart);
|
||||
if (idsArrayStart < 0) return null;
|
||||
|
||||
int idsArrayEnd = nodeJson.IndexOf(']', idsArrayStart);
|
||||
if (idsArrayEnd < 0) return null;
|
||||
|
||||
string idsContent = nodeJson.Substring(idsArrayStart + 1, idsArrayEnd - idsArrayStart - 1);
|
||||
|
||||
// 쉼표로 구분된 숫자 파싱
|
||||
var idStrings = idsContent.Split(',');
|
||||
var ids = new List<int>();
|
||||
|
||||
foreach (var idStr in idStrings)
|
||||
{
|
||||
string trimmed = idStr.Trim();
|
||||
if (int.TryParse(trimmed, out int id))
|
||||
{
|
||||
ids.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (ids.Count == 0) return null;
|
||||
|
||||
return new LodInfo
|
||||
{
|
||||
NodeIndex = nodeIndex,
|
||||
LodNodeIds = ids.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 매칭되는 괄호의 위치를 찾습니다.
|
||||
/// </summary>
|
||||
private static int FindMatchingBracket(string json, int startPos, char openBracket, char closeBracket)
|
||||
{
|
||||
int depth = 0;
|
||||
bool inString = false;
|
||||
char prevChar = '\0';
|
||||
|
||||
for (int i = startPos; i < json.Length; i++)
|
||||
{
|
||||
char c = json[i];
|
||||
|
||||
// 문자열 내부 처리
|
||||
if (c == '"' && prevChar != '\\')
|
||||
{
|
||||
inString = !inString;
|
||||
}
|
||||
else if (!inString)
|
||||
{
|
||||
if (c == openBracket)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
else if (c == closeBracket)
|
||||
{
|
||||
depth--;
|
||||
if (depth == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevChar = c;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/GLTF/MsftLodParser.cs.meta
Normal file
2
Assets/Scripts/UVC/GLTF/MsftLodParser.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21ab40cce627f404c895bbacea2ef6f9
|
||||
BIN
Assets/StreamingAssets/model_lod0.glb
Normal file
BIN
Assets/StreamingAssets/model_lod0.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/model_lod0.glb.meta
Normal file
7
Assets/StreamingAssets/model_lod0.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e015621a087aff4b8dc6d4ada0d428b
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/model_lod1.glb
Normal file
BIN
Assets/StreamingAssets/model_lod1.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/model_lod1.glb.meta
Normal file
7
Assets/StreamingAssets/model_lod1.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4ae011ff0a6f3b489d3c880ab8a5c89
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/model_lod2.glb
Normal file
BIN
Assets/StreamingAssets/model_lod2.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/model_lod2.glb.meta
Normal file
7
Assets/StreamingAssets/model_lod2.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87e82458b8f6c054d9fc363ad161af8f
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/model_lod3.glb
Normal file
BIN
Assets/StreamingAssets/model_lod3.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/model_lod3.glb.meta
Normal file
7
Assets/StreamingAssets/model_lod3.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca600dc515811e842989a645b784c59a
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/model_with_lod.glb
Normal file
BIN
Assets/StreamingAssets/model_with_lod.glb
Normal file
Binary file not shown.
7
Assets/StreamingAssets/model_with_lod.glb.meta
Normal file
7
Assets/StreamingAssets/model_with_lod.glb.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a797732bb8c481b48b8d6d6dca79a18b
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user