エッジ・コーナースナップ開発ガイド
LCC Unity SDK は、3DGS サーフェス上でのエッジ・コーナー検出とスナッププレビューに対応しており、計測、注釈、配置など正確な位置決めが必要なインタラクションシナリオで使用できます。以下にエッジ/コーナー検出とスナップを実装する完全な手順を説明します。
注:
スナップ機能(エッジ/コーナー検出、スナッププレビュー、スナップ付きレイキャスティング)は PC / Mac プラットフォームのみ対応です。モバイル / VR プラットフォームで関連 API を呼び出しても効果がなく、警告が出力されます。
手順 1:シーンと GameObject を作成する
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 データの座標系」を参照してください。
手順 2:Render Feature を追加する
- Unity メニューバーで File → Project Settings を選択し、左側の Graphics を選び、現在の Default Render Pipeline Asset を確認します。この例では URP(Universal Render Pipeline Asset)を使用します。

Graphics 設定で Default Render Pipeline Asset を確認
その Asset をクリックし、Project ウィンドウで対応するアセットファイルを特定し、関連付けられた Renderer(この例では
URP_Renderer.asset)を見つけます。その Renderer アセットファイルを選択し、Inspector ウィンドウで Add Renderer Feature をクリックし、Snap Frame Capture Feature を追加します。

URP Renderer に Snap Frame Capture Feature を追加
手順 3:スナップを有効化するスクリプトを書く
Snap.cs という名前の新しい C# スクリプトを以下のコードで作成します。
using LCCCore;
using UnityEngine;
public class Snap : MonoBehaviour
{
public LCCManager m_manager;
public string m_FilePath;
private LCCCore.Renderer m_renderer;
private bool m_loaded = false;
private Camera m_camera;
void Start()
{
m_camera = Camera.main;
m_manager.SetPlatformType(PlatformType.PC);
Debug.Log("開始しました ");
m_renderer = m_manager.GetRender(this.transform);
m_renderer.Load(m_FilePath, onProgress, onComplete);
}
private void onProgress(float _progress)
{
Debug.Log($"[LCCRenderer] ファイルの読み込み進捗: {_progress:P1}");
}
private void onComplete()
{
Debug.Log($"[LCCRenderer] ファイルを読み込みました");
m_loaded = true;
// エッジ/コーナー検出とスナッププレビューを有効化
m_manager.SetSnapEnabled(true);
m_manager.SetSnapPreviewEnabled(true);
}
private void Update()
{
if (!m_loaded) return;
// 現在のマウス位置に基づいてスナッププレビューをリアルタイムに更新
m_manager.UpdateSnapPreview(Input.mousePosition, m_camera);
if (Input.GetMouseButtonDown(1))
{
m_manager.RaycastWithSnap(Input.mousePosition, m_camera, out HitResult result);
if (result.isHit)
{
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = result.hitPos;
sphere.GetComponent<UnityEngine.Renderer>().material.color = Color.red;
sphere.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
}
}
}
}
注:
エッジ/コーナー検出(
SetSnapEnabled)とスナッププレビュー(SetSnapPreviewEnabled)は LCC シーンの読み込み完了後 に有効化する必要があるため、両方ともonCompleteコールバックで呼び出します。スナッププレビューはUpdateでUpdateSnapPreviewを継続的に呼び出して更新する必要もあります。
上記の例は以下の点を実演しています。
- エッジとコーナーの検出を有効化:
m_manager.SetSnapEnabled(true) - スナッププレビューを有効化:
m_manager.SetSnapPreviewEnabled(true) - スナッププレビューをリアルタイム更新:
m_manager.UpdateSnapPreview(Input.mousePosition, m_camera) - スナップ点でレイキャスティングし、ヒット点に球を作成:
m_manager.RaycastWithSnap(...)。ヒットするとresult.hitPosに赤い球が作成されます。
手順 4:スクリプトをアタッチしてデータパスを設定する
Snap.csスクリプトを Hierarchy ウィンドウの LCCRenderer GameObject にアタッチします。- Inspector ウィンドウで LCCManager GameObject をスクリプトの Manager プロパティへドラッグします。
- スクリプトの File Path プロパティに LCC ファイルパスを入力します。
手順 5:実行する
Game ウィンドウの Play ボタンをクリックすると、リアルタイムに検出されたエッジ・コーナーとスナップターゲットのプレビューアイコンが表示されます。マウスを動かすと、プレビューアイコンが最も近いスナップターゲットにリアルタイムに追従します。右クリックするとスナップ点でレイキャスティングを行い、赤い球を作成します。

