最適化ガイドライン
このページは、他のユーザーがNeosで作成した作品のパフォーマンスを最適化する方法を発見するために、コミュニティが作成したページです。Wiki全体と同様、このページは誰でも自由に編集することができます。ただし、このページでは特定の技術的な推奨を行っているため、情報は公式のソース(例:Neos開発チームの一人からのDiscordコメント)で裏付けられていることが望ましいです。
レンダリング(描画)
ブレンドシェープについて
ブレンドシェイプを変更するたびに、メッシュ全体の頂点を再変換する必要があります。[1] もし、メッシュの大部分がどのブレンドシェイプにも属していなければ、そのパフォーマンスは無駄になります。Neosでは、SkinnedMeshRenderer componentの下にある"使用していないブレンドシェイプを除去"を使って、自動的に最適化することができます。これに価値があるかどうかはケースバイケースなので、念のためブレンドシェイプを使用している間の前後のパフォーマンスをテストする必要があります。
ブレンドシェイプが変化していない場合でも、ゼロでないブレンドシェイプには、頂点の数に比例するパフォーマンスコストがあります。 たとえば、100万の頂点メッシュは、ブレンドシェイプが0の場合よりも、ブレンドシェイプが0.01の場合の方が、パフォーマンスへの影響が大幅に大きくなります。非駆動ブレンドシェイプがゼロ以外に設定されているメッシュがある場合は、それらをベイク処理することを検討してください。
マテリアル
いくつかのマテリアル(特にFur material)は、他のマテリアルよりもはるかに高負荷です。[2]
Alpha/Transparent/Additive/Multiplyのブレンドモードは、透明なマテリアルとしてカウントされ、背後にあるものをレンダリングしてフィルタリングする必要があるため、若干負荷が高くなります。透明マテリアルはフォワードレンダリングパイプラインを使用しているため、ダイナミックライトを一貫して処理することはできません。PBSマテリアルに使用されているOpaqueとCutoutのブレンドモードは、遅延レンダリングパイプラインを使用しているため、ダイナミックライトの処理がうまくいきます。
テクスチャサイズについて
ピクセルサイズが2の累乗(2、4、8、16、32、64、128、512、1024、2048、4096など)の正方形のテクスチャは、VRAMで効率よく処理されます。
テクスチャのアトラス化
同じ種類の異なるマテリアルが多数ある場合は、アトラス化(複数のテクスチャを1つの大きなテクスチャにまとめること)を検討してください。[3] メッシュの異なる部分が異なる設定を使用していても、マップを追加することで、多くのマテリアルを1つにまとめることができます。VRAMを浪費してしまうので、アトラスの中に大きな空洞ができないようにしてください。[4]
アトラス化が効果のない状況:
- ほとんどがPBSのアバターにFurが必要な場合など。
- アバターの一部に透過処理が必要だが、大部分はOpaqueやCutoutでよい場合。
プロシージャルとスタティックアセットの比較
プロシージャルメッシュのパラメータを駆動しないのであれば、スタティックメッシュにベイクすることでパフォーマンスを節約することができます。プロシージャルメッシュとテクスチャはパーワールドです。これはプロシージャルアセットがアイテムと一緒に複製されるからです。スタティックメッシュとテクスチャは、ワールド間で自動的にインスタンス化されるので、メモリ内には常に1つのコピーしか存在せず、アイテム自体に保存する必要はありません。[5]
GPUメッシュインスタンシング
同じスタティックメッシュ/マテリアルの組み合わせのインスタンスが複数ある場合、それらは(ほとんどのシェーダで)インスタンス化されます。これにより、同じオブジェクトの複数のインスタンスをレンダリングする際のパフォーマンスが大幅に向上します。例えば、環境にたくさんの木がある場合などです[6]。ただし、SkinnedMeshRenderersは、GPUインスタンス化の対象ではありません。[7]
ミラーとカメラ
ミラーとカメラは、特に高解像度の場合、追加のレンダリングパスが必要となるため、非常に高負荷になります。ミラーは、2つの追加パス(片目につき1つ)を必要とするため、一般的にカメラよりも負荷がかかります。
カメラのパフォーマンスは、適切なnear/far clip値を使用し、selective/exclusiveレンダリングリストを使用することで向上します。基本的には、必要のないものはレンダリングしないようにします。
ミラーやカメラをValueUserOverrideでローカライズして、ユーザーがパフォーマンスを犠牲にしてでも見たいと思ったときに使用できるようにするのは良い方法です。
リフレクションプローブ
ベイクされたリフレクションプローブは、特にデフォルトの128x128の解像度では、非常に低負荷です。唯一の実質的なコストは、キューブマップを格納するために使用されるVRAMです。[8]
リアルタイムのリフレクションプローブは非常に高負荷で、6台のカメラに匹敵するものです。[9]
ライティング
光の影響は、光が当たっているピクセル数に比例します。これは、影響を与えるジオメトリの数に関わらず、ワールド内の可視光のボリュームの大きさによって決まります。そのため、近距離のライトや部分的にオクルージョンしたライトは低負荷になります。
影のあるライトは、影のないライトよりもはるかにコストがかかります。遅延シェーディングでは、シャドウを適用したメッシュは、シャドウを適用したライトごとに1回以上のレンダリングが必要になります。さらに、シャドウを適用するライティングシェーダは、シャドウを無効にしたときに使われるシェーダよりもレンダリングの処理が大きくなります。[10] [11]
影付きのポイントライトは、周囲のシーンを6回レンダリングするため、非常にコストがかかります。影が必要な場合は、スポットや指向性のあるライトに限定するようにしてください。[12]
コンポーネントのShadowCastMode Enum値を使って、MeshRendererまたはSkinnedMeshRendererコンポーネントが影を落とすかどうかをコントロールすることができます。これを「Off」に変更することで、一部のメッシュに影を落としたいが、すべてのメッシュには影を落としたくない(したがって、関連するライトの影を無効にしたくない)場合に役立ちます。また、詳細度の高いメッシュのシャドウキャストをオフにして、類似しているが詳細度の低いメッシュを配置し、ShadowCastModeを 'ShadowsOnly'に設定することで、パフォーマンスが向上する場合もあります。Comment from Geenz in response to Medra during Office Hoursを参照してください。
カリング
「カリング」とは、パフォーマンスコストを削減するために、世界の特定の部分を処理しない、または少なくともレンダリングしないことを指します。
錐台カリング
Neosは自動的に視錐台カリングを行い、視野外のオブジェクトはレンダリングされません(例:ユーザーの背後にあるオブジェクト)。視錐台カリングでは、計算に若干のコストがかかりますが、通常は各メッシュで一定です。検出プロセスは各オブジェクトのバウンディングボックスに依存しており、バウンディングボックスは基本的にメッシュ全体を完全に包み込む軸に沿ったボックスです。そのため、メッシュの複雑さは関係ありません(最初のバウンディングボックスの計算や、いくつかの計算モードを持つスキンメッシュの場合にはボーンの数を除く)。このシステムで最適に動作するようにコンテンツを最適化するには、いくつかの考慮点があります。 [13]:
- このシステムでオブジェクトがカリングされるべきかどうかをチェックするコストは、アクティブなメッシュの数に応じて変化します。つまり、ユーザーが作ったカリングシステム(下を参照)と一緒に使うことで、テストしなければならないアクティブなメッシュの数を減らすことができます。
- Neosはメッシュの一部分だけをレンダリングすることはできません。
- そのため、通常はメッシュ全体が一度に表示されない場合、大きなメッシュを小さなピースに分けることが意味を持つことがあります。ワールドテレインのメッシュは、サブメッシュに分割するのに適しているかもしれません。
- 一方で、通常は同時に表示されるメッシュを結合した方がよい場合もあります。複数のマテリアルを持つメッシュを組み合わせることで、直接レンダリングコストを削減できるわけではありませんが、メッシュをカリングすべきかどうかをテストする手間は省けます。複数のメッシュを1つのメッシュにベイクした場合、バウンディングボックスのテストは、その結合されたメッシュに対して1回だけ実行すればよいのです。これは事実上、Unityで行われているスタティックメッシュバッチングのNeos版です。
なお、SkinnedMeshRenderer componentsには、バウンディング計算のための複数のモードがあり、異なるパフォーマンス・コストがかかります。計算モードは、各SkinnedMeshRendererコンポーネントのBoundsComputeMethodによって示されます[14]。
Static
は、メッシュのみに基づいた非常に安価な方法です。これはリアルタイムの計算を必要としないので、可能であればこれを使うのが理想的です。FastDisjointRootApproximate
は、まず全体のボーンの数を減らすために、すべてのボーンを分離したグループにマージします(重なっているボーンは1つにマージされます)。そして、それらを使ってリアルタイムでバウンドを近似します。最も速いリアルタイムの方法で、`Static`の使用時にメッシュの一部がカリングされている場合に推奨されます。MediumPerBoneApproximate
すべてのボーンのバウンドからメッシュのバウンドを計算します。より正確ですが、かなり遅くなります。SlowRealtimeAccurate
実際に変換されたジオメトリを使用し、スキンされたメッシュが常に処理されることを必要とします。非常に重いですが、ボーンに加えてブレンドシェープのようなものを尊重します。Proxy
は、他のものとは少し異なりますが、非常に低コストになる可能性があります。ProxyBoundsSourceフィールドで参照されている別のSkinnedMeshRendererのために計算されたバウンディングボックスに依存しています。大きなメインメッシュがあって、それにリンクされた小さなメッシュの可視性が必要な場合に便利です[15]。
ユーザーが作成したカリングシステム
ユーザーの位置に応じてスロットを無効にしたり、コンポーネントを無効にしたりすることで、あなたのワールドに追加のカリングシステムを作ることができます。非常に効率的なカリングソリューションは、ColliderUserTracker componentで作成できます。この方法は、NoClip ロコモーションとは互換性がないことに注意してください。このコンポーネントをカリングシステムに使用する方法の簡単なデモンストレーションは、ProbablePrimeによるこのチュートリアルにあります。
コライダーコンポーネントの特定のカリングに関する考慮事項
コライダが頻繁にアクティブ/非アクティブになるような、手動によるカリングの実行は避けてください。コライダのパフォーマンスへの影響は、レンダラーとは少し異なる仕組みになっています。コライダのパフォーマンスコストは、関連性がある場合にのみチェックされるため、すでに大幅に最適化されています。コライダーを定期的にオン/オフすると、このアンダーザフッド最適化プロセスが中断され、さらにコストがかかることになります。[16]
Logix
Logixは少なければ良いというものではありません。少ないスペースで計算をすることよりも、計算量を少なくすることの方が重要です。また、「コンポーネントのみ」を使用した方が最適であるという理由で、LogiXの使用を避けるように圧力を感じるべきではありません。複数のLogiXノードを使用するよりも、特定のコンポーネントを使用した方が良い解決策となるケースは多々ありますし、その逆も然りです。判断基準は主に、その仕事に最適なツールを使うかどうかです。基本的には、LogiXノードはコンポーネントですが、ProbablePrimeのビデオをご覧ください。
書込みとドライバ
Sync の値を変更すると、データモデルへの変更がセッションの他のユーザに送信される必要があるため、ネットワークトラフィックが発生します。ValueUserOverrideでは、オーバーライド自体がSyncなので、このネットワークアクティビティはなくなりません。[17]
例外:
- Driversはすべてのユーザーがローカルで計算を行い、ネットワークトラフィックを発生させません。
- "Self Driven values" (Writebackを有効にし、同じソースとターゲットを指定したValueCopyのことを指します)は、Write nodeを使用して値を変更した場合でも、ローカルで計算されます。
- 同一の更新で複数の値への書き込みが発生した場合、最後の値のみがネットワーク上に複製されます。
一般的には、ローカルで計算を行い、ネットワークの使用を避ける方が低負荷ですが、高負荷な計算を行う場合は、1人のユーザーが計算を行い、結果を同期させる方が良いでしょう。
ダイナミック・バリアブルとインパルス
Dynamic Variablesは非常に効率的で、パフォーマンスを気にせず使用することができますが、Dynamic Variable Spacesの作成と破棄にはコストがかかるため、頻繁には行わないようにしてください。[18]
また、Dynamic Impulsesも非常に効率的で、特にreceiverに近いスロットにtargetがあると、その効果は絶大です。[19]
頻繁なインパルス
UpdateノードやFire While Trueなどの高頻度の更新は、その動作がネットワークの複製につながる場合には可能な限り避けるべきです。これらをDriversで置き換えることを検討してください。
Updating Relay ノード
Updating Relaysは、Logixの通常の偶数駆動の性質を回避し、毎フレーム評価を強制するため、コストがかかります。ディスプレイが更新されないために更新リレーが必要なように見えるかもしれませんが、多くの場合、その問題はディスプレイに固有のものであり、最終的なLogixには必要ありません。このノードの使用は可能な限り避けるべきですが、避けて通れない場合もあります。
Sequence ノード
Sequence nodeは性能的には悪くありませんが、使いすぎるとコーディングの仕方がおかしくなります。ノードを連鎖させることで予期せぬエラーが伝播するのを防ぎますが、Sequenceはエラー時にも実行を継続するため、誤った使い方をするとLogixを悪い状態にしてしまいます。
キャッシュノード
Cache nodeを使うことは、将来Neosが自動的に行う非常に特殊な最適化なので、心配する必要はありません。[20]
サンプルカラーノード
Sample Colorノードは、小さくて狭い視野をレンダリングして動作するため、本質的に使用するにはコストがかかります。控えめに使うのが良いでしょう。NearClipおよびFarClip入力を使用してレンダリングしなければならない範囲を制限することにより、パフォーマンスコストを削減することができます。[21]
スロット数
スロット数とLogixノードのパック数は、パフォーマンス的にはあまり重要ではありません。複雑な設定の場合、ロードとセーブに負荷がかかるのは確かですが、この負荷はLogixノードを1スロットに配置しても最小限にはなりません。Neosは依然として、まったく同じ数のコンポーネントをロードおよびセーブする必要があります。[22]
プロファイリング
もし、高負荷になりそうな新しいアイテムを作っているなら、プロファイリングを検討してみてはいかがでしょうか。
- 「ダッシュメニュー」の「ホーム」タブにある「Debug」メニューには、役に立つタイミングがたくさんあります。
- SteamVRにはGPUのフレームタイムを表示できる"パフォーマンスグラフを表示"があります。これは、開発者設定のボタンからヘッドセット内に表示することもできます(設定メニューで「高度な設定」をオンにします)。