前回の成果
MatCapを理解した。
今回やること
CommandBufferを理解します。
CommandBuffer
レンダリングパイプラインの様々なタイミングで処理を挟むことのできるものになります。
以下の画像の緑の点のタイミングでCommandBufferを追加することができます。
Unity 5 の CommandBuffer を利用したレンダリングパイプラインの拡張について調べてみた - 凹みTips:より引用
CommandBufferを使ってみる
こちらのサイト様を参考にCommandBufferを使ってみたいと思います。
事前準備
以下アセットをAssetStoreからダウンロードします。
そして、BarrelをScene上に2つ配置します。

ソースコード
using UnityEngine; using UnityEngine.Rendering; public class PostEffectCommandBuffer : MonoBehaviour { [SerializeField] private Shader _shader; void Awake() { Initialize(); } private void Initialize() { Camera camera = this.GetComponent<Camera>(); Material material = new Material(_shader); CommandBuffer commandBuffer = new CommandBuffer(); commandBuffer.name = "commandBufferTest"; int tempTextureIdentifier = Shader.PropertyToID("_PostEffectTemp"); commandBuffer.GetTemporaryRT(tempTextureIdentifier, -1, -1); commandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, tempTextureIdentifier); commandBuffer.Blit(tempTextureIdentifier, BuiltinRenderTextureType.CameraTarget, material); commandBuffer.ReleaseTemporaryRT(tempTextureIdentifier); camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer); } }
まずはCommandBufferのソースになります。
ソースコード解説
では、ソースコードの解説に移ります。
RenderTextureの取得
int tempTextureIdentifier = Shader.PropertyToID("_PostEffectTemp"); commandBuffer.GetTemporaryRT(tempTextureIdentifier, -1, -1);
GetTemporaryRTは、一時的にRenderTextureを取得する関数となります。
引数は以下になります。
/// <summary> /// 一時的なRenderTextureを取得 /// </summary> /// <param name="nameID">テクスチャのシェーダープロパティ名</param> /// <param name="width">ピクセル単位の幅、カメラのピクセルの幅の場合は-1</param> /// <param name="height">ピクセル単位の高さ、カメラのピクセルの高さの場合は-1</param> public void GetTemporaryRT(int nameID, int width, int height);
今回はカメラの幅と高さをそのまま使用するので、-1を渡しています。
PostEffectを反映
commandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, tempTextureIdentifier); commandBuffer.Blit(tempTextureIdentifier, BuiltinRenderTextureType.CameraTarget, material);
Blitは、1つのRenderTextureを別のテクスチャへとコピーする関数となります。
引数の説明は以下です。
/// <summary> /// 1つのRenderTextureを別のテクスチャへとコピーする /// </summary> /// <param name="source">コピーするテクスチャやレンダーターゲット</param> /// <param name="dest">コピー先</param> /// <param name="mat">使用するマテリアル</param> public void Blit(RenderTargetIdentifier source, RenderTargetIdentifier dest, Material mat);
BuiltinRenderTextureType.CameraTargetは、現在レンダリングしているカメラのターゲットテクスチャとなります。
まず、先程取得したRenderTextureに現在のレンダーターゲットを描画します。
そして、そのRenderTextureにエフェクトをかけてレンダーターゲットに描画しています。
RenderTextureを解放
commandBuffer.ReleaseTemporaryRT(tempTextureIdentifier);
ReleaseTemporaryRTは、GetTemporaryRTで一時的に取得したRenderTextureを解放する関数となります。
/// <summary> /// GetTemporaryRTで一時的に取得したRenderTextureを解放する /// </summary> /// <param name="nameID">テクスチャのシェーダープロパティ名</param> public void ReleaseTemporaryRT(int nameID);
CommandBufferの追加
camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer);
AddCommandBufferは、指定された場所で実行されるようにCommandBufferを追加する関数となります。
/// <summary> /// 指定された場所で実行されるようにCommandBufferを追加する /// </summary> /// <param name="evt">CommandBufferの実行タイミング</param> /// <param name="buffer">CommandBuffer</param> public void AddCommandBuffer(CameraEvent evt, CommandBuffer buffer);
今回は、BeforeImageEffectsを指定しているのでImageEffectsの前となります。
以下画像のタイミングとなります。

これは、Unityの機能のFrameDebugを使用するとよくわかります。
FrameDebugは、Window -> Analysis > Frame Debuggerから開くことができます。

これでCommandBufferの準備は終わりました。
Stencilで描画結果を変える
今回は参考サイト様通り、ステンシルバッファを使用して描画結果を変えていきたいと思います。
ステンシルバッファについてはこちらを参照してください。
Barrelにアタッチするシェーダー
Shader "Unlit/StencilLambert" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Pass { Tags { "LightMode"="ForwardBase"} Stencil { Ref 1 Comp Always Pass Replace } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; void vert (in appdata v, out v2f o) { o.vertex = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.uv = TRANSFORM_TEX(v.uv, _MainTex); } void frag (in v2f i, out fixed4 col : SV_Target) { float3 lightDir = _WorldSpaceLightPos0.xyz; float3 normal = normalize(i.worldNormal); float NL = dot(normal, lightDir); float3 baseColor = tex2D(_MainTex, i.uv); float3 lightColor = _LightColor0; col = fixed4(baseColor * lightColor * max(NL, 0), 0); } ENDCG } } }
普通のランバート反射をするシェーダーになります。
着目してほしい点が、以下になります。
Stencil {
Ref 1
Comp Always
Pass Replace
}
これは、バッファに常に1を書き込む意味となります。
SerializeFieldにアタッチするシェーダー
Shader "Unlit/StencilColorInversion" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } Stencil { Ref 1 Comp Equal } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return 1 - col; } ENDCG } } }
色を反転させるシェーダーになります。
こちらもStencilに着目して頂きたいです。
Stencil {
Ref 1
Comp Equal
}
これは、バッファ値が1の場合のみレンダリングという意味となります。
各種アタッチする
まず、Main CameraにCommandBufferのScriptをアタッチします。
そのScriptのShaderに、色反転シェーダーをアタッチします。

そして、片方のBarrelのマテリアルにランバート反射マテリアルをアタッチします。
Textureは、Barrelのものをアタッチします。

アタッチ出来たらUnityを実行してください。
結果
ランバート反射をアタッチしたものだけ反転したら成功です。

ソースコードにコメントを付与
using UnityEngine; using UnityEngine.Rendering; public class PostEffectCommandBuffer : MonoBehaviour { [SerializeField] private Shader _shader; void Awake() { Initialize(); } private void Initialize() { Camera camera = this.GetComponent<Camera>(); Material material = new Material(_shader); // CommandBuffer生成 CommandBuffer commandBuffer = new CommandBuffer(); commandBuffer.name = "commandBufferTest"; int tempTextureIdentifier = Shader.PropertyToID("_PostEffectTemp"); // RenderTextureをカメラの解像度で取得 commandBuffer.GetTemporaryRT(tempTextureIdentifier, -1, -1); // PostEffectを反映 // RenderTextureに描画 commandBuffer.Blit(BuiltinRenderTextureType.CameraTarget, tempTextureIdentifier); // RenderTargetにmaterialの結果を反映させて描画 commandBuffer.Blit(tempTextureIdentifier, BuiltinRenderTextureType.CameraTarget, material); // RenderTextureの解放 commandBuffer.ReleaseTemporaryRT(tempTextureIdentifier); // ImageEffects前にCommnadBufferを追加 camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer); } }
今回は以上となります。
ここまでご視聴ありがとうございました。
