先日、待望の Unity5 がリリースされました!新機能として Physically Based Shading や Global Illumination など様々な機能が提供されるようになりましたが、今回はその中のひとつである Command Buffer という機能について紹介してみたいと思います。


Command Bufferとは

 一言で言うと、レンダリングコマンド(命令)リスト です。

 Command Buffer は Unity のスクリプト上で生成、実行され、複数のレンダリングコマンドを持ちます。それらのコマンドを実行するタイミングは、

 になります。Command Buffer は Unity Proのみ サポートされている機能です。

どんな時に使用するの?

 例えば

  • Deferred Rendering で G-buffer に全てのオブジェクトを描画した後にいくつか追加でオブジェクトを描画したい
  • スカイボックスが描画された後にカスタムのジオメトリを描画したい

 などといったケースが考えられます。

 一般的には、Unity のレンダリングパイプラインを拡張したい場合に使用されるようです。

Commandの種類

 リファレンスPublic Functions に記載されているものが現在サポートされている Command になります。

  • Blit
  • Clear
  • ClearRenderTarget
  • DrawMesh
  • DrawRenderer
  • GetTemporaryRT
  • ReleaseTemporaryRT
  • SetGlobalColor
  • SetGlobalFloat
  • SetGlobalMatrix
  • SetGlobalTexture
  • SetGlobalVector
  • SetRenderTarget

CameraEvent について

 CommandBuffer が実行されるタイミングは「カメラがレンダリングしている任意の時点」と説明しましたが、リファレンスVariables がそのタイミングとなります。具体的なイベントタイミングは以下になります。

  • BeforeDepthTexture
  • AfterDepthTexture
  • BeforeDepthNormalsTexture
  • AfterDepthNormalsTexture
  • BeforeGBuffer
  • AfterGBuffer
  • BeforeLighting
  • AfterLigting
  • BeforeFinalPass
  • AfterFinalPass
  • BeforeForwardOpaque
  • AfterForwardOpaque
  • BeforeImageEffectsOpaque
  • AfterImageEffectsOpaque
  • BeforeSkybox
  • AfterSkybox
  • BeforeForwardAlpha
  • AfterForwardAlpha
  • BeforeImageEffects
  • AfterImageEffects
  • AfterAnything

試してみました

 簡単なサンプルを作成してみました。試した結果が以下になります。

 G-Buffer から法線情報を格納したテクスチャをコピーして、平面に対して射影テクスチャマッピングで表示しています。G-Buffer を使用するため、レンダリングパスはDeferred を設定しています。

 G-Buffer からコピーしたテクスチャを表示するシェーダーは以下のように記述しています。

Shader "Custom/BlitGBuffer" {

    SubShader {

        Tags { "Queue"="Transparent" "RenderType"="Opaque" }

        Pass {
            Name "BASE"
            Tags { "LightMode" = "Always" }

            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata_t {
                float4 vertex : POSITION;
                float2 texcoord: TEXCOORD0;
            };

            struct v2f {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

            v2f vert (appdata_t v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                #if UNITY_UV_STARTS_AT_TOP
                float scale = -1.0;
                #else
                float scale = 1.0;
                #endif
                o.texcoord.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
                o.texcoord.zw = o.vertex.zw;
                return o;
            }

            sampler2D _GrabTexture;

            half4 frag (v2f i) : SV_Target
            {
                return tex2Dproj (_GrabTexture, UNITY_PROJ_COORD(i.texcoord));
            }
            ENDCG
        }
    }
}

 頂点シェーダー(vert関数)での処理は射影テクスチャマッピングのためにテクスチャ座標を設定しています。フラグメントシェーダー(frag関数)では tex2Dproj メソッドでテクスチャからカラー情報を取得しています。

sampler2D _GrabTexture;

 と記述されている箇所があるかと思いますが、これが G-Buffer の内容をコピーしたテクスチャとなります。このテクスチャは以下で紹介するスクリプト上で設定される事になります。

 続いて CommandBuffer のスクリプトを記述します。

using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;

public class CommandBufferTest : MonoBehaviour {

    private Camera m_Cam;
    private Dictionary m_Cameras = new Dictionary();
    private static readonly CameraEvent TargetCameraEvent = CameraEvent.AfterGBuffer;

    private void Cleanup()
    {
        foreach (var cam in m_Cameras)
        {
            if (cam.Key)
            {
                cam.Key.RemoveCommandBuffer (TargetCameraEvent, cam.Value);
            }
        }
        m_Cameras.Clear();
    }

    public void OnEnable()
    {
        Cleanup();
    }

    public void OnDisable()
    {
        Cleanup();
    }

    public void OnWillRenderObject()
    {
        var act = gameObject.activeInHierarchy && enabled;
        if (!act)
        {
            Cleanup();
            return;
        }

        var cam = Camera.current;
        if (!cam)
            return;

        CommandBuffer buf = null;
        if (m_Cameras.ContainsKey(cam))
            return;

        buf = new CommandBuffer();
        buf.name = "CommandBufferTest";
        m_Cameras[cam] = buf;

        int screenCopyID = Shader.PropertyToID("_ScreenCopyTexture");
        buf.GetTemporaryRT (screenCopyID, -1, -1, 0, FilterMode.Bilinear);
        buf.Blit (BuiltinRenderTextureType.GBuffer2, screenCopyID);

        buf.SetGlobalTexture("_GrabTexture", screenCopyID);

        cam.AddCommandBuffer (TargetCameraEvent, buf);
    }   
}

 重要な箇所は OnWillRenderObject メソッド内で行っている処理です。

  • CommandBuffer の生成
  • テンポラリの RenderTexture の取得
  • 取得した RenderTextureにGbuffer2 の内容をコピー
  • シェーダーの _GrabTexture 変数にコピーしたテクスチャの内容をセット
  • カメラに CommandBuffer を追加

 といった流れになっています。上記のシェーダー(マテリアル)とスクリプトをシーン内に配置している Quad に対して設定し、実行すると上記の結果になります。

 ちなみに G-Buffer に格納される情報に関してはこちらを参考にしました。

公式サンプルプロジェクト

 Unity公式のブログに Command Buffer に関する記事が公開されています。

 こちらのブログに3つのサンプルの説明と、サンプルプロジェクトのダウンロードリンクがありますので是非参考にしてみてください。ちなみに上記で作成したサンプルは blurry refractions というサンプルを参考にしています。

参考文献