シーン Mesh データ取得開発ガイド
LCC データを再構築する際、LCC Unity SDK はコリジョンに使用する Mesh データも生成します。コリジョン検出に加えて、SDK はこの Mesh データを 抽出 して Unity の Mesh オブジェクトに変換し、可視化する機能を提供しており、エクスポートや NavMesh のベイクなどのシナリオに便利です。
以下では、全 Mesh データの同期取得と、指定範囲の Mesh データをオンデマンドで取得する手順を完全な例で説明します。
環境の準備
Unity で新しいシーンを作成します。Unity はデフォルトで Main Camera と Directional Light を作成します。
Main Camera をセットアップする
Hierarchy ウィンドウで Main Camera を選択し、Inspector ウィンドウでそのプロパティを以下のとおり設定します。

Main Camera のプロパティを設定
LCCManager を作成する
Hierarchy ウィンドウの空の領域を右クリックし、ポップアップメニューから Create Empty を選択して空の GameObject を作成し、LCCManager と名前を付け、その Transform を正規化し、Add Component で LCCManager スクリプトを追加します。

LCCManager GameObject を作成し LCCManager スクリプトを追加
LCCRenderer を作成する
Hierarchy ウィンドウの空の領域を右クリックし、Create Empty を選択して空の GameObject を作成し、LCCRenderer と名前を付け、その Transform を Rotation.x = -90、Scale.x = -1 に設定します。

