すべての記事

Realsee、WebGPU で 3D レンダラーを全面再実装:究極の 3D 体験を追求

Realsee は、究極の 3D 体験を追求するため、WebGPU で空間レンダラーを全面的に再実装しました。パノラマ、テクスチャ付きモデル、詳細表示用のタイルから、マテリアル、空間プラグイン、補助ビュー、画像出力まで、その再実装を支える設計と実装上の判断を解説します。
パノラマ表示とテクスチャ付き3Dモデルの全体表示

Realsee は、究極の 3D 体験を追求するため、WebGPU で空間レンダラーを全面的に再実装しました。再実装の範囲は、パノラマとモデルの表示から、高解像度タイル、マテリアル、プラグイン、画像出力まで、描画パイプライン全体に及びます。

パノラマの向きを変える、表面を拡大する、撮影位置の間を移動する、テクスチャ付きモデルに切り替える。どの操作にも、レンダラーが応える必要があります。閲覧中の注釈、動画、測定、ガイド付きナビゲーションも、空間内の正しい位置に結び付いていなければなりません。 [1]

高解像度リソースは、現在のビューポートに追従する必要があります。画像とモデルでは、マテリアルと色の処理をそろえる必要があります。メインプレーヤー、空間プラグイン、補助キャンバス、スクリーンショットにも、共通のリソース・出力ルールが必要です。

WebGPUとWebGL、WebGL 2の違い

WebGL、WebGL 2、WebGPUはいずれもGPUを使って3Dシーンを描画します。WebGL 2はWebGLのグラフィックス機能を拡張し、インスタンス描画や複数のレンダーターゲットを標準機能にするとともに、ユニフォームバッファ、3Dテクスチャ、Transform Feedbackを追加しています。Transform Feedbackは、頂点シェーダーの出力をバッファに保存し、後続の処理に利用できるようにします。 [2] [3]

WebGPUは、Metal、Vulkan、Direct3D 12などの現代的なグラフィックスAPIを念頭に設計されています。パイプライン、リソースのバインド、コマンドの送信方法を再構成し、汎用GPU計算を提供します。 [4]

比較項目 WebGL WebGL 2 WebGPU
APIモデル OpenGL ES 2.0 OpenGL ES 3.0 現代的なGPU APIを念頭に設計
主な機能 グラフィックス描画 拡張されたグラフィックス描画とデータ処理 グラフィックス描画と汎用GPU計算
GPUでのデータ処理 シェーダーとテクスチャを中心にアルゴリズムを構成 Transform Feedbackで頂点処理の結果も保存可能 計算パイプライン、読み書き可能なストレージバッファ、ワークグループ
アプリケーション側での処理の組み立て コンテキストの状態を変更し、描画を呼び出す 同じモデルを継承し、ユニフォームバッファなどを追加 パイプラインとリソースのバインドを明示し、コマンドをエンコードしてキューに送信
シェーディング言語 GLSL ES 1.00 GLSL ES 3.00に対応し、1.00のサポートも維持 WGSL

描画状態を明示的に組み立て、再利用する。 WebGPUは、シェーダー、頂点レイアウト、深度状態、ブレンド状態をパイプラインオブジェクトにまとめます。 [5] 描画時にはパイプラインを選び、リソースをバインドするため、固定の設定を複数の処理で再利用できます。コマンドバッファは、ブラウザやドライバーがまとめて実行を計画できる単位で処理を渡します。Chromeは、同じグラフィックスを描画する際のJavaScriptの処理量を減らせることを、WebGPUの主な利点として挙げています。 [6]

描画と計算に、それぞれのプログラミングモデルを用意する。 計算パイプライン、ストレージバッファ、間接ディスパッチは、並列データ処理や追加のレンダリングアルゴリズムを支えます。 [5] 日常的なパノラマやテクスチャ付きモデルの閲覧には、主にグラフィックスパイプラインを使います。CPUが現在の視野に必要なリソースを組み立て、GPUが画像のサンプリング、マテリアルの計算、シーンの描画を行います。

拡張された輝度範囲をキャンバス出力まで維持する。 HDRデータを持つパノラマでは、復元したハイライトの値をレンダリング中も保持する必要があります。半精度浮動小数点のWebGPUキャンバスに extended 出力モードを設定すると、対応するHDR表示環境でこれらの値を表示できます。 [7] これにより、パノラマの輝度情報を最終表示まで引き継ぐことができます。

パノラマとモデルが画面を構成する仕組み

