スクリプティングランタイムアップグレード
旧トピック

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

ノート: この機能は実験的機能です。

スクリプタブルレンダーパイプラインは、 Unity Store からダウンロードした標準の Unity エディターインストーラーには 含まれていません。スクリプタブルレンダーパイプラインは Unity Technologies GitHub からダウンロードし、インストールしてください。

ここでは、以下について説明します。

機能の概要

レンダリングパイプラインを再考して、柔軟性と透明性を向上させました。 主要な Unity レンダリングパイプラインは、C++ 基盤の C# で構築された複数のレンダリングループに変更されます。レンダリングループの C# コードは GitHub 上でオープンソースになるため、ユーザーは独自のカスタムレンダリングループを研究、改良、作成を行うことができます。

この機能を開発した理由

Unity の現在のレンダリングパイプラインについて、いくつか改良したい点があるためです。 その主なものを以下に説明します。現在のレンダリングパイプラインについては既存の Unity のレンダリングパイプライン で説明します。

最新機能を持つハードウェアで、パフォーマンスを向上させる

「ドローコールごとに 1 つのライト」であるフォワードレンダリングと「ステンシルマーク + ライトごとに形状を描画」するディファードシェーディングは、どちらも最新の方法とは言えません。DX9 ハードウェアでは概ね問題ありませんでしたが、コンピュートシェーダーの出現で、より良いパフォーマンスを得られるようになりました。既存のフォワードシェーディングは、ドローコール (CPU + 頂点トランスフォームのコスト) が多く、サーフェスのテクスチャを繰り返しサンプリングしブレンドすることによって帯域幅の消費が大きいという問題を抱えています。一方、ディファードシェーディングでは、ドローコール数が多いこと、ライトカリングが十分でないこと、ライトごとに行うステンシルマーク + ドローコールのコストが高いこと、G バッファデータを繰り返しフェッチすることが問題になっています。さらに、タイルベースの GPU では、リアルタイムシャドウが関係するときに、タイルの保存 + ロードが多く、タイルストレージやフレームバッファフェッチを利用できません。

私たちは、最新機能をもつハードウェアをターゲットとする、すぐに使えるレンダリングパイプラインを搭載した Unity をリリースすることを望んでいます。それでは、コンピュートシェーダー、ドローコールインスタンス、定数バッファなどの API と GPU 技術を基盤にできます。

カスタマイズと拡張が簡単で、「ブラックボックス」的要素を少なくする

たいていの Unity ユーザーはおそらくビルトインのレンダリングパイプラインを改造することはないでしょうが、より高度な技術を使うチームでは、改造したり拡張したいと思う場合もあります。そのため、レンダリングパイプラインを拡張可能にし、既存のものより不透明部分をかなり減らさなければなりません。

既存のレンダリングパイプラインはある程度拡張可能ですが (ユーザーによる独自のシェーダーの作成、カメラレンダリングの手動での制御、設定の変更、コマンドバッファ を使用したレンダリングパイプラインの拡張が可能です)、拡張の可能が十分とは言えません。また、それはあまりにも「ブラックボックス」的であるため、ドキュメント、コンファレンスのプレゼンテーション、MIT ライセンスに準ずるビルトインシェーダーのソースコード、コミュニティの知識などがギャップを埋めてはいますが、部分によっては Unity ソースコードライセンスなしでは理解するのが難しい状態です。ポストプロセシングUIネットワーク がすでにそうであるように、すべてのハイレベルコードとシェーダー/計算のコードを MIT ライセンスのオープンソースプロジェクトにしたいと考えています。

「すべてに対し 1 つのレンダリングパイプライン」を用いることは、パフォーマンスを天秤にかけてより柔軟に対応するために、いくつかの点を妥協しなければならないことがあります。このようなレンダリングパイプラインは、例えば、以下のようなケースに利用すると有効です。

  • 最新の PC/コンソール (DX11 ベース、ハイエンドグラフィックス) のための最適化

  • モバイル GPU のタイルのストレージ (on-tile storage) のために最適化し、フレームバッファフェッチやその他の使用可能な技術を使用する

  • VR (例えば、フォワードシェーディング + MSAA、シングルパスレンダリング、離れたアイレンダリングの結果のキャッシュまたは共有、ビューポートや解像度の縫合の様々なスキーム) のための最適化

  • ローエンドデバイス (古いモバイルや PC) やシンプルな 2D ゲームのための最適化 - 単純なワンパスのライティング (限られた数のライト、かつ/または頂点ライティング)