LCCRenderer を作成し Transform(Rotation.x = -90、Scale.x = -1)を設定
注:
LCCRenderer GameObject の Transform 設定(Rotation.x = -90、Scale.x = -1)は、LCC データ座標系にアライメントするためのものです。座標系アライメントの詳細は Unity SDK の概要 の「LCC データの座標系」を参照してください。
データの前提条件
Mesh データ取得が有効になる前提条件は、シーンが対応するコリジョン Mesh データを保持していること(LCC Studio で再構築する際に生成されます)です。また、この機能には以下の制約があります。
- PC / Mac のみ、かつ ローカルデータソースのみ 対応(HTTP データソースは非対応)。
- メインスレッドでのみ呼び出せます。
- ネイティブ形式データ(PLY / SOG / SPZ / splat / ksplat)はコリジョン Mesh データを保持しません。関連メソッドを呼び出すと
nullが返ります。
以前のバージョンで再構築されたデータにはコリジョンデータが含まれない場合があり、その場合は GetMeshes() を呼び出すと null が返ります。
全 Mesh データの同期取得
全 Mesh データの同期取得とは、シーン内の すべての コリジョン Mesh ブロックのジオメトリデータを一度に読み取ることで、Renderer.GetMeshes() メソッドで実現します。
Meshes.cs という名前の新しい C# スクリプトを以下のコードで作成します。
using System.Collections.Generic;
using LCCCore;
using UnityEngine;
using UnityEngine.Rendering;
public class Meshes : MonoBehaviour
{
public LCCManager m_manager;
public string m_FilePath;
private LCCCore.Renderer m_renderer;
private bool m_isLoaded = false;
private Camera m_camera;
private const string kContainerName = "LCCVisualizedMeshes";
void Start()
{
m_camera = Camera.main;
m_isLoaded = false;
m_manager.SetPlatformType(PlatformType.PC);
m_renderer = m_manager.GetRender(this.transform);
m_renderer.Load(m_FilePath,
p => Debug.Log($"[Meshes] 読み込み進捗: {p:P1}"),
() =>
{
Debug.Log($"[Meshes] ファイルを読み込みました: {m_FilePath}");
m_isLoaded = true;
m_manager.SetAutoFov(false);
},
e => Debug.LogError($"[Meshes] ファイル読み込みエラー: {e}")
);
}
public void onGetAllMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] データの読み込みが完了していないため、Mesh を取得できません。");
return;
}
var meshes = m_renderer.GetMeshes();
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] Mesh を取得できませんでした(データにコライダーが含まれていない可能性があります)。");
return;
}
VisualizeMeshes(meshes);
}
private void VisualizeMeshes(List<LCCMeshBlock> meshes)
{
// 以前の可視化結果をクリーンアップして蓄積を防ぐ
Transform root = m_renderer.Transform;
Transform old = root.Find(kContainerName);
if (old != null) Destroy(old.gameObject);
var container = new GameObject(kContainerName).transform;
container.SetParent(root, false);
Material material = CreateVisualMaterial();
for (int i = 0; i < meshes.Count; i++)
{
LCCMeshBlock block = meshes[i];
if (block.vertices == null || block.vertices.Length == 0) continue;
if (block.triangles == null || block.triangles.Length < 3) continue;
var go = new GameObject(string.IsNullOrEmpty(block.id) ? $"MeshBlock_{i}" : block.id);
go.transform.SetParent(container, false);
var mesh = new Mesh { name = go.name };
// 1 ブロックの頂点数は 65535 を超える可能性があるため、32 ビットインデックスを使用する必要がある。さもないとジオメトリが壊れる
mesh.indexFormat = IndexFormat.UInt32;
mesh.vertices = block.vertices;
mesh.triangles = block.triangles;
mesh.RecalculateNormals();
mesh.RecalculateBounds();
var mf = go.AddComponent<MeshFilter>();
mf.sharedMesh = mesh;
var mr = go.AddComponent<MeshRenderer>();
mr.sharedMaterial = material;
}
Debug.Log($"[Meshes] GameObject を作成し、{meshes.Count} 個の Mesh ブロックを可視化しました。");
}
private Material CreateVisualMaterial()
{
Shader shader = Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard")
?? Shader.Find("Diffuse");
if (shader == null) return null;
var mat = new Material(shader) { color = new Color(0.7f, 0.7f, 0.7f, 1f) };
// コリジョンメッシュの三角形の巻き順は一貫しているとは限らない。完全表示のため背面カリングを無効化
mat.SetFloat("_Cull", (float)CullMode.Off);
return mat;
}
}
注:
GetMeshes()は 同期的な重い操作 です。ディスクからの読み取り、解析、メモリ割り当てを同期的に行います。大規模シーン(例:262 ブロックのデータセット)では、大量のディスクデータを読み取り、数 GB から数十 GB のマネージメモリを割り当てる必要がある場合があります。一度呼び出して結果を自分でキャッシュしてください。毎フレーム呼び出さないでください。vertices/boundsLocalは オブジェクトローカル座標系 なので、可視化 GameObject をRenderer.Transformの下にアタッチすると自動的にアライメントされます。- 1 ブロックの頂点数は 65535 を超える場合があります(実測最大 390,656)。独自の
Meshを構築する前にmesh.indexFormat = IndexFormat.UInt32を設定する必要があります。さもないとジオメトリが壊れます。- コリジョンメッシュの三角形の巻き順は一貫しているとは限りません。この例では完全表示のため背面カリングを無効化しています(
_Cull = Off)。
スクリプトを LCCRenderer GameObject にアタッチし、Inspector で LCCManager GameObject を Manager プロパティへドラッグし、File Path を入力します。
次に Button を作成し、onGetAllMeshes() メソッドをその onClick イベントに関連付けます。
実行後、Button をクリックするとシーン内のすべてのコリジョン Mesh を確認できます。

