Unity を検索

スクリプタブルレンダーパイプラインの概要

2018年1月31日 カテゴリ: テクノロジー | 7 分 で読めます
シェア

Is this article helpful for you?

Thank you for your feedback!

2018.1 ベータ版 で初登場したスクリプタブルレンダーパイプライン(SRP)は、Unity でのレンダリングの設定や実行を C# スクリプトで制御できる仕組みです。カスタムのレンダリングパイプラインを書くに当たっては、まずレンダリングパイプラインとは何かを理解する必要があります。

レンダリングパイプラインとは

「レンダリングパイプライン」は、オブジェクトを画面に表示させるために使用される様々な技術を指す包括的な用語です。レンダリングパイプラインには、大まかに言うと次の要素が含まれます。

  • カリング
  • オブジェクトのレンダリング
  • ポストプロセッシング

上記の各要素は、それぞれどのような方法で実行するかによってさらに細かく分けることができます。例えば、オブジェクトのレンダリングの実行方法には以下のような選択肢があります。

  • マルチパスレンダリング
    • オブジェクトに 1 パス、ライトに 1 パス
  • シングルパスレンダリング
    • オブジェクトに 1 パス
  • デファードレンダリング
    • 面のプロパティを G バッファにレンダーし、スクリーン空間ライティングを実行する

カスタム SRP を書くに当たっては、上記のような事柄の選択を行う必要があります。それぞれの選択肢の長所と短所を考慮した上で決断することが必要となります。

このコンテンツはサードパーティのプロバイダーによってホストされており、Targeting Cookiesを使用することに同意しない限り動画の視聴が許可されません。これらのプロバイダーの動画の視聴を希望する場合は、Targeting Cookiesのクッキーの設定をオンにしてください。

デモプロジェクト

本記事で解説している機能のすべてがGitHub 上にあるデモプロジェクトに網羅されています。

レンダリングのエントリポイント

SRP を使用する際には、レンダリングを制御するクラスを定義する必要があります。これが、作成していくレンダリングパイプラインとなります。エントリポイントは「Render」の呼び出しで、これは引数としてレンダリングのコンテキスト(以下参照)と、レンダリングされるカメラのリストを取ります。


public class BasicPipeInstance : RenderPipeline
{
   public override void Render(ScriptableRenderContext context, Camera[] cameras){}
}

レンダリングパイプラインコンテキスト

SRP は、遅延実行という概念に基づいてレンダリングを行います。ユーザーは、コマンドのリストを構築していき、それらを実行します。このコマンドの構築に使用するオブジェクトを ScriptableRenderContext と呼びます。コンテキストに処理を追加したら、Submit を呼び出すことによって、キューに入ったすべてのドローコールをサブミットします。

そのひとつの例は、レンダーターゲットを、レンダリングコンテキストの実行するコマンドバッファを使用して消去する場合です。


// レンダリングコンテキストへコマンドを発行するために
// 使用できる、新しいコマンドバッファを作成する
var cmd = new CommandBuffer();

// 空のレンダーターゲットコマンドを発行する
cmd.ClearRenderTarget(true, false, Color.green);

// コマンドバッファをキューに入れる
context.ExecuteCommandBuffer(cmd);
あまり面白くないレンダリングパイプラインですね?

画面に描画された図形をすべて消去するレンダリングパイプラインの全体はこちらでご確認いただけます。

カリング

カリングとは、画面に何を描画するかを算出していくプロセスです。

Unity におけるカリングには以下が含まれます。

  • 【錐台カリング】カメラのニアおよびファーのクリッピング平面の間にあるオブジェクトを計算します。
  • 【オクルージョンカリング】どのオブジェクトが別のオブジェクトの影に隠れるかを計算し、それらをレンダリングの対象から除きます。詳細はオクルージョンカリングに関するドキュメントをご覧ください。

レンダリング開始時、まず最初に計算されなければならないのは、何をレンダーするかです。この計算には、カメラの取得および、そのカメラの視点でのカリング処理の実行が含まれます。カリング処理は、そのカメラから見てレンダリングする意味のあるオブジェクトおよびライトのリストを戻します。これらのオブジェクトはレンダリングパイプライン内で後に使用されます。

SRP におけるカリング

SRP では基本的に、カメラの視点からオブジェクトのレンダリングを行います。このカメラは Unity がビルトインのレンダリングで使用するカメラオブジェクトと同じものです。SRP ではカリングに使用できる API を多数提供しています。大まかな処理の流れは以下のようになります。


// カリングのパラメーターを持つ構造体を作成する
ScriptableCullingParameters cullingParams;

// カメラからカリングのパラメーターを追加する
if (!CullResults.GetCullingParameters(camera, stereoEnabled, out cullingParams))
    continue;

// カリングパラメーターをここで修正することも可能です。
cullingParams.isOrthographic = true;

// カリング処理の結果を持つ構造体を作成する
CullResults cullResults = new CullResults();

// カリング処理を実行する
CullResults.Cull(ref cullingParams, context, ref cullResults);

カリング処理の結果が格納され、そのデータを使ってレンダリングを行う準備が整いました。

描画

カリング処理の結果が揃ったので、そのデータを使って画面に描画を行うことができます。

しかし、設定可能な要素が数多くあるため、この時点ですでにいくつかの決定を行う必要があります。これらの決定の多くは以下の条件に基づいて行います。

  • レンダリングパイプラインのターゲットにするハードウェア
  • 目指すビジュアルスタイルや雰囲気
  • どんなタイプのプロジェクトを開発するか

