Texture Drawing
このアドオンは、テクスチャ変更を同期する方法を提供します。
概要
このアドオンのロジックは、描画点の詳細(位置・色など、DrawinPointを参照)をユーザー間で共有するものです。
サンプルのペンが含まれ、描画可能な固定の平面を検知すると点を描画します(BlockingContactをご覧ください)。
機能:
- 同時編集に対応:複数のペンで同じ平面に同時に描画できます。
- 描画の補間:リモート上で表示される描画は補間が行われるため、
NetworkTransform
で位置が補間されているペンと同期されます。 - 途中参加者に対応:FusionのストリーミングAPIを使用して、描画が行われた後にルームに接続したプレイヤーにも、完全な描画が共有されます。
- シェーダーベースの描画:テクスチャー編集のパフォーマンスの影響を抑えます。同時編集でパフォーマンスをスケールさせるため、描画バジェットシステムも追加されています。
- Fusionのメモリアロケーションコストの抑制(描画に大きなネットワーク配列は不要):各描画は点の総数を同期します。同期は、最新の点のネットワーク配列を持つ「ペン」を通して行われ、途中参加者の対応はストリーミングAPIで行われます。
状態権限
各ペンと平面のロジックは、どちらもNetworkBehaviour
で、潜在的にペンと描画対象の状態権限者が異なる可能性があります。
- ペンの状態権限者は、ペンを掴んでいる人
- 描画対象の平面の状態権限者は、複数のユーザーが同時に描画している際には、描画しているユーザーではない場合がある
描画ステップ
ユーザーがシーンに接続した最初の初期化時に、ユーザーが(最初からいない)途中参加者なら、
TextureDrawing
は描画情報の不足を検知して、他のユーザーに不足分をリクエストする- 受信した点をローカルに保存して(途中参加者を参照)、次の
Render()
呼び出し内でそれらを描画する(パフォーマンスの問題を避けるため、1フレームで描画される最大の「バジェット」が設定され、途中参加者が大量の描画を復元する際にフリーズすることを防ぐ)
ペンによる描画が始まると、
3. ペンは、描画点を追加する平面を検知して(TexturePenを参照)、その情報を得る(座標など)
4. ペンの直近の描画点は、ネットワークのリングバッファに保存されるため(TextureDrawerを参照)、すべてのユーザーは描画点が追加されたことを認識できる
5. ペンは、点情報を直接ローカルのテクスチャに共有する
6. そして、次のRender()
内で、テクスチャは即時に更新される
そして、すべてのクライアント上の描画対象の平面では、
7. ペンが新しい描画点を追加したことを検知する
8. ローカルキャッシュに新しい描画点を保存する(TextureDrawingを参照)
9. 次のRender()
内で、ペンの補間係数に基づき(補間を参照)、実際のテクスチャに描画する(TextureSurfaceを参照)
テクスチャ描画
このアドオンは、テクスチャを編集する2つの方法を提供しています。
- デフォルトの方法は、Shader Graphで作成された特別な描画シェーダーを使用します。URPとHDRPレンダーパイプラインに互換性があります。この方法は、高解像度テクスチャに最適です。
- 代替方法は、サードパーティー製のビットマップ描画APIの
ProtoTurtle.BitmapDrawing
を使用して、テクスチャを編集します。
シェーダーベースの描画
LinePainter
シェーダーは、テクスチャ上に線を描画できます。点と描画される線との距離を決定するSubgraphのLineSDKを含んでいます。
描画ロジックは、以下の通りです。
- 描画対象の平面のマテリアルにRenderTextureを使用する
- 線を描画する必要が出るたびに、
Graphics.Blit
を使用して現在のRenderTextureと線の詳細をシェーダーに与えて、結果を保存する
クラス詳細
TexturePen
TexturePen
は(BlockableTip
コンポーネントがある)ペンに付いていて、BlockingSurface
コンポーネントとの接触の検知を試みます。またこれは、TextureDrawing
コンポーネントを持ちます。
接触が検知されると、TextureDrawer
のAddPointWithThrottle
が呼び出され、点を追加するように依頼します。
次のFixedUpdateNetwork()
内で、TextureDrawer
は依頼されたすべての点についてAddDrawingPoint
を呼び出します。
- ネットワークのリングバッファ(ネットワーク配列)に点を保存することで、すべてのユーザーはそれらの点を受信する
- すぐさまローカルの
TextureDrawing
に点を渡して、次のRender
呼び出しでテクスチャに描画される
リモートクライアントでは、OnNewEntries
がトリガーされて新しい点がリングバッファに追加されたことが通知され、ローカルのTextureDrawing
にそれらを保存します。ペンでは、点の位置インデックス(過去に描画された点の数)がデータに追加されて、描画の補間が行われます。
TextureDrawer
これは、DataSyncHelpersアドオンのRingBufferSyncBehaviour
クラスを継承しています。
ペンと描画対象の平面との接触が検知された時に、テクスチャを編集する描画点を記録するために使用されます。
TexturePen
によってAddDrawingPoint()
メソッドが呼び出され、最終的にTextureSurface
に置かれるDrawingPoint
をリストに追加します。
ローカルで描画点を追加した時か、リモートプレイヤーの新しい点がRingBufferSyncBehaviour
のOnNewEntries
コールバックで通知された時、TextureDrawing
に新しい点を追加するようにリクエストします。
備考:
- 描画者のリングバッファがすぐ埋まってしまうと、データが失われる可能性があります(現在の設定と通常のネットワーク環境では発生しません)。これを回避するには、
TexturePen.maxPointInsertionPerSeconds
で送信する点を制限するか、RingBufferSyncBehaviour.BUFFER_SIZE
をふやしてください。
TextureDrawing
新しい描画点が追加されて、TextureDrawer
がリクエストした線がまだ完了していない場合、TextureDrawing
は、前の点と新しい点との間に線を作成します。
TextureDrawing
は、TextureDrawer
毎に最新の点のキャッシュを持っているため、複数のTextureDrawer
が並列に描画を行うことができます。
参照されるTextureSurface
上のTextureDrawing
は最終的にDraw()
メソッドを呼び出して、点を追加したり線を描画したりします。
備考:
補間とそれに関連する描画バジェットは、次のフレームまで遅延することがあります。
補間
通常、リモートユーザー上のTexturePen
の位置は(NetworkTransform
の補間の実装によって)補間されます。つまり見えている位置はわずかに「過去」のものであるということです。
もしリモートユーザーの描画について、すべての存在する点を即時に描画した場合、実際のペンの動きよりも先に線が表示されることになります。
そのため、リモートユーザーのペンの位置を補間するのと同じ方法で、表示される描画点のインデックスを補間することが望ましいです。
そうするために、TextureDrawing
に保存されているすべての点は、位置インデックス(過去に描画された点の数)を保存します。
この方法によって、Render()
呼び出し内で補間する位置インデックスを取得することができるようになり、インデックスより低い値の点は描画して、そうでないものは少し待つことができます。
描画バジェット
途中参加者が接続した時や、ローカルで再描画を行う時(例えば背景色を変更した時など)は、大量の描画コールがトリガーされ、TextureSurface
のGraphics.Blit
から大量のシェーダーが実行されるため、パフォーマンスに影響する可能性があります。
これを防ぐため、TextureDrawing
全体で1フレーム辺りの最大描画数を決めます。これは、globalFrameLineDrawingBudget
の値を編集することで、コードから変更できます。
バジェットのロジック:
- 描画が必要な
TextureDrawing
コンポーネント間のバジェットを共有する - 各描画について、
TextureDrawer
間でバジェットを共有し、複数のペンが同時に点を追加していく
途中参加者
途中参加者は、Fusion 2のストリーミングAPIを使用して、開始時から追加された描画点の完全なリストを受信します。
TextureDrawingはStreamingSyncBehaviourを継承して、そのロジックを変更しています。
StreamingSyncBehaviour
はリアルタイムでデータを送信できるので、これで途中参加者に描画点を共有します。
DrawingPoints
データはローカルキャッシュに保存される(TextureDrawer
のネットワーク変数によって、リアルタイム通信が制御されている)- 途中参加者は、データ受信ロジックを変更して、
TextureDrawer
のネットワーク変数を通して既に受信したデータ(通常は即時)と、受信に数フレームかかる完全なデータのキャッシュを混ぜ合わせます
データのマージにはリコンシリエーションが必要です。完全なキャッシュの最後の点は、先にTextureDrawer
から受信した最初の点である(ストリーミングAPIから与えられる完全なキャッシュのバックアップと、TextureDrawer
のネットワーク変数から与えられるリアルタイムのデータとの間で、同じ点が含まれる)可能性があり、それらの点が未完了の線に関連している可能性があります。
この問題を解決する簡単な方法は、完全なキャッシュの全ての点を精査して、描画が完了していないすべての線を追加することです。
TextureDrawer
データはより最新のものとなり、いずれにせよ未完了の線を完了させます。
備考:
- 問題を解決する他の方法として、完全なバイト数を使用して、重複した点を正確に検知します
- このアプローチは、同時に同じピクセルに描画を行った際に、異なる結果になる可能性があります。これが問題になる場合、
StreamingSyncBehaviour
のかわりにRingBufferLosslessSyncBehaviour
のサブクラスを使用します。描画対象の状態権限を持たない描画者は一時的な線を描画して、正しいTextureDrawing
の状態を受信した後、実際の線を描画します。
TextureSurface
TextureSurface
はRederer
コンポーネントを参照し、テクスチャ編集用のユーティリティメソッド(テクスチャの初期化・テクスチャの色変更・点の描画・線の描画)を含みます。
そのため、このクラスはネットワーク化されていません。
このクラスは、IRenderTextureProvider
インターフェース(DataSyncHelpers
アドオン)を実装しています。外部からテクスチャが編集されるとonRedrawRequired
イベントが発生し、TextureDrawing
はすべての点の再描画を購読します。
DrawingPoint
このクラスは、平面上の描画点(位置・色・強さ・参照ID)を定義します。referenceId
は、対象のTextureDrawing
か、TextureDrawer
元を保存するために使用されます。
このクラスは、DataSyncHelpers
アドオンのRingBuffer.IRingBufferEntry
インターフェースを実装しています。
依存関係
- DataSyncHelpersアドオン
- BlockingContactアドオン
デモ
デモシーンはAssets\Photon\FusionAddons\TextureDrawing\Demo\Scenes\
フォルダーにあります。
ダウンロード
このアドオンの最新バージョンは、Industries アドオンのプロジェクトに含まれています。
また、無料のXR アドオンのプロジェクトにも含まれています。
対応するトポロジー
- 共有モード
サードパーティー
- ProtoTurtle.BitmapDrawing, MIT license, https://github.com/ProtoTurtle/UnityBitmapDrawing
更新履歴
- Version 2.1.0: Refactoring to add interpolation
- Version 2.0.0: First release