版本:Unity 6 (6000.0)
语言:英语
XR SDK 显示子系统
XR SDK 预初始化接口

XR SDK 网格子系统

网格子系统从外部提供程序提取网格Unity 的主要图形基元。网格构成 3D 世界的很大一部分。Unity 支持三角形或四边形多边形网格。Nurbs、Nurms、细分曲面必须转换为多边形。 更多信息
参见 词汇表
数据并将其转换为UnityEngine.Mesh。它还可以生成一个可选的UnityEngine.MeshCollider,而不会造成任何主线程停顿。

网格子系统的主要用例是显示程序生成的网格,通常来自空间映射将现实世界表面映射到虚拟世界中的过程。
参见 词汇表
算法,例如深度相机一个组件,用于在场景中创建特定视点的图像。输出要么绘制到屏幕上,要么捕获为纹理。 更多信息
参见 词汇表
生成的算法。网格的大小或更新频率没有限制。

网格生成在后台线程上异步发生,因此从外部提供程序提取数据不会阻塞主线程,例如烘焙网格碰撞器用于处理对象物理碰撞的不可见形状。碰撞器不需要与对象的网格完全相同形状 - 粗略的近似值通常更有效,并且在游戏过程中难以区分。 更多信息
参见 词汇表

控制流程

网格子系统有两个基本查询

  • 获取所有跟踪网格的状态(例如,新建、已更改、未更改、已移除)。
  • 生成特定网格。网格使用MeshId标识符进行识别。

获取 MeshInfos

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 不了解的网格的存在将显示为已添加,而未报告先前报告的网格则将网格标记为已移除。
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 的加速结构(上图中的“烘焙物理”)。对于大型网格,这可能很耗时,因此它也会在工作线程上发生。

最后,当数据准备就绪时,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);

这将返回一个结构体,其中包含基于这些attributes与从 C# 请求的顶点属性的交集的缓冲区指针。然后,提供程序应将相应的数据复制到缓冲区。

当您使用此范例时,您不必释放内存,因为 Unity 将在调用ReleaseMesh后回收内存。

提供程序管理的内存

您可以指向您自己的数据,而不是让 Unity 管理内存。数据必须保持有效,直到调用ReleaseMesh

使用MeshDataAllocator_SetMesh提供您自己的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 预初始化接口