ボタンをクリックすると全コリジョン Mesh データを可視化
Mesh データのオンデマンド取得
オンデマンド取得とは、全 Mesh データを一度に読み取るのではなく、指定した部分の Mesh データのみを読み取ることで、大きな I/O とメモリオーバーヘッドを回避します。以下では、カメラ周辺の一定範囲内の Mesh ブロックを読み取る例を説明します。
まず GetMeshBlockInfos() で全ブロックのメタデータ(ワールド空間のバウンディングボックスを含む)を取得し、ワールド座標で レンダリングカメラ中心からブロック BBOX 表面までの距離 を計算し、距離が 100 m 未満のブロック ID をフィルタリングし、その後パラメータ付きの GetMeshes(ids) でそれらのブロックのみを読み取ります。
Meshes.cs に以下のメソッドを追加します。
public void onRequireMeshes()
{
if (!m_isLoaded || m_renderer == null)
{
Debug.LogWarning("[Meshes] データの読み込みが完了していないため、Mesh を取得できません。");
return;
}
// 1. 全ブロックメタデータのゼロ I/O 読み出し。カメラから BBOX 表面までの距離で 100 m 以内のブロックをフィルタ
var infos = m_renderer.GetMeshBlockInfos();
if (infos == null || infos.Count == 0)
{
Debug.LogWarning("[Meshes] Mesh 情報を取得できませんでした(データにコライダーが含まれていない可能性があります)。");
return;
}
Vector3 camPos = m_camera.transform.position;
var ids = new List<string>();
foreach (var info in infos)
{
// カメラが BBOX 内にある場合、ClosestPoint はカメラ自身を返し距離は 0 になるため、それも選択される
if (calculateMinDistanceToAABB(info.boundsWorld, camPos) < 100f)
ids.Add(info.id);
}
if (ids.Count == 0)
{
Debug.LogWarning("[Meshes] カメラから 100 m 以内に Mesh ブロックがありません。");
return;
}
// 2. フィルタされたブロックのみを ID で読み込み、可視化
var meshes = m_renderer.GetMeshes(ids);
if (meshes == null || meshes.Count == 0)
{
Debug.LogWarning("[Meshes] ID による Mesh の取得に失敗しました。");
return;
}
VisualizeMeshes(meshes);
Debug.Log($"[Meshes] カメラから 100 m 以内の {ids.Count} 個のブロックを選択し、{meshes.Count} 個を可視化しました。");
}
// 点から AABB 表面までの最短距離を計算(点が AABB 内の場合は 0 を返す)
private static float calculateMinDistanceToAABB(Bounds _aabb, Vector3 _point)
{
Vector3 p = _aabb.ClosestPoint(_point);
return Vector3.Distance(p, _point);
}
注:
GetMeshBlockInfos()は ゼロ I/O・ゼロ解析 の操作で、メモリ上に既にあるメタデータのみを読み取ります。自由に呼び出せます。- 距離フィルタは
boundsWorld(ワールド空間のバウンディングボックス)を使用し、カメラ中心はCamera.transform.positionを取ります。Bounds.ClosestPointで点から AABB 表面までの最短距離を計算します。カメラが BBOX 内にある場合、ClosestPointはカメラ自身を返し距離は 0 になるため、そのブロックも選択されます。- 例のフィルタ半径
100f(単位:メートル)は必要に応じて調整できます。
Button を作成し、onRequireMeshes() をその onClick イベントに関連付けます。
実行後、ボタンをクリックするとカメラから 100 m 以内の Mesh ブロックを確認できます。