これらは実質的に別個のレンダリングパイプラインである必要はなく、他の既存のパイプラインのオプションとして使用できます。

古い技術との互換性をより簡易に扱えるようにする

Unity R&D チームにとって、レンダリングエンジンのしくみを大きく変更するのは基本的に非常に困難です。その大きな理由は、ユーザーは最新の Unity バージョンにアップデートしても「以前と同様に動作する」ことを期待するからです。以前と同様に動作する限り、積極的に新しい変更を望みます。例えば、Unity 5.3 ではスタンダードシェーダーを Blinn-Phong から GGX スペキュラーに変更しました。これは、ほとんどのユーザーにとっては歓迎されましたが、 中には、プロダクトの製作中にスペキュラーの挙動が変わってしまったというユーザーもいました (そのため、おそらくライトの設定とマテリアルの再調整が必要となったことでしょう)。

レンダリングコードとすべてのシェーダーコードの上位レベルの構造が簡単に「フォーク可能」かつバージョン対応可能になれば、この問題はより簡単になるはずだと考えます。

スクリプト可能なレンダリングループ - 新しい基盤

上に挙げた問題の全部または大部分は、堅固な直交的な高性能の基礎を築くことによってかなりエレガントに解決できると考えられます。これは基本的に「さまざまなフィルター基準を持つオブジェクト群を効率的に描画する機能」です。作業の分担は以下の通りです。

Unity C++ コード C#/シェーダーコード (MIT オープンソース)
カリング
フィルター/ソート/パラメーターでオブジェクト群を描画
内部グラフィックスプラットフォーム抽象化
カメラ設定
ライト設定
シャドウ設定
フレームレンダーパス構造 & ロジック
シェーダー/計算コード

C++ 側は、「カメラ」や「ライト」のようなものが存在することをほとんど認識していません。例えばカリングコードは、入力を通してバウンディングプリミティブと行列/カリング平面の配列を取得します。それが、メインビュー、リフレクションレンダリングビュー、シャドウマップビューなど何であっても関係ありません。

同様に、レンダリングコードは、「カリング結果に基づき不透明レンダーキューの範囲内にあるすべてのものを描画し、このシェーダーパスを持つもの、シェーダーパスを持たないもの、マテリアルによってソートしてから距離によってソート、各オブジェクトにライトプローブ定数の設定」で表現されます。いくらかの規約とビルトインはたいてい、どの種類のデータが各オブジェクトのインスタンスごとのデータとして設定されるべきか (ライトプローブ、リフレクションプローブ、ライトマップ、オブジェクトごとのライトリストなど) という点に存在します。

私たちは、スクリプト可能なレンダリングループを構築するための、堅牢で高性能かつ直交する構成要素のセットを提供可能にするために、根本的なプラットフォームのグラフィックス抽象化の変更を多く行いました。ただし、それらのほとんどはこのドキュメントのスコープ外です。行われた変更のいくつかは以下のとおりです。

  • C# クラスのBufferを公開。これはあらゆる種類のバッファデータ (頂点、インデックス、ユニフォーム、計算データなど) に使用できます。 C# からユニフォーム/定数バッファを作成して手動で更新できる機能です。

  • コンピュートシェーダー、特にデータが受け渡される方法に関する改善。

  • TextureFormat と RenderTextureFormat の分割を廃し、代わりに、すべてのグラフィックスに関連するコード (Direct3D の DXGI 形式と類似) で使用される DataFormat のようなものを使用。現在よりも多くの形式を公開します。

  • GPU データの非同期リードバック。非同期計算。


API 概要

ノート: API は流動的なので、このドキュメントはテストしようとする Unity バージョンに完全には合わない場合があります。

