Unity を検索

ユニバーサルレンダーパイプラインで実現する、美しくスケーラブルで高パフォーマンスなグラフィックス

2020年2月10日 カテゴリ: Engine & platform | 15 分 で読めます
取り上げているトピック
シェア

Is this article helpful for you?

Thank you for your feedback!

ユニバーサルレンダーパイプラインは、コンテンツ制作用のアーティスト向けツールを網羅した、すぐに使える便利なソリューションです。Unity 対応プラットフォーム全体をターゲットにして、業界最高レベルのビジュアルクオリティーとパフォーマンスを備えたゲームを制作するなら、このレンダリングパイプラインをぜひご利用ください。ユニバーサルレンダーパイプラインのメリットについては、こちらのブログ記事をご覧ください。このブログ記事では、バーティカルスライスとして制作された『Boat Attack』のデモで、ユニバーサルレンダーパイプラインがどのように使われたのかを詳しく解説しています。

最初に『Boat Attack』のデモを制作したのは、ユニバーサルレンダーパイプライン(当時は軽量レンダーパイプラインと呼ばれていました)の検証とテストを行うためでした。また、開発プロセスの一環としてバーティカルスライスを制作したのには、機能開発に現実世界の制作プロセスを応用してみるという意味合いもありました。

『Boat Attack』のデモは、当初制作されたものから、かなり改善されています。現在では、C# Job SystemBurst コンパイラーシェーダーグラフInput System などの最新の Unity 機能とともに、ユニバーサルレンダーパイプラインの新しいグラフィック機能が使われています。

今すぐ『Boat Attack』のデモをダウンロードし、Unity 2019.3 で実際に体験してみてください。

デモ

『Boat Attack』のデモは、ボートレースゲームの形をとったコンパクトなバーティカルスライスです。実際にプレイ可能で、Unity の最新機能を最大限活用するよう頻繁に調整を行っています。

このデモは、ミッドレンジからハイエンドのモバイルデバイス、現行世代のあらゆるコンソール、スタンドアロンアプリなど、幅広いプラットフォームで快適に動作するように設計されています。Unite Copenhagen 2019 では、iPhone 7 から PlayStation 4 まで、さまざまなデバイスで『Boat Attack』のデモをライブで披露しました。

デモを使用するには、Unity 2019.3 の最新バージョンをインストールし、GitHub からプロジェクトを入手してください(必ず README ファイルで使い方に関する指示を確認してください)。

シェーダーグラフ

シェーダーグラフは、アーティストにとって使いやすい、シェーダーを作成するためのインターフェースです。これは、テクニカルアーティスト向けの便利なプロトタイプ作成ツールです。『Boat Attack』のデモでは、さまざまな独自のシェーディングエフェクトを生み出すためにシェーダーグラフが使われています。

シェーダーグラフを使用すれば優れたシェーディングエフェクトを作成できるだけでなく、作成したエフェクトを多くのバージョンのライトウェイトレンダーパイプラインやユニバーサルレンダーパイプラインで簡単に維持できます。

『Boat Attack』の崖のシェーダーは、メッシュデータで実現できるエフェクトの例です。データはシェーダーグラフのメッシュから簡単に取得できます。デモではメッシュの法線ベクトルを使用して崖の上向きの平らな面に草を描画し、メッシュのワールド空間の高さを使用して海水面に近い崖や岩に草を生やさないようにしています。

左から右に向かって:Y 高さマスク、Y 法線マスク、リマップされた高さ + 法線マスク、完成版のシェーダー

植生生成シェーディング

『Boat Attack』の植生生成は当初カスタムの頂点/フラグメントシェーダーで行っていましたが、この方法はレンダーパイプラインが開発初期段階でコードが頻繁に変更される状況下では、維持するのが大変でした。シェーダーグラフでシェーダーを作り直したことで、簡単にアップグレードできるシェーダーグラフをうまく利用できるようになります。

このシェーダーグラフエフェクトは、Crytek 社の Tiago Sousa 氏が実装した、頂点色をフル活用して頂点ディスプレイスメントを通じて風のアニメーションを制御するしくみに基づいています。『Boat Attack』ではサブグラフを作成して、そこに風のエフェクトを計算するために必要なすべてのノードを格納しました。このサブグラフには、ネストされたサブグラフが格納されています。サブグラフは、繰り返し演算を実行するユーティリティグラフのコレクションです。

