Skip to content

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.unityUI Toolkit による操作画面。BridgeInterface / QuiltImagePipeline / QuiltVideoPipeline / LKGLauncher を配置
LKGApp.unityLKGFullscreenBoot によるフルスクリーン表示。ビルド後に 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.jsonserial / 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. キャリブレーション反映

CalibrationSyncMultiviewDatacal から以下のパラメータを取得し、マテリアルとシェーダーグローバルに反映します。

  • pitch / slope / center / viewCone / DPI
  • flipSubp / screenW / screenH

5. レンチキュラー合成と保存

LenticularBaker.BakeArrayToPngAndSave() で以下の処理を実行します。

  1. sRGB の RenderTexture にレンチキュラー合成をレンダリングします
  2. EncodeToPNG() で PNG 化します
  3. Documents/AM3D-Lenticular/{Serial}/Image/ に保存します

6. 表示

LenticularPlayercontrol.json を読み、RawImage に PNG を表示します(sRGB 考慮)。


データフロー(動画編)

キルト動画をレンチキュラー動画に変換する処理の流れです。

Quilt MP4 → ffmpeg フレーム抽出 → フレームごとにレンチキュラー化 → ffmpeg で音声付き MP4 再多重化

処理の詳細

QuiltVideoPipelineFFmpegRunner を介して ffmpeg をサブプロセス起動します。

  1. フレーム抽出: 入力 MP4 から out_000000.png 形式で PNG 連番を抽出します
  2. デコーダ選択: コーデックに応じたデコーダ候補を順次試行します(AV1 失敗時は中間 H.264 化へフォールバック)
  3. フレーム変換: 各フレームに対して画像パイプラインと同じ手順でレンチキュラー合成を行います
  4. 再多重化: 変換済みフレームと元動画の音声を ffmpeg で MP4 に結合します

出力先は Documents/AM3D-Lenticular/{Serial}/Video/ です。

再生

LenticularPlayerVideoPlayer + 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)用途
pitch80.755レンズの繰り返し間隔(px単位)
slope-6.660傾き補正
center0.533レンチキュラーパターンの位相
viewCone54.0ビュー間の角度範囲
DPI491ディスプレイのピクセル密度
screenW / screenH1440 / 2560ディスプレイ解像度
flipSubp0サブピクセル反転フラグ

取得経路

プラットフォーム取得方法
デスクトップBridgeInterfaceLookingGlass.Toolkit.BridgeConnectionHTTP)経由で Bridge に接続し、LKG ディスプレイ一覧とキャリブを取得します
iOSCalibrationLoader が初回実行時に端末から取得し永続化します

シェーダーへの反映

CalibrationSync が以下のシェーダープロパティにキャリブレーション値を設定します。

_Pitch / _Slope / _Center / _ViewCone / _DPI
_FlipSubp / _ScreenWidth / _ScreenHeight

ApplyToMaterial でマテリアル単位に、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 の経路を使用しています。


開発で遭遇した問題と解決策

#時期問題原因解決策
15/28出力画像が真っ黒シェーダーへのパラメータ渡しの不具合デバッグログ追加で箇所を特定し修正
25/29キャリブレーションデータが接続デバイスと不一致Bridge からの取得ロジックの誤り接続中デバイスの値を正しく反映するよう修正
36/5サイズ違いのキルトで画像が半分に割れて反転シェーダーパラメータの例外処理不足サイズ違い時のパラメータ再計算ロジックを実装
46/18動画プレビューが表示されない変換完了前に表示処理が走る変換完了を待ってから表示するようタイミング調整
57/18LKG 側で出力画像が回転2 アプリ構成への変更で座標系の扱いが変化変換パイプラインの座標系処理を再調整
610/22動画の保存先パスが不正動画パイプラインの保存方法が画像と異なる実装LKGFolderLayout の正規化を動画にも適用
710/22ビルド後に LKG へ描画されない(Skybox のみ)ビルド環境でのフォルダ参照パスの不整合ログ増強で不整合を特定しパス解決ロジックを修正

特に問題 2 のキャリブレーション不一致は、正しい値でないとレンチキュラーが完全に崩れるため影響が大きく、デバッグに時間を要しました。


主要スクリプト一覧

レイヤースクリプト役割
AM3DQuiltImagePipeline / Improvedキルト画像の読み込み → レンチキュラー PNG 出力
AM3DQuiltVideoPipeline動画の一括処理(抽出 → 合成 → 再多重化)
AM3DLenticularBaker / ImprovedQuilt → Texture2DArray → 合成 → PNG 保存
AM3DLenticularPlayercontrol.json に基づく PNG/MP4 の表示再生
AM3DCalibrationSyncMultiviewData.cal をマテリアル / グローバルに反映
AM3DQuiltNameParserファイル名から cols/rows/aspect を抽出
AM3DControlJsonIO / LKGFolderLayoutプレイリスト管理とフォルダ規約
AM3DFFmpegRunnerffmpeg 呼び出し・ログ解析・フォールバック
PluginCalibrationLoaderキャリブレーション取得・適用(iOS / Standalone / Bridge)
PluginBridgeInterfaceBridge 接続・LKG ディスプレイ列挙(HTTP)
PluginKlakPreviewSenderSpout / 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