パノラマ写真は表面の色を、モデルは空間上の位置と形状を提供します。モデルのマテリアルは、表面の位置とパノラマの撮影位置・向きを使ってサンプリング位置を求め、そのパノラマの色をモデル自身のマテリアル出力と組み合わせます。また、モデルの表面で覆われない視野を埋めるため、パノラマ背景も描画します。

撮影位置を切り替える際のトランジションでは、カメラの動きと画像を連携させます。移動前と移動先のパノラマのリソースがマテリアルの更新に使われます。カメラの位置、向き、視野角は選択した移動エフェクトに従って変化し、2組のパノラマの色はトランジションのルールに従って組み合わされます。モデル表示に移るときは、パノラマとモデルのマテリアル出力の比重を調整し、写真の細部から空間全体の構造へと見せ方を変えます。マテリアルの移行では、こうしたサンプリングの対応関係とトランジションをまとめて保つ必要があります。

現在の視野に必要な細部を読み込む

パノラマに必要な精細度は、視野角、キャンバスの寸法、ピクセル比によって変わります。私たちはそれらの値からタイルレベルを選び、CPUで各タイルが視錐台と交差するかを判定します。リクエストは低いレベルを優先し、同じレベルでは画面中央に近い領域を優先するとともに、同時読み込み数を制限します。そのため、窓や壁を拡大したときは、現在の視野に必要な細部が優先して読み込まれます。

新しく読み込んだタイルがフェードインする間も、基本パノラマが全体の画像を提供します。基本画像と利用可能な高解像度タイルを、現在の視線方向に対応する細部用テクスチャへ描画します。メインシーンのマテリアルは、対応する投影を使ってこのテクスチャをサンプリングします。この視野の細部テクスチャと最終的なシーン画像は、役割が異なります。前者はパノラマのマテリアルに細部を加え、後者は出力と後処理に使うシーンの描画結果を保持します。

基本パノラマとCPUが選択した高精細タイルから視野の細部テクスチャを生成し、モデルの形状や空間内メディアとともにシーン描画に使う流れ

視野の細部テクスチャはパノラマのマテリアルに、シーン画像は出力と必要に応じた後処理に使われます。

レンダリング全体の移行で取り組むこと

Realseeの移行対象は、メインのツアー、空間プラグイン、間取り図、ガイド付きナビゲーション、ミニマップ、商品・モデルのプレビュー、スクリーンショットとカバー画像の出力に及びます。独自のカメラとキャンバスを持つコンポーネントもあれば、既存のマテリアル、色、キャプチャの挙動に依存するものもあります。これらすべてが、新しいレンダリングシステムで連携する必要があります。

マテリアルを変えても、既存の見え方を保つ

Three.jsのWebGPUレンダラーはノードマテリアルを使うため、既存のカスタムGLSLマテリアルをThree.js Shading Language(TSL)に移植する必要があります。TSLはシェーディングをノードの組み合わせで記述し、選択したバックエンドに応じてWGSLまたはGLSLを生成できます。 [8]

私たちは、パノラマ動画、ガイド線、ミニマップのモデル背景、パーティクルなどのマテリアルを移行し、投影、マスク、フェード、深度に関する挙動を維持しました。測定線には、見落としやすい状態があります。ユーザーが測定を始めても、最初の線分はまだ完成していないという状態です。この時点ですでに有効なジオメトリの入力レイアウトを用意し、両端点がそろうまでは表示するインスタンス数をゼロにする必要があります。

パッケージ間の依存関係にも、同じように注意が必要です。ホストアプリケーションが、メインエンジンと空間プラグインにThree.jsを提供します。プラグインのビルドではこの依存関係を外部参照として扱い、ホスト側がバージョンとモジュール解決の入口を定義します。独立したモデルプレビューにも、メインエンジンと同じモデルマテリアルの対応付けを登録し、読み込んだモデルが対応するノードマテリアルを使えるようにします。

合成処理全体で色と透明度を確認する

レンダラーを変更すると、動画が以前より明るく見えることがあります。私たちが特定した原因の一つは、sRGBで符号化された動画を線形の色として扱い、出力時にもう一度符号化していたことでした。動画テクスチャの色空間を明示し、入力のデコードと最終出力の変換に、それぞれの役割を持たせることで対処しました。Three.jsの動画テクスチャのドキュメントにも、この入力要件が記載されています。 [9]

