网格子系统从外部提供程序提取网格Unity 的主要图形基元。网格构成 3D 世界的很大一部分。Unity 支持三角形或四边形多边形网格。Nurbs、Nurms、细分曲面必须转换为多边形。 更多信息
参见 词汇表数据并将其转换为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 不了解的网格的存在将显示为已添加,而未报告先前报告的网格则将网格标记为已移除。 |
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 管理内存,请使用
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
实现。当您使用提供程序管理的内存时,这特别有用。
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);
}