Version: 2023.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 在后台线程上进行调用,因此您可以在此方法中进行任意数量的处理,包括计算密集型工作(例如生成网格本身)。此函数可以立即返回或跨越多个帧。

如果向 GenerateMeshAsync 提供 MeshCollider,则 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 可提供您自己的 UnityXRMeshDescriptor,其非 null 指针指向有效数据:

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);

                    // 从处理队列中移除
                    m_MeshesNeedingGeneration.Remove(meshInfo.MeshId);

                    // 销毁游戏对象
                    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)
    {
        // 获取要生成的下一个网格。无法基于网格的
        // priorityHint、它是新的还是已更新等
        var meshId = GetNextMeshToGenerate();

        // 收集生成请求所需的 Unity 对象
        var meshGameObject = GetOrCreateGameObjectForMesh(meshId);
        var meshCollider = meshGameObject.GetComponent<MeshCollider>();
        var mesh = meshGameObject.GetComponent<MeshFilter>().mesh;
        var meshAttributes = shouldComputeNormals ?MeshVertexAttributes.Normals : MeshVertexAttributes.None;

        // 请求生成
        s_MeshSubsystem.GenerateMeshAsync(meshId, mesh, meshCollider, meshAttributes, OnMeshGenerated);

        // 更新内部状态
        m_MeshesBeingGenerated.Add(meshId, m_MeshesNeedingGeneration[meshId]);
        m_MeshesNeedingGeneration.Remove(meshId);
    }
}

void OnMeshGenerated(MeshGenerationResult result)
{
    if (result.Status != MeshGenerationStatus.Success)
    {
        // 处理错误、重新生成等
    }

    m_MeshesBeingGenerated.Remove(result.MeshId);
}
XR SDK 显示子系统
XR SDK PreInit 接口