Version: 2022.2
언어: 한국어
XR SDK 디스플레이 하위 시스템
XR SDK PreInit 인터페이스

XR SDK 메싱 하위 시스템

메싱 하위 시스템은 외부 공급자에서 메시 데이터를 추출하여 UnityEngine.Mesh로 전환합니다. 또한 메인 스레드 중단 없이 UnityEngine.MeshCollider(선택 사항)를 생성할 수도 있습니다.

메싱 하위 시스템의 주요 사용 사례는 일반적으로 공간 매핑 알고리즘에서 절차적으로 생성된 메시(뎁스 카메라에서 생성된 메시와 유사함)를 표시하는 것입니다. 메시의 크기나 업데이트 빈도에는 제한이 없습니다.

메시 생성은 백그라운드 스레드에서 비동기적으로 발생하므로, 외부 공급자에서 데이터를 추출해도 메인 스레드의 작업(예: 메시 콜라이더 베이크)이 차단되지 않습니다.

컨트롤 플로

메싱 하위 시스템에는 다음의 두 가지 기본 쿼리가 있습니다.

  • 모든 트래킹된 메시의 상태를 가져옵니다(예: New, Changed, Unchanged, Removed).
  • 특정 메시를 생성합니다. 메시는 MeshId 식별자를 사용하여 식별됩니다.

메시 정보 가져오기

C# 사용자는 XRMeshSubsystem 인스턴스 메서드에서 메시 정보를 가져올 수 있습니다.

public bool TryGetMeshInfos(List<MeshInfo> meshInfosOut);

UnityXRMeshProvider::GetMeshInfos에 대한 C 호출에 직접 매핑되며, 일반적으로 프레임당 한 번 호출되어 트래킹된 메시의 현재 리스트를 가져옵니다.

다음 C 구현은 제공된 allocator 오브젝트를 사용하여 UnityXRMeshInfo 배열을 할당할 수 있습니다. 할당 후에는 이 배열을 채워야 합니다.

UnitySubsystemErrorCode(UNITY_INTERFACE_API * GetMeshInfos)(
        UnitySubsystemHandle handle, void* pluginData, UnityXRMeshInfoAllocator * allocator);

할당된 메모리는 Unity가 소유합니다(일반적으로 스택 할당자를 사용하므로 할당 속도가 매우 빠름).

typedef struct UnityXRMeshInfo
{
    UnityXRMeshId meshId;
    bool updated;
    int priorityHint;
} UnityXRMeshInfo;

TryGetMeshInfos에 대한 마지막 호출 이후 변경된 사항이 없으면 false를 반환하여 프레임마다 배열을 채우지 않을 수 있습니다.

필드 설명
meshId 128비트 고유 식별자입니다. 공급자는 메시 데이터에 대한 포인터일 수 있는 이러한 값을 생성하지만, 해당 ID로 특정 메시를 생성할 수 있어야 합니다.
updated Unity에 필요한 유일한 상태는 메시가 마지막으로 생성된 이후 업데이트되었는지 여부입니다. 메시가 추가 또는 제거되었는지 여부에 대한 결정은 자동으로 수행됩니다. Unity가 알지 못하는 메시의 존재를 보고하면 Added로 표시되고, 이전에 보고된 메시를 보고하지 않으면 메시가 Removed로 표시됩니다.
priorityHint C#은 이 값은 해석하지만, 이를 기반으로 생성할 메시의 우선 순위를 지정하는 C# 컴포넌트를 제공해야 할 수 있습니다. Unity는 이 값을 사용하지 않습니다.

C#에서 TryGetMeshInfos메시 상태를 포함하는 List<MeshInfo>를 채웁니다.

public enum MeshChangeState
{
    Added,
    Updated,
    Removed,
    Unchanged
}

메시 변경 상태 및 우선 순위 힌트 값에 따라 C# 컴포넌트는 다음에 생성할 메시를 결정할 수 있습니다.

메시 생성

C#에서 XRMeshSubsystem 인스턴스 메서드를 사용하여 특정 메시를 비동기적으로 생성할 수 있습니다.

public extern void GenerateMeshAsync(
    MeshId meshId,
    Mesh mesh,
    MeshCollider meshCollider,
    MeshVertexAttributes attributes,
    Action<MeshGenerationResult> onMeshGenerationComplete);

이렇게 하면 생성을 위해 메시가 대기열에 추가됩니다. 원하는 수의 메시를 대기열에 추가할 수 있지만, 동시에 생성되는 메시 수는 몇 개로 제한하는 것이 좋습니다.

Unity는 오류가 발생하더라도 항상 제공된 onMeshGenerationComplete 델리게이트를 호출합니다.

메시는 두 가지 단계로 생성되며, 다음의 획득 및 릴리스 모델을 따릅니다.

UnitySubsystemErrorCode(UNITY_INTERFACE_API * AcquireMesh)(
    UnitySubsystemHandle handle,
    void* pluginData,
    const UnityXRMeshId * meshId,
    UnityXRMeshDataAllocator * allocator);

AcquireMesh는 백그라운드 스레드에서 호출되므로, 계산 성능을 많이 소모하는 작업(예: 메시 자체를 생성)을 비롯하여 이 메서드에서 원하는 만큼 작업을 처리할 수 있습니다. 이 함수는 즉시 반환하거나 여러 프레임에 걸쳐 있을 수 있습니다.