主なエントリーポイントは、RenderLoop.renderLoopDelegate です。この形式は、bool PrepareRenderLoop(Camera[] cameras, RenderLoop outputLoop);

レンダリングループデリゲートが登録されるとすべてのレンダリングがその関数に入り、既存のビルトインのレンダリングループは まったく実行されません

レンダリングループデリゲートの内部では、通常、新しい CullResults クラスを通してすべてのカメラのカリングを行います。その後、CommandBuffer 呼び出しと混合して RenderLoop.DrawRenderers への一連の呼び出しを行い、グローバルシェーダープロパティーの設定、レンダーターゲットの変更、コンピュートシェーダーなどの割り当てを行います。

概して、C# のレンダリングループのコードはカメラごとのロジック (入力ですべてのカメラを取得) とすべてのライトごとのロジック (カリング結果ですべての可視ライトを取得) を完全に制御しますが、通常はオブジェクトごとのロジックを制御しないように設計されています。オブジェクトは「群」として描画されます。DrawRenderers コールは描画する可視オブジェクトのサブセット、オブジェクトのソート方法、設定するオブジェクトごとのデータ種類を指定します。

最も単純なレンダリングループは、以下のようになります。

public bool __Render__(Camera[] cameras, RenderLoop renderLoop)
{
    foreach (var camera in cameras)
    {
        // カメラにカリング設定をします
        CullResults cull;
        CullingParameters cullingParams;
        if (!CullResults.GetCullingParameters (camera, out cullingParams))
            continue;
        cull = __CullResults.Cull__ (ref cullingParams, renderLoop);
        renderLoop.SetupCameraProperties (camera);
        // レンダーターゲットの設定と解除をします
        var cmd = new CommandBuffer();
        cmd.SetRenderTarget(BuiltinRenderTextureType.CameraTarget);
        cmd.ClearRenderTarget(true, true, Color.black);
        renderLoop.__ExecuteCommandBuffer__(cmd);
        cmd.Dispose();
        // ForwardBase シェーダーパスを使ってすべての不透明オブジェクトを描画
        var settings = new __DrawRendererSettings__(cull, camera, "ForwardBase");
        settings.sorting.sortOptions = SortOptions.SortByMaterialThenMesh;
        settings.inputFilter.SetQueuesOpaque();
        renderLoop.__DrawRenderers__(ref settings);
        renderLoop.Submit ();
    }
    return true;
}

最も重要な新しいスクリプティング API:

// 主なエントリーポイント
struct RenderLoop
{
    void ExecuteCommandBuffer (CommandBuffer);
    void DrawRenderers (ref DrawRendererSettings);
    void DrawShadows (ref DrawShadowsSettings); // 似ているが、少し専用化
    void DrawSkybox (Camera);
    static PrepareRenderLoop renderLoopDelegate;
}
// RenderLoop.DrawRenderers によってオブジェクトセットを描画する方法を設定管理
struct DrawRendererSettings
{
    DrawRendererSortSettings sorting;
    ShaderPassName shaderPassName;
    InputFilter inputFilter;
    RendererConfiguration rendererConfiguration;
    CullResults cullResults { set };
}
struct DrawRendererSortSettings
{
Matrix4x4 worldToCameraMatrix;
Vector3 cameraPosition;
SortOptions sortOptions;
    bool sortOrthographic;
}
enum SortOptions { None, FrontToBack, BackToFront, SortByMaterialThenMesh, ... };
struct InputFilter
{
    int renderQueueMin, renderQueueMax;
    int layerMask;
};
// 描画時にオブジェクトごとに設定するデータ種類
[Flags] enum RendererConfiguration
{
    None,
    PerObjectLightProbe,
    PerObjectReflectionProbes,
    PerObjectLightProbeProxyVolume,
    PerObjectLightmaps,
    ProvideLightIndices,
    // ...
};
// カリングとその結果
struct CullResults
{
    VisibleLight[] visibleLights;
    VisibleReflectionProbe[] visibleReflectionProbes;
    bool GetCullingParameters(Camera, out CulingParameters);
    static CullResults Cull(ref CullingParameters, RenderLoop renderLoop);
    // ComputeDirectionalShadowMatricesAndCullingPrimitives などのような
//ユーティリティー関数 
}
struct CullingParameters
{
    int isOrthographic;
    LODParameters lodParameters;
    Plane cullingPlanes[10];
    int cullingPlaneCount;
    int cullingMask;
    float layerCullDistances[32];
    Matrix4x4 cullingMatrix;
    Vector3 position;
    float shadowDistance;
ReflectionProbeSortOptions reflectionProbeSortOptions;
Camera camera;
}
struct VisibleLight
{
    LightType lightType;
    Color finalColor;
    Rect screenRect;
    Matrix4x4 localToWorld;
    Matrix4x4 worldToLocal;
    float range;
    float invCosHalfSpotAngle;
    VisibleLightFlags flags;
    Light light { get }
}

