【Unity】CommandBuffer とステンシルバッファで特定のモデルにモザイクをかけてみる

どーも、ぐるたか@guru_takaです。

前にポストエフェクトで特定のオブジェクトにモザイクをかける方法を紹介しました。ザックリまとめると、複数のカメラを用意して、レイヤーごとにポストエフェクトする感じです。


【Unity】ポストエフェクトで特定のオブジェクトにモザイクをかけてみた

色々調べてみたら、コマンドバッファとステンシルバッファの仕組みを使えば、同じことができると判明。実際に創ってみました。

ユニティちゃんのスキン(顔や腕など)のみにモザイクがかかっています。こちらは1つのカメラのみで、非常にシンプルでした。

備忘録もかねて、さっそく方法を紹介していきます!

コマンドバッファとステンシルバッファ

さっくりと登場人物であるコマンドバッファとステンシルバッファについて説明します。詳細は掲載している参考リンクをチェックしてみてください!

コマンドバッファとは?

コマンドバッファとは、レンダリングパイプラインにて、様々なタイミングで処理を差し込める機能です。

公式で掲載されているパイプラインの図の緑点に、コマンドバッファを追加できます。

具体的な使用例として、ブラーの処理が掲載されていました。

ステンドグラス風の壁があり、裏側でブラーがかかった状態で背景が透けていますよね。

実はSkyboxの描画後に、カメラのレンダリング結果をコピーしてブラーをかけ、マテリアルのテクスチャに打ち込んでいます。

こんな感じで、コマンドバッファを使うことで、表現の幅が広がります。
参考 グラフィックスコマンドバッファ公式 参考 Unity 5 の CommandBuffer を利用したレンダリングパイプラインの拡張について調べてみた凹みTips

ステンシルバッファとは?

ステンシルバッファとは、超わかりやすくまとめると、マスクのような機能です。こちらの図をみれば、一目瞭然!

引用:【Unityシェーダ入門】ステンシルバッファを使って隠れた部分を描く

このようにステンシルバッファを指定してあげると、色分けができます。他にもこんな面白い表現まで!

引用:【Unity】ステンシルバッファを使った2D面白表現を色々と試す

参考 【Unity】 UnityのShaderでステンシルバッファを試すQiita

特定の場所にモザイクをかける方法

まずは大まかな流れから解説します。

STEP.1
コマンドバッファでモザイクのイメージエフェクトをかける
STEP.2
モザイクかけたいモデルのステンシルバッファを1に変える
STEP.3
モザイクshaderに、ステンシルバッファが1のときだけ処理するようにする

STEP1. コマンドバッファ

まずはコマンドバッファのソースから!

using UnityEngine;
using UnityEngine.Rendering;

[RequireComponent(typeof(Camera))]
public class CommandBufferPostEffect : MonoBehaviour
{

    //イメージエフェクトかけたいshader
    [SerializeField]
    private Shader _shader;

    private void Awake()
    {
        Initialize();
    }

    private void Initialize()
    {
        var camera = GetComponent();

        var material = new Material(_shader);

        //コマンドバッファのインスタンス化
        var commandBuffer = new CommandBuffer();
        commandBuffer.name = "mosaic";

        //今の地点でのレンダリング結果を一時的にRender Textureへコピー
        int tempTextureIdentifier = Shader.PropertyToID("_PostEffect");
        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.Blit(BuiltinRenderTextureType.CameraTarget, BuiltinRenderTextureType.CameraTarget, material);

デバックでFrame Debugを使うと、ひと目でわかるのでオススメです!

注意
レンダーテクスチャをコピーせずにやることもできますが、追加するフェーズによって想定外の処理になったりするので、ご注意してください。

たとえば、AfterEverythingの場合、最初に提示した方法(レンダーテクスチャにコピーする方法)でないと、うまくいきませんでした。

参考 【Unity】CommandBufferでポストエフェクトを掛けてステンシルバッファも使ってみるLIGHT11 参考 【Unity】イメージエフェクトを特定のモデルだけにかけるQiita

STEP3.ステンシルバッファを1に変える

エフェクトをかけたいマテリアルに対し、ステンシルバッファを0から1に変更します。

シンプルにshaderのpass内に以下をぶちこめばOK!

           Stencil
            {
                Ref 1
                Comp Always
                Pass Replace
            }

STEP2. モザイクShader

モザイクshaderの説明はメインではないので割愛します。大事なのは、以下の部分です。

           Stencil
            {
                Ref 1
                Comp Equal
            }

ステンシルバッファが1のときに処理する意味です。これで準備完了!

Shader "CommandBuffer/mosaic" {
    Properties {
        _MainTex ("MainTex", 2D) = "white" {}
        _PixelNumber ("PixelNum", float) = 100
    }

    SubShader {
        Pass {
            Tags { "RenderType"="Opaque" }

            Stencil
            {
                Ref 1
                Comp Equal
            }

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

            sampler2D _MainTex;
            float _PixelNumber;

            struct v2f {
                half4 pos : POSITION;
                half2 uv : TEXCOORD0;
            };

            float4 _MainTex_ST;

            v2f vert(appdata_base v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            half4 frag(v2f i) : COLOR {
                half2 uv = floor(i.uv * _PixelNumber) / _PixelNumber;
                fixed4 col = tex2D(_MainTex, uv);
                return col;
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

最後にSTEP1で作ったコンポーネントをカメラにアタッチ。インスペクターからモザイクshaderをアタッチすれば完成です!

最後に

以上になります。個人的ですが、カメラやレイヤーを複数台容易するやり方よりも遥かにシンプルかつ直感的で良いなぁと感じました。

ぜひ皆様の参考になれば幸いです!
©UTJ/UCL

コメントを残す