例えば、モバイル 2D 横スクロールアクションゲームと、PC 向けのハイエンドの一人称視点ゲームを考えてみましょう。これらのゲームそれぞれの持つ制約は非常に異なっているため、レンダリングパイプラインもまた非常に異なるものとなります。具体的にここで決定が必要になるのは、例えば以下のような事柄です。

  • 高ダイナミックレンジ(HDR) vs 低ダイナミックレンジ(LDR)
  • リニアワークフロー vs ガンマワークフロー
  • マルチサンプルアンチエイリアシング vs ポストプロセスアンチエイリアシング
  • 物理ベースレンダリングマテリアル vs 通常のマテリアル
  • ライティングあり vs ライティングなし
  • ライティングの手法
  • シャドーイングの手法

レンダリングパイプラインを書くに当たっては、上記のような事柄を決定しておくことで、実際に作成する際にどんな制約が課せられるか把握しやすくなります。

ここでは、一部のオブジェクトを不透明に描画できる、ライトなしのシンプルなレンダラーを例に取って見てみましょう。

フィルタリング(レンダリングバケットとレイヤー)

基本的には、レンダリングするオブジェクトに特定の分類がある場合、それは不透明オブジェクトであったり、透明オブジェクトであったり、表面下オブジェクトであったり、あるいはその他多数の分類が当てはまる場合もあります。Unity ではオブジェクトがレンダーされるべきタイミングをキューの概念によって表します。このキューがバケットを形成し、その中にオブジェクトが置かれます(そのオブジェクト上のマテリアルがソースになります)。SRP からレンダリングの呼び出しがあった時に、どのレンジ(範囲)のバケットを使用するかを指定できます。

バケットの他に、Unity 標準のレイヤーもフィルタリングに使用できます。

これにより、SRP 経由でオブジェクトを描画する際に、より豊富な種類のフィルタリングを行えるようになります。


// 不透明オブジェクトのレンダリングフィルターの設定を取得する
var opaqueRange = new FilterRenderersSettings();

// レンジを不透明キューのレンジに設定する
opaqueRange.renderQueueRange = new RenderQueueRange()
{
    min = 0,
    max = (int)UnityEngine.Rendering.RenderQueue.GeometryLast,
};

// すべてのレイヤーを含める
opaqueRange.layerMask = ~0;

描画設定(どのように描画するか)

フィルタリングとカリングの使用により何を描画するかが設定されますが、どのように描画するかも設定する必要があります。SRP では、フィルタリングを通過したオブジェクトの描画方法を多様に設定できます。このデータの設定に使用されている構造体は「DrawRenderSettings」です。この構造体で様々な設定を行えます。

  • Sorting - オブジェクトをレンダーする順序です(背面から前面の順、前面から背面の順など)。
  • Per Renderer フラグ - どの「ビルトイン」設定を Unity からシェーダーにパスするかの設定です。これにはオブジェクト毎のライトプローブや、オブジェクト毎のライトマップなどが含まれます。
  • Rendering フラグ - バッチングの使用有無、インスタンシングの使用有無を制御するフラグです。
  • Shader Pass - 現在のドローコールにどのシェーダーパスを使用するかの設定です。

// 描画レンダー設定を作成する
// これはシェーダーパス名を 1 つ取ります。
var drs = new DrawRendererSettings(Camera.current, new ShaderPassName("Opaque"));

// ドローコールのインスタンシングを有効にする
drs.flags = DrawRendererFlags.EnableInstancing;

// ライトプローブとライトマップのデータを各レンダラーに渡す
drs.rendererConfiguration = RendererConfiguration.PerObjectLightProbe | RendererConfiguration.PerObjectLightmaps;

// 通常の不透明オブジェクトと同様にオブジェクトをソートする
drs.sorting.flags = SortFlags.CommonOpaque;

描画

これで、ドローコールを発行するために必要な 3 つの要素が揃いました。

  • カリング処理の結果
  • フィルタリングの規則
  • 描画の規則

これでドローコールを発行できます!SRP における他の要素と同様、ドローコールもコンテキスト内への呼び出しとして発行されます。SRP では通常、個々のメッシュのレンダリングを行う代わりに、一度に大量のメッシュをレンダーする 1 回のコールを発行します。これにより、スクリプトの実行オーバーヘッドが削減されるだけでなく、CPU 上でのジョブ化・実行も速くなります。

ドローコールを発行するために、これまで構築してきた関数を組み合わせます。


// すべてのレンダラーを描画する
context.DrawRenderers(cullResults.visibleRenderers, ref drs, opaqueRange);



// コンテキストをサブミットすることで、キューに入った全てのコマンドが実行されます。
context.Submit();

これにより、現在紐づけられているレンダーターゲットにオブジェクトが描画されます。コマンドバッファを使用してレンダーターゲットを切り替えることもできます。

不透明オブジェクトを描画するレンダラーをこちらのリンクから入手できます。

https://github.com/stramit/SRPBlog/blob/master/SRP-Demo/Assets/SRP-Demo/2-OpaqueAssetPipe/OpaqueAssetPipe.cs

下記リンクの例をさらに拡張して透明オブジェクトのレンダリングを加えることも可能です。

https://github.com/stramit/SRPBlog/blob/master/SRP-Demo/Assets/SRP-Demo/3-TransparentAssetPipe/TransparentAssetPipe.cs

透明オブジェクトをレンダリングする際は、レンダリングの順序は背面から前面の順に変更されるということにご注意ください。

本記事が、皆様がカスタム SRP の作成を始めるに当たっての足掛かりとなれば嬉しく思います。早速 2018.1 ベータ版 をダウンロード して作成を始めてみてください。そして、こちらのフォーラムのスレッドに是非ご意見をお寄せください!

2018年1月31日 カテゴリ: テクノロジー | 7 分 で読めます

Is this article helpful for you?

Thank you for your feedback!