実行後にリアルタイムで検出されたエッジ・コーナーとスナッププレビューアイコン
FAQ:互換モードを無効にする
Unity の Console ウィンドウにエラーが報告され、スナッププレビューアイコンが表示されない場合は、互換モードを無効にします。Edit → Project Settings → Graphics から設定パネルを開き、URP タブを選択し、Render Graph → Compatibility Mode (Render Graph Disabled) の後ろのチェックボックスにチェックを入れないでください。

Render Graph 互換モード(Render Graph Disabled)にチェックしない
重要な手順
エッジ/コーナー検出とスナップを実装するための重要な手順は以下のとおりです。
- URP Renderer に Snap Frame Capture Feature が追加されていることを確認します。
- LCC シーンの読み込み完了後 にコードでエッジ/コーナー検出(
SetSnapEnabled(true))とスナッププレビュー(SetSnapPreviewEnabled(true))が有効化されていることを確認します。 - プレビューアイコンを更新するため、
UpdateでUpdateSnapPreviewがリアルタイムに呼び出されていることを確認します。
スナップ関連 API
LCC Unity SDK は、LCCManager を通じて以下のスナップ関連 API を提供します。これらは PC / Mac プラットフォームのみ対応 です(モバイル / VR プラットフォームで呼び出しても効果がなく、警告が出力されます)。また、いずれも LCC シーンの読み込み完了後 に呼び出す必要があります。
SetSnapEnabled
// 戻り値: void
public void SetSnapEnabled(bool enable)
スナップ機能を切り替えます。有効にすると、RaycastWithSnap が近くのエッジ/コーナーを自動検索してスナップします。
| パラメータ | 型 | 説明 |
|---|---|---|
enable | bool | スナップを有効にするかどうか |
使用例:
m_manager.SetSnapEnabled(true);
注:
- デフォルトでは無効です。
- PC / Mac のみ対応。モバイル / VR で呼び出しても効果がなく、警告が出力されます。
- 有効にすると GPU リソースが割り当てられます(EdgeDetect / CornerDetect / SnapSearch)。
SetSnapRadius
// 戻り値: void
public void SetSnapRadius(float pixels)
スナップ検索半径を設定します。このピクセル範囲内で最も近いエッジ/コーナーを検索します。
| パラメータ | 型 | 説明 |
|---|---|---|
pixels | float | スナップ検索半径(ピクセル) |
使用例:
m_manager.SetSnapRadius(30f);
注:
- デフォルトは 20 ピクセルです。
- 値が大きいほど検索範囲は広くなりますが、意図しないターゲットにスナップする可能性があります。
- PC / Mac のみ対応。
SetEdgeThreshold
// 戻り値: void
public void SetEdgeThreshold(float threshold)
Sobel エッジ検出の閾値を設定します。値が小さいほど、より多くのエッジが検出されます。
| パラメータ | 型 | 説明 |
|---|---|---|
threshold | float | エッジ検出閾値 |
使用例:
m_manager.SetEdgeThreshold(0.2f);
注:
- デフォルトは 0.3 です。
- 推奨範囲
[0.1, 0.8]。 - 値が小さすぎると大量のノイズエッジが発生します。
- PC / Mac のみ対応。
SetSnapPreviewEnabled
// 戻り値: void
public void SetSnapPreviewEnabled(bool enable)
スナッププレビュー機能を切り替えます。有効にすると、マウスの移動に応じて最も近いスナップターゲットのインジケータがリアルタイムに表示されます。
| パラメータ | 型 | 説明 |
|---|---|---|
enable | bool | スナッププレビューを有効にするかどうか |
使用例:
m_manager.SetSnapPreviewEnabled(true);
注:
- 先に
SetSnapEnabled(true)を呼び出す必要があります。 - プレビューは継続的に GPU リソースを消費します(毎フレーム検出)。
- PC / Mac のみ対応。
UpdateSnapPreview
// 戻り値: void
public void UpdateSnapPreview(Vector2 mousePos, Camera cam)
スナッププレビュー状態を更新します。マウスの移動に応じて呼び出し、プレビューインジケータの位置を更新します。
| パラメータ | 型 | 説明 |
|---|---|---|
mousePos | Vector2 | 現在のマウスのスクリーン座標 |
cam | Camera | カメラ |
使用例:
void Update()
{
m_manager.UpdateSnapPreview(Input.mousePosition, Camera.main);
}
注:
- 先に
SetSnapPreviewEnabled(true)を呼び出す必要があります。 Updateで継続的に呼び出すことを推奨します。- PC / Mac のみ対応。
GetSnapPreviewState
// 戻り値: SnapPreviewState(screenPos はスクリーン座標)
public SnapPreviewState GetSnapPreviewState()
現在のスナッププレビュー状態を取得します。スナップターゲットがあるかどうか、ターゲットのタイプ、ターゲットのスクリーン座標、マウスまでのピクセル距離が含まれます。
使用例:
var state = m_manager.GetSnapPreviewState();
if (state.hasTarget)
{
// state.snapType: Corner / Edge
// state.screenPos: スナップターゲットのスクリーン座標
// state.distanceToMouse: ピクセル距離
}
注:
- 先に
SetSnapPreviewEnabled(true)とUpdateSnapPreviewを呼び出す必要があります。 - ターゲットがない場合は
default(SnapPreviewState)を返します。 screenPosはスクリーン座標であり、ワールド座標ではありません。ワールド座標が必要な場合はRaycastWithSnapでHitResult.hitPosを取得します。- PC / Mac のみ対応。
RaycastWithSnap
// 戻り値: bool(true = ヒット)
public bool RaycastWithSnap(Vector2 screenPos, Camera cam, out HitResult result)
スナップ付きレイキャスティング。まず通常の Raycast を実行し、その後ヒット点付近のエッジ/コーナーを検索してスナップします。
| パラメータ | 型 | 説明 |
|---|---|---|
screenPos | Vector2 | スクリーン座標 |
cam | Camera | カメラ |
result | out HitResult | ヒット結果(スナップ情報を含む) |
使用例:
if (m_manager.RaycastWithSnap(Input.mousePosition, m_camera, out HitResult hit))
{
// hit.snapType: Corner / Edge / None
// hit.hitPos: スナップ後のワールド座標
// hit.originalHitPos: スナップ前の元のヒット点
}
注:
- 先に
SetSnapEnabled(true)を呼び出す必要があります。 - スナップ優先順位:Corner > Edge > None。
- このエントリポイントは メインスレッドを同期的にブロックします(GPU 読み戻し結果を待つため)。毎フレームではなく、クリックなどのインタラクションイベントでのみ呼び出すことを推奨します。
- プレビューモード(
SetSnapPreviewEnabled(true))では、スナップターゲットが既に存在する場合、プレビューキャッシュが再計算なしで直接再利用されます。それ以外は通常のスナップフローを使用します。 - PC / Mac のみ対応。
関連データ構造
SnapType
public enum SnapType { None, Edge, Corner }
スナップターゲットタイプ:なし / エッジ / コーナー。
SnapPreviewState
public struct SnapPreviewState {
public bool hasTarget; // スナップターゲットがあるかどうか
public SnapType snapType; // None / Edge / Corner
public Vector2 screenPos; // スナップターゲットのスクリーン座標(ワールド座標ではない)
public float distanceToMouse; // マウス位置までのピクセル距離
}
⚠️
screenPosはスクリーンピクセル座標であり、ワールド座標ではありません。ワールド座標が必要な場合はRaycastWithSnapでHitResult.hitPosを取得します。
HitResult
public struct HitResult {
public bool isHit; // ヒットしたかどうか
public float distance; // 距離
public Vector3 hitPos; // ヒット点のワールド座標(フィールド名は position ではなく hitPos)
public Vector3 normal; // ヒット法線
public float hitAlpha; // ヒットした楕円体のアルファ
public float dist2Origin; // レイ原点までの距離
public float scale; // 楕円体のスケール
public SnapType snapType; // スナップタイプ(None / Edge / Corner)
public Vector3 originalHitPos; // スナップ前の元のヒット点
}
⚠️ フィールド名の違い:ヒット点ワールド座標のフィールドは
hitPos(positionではありません)。呼び出し側は実際のフィールド名でアクセスする必要があります。