個々の頂点アニメーションとそのマスク。左から右に向かって:原点への距離に基づくメインのしなり、頂点色の赤(R)チャンネルに基づく葉の周縁、頂点色の青(B)に基づいて位相オフセット用に頂点色の緑(G)チャンネルを使用した枝

リアルな植生を作成するためのもう 1 つ大きな要素が、サブサーフェススキャッタリング(SSS)です。SSS は、現時点ではユニバーサルレンダーパイプラインでは利用できません。とはいえ、シェーダーグラフのカスタム関数ノードを使用してユニバーサルレンダーパイプラインからライティング情報を取得することで、独自の SSS 風エフェクトを創出できます。

ノードのレイアウト。頂点色の緑(葉の位相)とアルベドテクスチャーマップから作成された SSS マスク

カスタム関数ノードにより、自由な創作の幅が広がります。カスタムレンダリングの手法については、こちらの記事が参考になります。または、『Boat Attack』のリポジトリにあるノード用のコードを取得して、自作のカスタムライティングのアイデアをお試しいただいてもかまいません。

左から右に向かって:SSS なし、SSS のみ、完成版のシェーダー

ボートのカスタム化

ボートには色のバリエーションが複数必要でした。Substance Painter で模様の塗装の形にあわせたマスクを 2 つ描画して、メタリック(赤)、スムースネス(緑)、塗装 1(青)、塗装 2(アルファ)を含むパック済みテクスチャーに格納しました。シェーダーグラフ経由でマスクを使用することで、マスクされたこれらの領域にカラーリングを選択的に適用できます。

ボートのカラーリング方法の概要。オーバーレイブレンディングを使用すると、ベースのアルベドマップに繊細なカラーリングを適用可能。
親の RaceBoats グラフで使いやすいようにサブグラフにまとめられたシェーダーグラフ内のノードのレイアウト

『Boat Attack』では、昼と夜のサイクルが完全に再現されます。これをよりリアルに見せるため、レベル全体にわたって建物の窓にシェーダーグラフを作成しました。シェーダーグラフによって、夕暮れ時になると窓に明かりが灯り、夜明けとともに明かりが消えます。

これを実現するために、昼/夜の値にマッピングされたシンプルな発光テクスチャーを使用しました。オブジェクトの位置を使って順番を若干ランダム化するエフェクトを追加することで、家々に異なるタイミングで明かりが灯るようにしました。

ランダムな発光を実現するノードマップ

『Boat Attack』に変化するライティングを追加したので、シンプルなハイダイナミックレンジイメージング(HDRI)スカイボックスではもの足りなくなりました。シーン内のライティングで雲を動的に照らす必要があります。

しかし、ふわふわの大きな雲のリアルタイムレンダリングは要求が厳しくなります。モバイルハードウェアで実行する必要があるなら、なおさらです。雲をさまざまな角度から見せる必要はないので、パフォーマンスを節約するためにテクスチャーを持つカードを使うことにしました。

現在雲のレンダリングに使用しているグラフ全体

シェーダーグラフは外観のプロトタイピングにおいて、重要な役割を果たしました。Houdini から雲のボリューメトリックデータをいくつかベイクして、シェーダーグラフで詳細にカスタマイズしたライティングを作成しました。これらの雲はまだ作成途上ですが、ノードベースのエディターでさまざまなサーフェスを作り出せることを証明しています。

API からのレンダリングでシームレスな平面反射を表現

Unity がスクリプタブルレンダーパイプラインで目指しているのは、レンダリングコードをブラックボックス化することではなく、ユーザーがレンダリングコードをカスタマイズできるようにすることです。Unity は、既存のレンダリングコードを単にオープン化することよりも、新しい API やハードウェアを念頭に置いた Unity のレンダリング技術を推進してきました。

ユニバーサルレンダーパイプラインに標準搭載されているレンダリング機能は、独自の C# コードで拡張できます。以下の 4 つのフックが公開されています。

  • RenderPipelineManager.beginFrameRendering
  • RenderPipelineManager.beginCameraRendering
  • RenderPipelineManager.endCameraRendering
  • RenderPipelineManager.endFrameRendering

これらのフックを使えば、シーンや特定のカメラをレンダリングする前に、簡単に自作のコードを実行できます。『Boat Attack』では、これらのフックを使って、メインフレームがレンダリングされる前にシーンをテクスチャー内にレンダリングすることで平面反射を実装しています。

private void OnEnable()
{
   RenderPipelineManager.beginCameraRendering += ExecutePlanarReflections;
}

これはサブスクライブ先のコールバックで、OnDisable では逆にサブスクライブを解除します。