struct VisibleReflectionProbe; // VisibleLight に類似…

上の API はおそらく最終版ではありません。これから更新される可能性は大きいです。

  • RenderLoop クラスを持たず、代わりに CommandBuffer に DrawRenderers などの関数を持ち、可能ならネストになったコマンドバッファを持つオプションを検討中です。

  • カリング API を変更して、より多くの作業を可能にします。つまり、カリングをジョブ化し、他の作業をカバーするようにします。

  • レンダラーフィルタリングオプションが増える可能性があります。

  • 既存の「レンダーターゲット設定」APIではなく、より明示的な「レンダーパス」制御にします。

使用法、内部の仕組み、パフォーマンス

一般的な流れは、独自のレンダリングループのコードがカリングとすべてのレンダリングを処理するということです。フレームごとまたはレンダーパスごとのシェーダーユニフォーム変数の設定、一時的なレンダーターゲットの管理と設定、コンピュートシェーダーの割り当てなどがこれに含まれます。

可視のライトとプローブは、カリング結果から照会することができます。例えば、それらの情報はタイルベースのライトカリングのためにコンピュートシェーダーバッファに加えられます。あるいは、レンダリングループには、DX9 スタイルのフォワードレンダリング用にオブジェクトごとのライトリストを設定するいくつかの方法があります。

CPU パフォーマンスの面では、API は通常 オブジェクトごと の操作を行わないように構築されています。そのため、C# 側のコードはシーンの複雑さとは無関係です。通常、カメラをループし、シャドウをレンダリングするため、またはシェーダーで使用するためにライトのデータをパックするために、可視ライトをイテレーションします。C# で書かれたコードの残りの部分は、レンダーパス/レンダーテクスチャを設定し、「この可視オブジェクトのサブセットを描画する」コマンドを発します。