カメラから 100 m 以内の Mesh データをオンデマンドで取得
重要な手順
シーン Mesh データを取得して可視化するための重要な手順は以下のとおりです。
SetPlatformTypeなどの事前設定を完了し、GetRender+Loadで LCC シーンを読み込みます。- データがコリジョン Mesh データを保持していることを確認します(ネイティブ形式や以前のバージョンで再構築されたデータにはない場合があります)。
- 全量取得:
GetMeshes()で全ブロックを一度に読み取り、繰り返し呼び出しを避けるため結果を自分でキャッシュします。 - オンデマンド取得:まず
GetMeshBlockInfos()でブロックのワールドバウンディングボックスを取得し、カメラ距離で ID をフィルタし、GetMeshes(ids)で対象ブロックのみを読み取ります。 - 可視化時は、ローカル座標にアライメントするため GameObject を
Renderer.Transformの下にアタッチし、IndexFormat.UInt32で Mesh を構築します。
Mesh 取得 API
以下のメソッドはすべて Renderer にあり、シーン読み込み完了後 に呼び出す必要があり、データがコリジョン Mesh データを保持している必要があります。
GetMeshBlockInfos
// 戻り値: List<LCCMeshBlockInfo> → コリジョン Mesh ブロックのメタデータ。Mesh がないか取得不可の場合は null
public List<LCCMeshBlockInfo> GetMeshBlockInfos()
全コリジョン Mesh ブロックのメタデータ(ID、ワールド/ローカルバウンディングボックス、頂点数、面数)を返します。ゼロ I/O・ゼロ解析で、メモリ上に既にあるメタデータのみを読み取ります。
使用例:
var infos = renderer.GetMeshBlockInfos();
foreach (var info in infos)
{
Debug.Log($"{info.id}: verts={info.vertexCount}, faces={info.faceCount}, bbox={info.boundsWorld.size}");
}
注:
- PC / Mac のみ + ローカルデータソース。メインスレッドのみ。
- 典型的な使い方:まず各ブロックのワールド BBox を取得し、必要な ID を選び、
GetMeshes(ids)でそれらのブロックのみを読み取ります。 - ネイティブ形式(PLY / SOG / SPZ / splat / ksplat)には Mesh がなく、
nullを返します(正常であり、ログは出力されません)。
GetMeshes
// 戻り値: List<LCCMeshBlock> → 全コリジョン Mesh ジオメトリデータ。取得不可の場合は null
public List<LCCMeshBlock> GetMeshes()
全コリジョン Mesh ブロックのジオメトリデータ(頂点 + 三角形インデックス、ローカル座標系)を同期的に返します。これは重い操作(同期ディスク読み取り + 解析 + 割り当て)なので、一度呼び出して自分でキャッシュし、毎フレーム呼び出さないでください。
使用例:
var meshes = renderer.GetMeshes();
foreach (var block in meshes)
{
// block.vertices / block.triangles はオブジェクトローカル座標系
}
注:
- PC / Mac のみ + ローカルデータソース。メインスレッドのみ。
- 大規模シーンでは、一度に大量のマネージメモリを割り当てるのを避けるため、
GetMeshBlockInfos()で ID を選び +GetMeshes(ids)を使用します。 - 1 ブロックの頂点数は 65535 を超える場合があります。独自の Mesh を構築する前に
mesh.indexFormat = IndexFormat.UInt32を設定する必要があります。
GetMeshes(ID 指定)
// 戻り値: List<LCCMeshBlock> → 指定 ID のコリジョン Mesh ジオメトリデータ。ヒットがない場合は null
public List<LCCMeshBlock> GetMeshes(IEnumerable<string> _ids)
指定した ID のコリジョン Mesh ブロックのジオメトリデータ(ローカル座標系)を同期的に返します。ID は GetMeshBlockInfos() が提供します。
| パラメータ | 型 | 説明 |
|---|---|---|
_ids | IEnumerable<string> | 対象ブロック ID コレクション(GetMeshBlockInfos() が提供) |
使用例:
var ids = new List<string> { "block-0", "block-3" };
var meshes = renderer.GetMeshes(ids);
注:
- 不明な ID は警告付きでスキップされます。重複 ID は重複排除されます(ディスクから繰り返し読み取りません)。戻り順は ID の走査順(重複排除後)です。
null、空入力、ヒットなしの場合は警告付きでnullを返します。- その他の制約は
GetMeshes()と同じです。
LCCMeshBlockInfo データ構造
public struct LCCMeshBlockInfo {
public string id; // ノード ID。LCCMeshBlock.id に対応
public Bounds boundsWorld; // ワールド空間 AABB。ID の選択に使用
public Bounds boundsLocal; // ローカル空間 AABB
public int vertexCount; // 頂点数
public int faceCount; // 面数
}
| フィールド | 型 | 説明 |
|---|---|---|
id | string | ノード ID。LCCMeshBlock.id に対応 |
boundsWorld | Bounds | ワールド空間 AABB。ID の選択に使用 |
boundsLocal | Bounds | ローカル空間 AABB |
vertexCount | int | 頂点数 |
faceCount | int | 面数 |
LCCMeshBlock データ構造
public struct LCCMeshBlock {
public string id; // ノード ID
public Vector3[] vertices; // 頂点。ローカル座標系
public int[] triangles; // 三角形インデックス。1 三角形あたり 3 個
public Bounds boundsLocal; // 頂点と同じ空間のバウンディングボックス
}
| フィールド | 型 | 説明 |
|---|---|---|
id | string | ノード ID |
vertices | Vector3[] | 頂点配列。オブジェクトローカル座標系 |
triangles | int[] | 三角形インデックス。1 三角形あたり 3 個 |
boundsLocal | Bounds | 頂点と同じ空間のバウンディングボックス |
注:
vertices/boundsLocalはどちらも オブジェクトローカル座標系 です。Unity で可視化するには、Mesh を保持する GameObject をRenderer.Transformの下にアタッチして自動的にアライメントします。1 ブロックの頂点数は 65535 を超える場合があります。独自の Mesh を構築する前にmesh.indexFormat = IndexFormat.UInt32を設定する必要があります。