これは平面反射スクリプトのエントリポイントです。このコードで、ユニバーサルレンダーパイプラインがカメラをレンダリングしようとするたびにカスタムメソッドを呼び出すことができます。呼び出すのは、ExecutePlanarReflections メソッドです。

public void ExecutePlanarReflections(ScriptableRenderContext context, Camera camera)
{
    //レンダリングコード...
}

コールバックに [beginCameraRendering] を使用しているので、メソッドは [ScriptableRenderContext] と [Camera] をパラメーターとして持つ必要があります。このデータはコールバックと一緒に受け渡され、レンダリングされるカメラを知らせてくれます。

このコードは平面反射を実装するために通常使われるコードとほぼ同じで、カメラと行列の処理を行います。唯一の違いは、ユニバーサルレンダーパイプラインにはカメラをレンダリングするための新しい API が用意されていることです。

平面反射を実装するためのメソッド全体は、以下のとおりです。

private void ExecutePlanarReflections(ScriptableRenderContext context, Camera camera)
{
   // リフレクションやプレビューには平面反射をレンダリングしないようにします
   if (camera.cameraType == CameraType.Reflection || camera.cameraType == CameraType.Preview)
       return;

   UpdateReflectionCamera(camera); // 反射カメラを作成します
   PlanarReflectionTexture(camera); // レンダーテクスチャーを作成して割り当てます

   var data = new PlanarReflectionSettingData(); // 画質設定を保存して平面反射用に画質を下げます

   beginPlanarReflections?.Invoke(context, m_ReflectionCamera); // PlanarReflection 用のコールバックアクションです
   UniversalRenderPipeline.RenderSingleCamera(context, m_ReflectionCamera); // 平面反射をレンダリングします

   data.Restore(); // 画質設定を復元します
   Shader.SetGlobalTexture(planarReflectionTextureID, m_ReflectionTexture); // 水のシェーダーにテクスチャーを割り当てます
}

ここでは新しい [UniversalRenderPipeline.RenderSingleCamera()] メソッドを使って所定のカメラをレンダリングしています。この場合、カメラは平面反射カメラです。

このカメラはテクスチャー([Camera.targetTexture] を使用して設定)にレンダリングされるので、この後のレンダリングで水のシェーディングに使用できるレンダーテクスチャーをこの時点で入手します。PlanarReflection のスクリプト全文は、GitHub のページで確認してください。

平面反射の合成。左から右に向かって:平面反射カメラの生の出力、フレネルを暗くして法線をオフセット、完成版の水のシェーダー、平面反射がない水のシェーダー

これらのコールバックは、ここではレンダリングを呼び出すために使用されていますが、その他の用途もあります。たとえば、平面反射カメラのシャドウを無効にしたり、カメラに使用するレンダラーを選んだりするためにも使用できます。シーン内やプレハブ内の動作をハードコーディングするよりも、API を使う方が、より複雑な動作をより細かく制御できます。

カスタムレンダーパスの挿入による特殊エフェクト

ユニバーサルレンダーパイプラインでは、ScriptableRenderPass に基づいてレンダリングが行われます。ScriptableRenderPass は、レンダリング対象とレンダリング方法に関する命令セットです。ScriptableRenderPass の多くはまとめてキューに追加され、ScriptableRenderer と呼ばれるものを形成します。

ユニバーサルレンダーパイプラインのもう 1 つの要素が、ScriptableRendererFeature です。ScriptableRendererFeature は基本的にカスタム ScriptableRenderPass のデータコンテナで、多数のパスをアタッチされている各種データとともに格納できます。

ForwardRenderer と 2DRenderer という 2 つの ScriptableRenderer が最初から付属しています。ForwardRenderer は ScriptableRendererFeature の挿入をサポートします。