コードの C++ 部分 (カリング、DrawRenderers、DrawShadows) は、通常は、きっちりとパックされたデータ配列を処理する高パフォーマンススタイルで書かれており、内部的にマルチスレッドになっています。現在の実験では、この分割 (C# での高レベルのフレーム設定、C++ でのカリング/レンダリング) を使用すると、以前のレンダリングループの実装と同じまたはそれ以上のパフォーマンスが得られることが示されています。

C# 側は、たくさんのガベージコレクションされたオブジェクトを作成するかのように見えます。余分な往復なしに、「ネイティブの」(C++ 側) データを直接 C# に公開する方法を検討しています。C# では、ネイティブ側のメモリに直接書き込む配列と非常によく似ています。これはやや別のトピックなので、これについては別に説明します。


新しいビルトインの「HD レンダーループ」

私たちは、最新の (計算可能な) プラットフォームをターゲットとしたビルトインの「HD レンダーループ」を Unity に搭載する予定です。現在は PC と PS4/XB1 コンソールを念頭に置いて開発していますが、ハイエンドのモバイルプラットフォーム向けに最適化することも検討しています。モバイルにとって特に重要なのは、タイルのストレージ/フレームバッファのフェッチとその他の帯域幅節約技術のために HD レンダーループを最適化することです。

内部的には、考えられるすべてのノブについて個別のシェーダーバリアントへの依存を減少するようにシェーダーを作成して「静的」(ユニフォームベース) 分岐を使用します。シェーダーバリアントの特殊化は、最新の GPU のシェーダー解析/プロファイリングに基づいて意味がある場合にのみ使用します。

新しい HDRenderLoop は github ScriptableRenderLoop で開発されています (ある時点ではまとまっていないこともあるので、とても興味がある場合にのみ使用してください)。

ライト機能

  • コンピュートシェーダー付きのタイルベースのライトカリング

    • ディファードシェーディングされた不透明オブジェクトのための精密に裁断されたタイルベースのライティング(FPTL)

    • フォワードレンダリングしたオブジェクトと透明性のための群にまとめたタイルベースのライティング

    • プロジェクトに応じてディファードレンダリングとフォワードレンダリングの切り替えが可能

  • ライト

    • 通常のポイントライト/スポットライトとディレクショナルライト

    • エリアライト (ポリゴンライト とラインライト)

    • 正確なリニアライト & PBR

    • 物理ライトユニット、IES ライト

    • (予定) 錐台ライト (いわゆる範囲のあるディレクショナルライト)

  • シャドウ

    • すべてのリアルタイムシャドウはシングルアトラスから 割り当てされます

    • シャドウメモリの許容量とライトごとの解像度のオーバーライドに対する直感的な制御

    • 特にスポットライト/ポイントライトの PCF フィルタリングの向上

    • 半透明オブジェクトのシャドウ

  • GI

    • 正確な HDR

    • 直接光との整合性

  • (予定) シャドウの改良

    • 指数シャドウマップ (ESM/EVSM)

    • エリアライトのシャドウの改良

  • (予定) 容量基準ライティング

    • 空/フォグの大気散乱モデル

    • ローカルフォグ

マテリアル機能

  • 現在のスタンダードシェーダと同様に、Metal と Specular のパラメーター化を使用した GGX

  • 異方性 GGX (Metal のパラメーター化)

  • サブサーフェス散乱と透過

  • クリアコート

  • 両面サポート

  • 高品質スペキュラーオクルージョン

  • 重なったマテリアル (他のマテリアルの混合およびマスク入力、最大 4 層まで)

  • 視差またはディスプレイスメントテッセレーションによるハイトマップ

  • (予定) ビルトインの LOD クロスフェード/ディザリング

  • (予定) 髪、目、布のシェーディングモデル

カメラ機能

  • 物理ベースのカメラパラメーター

  • Unity の ポストプロセススタック のサポート

  • 歪み

  • 速度バッファ (モーションブラー/時間的なアンチエイリアス (Temporal Anti-Aliasing))

  • *(予定) * 1/2 か 1/4 解像度レンダリング (例えば、パーティクル用) と合成

ワークフロー/デバッグ機能

  • シェーダー入力 (アルベド、法線など) の表示

  • レンダリングの中間バッファすべての表示 (ライト、モーションベクトルなど)

  • 様々なパスのレンダリングを制御するデバッグメニュー


既存の Unity のレンダリングパイプライン

現在 (Unity 5.5 以前) Unity はシーン用に 2 つのレンダリングパイプライン (フォワードレンダリングとディファードシェーディング) と、リアルタイムシャドウをレンダリングする 1 つの方法をサポートしています。以下は、現在のパイプラインの詳細な説明です。

シャドウ

シャドウのシステムは、フォワードシェーディングとディファードシェーディングのどちらが使用されているかにかかわらず、ほとんど同じように機能します。

  • シャドウを有効にした各リアルタイムライトは、個別のシャドウマップを取得します。

  • シャドウマップは、シェーダーで PCF フィルタリングでサンプリングされた 従来の深度テクスチャマップです (VSM/EVSM などのシャドウではありません)。

  • ディレクショナルライトは、カスケードシャドウマップ (2 または 4 カスケード) を使用できます。シャドウマップの空間は、アトラスのようにカスケードに分割されます。

  • スポットライトは常に単純な 2D シャドウマップを使用します。ポイントライトはキューブマップを使用します。

  • シャドウマップのサイズは、品質設定、画面解像度、画面上のライトの投影サイズに基づいて計算されます。あるいは、ゲーム開発者が各ライトのスクリプトによって明示的に制御することもできます。

  • カスケードシャドウマップは「スクリーンスペース」に適用されます。スクリーンスペースのシャドウマスクテクスチャを生成する「PCF フィルタリングの収集と実行」のステップが別にあります。後に通常のオブジェクトレンダリングでは、このテクスチャにサンプルを 1 つだけ加えます。

  • 半透明のオブジェクトが影を受けることはサポートされていません。

フォワードレンダリング

操作のデフォルトモードは、主に DX9 スタイルの「各ライトにつき 1 ドローコールと加算ブレンド」です。ゲームの品質設定によって、各オブジェクトでリアルタイムでレンダリングされるライトの数が決定されます。残りは球面調和関数 (SH) による表現に混合され、他の環境光とともにレンダリングされます。

  • シーンの主レンダリングの前の任意オプションとして、「深度テクスチャ」レンダリングパスがあります。これは、スクリプトに必要な場合、または、他の機能 (例えば、リアルタイムのカスケードシャドウ) がそれを必要な場合に使用されます。概念的には、シーンの深度バッファを使ってテクスチャを作成する Z プレパスに似ています。

  • シーンの主レンダリングの前の任意オプションとして、「モーションベクトル」レンダリングパスがあります。これは、スクリプト (モーションブラーや時間的なアンチエイリアス など) に必要な場合に使用されます。モーションベクトルレンダーパスは速度ベクトルを必要とするオブジェクトの速度ベクトルのテクスチャをレンダリングします。

  • リアルタイムシャドウマップはシーンの主なレンダリングの前にレンダリングされます。すべてのシャドウはまとまってメモリ内にあります。

  • 実際のシーンのレンダリングパスは、2 つのシェーダーセット専用になっています。「ForwardBase」 (アンビエント/プローブ + ライトマップ + 主要ディレクショナルライトからのライト/シャドウ)、これに続き、一度に 1 つずつリアルタイムライトを生成する加算ブレンド ForwardAdd。

ディファードシェーディング

これは、従来の DX9 スタイルのディファードシェーディングです。G バッファレンダーパスとそれに続く「ライト形状を 1 つずつ描画する」パス。このパスのそれぞれが G バッファデータを読み取り、照明を計算してライティングバッファに追加します。

  • フォワードレンダリングと同様に、G バッファの前に任意オプションのモーションベクトルパスがあります。

  • リフレクションプローブ は、ボックス形状をレンダリングしテクスチャにリフレクションを加えることによって、ライト同様に 1 つずつ描画されます。

  • ライト形状 (フルスクリーンのクアッド、スフィア、円錐形) をレンダリングし、テクスチャにリフレクションを加えることによって、ライトは 1 つずつ描画されます。

  • ライトのシャドウマップは、各ライトをレンダリングする直前にレンダリングされ、通常、ライトを使用した直後に削除されます。

  • ステンシルマークはライトとリフレクションプローブの両方に使用され、実際に計算されるピクセル数を制限します。

  • ディファードシェーディングをサポートしていないオブジェクトとすべての半透明オブジェクトは、フォワードレンダリングを使用してレンダリングされます。

カスタマイズ

上記の挙動をある程度カスタマイズすることは可能ですが、あまり多く行うことはできません。例えば、Valve の The Lab Renderer (Asset Store で入手可能) を使うと、ビルトインの挙動を (純粋に C# + シェーダーでは) 以下に置き換えることができます。

  • カスタムのシャドウシステムを実装し、すべてのシャドウを 1 つのアトラスにまとめることができます。

  • カスタムのフォワードレンダリングシステムで、すべてのライトが 1 つのパスでレンダリングされます。 ライトの情報はカスタムシェーダーのユニフォーム変数に設定されます。




  • 2017–05–26 限られた 編集レビュー でパブリッシュされたページ

  • 5.6 の新機能

スクリプティングランタイムアップグレード
旧トピック