网格子系统从外部提供程序中提取网格数据并将其转换为 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);
}
Did you find this page useful? Please give it a rating:
Thanks for rating this page!
What kind of problem would you like to report?
Thanks for letting us know! This page has been marked for review based on your feedback.
If you have time, you can provide more information to help us fix the problem faster.
Provide more information
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:
You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:
You've told us there is information missing from this page. Please tell us more about what's missing:
You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:
You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:
You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:
You've told us this page has a problem. Please tell us more about what's wrong:
Thank you for helping to make the Unity documentation better!
Your feedback has been submitted as a ticket for our documentation team to review.
We are not able to reply to every ticket submitted.