ScriptableRendererFeature をより簡単に作成できるように、Unity は、テンプレートファイルから作成する機能を追加しました(C# の MonoBehaviour スクリプトの場合と非常に似ています)。Project ビューで右クリックして [Create/Rendering/Universal Pipeline/Renderer Feature] を選択するだけで、元になるテンプレートファイルが生成されます。生成されたテンプレートで、ForwardRendererData アセットの「Render Feature」リストに ScriptableRendererFeature を追加できます。

『Boat Attack』のデモでは、ScriptableRendererFeatures を使って水のレンダリング用に 2 つのレンダリングパスを追加しました。1 つはコースティクス用で、もう 1 つは WaterEffects と呼ばれるものです。

コースティクス

Caustics ScriptableRendererFeature により、シーンの不透明なパスと透明なパスの間にカスタムコースティクスシェーダーをレンダリングするパスが追加されます。これを実現するため、水に沿って大型のクアッドをレンダリングして空中のピクセルがレンダリングされないようにします。クアッドはカメラを追いかけますが水の高さにスナップされ、シェーダーが不透明なパスから画面に写っているものの上から付加的にレンダリングされます。

コースティクスのレンダーパスの合成。左から右に向かって:深度テクスチャー、深度に基づくワールド空間位置の再構成、ワールド空間位置でマッピングされたコースティクステクスチャー、不透明なパスとの最終的なブレンド

[CommandBuffer.DrawMesh] を使用すると、クアッドを描画し、(水とカメラの座標に基づいて)メッシュの位置を決めるための行列を指定して、コースティクスマテリアルを設定することができます。このコードは次のようになります。

public class WaterCausticsPass : ScriptableRenderPass
{
   const string k_RenderWaterCausticsTag = "Render Water Caustics";
   public Material m_WaterCausticMaterial;
   public Mesh m_mesh;

   public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
   {
       var cam = renderingData.cameraData.camera;
       if(cam.cameraType == CameraType.Preview) // プレビューでのパスレンダリングを停止します
           return;

       // コースティクスメッシュの位置を決める行列を作成します
       Vector3 position = cam.transform.position;
       position.y = 0; // TODO:グローバルな「water height」変数を読み込む
       Matrix4x4 matrix = Matrix4x4.TRS(position, Quaternion.identity, Vector3.one);

       // CommandBuffer を設定し、コースティクスマテリアルと行列を使ってメッシュを描画します
       CommandBuffer cmd = CommandBufferPool.Get(k_RenderWaterCausticsTag);
       cmd.DrawMesh(m_mesh, matrix    , m_WaterCausticMaterial, 0, 0);
       context.ExecuteCommandBuffer(cmd);
       CommandBufferPool.Release(cmd);
   }
   }

水のエフェクト

WaterFXPass の動作を説明するためのスプリットビュー。左側は最終的なレンダリング、右側は水上のパスの結果のみを表示しているデバッグビュー

WaterFXPass はもう少し複雑です。このエフェクトの目的は、波や泡の発生など、オブジェクトによる水への影響を生み出すことです。これを実現するために、テクスチャーの各チャンネルにそれぞれ異なる情報を書き込むことができるカスタムシェーダーを使って、オフスクリーンのレンダーテクスチャーに特定のオブジェクトをレンダリングします。泡のマスクは赤のチャンネルに、法線オフセットの X と Z は緑と青のチャンネルに、最後に水のディスプレイスメントはアルファチャンネルに書き込まれます。

WaterFXPass の合成。左から右に向かって:最終的な出力、ワールド空間法線の作成に使用される緑と青のチャンネル、泡のマスクに使用される赤のチャンネル、水のディスプレイスメントの作成に使用されるアルファチャンネル(赤 = 正、黒 = 一定、青 = 負)

まずはレンダリング先となるテクスチャーが必要なので、半解像度で作成します。次に、WaterFX というシェーダーパスを持つ透明オブジェクト用にフィルターを作成します。その後、[ScriptableRenderContext.DrawRenderers] を使用してシーンにオブジェクトをレンダリングします。最終的なコードは次のようになります。

class WaterFXPass : ScriptableRenderPass
   {
   const string k_RenderWaterFXTag = "Render Water FX";
   private readonly ShaderTagId m_WaterFXShaderTag = new ShaderTagId("WaterFX");
   private readonly Color m_ClearColor = new Color(0.0f, 0.5f, 0.5f, 0.5f); // r = 泡のマスク、g = 法線 X、b = 法線 Z、a = ディスプレイスメント
   private FilteringSettings m_FilteringSettings;
   RenderTargetHandle m_WaterFX = RenderTargetHandle.CameraTarget;

   public WaterFXPass()
   {
       m_WaterFX.Init("_WaterFXMap");
       // 透明オブジェクトのみをレンダリングします
       m_FilteringSettings = new FilteringSettings(RenderQueueRange.transparent);
   }

   // レンダーテクスチャーにレンダリングしてクリートを制御するために Configure を呼び出します
   public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
   {
       // デプスバッファーは不要です
       cameraTextureDescriptor.depthBufferBits = 0;
       // 半解像度
       cameraTextureDescriptor.width /= 2;
       cameraTextureDescriptor.height /= 2;
       // デフォルトのフォーマットです。TODO:HDR フォーマットの有用性について調査する
       cameraTextureDescriptor.colorFormat = RenderTextureFormat.Default;
       // レンダリング先となる TemporaryRT を取得します
       cmd.GetTemporaryRT(m_WaterFX.id, cameraTextureDescriptor, FilterMode.Bilinear);
       ConfigureTarget(m_WaterFX.Identifier());
       // パックされたデータ用に特定の色で画面をクリアします
       ConfigureClear(ClearFlag.Color, m_ClearColor);
   }

   public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
   {
       CommandBuffer cmd = CommandBufferPool.Get(k_RenderWaterFXTag);
       using (new ProfilingSample(cmd, k_RenderWaterFXTag)) // プロファイリングできることを確認します
       {
           context.ExecuteCommandBuffer(cmd);
           cmd.Clear();

           // ここで "WaterFX" シェーダーパスに基づいてレンダラーを選択して、逆順にソートします
           var drawSettings = CreateDrawingSettings(m_WaterFXShaderTag, ref renderingData,
               SortingCriteria.CommonTransparent);

           // 設定したルールに合致するすべてのレンダラーを描画します
           context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref m_FilteringSettings);
       }
       context.ExecuteCommandBuffer(cmd);
       CommandBufferPool.Release(cmd);
   }

   public override void FrameCleanup(CommandBuffer cmd)
   {
       // テクスチャーが 1 台のカメラ内で使用されているため、後で RT をクリーンアップする必要があります
       cmd.ReleaseTemporaryRT(m_WaterFX.id);
   }
   }

