网格子系统从外部提供程序中提取网格数据并将其转换为 UnityEngine.Mesh。它还可以生成可选的 UnityEngine.MeshCollider 而不会导致任何主线程停顿。
网格子系统的主要用例是展示以程序方式生成的网格(通常来自空间映射算法,如从深度摄像机生成的网格)。对网格大小或更新频率没有限制。
网格生成在后台线程上异步进行,因此从外部提供程序中提取数据不会阻止主线程(例如烘焙网格碰撞体)。
网格子系统有两个基本查询:
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 可以管理内存,请使用:
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
实现。这在您使用提供程序管理的内存时特别有用。
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);
}