228 lines
8.4 KiB
C#
228 lines
8.4 KiB
C#
|
|
#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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|