透明なメディア、ガイド線、保存済みの注釈も、背景と適切に合成する必要があります。透明なメディアでは色とアルファのブレンドを別々に設定し、線ではすでに描画された背景を保持します。保存済みの注釈は、合成の境界で作業色空間と表示用の符号化を扱い、保存された色の意味を維持します。入力の色、線形の作業色空間、表示用の符号化が整合している必要があります。明るさだけを変えると、これらの変換にある誤りが隠れてしまうことがあります。 [10]

初期化、終了処理、デバイス復旧を管理する

WebGPUレンダラーの初期化は非同期処理です。 [8] モデルプレビューや独立したコンパス表示は、初期化が完了してから依存するリソースを作成したり、描画を始めたりします。ユーザーが素早くプレビューを切り替えた場合、読み込まれたモデルが現在のリクエストに属しているかを確認する必要があります。不要になった結果は破棄し、現在のビューが最新の操作に応答できるようにします。

商品プレビューを閉じるときは、まずリクエストとアニメーションをキャンセルし、リスナーを解除します。初期化処理が終了してから、インスタンスとキャンバスを解放します。GPUデバイスの喪失には、別の復旧手順を用意しています。メインビューは閲覧状態を保存し、古いセッションを解放して新しいセッションを作成し、リソースの読み込みに合わせて視点と表示階を復元します。独立描画用の共通ラッパーを使うプラグインのキャンバスは、レンダラーを再構築し、サイズ、ピクセル比、透明な背景を復元したうえで、新しいインスタンスの準備が整った時点で表示中のキャンバスを置き換えます。メインビューとこれらのプラグインのキャンバスは、それぞれ自身が所有するリソースを再構築します。

描画した画像をスクリーンショットまで引き継ぐ

このキャプチャ処理では、指定領域をRGBA8のターゲットに描画し、そのピクセルをGPUからCPUへ非同期で読み戻します。WebGPUの copyTextureToBuffer でこれらのピクセルを複数行コピーする場合、行ストライドは256バイトの倍数でなければなりません。例えば、幅100ピクセルのRGBA8画像は1行あたり400バイトの色データを持ち、コピー時には512バイトの行ストライドを使えます。画像処理では、その各行から有効な400バイトを取り出す必要があります。 [5]

リードバック処理は、返されたデータの長さを確認します。データが隙間なく並んでいればそのままコピーし、パディングがあれば行ごとに有効なピクセルを取り出します。その後、ホスト側が使用中のSDKのバックエンドの挙動に合わせて行の向きを統一します。切り抜き領域の座標は下端を原点とする既存の規約を維持し、画像キャンバスに渡す出力行は上から下へ並べます。切り抜きとカバー画像はこのアダプターを共用し、キャプチャのために隠した補助要素は処理後に元へ戻します。

画像が有効な間は再利用する

画像の再利用とオンデマンド更新は、WebGL 2にもWebGPUにも適用でき、その更新ルールはレンダラーが定めます。カメラが静止していても、アニメーション付きのエフェクトは動き続ける場合があります。エフェクトを更新するたびにシーン全体を再描画すると、結果が変わっていない処理まで繰り返すことになります。

私たちはシーンを中間画像へ描画し、その後で後処理エフェクトを適用します。シーン画像が有効な間は、エフェクトがその画像を引き続き利用できます。

更新は次のルールに従います。

変化 必要な処理
カメラまたはシーンが変化する 新しいビューに合わせてシーン画像を再生成する
画像の読み込みが完了する、または新しい動画フレームが届く 新しい内容を含めるために更新を要求する
少なくとも1つのエフェクトが有効な状態で、有効なエフェクトの数または必要な入力の種類が変わる 対応する合成結果を再び準備する
最後の後処理エフェクトを無効にする そのエフェクトを除いた画像を表示するため、アプリケーションが更新を要求する
ビューポートのサイズが変わる 出力リソースを再構築し、新しいサイズで合成する
アニメーション付きの後処理エフェクトだけが変わる シーン画像を再利用し、エフェクトの更新を続ける

エフェクトを切り替えるときは、呼び出し側が1フレームの更新を要求する必要があります。最後の後処理エフェクトを無効にした場合、そのフレームでエフェクトを除いた画像を表示します。それ以降は、後処理が無効でシーンにも変化がなければ、レンダラーは次のフレームの描画を省略できます。有効なままのエフェクトは、保持されたシーン画像を引き続き読み取り、必要に応じて新しいコマンドをエンコードして送信します。

