URP と HDRP のどちらを選ぶか:Unity レンダーパイプラインの選び方を実測データで検証する
対象プラットフォームの一覧が答えをすでに出していることがある。HDRP の volumetric と SSR にかかる実測コスト、URP がモバイルで得る帯域幅の余裕、そして途中で切り替えたときに本当に払う金額を数字で並べた。
デモは良かったが、Switch ビルドが通らなかった
2024年11月、3人のチームで縦切りのプロトタイプを作っていた。Unity のレンダーパイプラインは5分で決めた。Scene ビューで volumetric fog がきれいに見えたから HDRP、それだけだった。対象プラットフォームの一覧は、その日誰も開かなかったスプレッドシートの中にあった。Steam、Nintendo Switch、そして可能性としての Quest 移植。チームに HDRP で製品を出した経験のある人間はおらず、判断の根拠はスクリーンショット1枚だった。
5週目に Switch ビルドを試した。HDRP はそのプラットフォームではそもそもビルドが通らない。HDRenderPipelineAsset は compute shader と DX11/Vulkan/Metal クラスの API を前提にしている。Quest でも同じ壁にぶつかった。つまり見落とした設定があったのではなく、いちばん最初の決定そのものが間違っていた。
巻き戻しには11営業日かかった。190個のマテリアル、60個のライト、2つの VolumeProfile、そして4本のカスタムシェーダー。URP と HDRP のどちらを選ぶかという問いは、初日に5分のプラットフォーム表で答えが出せたはずだ。私はその5分を11日に変えてしまった。だからこの記事を書いている。それ以来、新しいプロジェクトの最初のコミットには必ずプラットフォーム表を入れ、そこにパイプラインの行を書き込んでいる。
対象プラットフォームの一覧がすでに答えを出している
いまはパイプラインの選定をアートディレクションからではなく、対象デバイスの表から始めている。HDRP は OpenGL ES、Nintendo Switch、Quest、WebGL では未サポートだ。この4行のうち1つでもプロジェクトに含まれるなら、議論はそこで終わる。好みの問題ではなく Unity の公式サポート表であり、リリースノートにも同じことが書いてある。
Universal Render Pipeline にはそうした足切りがない。GLES3.0 で動くミドルレンジのスマートフォンから Xbox Series X まで、同じ UniversalRenderPipelineAsset の構造で出荷できる。デバイスの区分ごとにアセットを用意し、QualitySettings から紐づけるだけだ。レンダラーのアセットを性能帯で分けておくと、低い設定と高い設定を1つのプロジェクトで抱えるのが楽になる。HDRP でも同じ分け方はできるが、下限はやはり HDRP の下限のままだ。
基礎コストを測るために簡単なテストをした。RTX 3060、1080p、directional light が1つだけの空のシーン。URP は GPU 時間で0.9 ms、HDRP は4.3 ms だった。この3.4 ms は、メッシュもマテリアルもまだ1つも置かないうちから払っている。depth prepass、motion vector、GBuffer、そして待機している volumetric froxel のグリッドは、どれも無料ではない。数値は FrameTimingManager ではなく RenderDoc で取った。エディタ内の数字は HDRP に都合よく出てしまうからだ。
メモリも同じ話だ。同じシーンの1440p で、HDRP の RTHandle プールはおよそ640 MB、URP は180 MB だった。8 GB のカードではこの差がそのままテクスチャ予算から引かれ、Unity のグラフィックス設定を下げても取り返せない。同じテストで HDRP は shader variant のキャッシュのために、初回起動時に40秒のコンパイル待ちまで上乗せしてきた。
URP がモバイルとミドルレンジ PC で得るもの
同じシーンを両方のパイプラインで組んで測った。42万ポリゴン、38個のライト、SSAO 有効。GTX 1650 の1080p で URP は7.1 ms、HDRP は15.6 ms。ミドルレンジのカードを狙うなら、この差は60 fps と45 fps の差になる。どちらも同じアセット、同じシャドウ解像度、同じシャドウ距離で走らせているので、設定の綾ではない。
URP 14 で入った Forward+ の経路は、オブジェクトあたり8灯という制限を取り払った。いまは deferred に移ることも、ライトを手でグループ分けすることもなく、200を超える point light を1パスで処理できる。かつて HDRP を正当化していた理由の1つがこれだったが、もう当てはまらない。コストはライトの数ではなく画面上の占有面積で増えるので、角度の狭い spot light はほぼ無料で入る。
モバイルでの本当の利得は帯域幅にある。Adreno 730 では、60 fps のために16.6 ms の予算があった。URP のシングルパスの forward 経路は MSAA 4x を tile メモリに収め、ポストプロセスを UberPost の1パスにまとめる。bloom と tonemap を別々のパスに分けた実験では、帯域幅だけで2.4 ms が戻ってきた。スマートフォンで削った1ミリ秒は発熱の予算でもある。10分のセッションで、フレーム時間の悪化が15%小さくなった。
足りないものは ScriptableRendererFeature で足している。平面反射、アウトラインのパス、独自の decal。HDRP が最初から用意しているものをいくつか取り戻すのは2〜3日の作業だ。一方で HDRP の基礎コストを取り戻す方法は存在しない。これらの feature は別の assembly に置いてあるので、低性能デバイス向けのアセットでは一度も読み込まれない。
HDRP の請求書:volumetric、SSR、そして path tracing
HDRP を支える機能は本物だ。ただ無料ではない。volumetric fog は froxel ベースで、ライトごとに正しい散乱を返す。Fog の override で品質 Medium なら1.3 ms、High なら3.6 ms を計測した(3060、1440p)。単一のエフェクトとしてはかなり大きい取り分だ。しかもシーンの半分が屋内になると見た目への寄与は一気に落ちるのに、請求は毎フレーム届き続ける。
ScreenSpaceReflection は1440p で1.8〜2.4 ms のあたりを漂い、それでいて画面の外にあるものは何も映せない。ray tracing 版は DXR 対応のハードウェアを要求し、同じシーンの3060では5.9 ms まで上がった。HDRP のパフォーマンスを巡る議論は、たいていこの個々のエフェクトの値札のところでこじれる。屋内なら、同じ見た目の6割を平面の reflection probe で0.4 ms から作れた。
path tracing はリアルタイムではない。フレームをまたいでサンプルを蓄積する方式で、512サンプルのきれいな1枚に20〜40秒かかる。シネマティック、キービジュアル、マーケティング用のレンダリングには申し分ない道具だが、ゲームプレイの選択肢にはならない。だからパイプラインの選定にはいっさい含めず、別のレンダリング用シーンで走らせている。
この3つは、屋内が中心でカメラを制御でき、PC とコンソールに出す映像重視のプロジェクトには正しい答えだ。ただし広いハードウェア帯をカバーするために HDRP を最低設定まで落としていくと、手元に残る絵は URP によく似てくる。その時点で基礎コストをなぜ払い続けているのか説明できないなら、選択が間違っていた。さらに HDRP の volume の階層と exposure の連鎖を理解している人がチームにいなければ、請求額はもう一段上がる。
Shader Graph か、手書きの HLSL か
Shader Graph はパイプライン間で移植できるように見えるが、それが本当なのは master stack のレベルまでだ。グラフに Custom Function ノードを置いて URP の Lighting.hlsl を include した瞬間、そのグラフは HDRP でコンパイルが通らなくなる。移植性は、特別なことを何もしない限りにおいて成立する。生成されるコードも読めたものではない。不具合を追うときは Show Generated Code の出力900行をスクロールすることになる。
HLSL を手で書くのは、パイプラインに直接身を預けることを意味する。URP では Packages/com.unity.render-pipelines.universal/ShaderLibrary/ 以下の関数に寄りかかり、HDRP では SurfaceData と BSDFData を中心に組まれた、まったく別の照明 API を渡される。自作のトゥーンシェーダーを HDRP へ移したときは、行数のおよそ70%を書き直した。いまは照明の計算を共有の .hlsl ファイルに切り出して両側から include している。移植が無料になるわけではなく、安くなるだけだ。
バリアントの数も判断材料に入る。Shader Graph は multi_compile を気前よく吐く。グラフ4本のセットで、シェーダーのコンパイルが11分まで伸びた。不要なものを shader_feature に置き換え、ShaderVariantCollection で削ったら3分に戻った。私の基準は単純だ。アートチームが触るマテリアルは Shader Graph、ホットパスとプラットフォーム固有のシェーダーは手書きの HLSL。
プロジェクト途中の移行と、私の結論
一度だけ移行の請求書を記録したことがあり、数字はこうなった。640個のマテリアルのうち Render Pipeline Converter が68%を自動で処理し、残りの205個は手で直した。その多くはカスタムシェーダーか detail map を使っていたもので、converter はそういうマテリアルに黙って空の Lit を割り当てる。レポートを読まずに先へ進んではいけない。静かに落ちたものが見えるのは、そこだけだ。
いちばん厄介なのはライトだ。HDRP は物理単位を使い、太陽はおよそ100,000 lux になる。一方 URP では、同じライトが任意の強度値を持つ。converter はおおよその係数を掛けてくれるが、60灯のシーンでは20灯を目視で調整し直すことになった。そのあとは lightmap と reflection probe の全面的な焼き直しで、マシン時間が6時間。
ポストプロセスは両方のパイプラインが Volume システムを使うが、override の顔ぶれは重ならない。Exposure、Fog、ScreenSpaceReflection は URP に存在せず、ColorAdjustments と手書きの renderer feature がその席に座る。さらにサードパーティのアセットがある。ストアで買ったシェーダーパッケージはどれも、対応パイプラインという固有の問題を抱えている。合計は2人×3週間で、その間ゲームプレイの新機能は1つも入らなかった。
私の判断表はこうだ。モバイル、Quest、Switch、WebGL のどれかが一覧にあるなら、あるいは出荷先のハードウェアがまだ分からないなら、URP を選んで議論を終える。PC とコンソールだけを狙い、下限を GTX 1660 に保てて、屋内中心の映像的なゲームを作っていて、専任のグラフィックスプログラマーがチームにいるなら、HDRP はコストに見合う。その中間にいるなら URP を取り、足りない部分を ScriptableRendererFeature で埋めればいい。そしてこの判断はプロトタイプの初日に下すこと。5週目ではなく。