ユーセンブログ

ゲーム開発に関することをたまに書きます

【Unity】選択したオブジェクトを強調表示するためのアウトラインシェーダーの作成

はじめに

ゲームを製作しているときに選んでいるオブジェクトをわかりやすくするために分かりやすいアウトラインを出して強調したいという場面は割とよくあるのですが、簡単に使えて綺麗にアウトラインを表示する方法がなかなか見つからなかったので作成して見ました。 単純にアウトライン部分のみを深度を無視して描画するシェーダーなので使い方は簡単でカメラにアウトライン表示用のスクリプト、オブジェクトに今回作成するシェーダをアタッチしたマテリアルを追加するだけでこんな感じに裏に回った部分もアウトラインだけ表示される形で強調表示ができるようになります。 f:id:k_tachiban:20180917151938p:plain

実装

今回のシェーダーの実装ですが方法としてはステンシルバッファにステンシルのみ書き込んで後からポストプロセスでアウトライン部分を描画しています。アウトラインの実装はよくある裏面を法線方向に拡大して描画する手法を使っています。

オブジェクト用のシェーダー

アウトラインをつけるオブジェクト用のシェーダーコードです。 今回は2Passを使って描画をしています。

1Pass目

1Pass目ではZWrite、ZTestをオフ、アルファを0にした状態でポリゴンの裏面を法線方向に伸ばした上で描画しています。この時あとでアウトラインとして描画するためのステンシルを書き込んでおきます。

2Pass目

2Pass目でもZWrite、ZTestをオフ、アルファを0にした状態に設定し、ステンシルの番号を変えて描画処理を行います。 これで法線方向が反転しているような特殊なモデル以外はアウトライン部分が1のStencilが書き込まれた状態になります。(深度テストを行わないため法線が反転していると表示がおかしくなりがち)

全文

ポストプロセス

ポストプロセス用スクリプト

続いては、アウトラインを実際に書き込むためにスクリプトを作成します。 本来ではOnRenderImage内でBlitを使ってポストプロセス処理を行うのですが、Blitを使ってポストプロセスを使ってステンシルを利用しようとするとかきこまれたステンシルを参照できないというバグ(?)がありました。 なので、この場合はCommandBuffer経由で描画してやる必要があるようです。 (今回はこちらのサイトのものをお借りしました)

[Unity]イメージエフェクトを特定のモデルだけにかける

using UnityEngine;
using UnityEngine.Rendering;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class ImageEffect : MonoBehaviour
{
    /// <summary>
    /// コマンドバッファ名
    /// </summary>
    private const string CommandBufferName = "StencilImageEffect";

    /// <summary>
    /// イメージエフェクトで使用するマテリアル
    /// </summary>
    [SerializeField] private Material material;

    /// <summary>
    /// イメージエフェクト用コマンドバッファ
    /// </summary>
    private CommandBuffer commandBuffer;


    private void OnEnable()
    {
        if (material == null) return;
        if (commandBuffer != null) return;

        var cam = GetComponent<Camera>();
        var cbs = cam.GetCommandBuffers(CameraEvent.BeforeImageEffects);
        foreach (var cb in cbs)
        {
            // 多重登録を回避するため、名前でチェック
            if (cb.name == CommandBufferName) return;
        }

        commandBuffer = new CommandBuffer();
        commandBuffer.name = CommandBufferName;

        // Blitでmaterialを適用してイメージエフェクトをかける
        commandBuffer.Blit(
            BuiltinRenderTextureType.CameraTarget,
            BuiltinRenderTextureType.CameraTarget,
            material);

        // カメラにコマンドバッファを登録
        cam.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer);
    }

    private void OnDisable()
    {
        if (commandBuffer == null) return;

        var cam = GetComponent<Camera>();
        cam.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer);
        commandBuffer = null;
    }
}

ポストプロセス用シェーダ

ポストプロセス用のシェーダーはシンプルにステンシルが書き込まれている場所に指定したカラーを描画するだけのシンプルなものになります。

StencilOutlinePostEffect.shader

この方法のメリット、デメリット

メリット

  • アウトラインの付け外しが楽
  • 半透明なものに対しても綺麗なアウトラインが描画できる
  • 既存のマテリアルに基本的に干渉しないため応用しやすい

デメリット

  • 通常の描画に加えて2Pass+ポストプロセス1Passなのでオーバヘッドが多い
  • アウトライン部分は深度チェックを行っていないため法線が反転しているものは相性が悪い
  • 鋭角なモデルに対してはポリゴンが浮いて見える

今回のスクリプト、シェーダはGitHubに上げておきました。 便利に使ってもらえると嬉しいです。 GitHub github.com

UnityPackerge Release StencilOutline.unitypackage · TachibanaKoki/StencilOutline · GitHub

参考サイト

ToonLitOutlineとステンシルによるOutlineの違い - AIプログラムとかUnityゲーム開発について

【Unity】【シェーダ】ステンシルバッファでアウトラインを描画する - LIGHT11

【Unity】ステンシルバッファをポストエフェクトで使う方法(と謎の挙動) - LIGHT11

[Unity]イメージエフェクトを特定のモデルだけにかける