これらの ScriptableRenderPass は両方とも 1 つの ScriptableRendererFeature 内に存在します。この機能には [Create()] 関数が含まれており、これを使用すると、リソースを設定したり、設定を UI から渡したりすることができます。これらは水をレンダリングする際は常に一緒に使用されるため、1 つの機能で ForwardRendererData に追加できます。コード全体は、GitHub で確認できます。

今後の計画

Unity は、2019.4 LTS を含む Unity 2019 のサイクルを通じて引き続きこのプロジェクトを更新していきます。Unity 2020.1 以後は、プロジェクトが動作するように保持するつもりですが、新しい機能は追加しない予定です。

今後予定されている改善点の一部を紹介します。

  • 昼/夜サイクルを完成させる(カスタム化の必要性を低減するためにユニバーサルレンダーパイプラインにより多くの機能を統合する必要があります)
  • 水の UX/UI を洗練する
  • Imposter を実装する
  • コードのクリーンアップとパフォーマンスの微調整を継続する

役立つリンク

GitHub の『Boat Attack』リポジトリ

2019.3 プロジェクト全体へのリンク(GitHub を利用したくない場合)

ユニバーサルレンダーパイプラインのマニュアル

ユニバーサルレンダーパイプラインと HD レンダーパイプライン

ユニバーサルレンダーパイプライン(UniversalRP)は HD レンダーパイプライン(HDRP)の代替となるものではなく、HDRP の機能を含むものでもありません。

ユニバーサルレンダーパイプラインは、今後 Unity におけるデフォルトのレンダーパイプラインになる予定です。「1 回の開発でどこにでもデプロイ」を合言葉に、高い柔軟性と拡張性を備えたユニバーサルレンダーパイプラインは、組み込みのレンダーパイプラインよりも高いパフォーマンスを発揮し、さまざまなプラットフォームに拡張可能です。また、グラフィックスの品質も優れています。

HDRP は最先端のグラフィックスをハイエンドのプラットフォームで実現します。HDRP は、ハイエンドなハードウェア向けのグラフィックスに重点を置いた、高パフォーマンスで強力な、忠実度の高いビジュアルを目標とするプロジェクトに最適です。

使用するレンダリングパイプラインは、プロジェクトの機能やプラットフォームの要件に応じて選択ください。

UniversalRP を使ってみよう

製品版のすべての機能と優れたパフォーマンスを、すぐにご利用いただけます。アップグレードツールを使用してプロジェクトをアップグレードするか、Unity Hub からユニバーサルプロジェクトテンプレートを使用して新しいプロジェクトを開始してください。

ユニバーサルレンダーパイプラインに関するフォーラムに、ぜひフィードバックをお寄せください!

2020年2月10日 カテゴリ: Engine & platform | 15 分 で読めます

Is this article helpful for you?

Thank you for your feedback!

取り上げているトピック