GenerateMeshAsyncMeshCollider를 제공하면 Unity는 MeshCollider의 가속도 구조도 계산합니다(위 다이어그램의 “Bake Physics”). 메시가 큰 경우 시간이 많이 소모될 수 있으므로, 이 작업은 워커 스레드에서 발생합니다.

마지막으로, 데이터가 준비되면 Unity는 해당 데이터를 메인 스레드의 UnityEngine.Mesh 또는 UnityEngine.MeshCollider에 기록합니다. Unity는 메인 스레드에서도 ReleaseMesh를 호출합니다.

UnitySubsystemErrorCode(UNITY_INTERFACE_API * ReleaseMesh)(
    UnitySubsystemHandle handle,
    void* pluginData,
    const UnityXRMeshId * meshId,
    const UnityXRMeshDescriptor * mesh,
    void* userData);

ReleaseMesh는 메인 스레드에서 호출되므로 빠르게 반환됩니다. 이는 일반적으로 AcquireMesh 동안 할당된 리소스를 해제하는 데 사용됩니다.

메모리 관리

AcquireMesh는 Unity에 메시 데이터를 제공하는 두 가지 수단, 즉 Unity 관리 메모리와 공급자 관리 메모리를 제공합니다.

Unity 관리 메모리

Unity가 메모리를 관리하도록 하려면 다음을 사용하십시오.

UnityXRMeshDescriptor* (UNITY_INTERFACE_API * MeshDataAllocator_AllocateMesh)(
    UnityXRMeshDataAllocator * allocator,
    size_t vertexCount,
    size_t indexCount,
    UnityXRIndexFormat indexFormat,
    UnityXRMeshVertexAttributeFlags attributes,
    UnityXRMeshTopology topology);

그러면 이러한 속성과 C#에서 요청한 버텍스 속성의 교차점을 기반으로 버퍼에 대한 포인터가 포함된 구조체가 반환됩니다. 공급자는 적절한 데이터를 버퍼에 복사해야 합니다.

이 패러다임을 사용하면 Unity가 ReleaseMesh 호출 후 메모리를 재활용하므로 메모리를 해제할 필요가 없습니다.

공급자 관리 메모리

Unity가 메모리를 관리하도록 허용하는 대신, 자체 데이터를 가리키도록 만들 수 있습니다. 데이터는 ReleaseMesh가 호출될 때까지 유효한 상태여야 합니다.

MeshDataAllocator_SetMesh를 사용하여 null이 아닌 포인터가 유효한 데이터를 가리키는 자체 UnityXRMeshDescriptor를 제공하십시오.

void(UNITY_INTERFACE_API * MeshDataAllocator_SetMesh)(
    UnityXRMeshDataAllocator * allocator, const UnityXRMeshDescriptor * meshDescriptor);

사용자 데이터

AcquireMesh 구현은 다음을 호출할 수 있습니다.

void(UNITY_INTERFACE_API * MeshDataAllocator_SetUserData)(
    UnityXRMeshDataAllocator * allocator, void* userData);

Unity는 userData 포인터를 ReleaseMesh 구현에 다시 전달합니다. 이는 공급자 관리 메모리를 사용할 때 특히 유용합니다.

C# 컴포넌트 예제

void Update()
{
    if (s_MeshSubsystem.TryGetMeshInfos(s_MeshInfos))
    {
        foreach (var meshInfo in s_MeshInfos)
        {
            switch (meshInfo.ChangeState)
            {
                case MeshChangeState.Added:
                case MeshChangeState.Updated:
                    AddToQueueIfNecessary(meshInfo);
                    break;

                case MeshChangeState.Removed:
                    RaiseMeshRemoved(meshInfo.MeshId);

                    // Remove from processing queue
                    m_MeshesNeedingGeneration.Remove(meshInfo.MeshId);

                    // Destroy the GameObject
                    GameObject meshGameObject;
                    if (meshIdToGameObjectMap.TryGetValue(meshInfo.MeshId, out meshGameObject))
                    {
                        Destroy(meshGameObject);
                        meshIdToGameObjectMap.Remove(meshInfo.MeshId);
                    }

                    break;

                default:
                    break;
            }
        }
    }

    // ...

    while (m_MeshesBeingGenerated.Count < meshQueueSize && m_MeshesNeedingGeneration.Count > 0)
    {
        // Get the next mesh to generate. Could be based on the mesh's
        // priorityHint, whether it is new vs updated, etc.
        var meshId = GetNextMeshToGenerate();

        // Gather the necessary Unity objects for the generation request
        var meshGameObject = GetOrCreateGameObjectForMesh(meshId);
        var meshCollider = meshGameObject.GetComponent<MeshCollider>();
        var mesh = meshGameObject.GetComponent<MeshFilter>().mesh;
        var meshAttributes = shouldComputeNormals ? MeshVertexAttributes.Normals : MeshVertexAttributes.None;

        // Request generation
        s_MeshSubsystem.GenerateMeshAsync(meshId, mesh, meshCollider, meshAttributes, OnMeshGenerated);

        // Update internal state
        m_MeshesBeingGenerated.Add(meshId, m_MeshesNeedingGeneration[meshId]);
        m_MeshesNeedingGeneration.Remove(meshId);
    }
}

void OnMeshGenerated(MeshGenerationResult result)
{
    if (result.Status != MeshGenerationStatus.Success)
    {
        // Handle error, regenerate, etc.
    }

    m_MeshesBeingGenerated.Remove(result.MeshId);
}
XR SDK 디스플레이 하위 시스템
XR SDK PreInit 인터페이스