メッシュ化 (Meshing) サブシステムは、外部のプロバイダーからメッシュデータを抽出し、UnityEngine.Mesh に変換します。また、メインスレッドのストールを発生させることなく、任意の UnityEngine.MeshCollider を生成することができます。
メッシュ化サブシステムの主な使用例は、一般に深度カメラから生成されるような空間マッピングアルゴリズムから、プロシージャルに生成されたメッシュをサーフェス化することです。メッシュのサイズや更新頻度には制限がありません。
メッシュの生成は、バックグラウンドのスレッドで非同期的に行われます。そのため、外部のプロバイダーからデータを抽出しても、メインスレッドがブロックされることはなく、例えばメッシュコライダーのベイク処理を行うことができます。
メッシュ化サブシステムには 2 つの基本的なクエリがあります。
C# ユーザーは、 XRMeshSubsystem
インスタンスメソッドからメッシュの情報を得ることができます。
public bool TryGetMeshInfos(List<MeshInfo> meshInfosOut);
これは UnityXRMeshProvider::GetMeshInfos
への C 言語の呼び出しに直接対応しており、通常はフレームごとに 1 回呼び出され、追跡されたメッシュの現在のリストを取得します。
以下の 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 は、mesh state を含む List<MeshInfo>
を生成します。
public enum MeshChangeState
{
Added,
Updated,
Removed,
Unchanged
}
C# コンポーネントは、メッシュの変更状態と優先順位を左右する値 (priorityHint) に基づいて、次にどのメッシュを生成するかを決定します。
C# からは、XRMeshSubsystem インスタンスメソッドを使って、特定のメッシュを非同期に生成することができます。
public extern void GenerateMeshAsync(
MeshId meshId,
Mesh mesh,
MeshCollider meshCollider,
MeshVertexAttributes attributes,
Action<MeshGenerationResult> onMeshGenerationComplete);
これは、生成のためにメッシュをキューに入れます。数に制限なくメッシュをキューに入れることができますが、同時に生成されるメッシュの数を数個に制限したほうが良い場合もあります。
Unity は、エラーが発生しても、提供された onMeshGenerationComplete
デリゲートを常に呼び出します。
メッシュの生成は、取得と解放の 2 つのフェーズで行われます。
UnitySubsystemErrorCode(UNITY_INTERFACE_API * AcquireMesh)(
UnitySubsystemHandle handle,
void* pluginData,
const UnityXRMeshId * meshId,
UnityXRMeshDataAllocator * allocator);
AcquireMesh
は、バックグラウンドのスレッドで呼び出されます。そのため、このメソッドでは、メッシュ自体を生成するような計算負荷の高い作業を含め、好きなだけ処理を行うことができます。この関数はすぐに返すことも、複数のフレームにわたって行うこともできます。
MeshCollider
を GenerateMeshAsync
に提供する場合、Unity はメッシュコライダーの加速構造も計算します (上図の “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 に提供するための 2 つの方法があります。Unity-managed (Unity マネージ) と provider-managed (プロバイダーマネージ) です。
Unity にメモリを管理させるには、以下を使用します。
UnityXRMeshDescriptor* (UNITY_INTERFACE_API * MeshDataAllocator_AllocateMesh)(
UnityXRMeshDataAllocator * allocator,
size_t vertexCount,
size_t indexCount,
UnityXRIndexFormat indexFormat,
UnityXRMeshVertexAttributeFlags attributes,
UnityXRMeshTopology topology);
これは、これらの 属性
と C# から要求された頂点属性の交点に基づくバッファへのポインターを持つ構造体を返します。プロバイダーは、適切なデータをバッファにコピーする必要があります。
このパラダイムを使うと、ReleaseMesh
を呼び出した後に Unity がメモリを再利用するので、メモリを解放する必要がありません。
メモリの管理を Unity に任せるのではなく、自身のデータで指し示すことができます。このデータは、 ReleaseMesh
が呼び出されるまで有効でなければなりません。
MeshDataAllocator_SetMesh
を使用して、null 以外のポインターが有効なデータを指す独自の 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);
// 処理キューから削除します
m_MeshesNeedingGeneration.Remove(meshInfo.MeshId);
// 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)
{
// 生成する次のメッシュを取得します。
//メッシュの priorityHint、New か Updated かなどに基づく可能性があります。
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;
// 生成を要求
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);
}