Unityのビルボードシェーダーを実装する際に躓いた点と改善方法


概要

Unityで以下のようにカメラを傾けてもスプライトが正面を向いた状態(ビルボード)を作りたくなり、
調べていたところシェーダーを使ってビルボード表示が出来るということで、
幾つかのサイトを参考に試してみました。
(カメラのprojectionをorthographicにするだけだと正面は向いても傾けた分だけ縦横比が崩れてしまいうまく行きませんでした)

試したところいくつかの記事の内容を組み合わせることで実現出来たため、
躓いた点と解消方法をまとめます。

※Unityを使用してこのような2.5D風(くにお君、River city ransom風?)の表現をするのにこういった方法が
 合っているか分かっていない状態です。この他に良い実現方法があれば教えていただきたいです...。

エディタ上の表示 実行中(カメラが30度傾いている)

最終的に使用したスクリプト

現在使用しているスクリプトは以下の通りです。

座標変換は以下の記事を参考にさせていただきました。
* Y軸ビルボードシェーダーの実装と解説


BillboardShader.shader
Shader "Custom/Billboard"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
            "DisableBatching" = "True"  // Prefabなどで複製した複数のスプライトを正しく表示させるために必要だった
        }

        Cull Off  //左右反転させた時に描画させるために必要だった
        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };


            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert(appdata v)
            {
                // 座標変換は以下の記事を参考にさせていただきました。
                // https://gam0022.net/blog/2019/07/23/unity-y-axis-billboard-shader/

                v2f o;
                // ①Meshの原点をModelView変換
                float3 viewPos = UnityObjectToViewPos(float3(0, 0, 0));

                // ②スケールと回転(平行移動なし)だけModel変換して、View変換はスキップ
                float3 scaleRotatePos = mul((float3x3)unity_ObjectToWorld, v.vertex);

                // ③View行列からX軸の回転だけ抽出した行列を生成
                float3x3 ViewRotateX = float3x3(
                    UNITY_MATRIX_V._m00, 0, 0,
                    UNITY_MATRIX_V._m10, 1, 0,
                    UNITY_MATRIX_V._m20, 0, -1// Zの符号を反転して右手系に変換
                );
                viewPos += mul(ViewRotateX, scaleRotatePos);

                // ④最後にプロジェクション変換
                o.pos = mul(UNITY_MATRIX_P, float4(viewPos, 1));
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}


躓いた点

シェーダーを使ってビルボードを表現する方法はいくつかの記事で紹介されていますが、
自分の環境で試した時は以下の点で問題がありました。

  1. 左右反転するとスプライトが消える (SpriteRenderer.flipX、localScaleのどちらの場合でも)
  2. Prefabなどから複数のスプライトを生成すると表示がガタガタなる

以下は、それぞれの対応方法です。

1. 左右反転するとスプライトが消える

これには以下の宣言の追加が必要でした。

Cull Off

参考: https://docs.unity3d.com/ja/2020.2/Manual/SL-CullAndDepth.html

シェーダーのデフォルトの設定では、描画の最適化のためにポリゴンの背面を描画しない設定(カリング)が有効になっているようで、
左右反転させた状態でも描画させるにはこのカリングを無効にする必要があるようでした。

2. Prefabなどから複数のスプライトを生成すると表示がガタガタなる

表現が難しいですが、Prefabなどから同じスプライトのインスタンスを複製すると以下のように正しく表示されなくなりました。
(Z座標が0の状態で描画されたり、スプライト同士が重なると正しく描画されない)

これは参考にさせていただいた記事上でもよく見ると指定されていたのですが、Tagsに以下の宣言が必要でした。

"DisableBatching" = "True"

参考: https://docs.unity3d.com/ja/2018.4/Manual/SL-SubShaderTags.html#:~:text=DisableBatching