Appearance
AM3D — Looking Glass キルト再生アプリ開発
本記事では、展示会向けに開発した Looking Glass キルト再生アプリ「AM3D」の設計・実装・問題解決をまとめています。 水上が 2025 年 5 月から 10 月にかけて行った開発作業記録 3 本を統合した内容です。
概要
AM3D は、Looking Glass ディスプレイ上でキルト画像・動画をレンチキュラー変換して再生する専用プレイヤーアプリです。 LKG Studio ではなく、自社製品として展示会で「XSE 製のアプリです」と紹介できることが企画上の重要な要件でした。
主な機能は以下のとおりです。
| 機能 | 説明 |
|---|---|
| キルト画像再生 | PNG/JPG のキルト画像をレンチキュラー変換して LKG に表示します |
| キルト動画再生 | MP4 のキルト動画をフレーム単位でレンチキュラー変換し再生します |
| ループ再生 | 単一ファイルループ、全体ループの 2 モードがあります |
| 再生順制御 | ファイル名順での再生に加え、プレイリスト管理に対応します |
| フォルダ切り替え | キー操作でコンテンツフォルダを切り替えて表示できます |
| デバイス別管理 | LKG のシリアル番号ごとにレンチキュラーデータを保存します |
プロジェクト要件
技術スタック
- エンジン: Unity 6000.1.5(Windows)
- レンダーパイプライン: URP(Forward / Forward+、RenderGraph 有効)
- LKG プラグイン: Looking Glass Plugin 4.0.1-alpha-6.1
- ミドルウェア: Looking Glass Bridge 2.6.0(常駐)
- UI フレームワーク: UI Toolkit(UXML / USS)
- ファイル選択: StandaloneFileBrowser
- 動画処理: ffmpeg(StreamingAssets/ffmpeg/ffmpeg.exe に配置)
- ランタイム: .NET Framework 4.x(ごみ箱削除に Microsoft.VisualBasic.FileIO を使用)
機能要件の優先順位
UI は優先度が低く設定されていました。 まずキーボード操作で全機能が動作する状態を目標とし、余力があれば GUI を整える方針で進めています。 iPad 対応は将来的な検討事項として残しており、Win 専用の DLL 依存を避ける調査に最大 1 時間までと制限されていました。
アーキテクチャ
2 アプリ構成
AM3D は UI アプリと LKG 表示アプリの 2 つに分かれています。 当初は 1 つのアプリ内で 2 カメラを使って 2 ディスプレイに出力していましたが、以下の理由で分離しました。
- UI 側のアスペクト比設定が不要になります
- LKG アプリを完全フルスクリーン化できます
- 65 インチなどの大型ディスプレイへの対応が容易になります
起動フロー
UI アプリ起動
→ Bridge の起動・LKG 接続確認
→ LKG アプリ自動起動(--serial=XXXX で対象ディスプレイ指定)
→ LKG にフルスクリーン表示UI アプリを終了すると LKG アプリも連動して終了します。
シーン構成
| シーン | 役割 |
|---|---|
| UIApp.unity | UI Toolkit による操作画面。BridgeInterface / QuiltImagePipeline / QuiltVideoPipeline / LKGLauncher を配置 |
| LKGApp.unity | LKGFullscreenBoot によるフルスクリーン表示。ビルド後に UI アプリから自動起動されます |
ディレクトリ構成(Unity プロジェクト)
Assets/AM3D/LKG/Core/ 共通ユーティリティ(CalibrationSync, ControlJsonIO)
Assets/AM3D/LKG/Core/Shaders/ Hidden/AM3D/Crop.shader
Assets/AM3D/LKG/UI/ UI スクリプト + QuiltImagePipeline / QuiltVideoPipeline
Assets/AM3D/LKG/UI/UITK/ AM3D_UI.uxml / AM3D_UI.uss
Assets/AM3D/LKG/Player/ LKGFullscreenBoot / LenticularPlayer保存フォルダ構成
デバイスごとにレンチキュラーデータを分離管理します。
%USERPROFILE%\Documents\AM3D-Lenticular\{Serial}\
├── Image\ レンチキュラー画像(PNG)
├── Video\ レンチキュラー動画(MP4)
├── Thumbnails\ サムネイル(PNG)
├── control.json プレイリスト管理
└── visual.json キャリブレーション キャッシュcontrol.json は serial / selected / loopMode(single|all)/ entries[]({path, type})で構成されます。 パスは Image/ / Video/ 基準の相対パスで、LKGFolderLayout.NormalizeRelative で正規化されます。
データフロー(画像編)
キルト画像をレンチキュラー画像に変換する処理の流れです。
Quilt PNG/JPG → Texture2DArray → Lenticular shader → PNG 保存 → 表示1. ファイル選択
QuiltImagePipeline(改善版は QuiltImagePipelineImproved)が StandaloneFileBrowser で画像を開き、ProcessFile(path) を呼び出します。
2. メタ解析
QuiltNameParser がファイル名からタイル構成を解析します。
ファイル名の規則: {名前}_qs{cols}x{rows}a{aspect}.png
例: Clark_Kent_Dog_qs8x6a0.75.png → cols=8, rows=6, aspect=0.75_qs{cols}x{rows}a0.75 もしくは _qs{cols}x{rows}a.75 の形式のみを許容しています。
3. Quilt から Texture2DArray への展開
LenticularBaker.BuildArrayFromQuilt() がタイルを展開します。
- 各タイルを
viewIdx = row * cols + colの順でTexture2DArrayに積層します - フィルタ / ラップは Point / Clamp を使用します(改善版では滑らかさ調整あり)
4. キャリブレーション反映
CalibrationSync が MultiviewData の cal から以下のパラメータを取得し、マテリアルとシェーダーグローバルに反映します。
- pitch / slope / center / viewCone / DPI
- flipSubp / screenW / screenH
5. レンチキュラー合成と保存
LenticularBaker.BakeArrayToPngAndSave() で以下の処理を実行します。
- sRGB の
RenderTextureにレンチキュラー合成をレンダリングします EncodeToPNG()で PNG 化しますDocuments/AM3D-Lenticular/{Serial}/Image/に保存します
6. 表示
LenticularPlayer が control.json を読み、RawImage に PNG を表示します(sRGB 考慮)。
データフロー(動画編)
キルト動画をレンチキュラー動画に変換する処理の流れです。
Quilt MP4 → ffmpeg フレーム抽出 → フレームごとにレンチキュラー化 → ffmpeg で音声付き MP4 再多重化処理の詳細
QuiltVideoPipeline が FFmpegRunner を介して ffmpeg をサブプロセス起動します。
- フレーム抽出: 入力 MP4 から
out_000000.png形式で PNG 連番を抽出します - デコーダ選択: コーデックに応じたデコーダ候補を順次試行します(AV1 失敗時は中間 H.264 化へフォールバック)
- フレーム変換: 各フレームに対して画像パイプラインと同じ手順でレンチキュラー合成を行います
- 再多重化: 変換済みフレームと元動画の音声を ffmpeg で MP4 に結合します
出力先は Documents/AM3D-Lenticular/{Serial}/Video/ です。
再生
LenticularPlayer が VideoPlayer + sRGB RenderTexture の組み合わせで動画を再生します。 画像の初期ローテーション間隔は 20 秒、動画は 1 再生で次のコンテンツに切り替わります。
ffmpeg コマンド例
bash
# フレーム抽出
ffmpeg -i input.mp4 -vf "fps=30" out_%06d.png
# レンチキュラー動画の組み立て
ffmpeg -y -framerate 30 -i "lenticular_%06d.png" \
-i input.mp4 -map 0:v -map 1:a \
-c:v libx264 -preset fast -crf 8 -pix_fmt yuv420p \
output_lenticular.mp4キャリブレーション
Looking Glass ディスプレイは個体ごとにレンズ特性が異なるため、デバイス固有のキャリブレーションデータが必要です。
主要キャリブレーションパラメータ
| パラメータ | 例(LKG-E07151) | 用途 |
|---|---|---|
| pitch | 80.755 | レンズの繰り返し間隔(px単位) |
| slope | -6.660 | 傾き補正 |
| center | 0.533 | レンチキュラーパターンの位相 |
| viewCone | 54.0 | ビュー間の角度範囲 |
| DPI | 491 | ディスプレイのピクセル密度 |
| screenW / screenH | 1440 / 2560 | ディスプレイ解像度 |
| flipSubp | 0 | サブピクセル反転フラグ |
取得経路
| プラットフォーム | 取得方法 |
|---|---|
| デスクトップ | BridgeInterface(LookingGlass.Toolkit.BridgeConnectionHTTP)経由で Bridge に接続し、LKG ディスプレイ一覧とキャリブを取得します |
| iOS | CalibrationLoader が初回実行時に端末から取得し永続化します |
シェーダーへの反映
CalibrationSync が以下のシェーダープロパティにキャリブレーション値を設定します。
_Pitch / _Slope / _Center / _ViewCone / _DPI
_FlipSubp / _ScreenWidth / _ScreenHeightApplyToMaterial でマテリアル単位に、ApplyShaderGlobals でグローバルに反映する 2 つの経路があります。
プレビュー確認
KlakPreviewSender が Spout / Syphon でプレビューを送出します。 Bridge の Debug View で表示結果を確認できます。
シェーダー
標準シェーダー(Lenticular.shader)
Looking Glass Plugin に含まれる標準のレンチキュラー合成シェーダーです。 Texture2DArray の各ビューを、サブピクセル配列・ピッチ / スロープ / センター・回転情報を使って縦縞インターレースとして合成します。
改善版シェーダー(Lenticular_Improved.shader)
AM3D 側で段差・暗化対策として開発した改善版です。以下の改良が含まれています。
| 改善点 | 内容 |
|---|---|
| 滑らかなビューブレンド | smoothstep 関数によりビュー間の遷移がなめらかになります |
| ガンマ補正 | リニア空間での合成後に正しいガンマ変換を行い、明度を改善します |
| 適切なフィルタリング | Texture2DArray 生成時のフィルタ設定を最適化しています |
| sRGB 徹底 | RenderTexture の sRGB フラグを正しく設定し、色空間の一貫性を確保します |
多視点生成シェーダー
GenViews.shader / GenViewsCompute.compute は深度+色から多視点を生成する経路ですが、 AM3D の静止画 / 動画パイプラインでは主に Quilt → Texture2DArray → Lenticular の経路を使用しています。
開発で遭遇した問題と解決策
| # | 時期 | 問題 | 原因 | 解決策 |
|---|---|---|---|---|
| 1 | 5/28 | 出力画像が真っ黒 | シェーダーへのパラメータ渡しの不具合 | デバッグログ追加で箇所を特定し修正 |
| 2 | 5/29 | キャリブレーションデータが接続デバイスと不一致 | Bridge からの取得ロジックの誤り | 接続中デバイスの値を正しく反映するよう修正 |
| 3 | 6/5 | サイズ違いのキルトで画像が半分に割れて反転 | シェーダーパラメータの例外処理不足 | サイズ違い時のパラメータ再計算ロジックを実装 |
| 4 | 6/18 | 動画プレビューが表示されない | 変換完了前に表示処理が走る | 変換完了を待ってから表示するようタイミング調整 |
| 5 | 7/18 | LKG 側で出力画像が回転 | 2 アプリ構成への変更で座標系の扱いが変化 | 変換パイプラインの座標系処理を再調整 |
| 6 | 10/22 | 動画の保存先パスが不正 | 動画パイプラインの保存方法が画像と異なる実装 | LKGFolderLayout の正規化を動画にも適用 |
| 7 | 10/22 | ビルド後に LKG へ描画されない(Skybox のみ) | ビルド環境でのフォルダ参照パスの不整合 | ログ増強で不整合を特定しパス解決ロジックを修正 |
特に問題 2 のキャリブレーション不一致は、正しい値でないとレンチキュラーが完全に崩れるため影響が大きく、デバッグに時間を要しました。
主要スクリプト一覧
| レイヤー | スクリプト | 役割 |
|---|---|---|
| AM3D | QuiltImagePipeline / Improved | キルト画像の読み込み → レンチキュラー PNG 出力 |
| AM3D | QuiltVideoPipeline | 動画の一括処理(抽出 → 合成 → 再多重化) |
| AM3D | LenticularBaker / Improved | Quilt → Texture2DArray → 合成 → PNG 保存 |
| AM3D | LenticularPlayer | control.json に基づく PNG/MP4 の表示再生 |
| AM3D | CalibrationSync | MultiviewData.cal をマテリアル / グローバルに反映 |
| AM3D | QuiltNameParser | ファイル名から cols/rows/aspect を抽出 |
| AM3D | ControlJsonIO / LKGFolderLayout | プレイリスト管理とフォルダ規約 |
| AM3D | FFmpegRunner | ffmpeg 呼び出し・ログ解析・フォールバック |
| Plugin | CalibrationLoader | キャリブレーション取得・適用(iOS / Standalone / Bridge) |
| Plugin | BridgeInterface | Bridge 接続・LKG ディスプレイ列挙(HTTP) |
| Plugin | KlakPreviewSender | Spout / Syphon によるプレビュー送出 |
AI 補完
AI 補完
以下のセクションは、元の作業記録にはない補足情報を AI が追記したものです。 技術的背景の理解や、類似プロジェクトでの応用を助ける目的で記載しています。
レンチキュラー変換にデバイス個別キャリブレーションが必要な理由
Looking Glass ディスプレイはレンチキュラーレンズのピッチやサブピクセル配列が個体ごとに異なります。 pitch(レンズ間隔)、slope(傾き補正)、center(位相)、viewCone(角度範囲)が正しくないと、ビューの割り当てがずれて立体視が崩れます。 AM3D の問題 2 はこの原理に起因しており、Bridge が動作している状態でアプリを起動し、正しいキャリブレーションを取得することが必須です。
URP vs HDRP の LKG 互換性
Looking Glass Plugin 4.0.1 は URP(Forward / Forward+)+ RenderGraph を前提としており、HDRP はサポート対象外です。 HDRP のカメラスタック制御方式が LKG の多視点レンダリング挿入と互換性がないことが主な理由です。 URP の Forward レンダラーはパス構成がシンプルで、多視点分離処理の挿入に適しています。 Unity 6 では RenderGraph が URP の標準となっており、Looking Glass/Project Setup エディタ拡張から設定を適用できます。
Unity における ffmpeg 統合のベストプラクティス
AM3D では動画パイプラインに ffmpeg を使用しています。統合時の推奨事項は以下のとおりです。
- 配置:
StreamingAssets/ffmpeg/ffmpeg.exeに同梱し、Application.streamingAssetsPathでパス解決します。展示環境の再現性を考慮すると PATH 依存より同梱が安全です - プロセス管理:
System.Diagnostics.Processで起動し、RedirectStandardErrorで ffmpeg の進捗(stderr 出力)を非同期読み取りして Unity 側で進捗表示できます - フォールバック: AM3D の
FFmpegRunnerは AV1 デコード失敗時に中間 H.264 化を挟む設計です。複数デコーダ候補の順次試行が多様な動画形式への対応力を高めます - 非同期実行: コルーチンで
Process.HasExitedをポーリングするか、Task.Runで別スレッドに逃がしてメインスレッドのブロックを避けます
sRGB カラースペースの考慮事項
AM3D のパイプラインでは sRGB の一貫した取り扱いが重要です。
- RenderTexture: レンチキュラー合成先は
sRGB = trueで作成します。false だとリニア空間出力がそのまま書き込まれ暗くなります - Texture2D 読み込み:
LoadImage()はデフォルトで sRGB として扱い、シェーダー内でリニア変換 → 計算 → sRGB 書き戻しの流れになります - 改善版シェーダー:
Lenticular_Improved.shaderはリニア空間でsmoothstepブレンドを行い、最終段でガンマ変換を適用して暗化問題を解決しています - PNG 保存:
ReadPixelsは sRGB フラグに応じて自動変換が行われるため、フラグの設定ミスで色味が変わる可能性があります
Author: 水上 | Sources:
lkg-キルト-再生アプリ.md,lkg-キルト-再生アプリ2.md,lkg-キルト-再生アプリ3.md