中間画像の保持にはGPUメモリを使い、そのフォーマットは読み書きするデータ量にも影響します。色の出力を保持するテクスチャ、すなわちカラーアタッチメントを例に考えます。サイズは1920 × 1080、1レイヤー、1ピクセルあたり1サンプルで、ミップマップは生成しないものとします。RGBA8は4つの8ビットチャンネル、RGBA16Fは4つの16ビット浮動小数点チャンネルで構成されます。テクセル、つまりテクスチャのピクセルを数えると、保存に必要な容量は次のようになります。 [5]

色フォーマット 1テクセルあたりのバイト数 width × height × bytes 保存容量
RGBA8 4 1920 × 1080 × 4 約7.9 MiB
RGBA16F 8 1920 × 1080 × 8 約15.8 MiB

1 MiBは1,048,576バイトです。これに加え、深度、追加のバッファのコピー、その他のアタッチメント、メモリ割り当てのアライメントにも容量が必要です。同じフォーマットで縦横の寸法をそれぞれ2倍にすると、色データの保存容量は4倍になります。これらの数値は、色テクセルを保存するための容量を示しています。読み書きのコストは、レンダリングの各処理がそれらのテクスチャにどうアクセスするかによっても変わります。出力精度を選ぶ際には、容量とアクセスの仕方を考慮し、Three.jsのドキュメントで説明されている画質、メモリ、帯域幅のトレードオフと合わせて判断する必要があります。 [11]

タイルのリソースには、独自の再利用方針があります。最近使われたタイルはキャッシュに残っている間、テクスチャを引き続き提供でき、キャッシュの対象から外れた後にリソースを解放します。テクスチャオブジェクトが必要になったときは、再利用プールでサイズと色空間を照合します。これにより、読み込み済みの細部を利用できる状態に保ちながら、カラー画像とデータテクスチャをそれぞれ正しく解釈できます。

オンデマンドレンダラーでも動画を動かし続ける

オンデマンドレンダラーは、シーンが変わったタイミングを知る必要があります。パノラマ内の動画はカメラが静止していても新しい内容を生成するため、メディアコンポーネントがレンダラーへ通知しなければなりません。

再生イベントが届いても、描画できるフレームが準備できているとは限りません。私たちのコンポーネントは、利用可能なフレームがあり、その動画が現在閲覧中のパノラマに属していることを確認してから、シーンに追加します。再生中はシーンの再描画を継続的に要求し、動画テクスチャが更新されていない場合は更新対象としてマークします。こうすることで、カメラが静止していてもメディアの映像を進められます。

一時停止時には、再生中に使っていた描画スケジュールを解除します。コンポーネントの破棄時には、動画テクスチャ、ジオメトリ、マテリアルを解放します。遅れて届いた再生、一時停止、アンロードのイベントは、コンポーネントの現在の状態に照らして処理し、以前の再生セッションが新しい画面に干渉しないようにします。

WebGPUとWebGL 2への対応

WebGPUを利用できるかどうかは、ブラウザ、OS、GPUによって異なります。ブラウザがAPIを提供するだけでなく、必要な機能を備えた利用可能なGPUデバイスを取得できる必要があります。 [6] [4]

Three.jsのWebGPUレンダラーは、WebGPUとWebGL 2の両方のバックエンドに対応しています。 [11] Realseeはそれぞれに対応するレンダリング経路を維持し、初期化後に実際のバックエンドを確認します。パノラマ、モデル、空間コンポーネントは、そのバックエンドに対応するマテリアルとリソースを使い、スクリーンショット出力も同様に適合させます。

アプリケーションは、視点移動、表示モードの切り替え、メディアの更新を、選択したバックエンドにつなぎます。同じコンテンツでも異なる描画方法を使い、個々のエフェクトをその経路の機能に合わせることができます。

npmのWebGPUパッケージを試す

空間閲覧機能の構築には、 npmのFive WebGPUを利用できます。コアSDKは alpha チャンネルでWebGPU版を公開しています。本記事では 1.0.0-alpha.11 と three@0.183.2を使用します。 [12]

npm install --save-exact three@0.183.2 @realsee/five-webgpu@1.0.0-alpha.11

空間タグ、測定、ガイド付きナビゲーションなどのプラグインが必要な場合は、 DnalogelのWebGPU版を追加します。本記事では 4.0.0-webgpu.6を使用し、コアSDKと同じThree.jsのインスタンスを共有します。 [13]

npm install --save-exact @realsee/dnalogel@4.0.0-webgpu.6

レストラン、住宅、博物館、小売店などの公開ツアーは、 Realsee Discoverで閲覧できます。 [14]

Realsee開発者プラットフォーム にアクセスして、空間閲覧の機能を確認し、自分のアプリケーションを構築してみてください。

参考資料