【Unity】Y軸ビルボードシェーダー


ビルボードとは

ビルボードとは、常にカメラ方向に向いている板ポリゴンのことです。
UIやエフェクトなどで使われていて、頂点数が少ないので、処理負荷を抑えることができます。

この記事ではUnityのBillboardAssetに関しては触れず、ビルボードをシェーダーでどう実装するかについて記載します。

ShaderLabで実装する

環境

  • Unity 2021.2.11f1
  • Build-in Rendering Pipline
  • Metatex

ソースコード

Billboard.shader

https://gist.github.com/went5/e34f33944427ab03de34738c91c50359

Tags

Tags
{
    "Queue"="AlphaTest" "RenderType"="TransparentCutout" "IgnoreProjector"="True"
}
  • Queue (AlphaTest): アルファテストを指定し、透明部分をくり抜いている。(半透明はなし)
  • RenderType(TransparentCutout): 透明部分と不透明部分で分かれているので指定
  • IgnoreProjector(true): 透明部分と不透明部分で分かれているため指定

常にカメラ方向を向くようにする処理について

MVP変換のうち、モデル行列の回転部分をカメラ方向を向くように修正します。
モデル行列の回転部分を抽出し考えてみます。
カメラ方向を向くようにする回転行列は、座標軸方向のベクトルをカメラ方向に変換したものと考えると分かりやすいです。

モデル行列を以下のように表すとき - R: 回転後のRightベクトル - U:
回転後のUpベクトル - F: 回転後のForwardベクトル - T:
回転後のTranslation(移動)ベクトル となります。

(RxUxFxTxRyUyFyTyRzUzFzTz0001)\begin{pmatrix} R_x & U_x & F_x & T_x \\ R_y & U_y & F_y & T_y \\ R_z & U_z & F_z & T_z \\ 0 & 0 & 0 & 1 \\ \end{pmatrix}

このモデル行列をそのまま使うと、カメラの方向を向くようにはならないため、
新しいモデル行列を作ります。

TranlationとUpのベクトルは元のモデル行列と同じで問題ないため、モデル行列をそのまま使います。

float3 up = unity_ObjectToWorld._m01_m11_m21;
float3 worldPos = unity_ObjectToWorld._m03_m13_m23;

RightベクトルはUpベクトルとカメラ方向のベクトルの外積から求めます。
カメラ方向のベクトルは、カメラのワールド座標から頂点のワールド座標を引くと求められます。

float3 toCamera = _WorldSpaceCameraPos - worldPos;
float3 right = normalize(cross(toCamera, up));

最後に、UpベクトルとRightベクトルの外積からForwardベクトルを求めます。

float3 forward = normalize(cross(up, right));

ちなみに、Unityは左手座標系のため、それを考慮して外積の順番を考えています。
これらをまとめて、View行列を求めたものが以下のものとなります。

v2f vert(appdata v)
{
    float3 up = unity_ObjectToWorld._m01_m11_m21;
    float3 worldPos = unity_ObjectToWorld._m03_m13_m23;
    float3 toCamera = _WorldSpaceCameraPos - worldPos;
    float3 right = normalize(cross(toCamera, up)) * length(unity_ObjectToWorld._m00_m10_m20);
    float3 forward = normalize(cross(up, right)) * length(unity_ObjectToWorld._m02_m12_m22);

    float4x4 viewMatrix = {
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1,
    };
    viewMatrix._m00_m10_m20 = right;
    viewMatrix._m01_m11_m21 = up;
    viewMatrix._m02_m12_m22 = forward;
    viewMatrix._m03_m13_m23 = worldPos;
}

スケール値について

スケール値は、モデル行列から抽出することができます。
先ほどのモデル行列は、回転と平行移動のみ入っていましたがスケール値を合成すると以下のようになります。

(SxRxSyUxSzFxTxSxRySyUySzFyTySxRzSyUzSzFzTz0001)\begin{pmatrix} S_xR_x & S_yU_x & S_zF_x & T_x \\ S_xR_y & S_yU_y & S_zF_y & T_y \\ S_xR_z & S_yU_z & S_zF_z & T_z \\ 0 & 0 & 0 & 1 \\ \end{pmatrix}

この行列にForwardベクトル(0,0,1)をかけると、Forwardベクトルを回転して拡大・縮小したあとのベクトルを取り出すことが出来ます。

(SxRxSyUxSzFxTxSxRySyUySzFyTySxRzSyUzSzFzTz0001)(0010)=(SxRxSxRySxRz0)\begin{pmatrix} S_xR_x & S_yU_x & S_zF_x & T_x \\ S_xR_y & S_yU_y & S_zF_y & T_y \\ S_xR_z & S_yU_z & S_zF_z & T_z \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} \begin{pmatrix} 0 \\ 0 \\ 1 \\ 0 \\ \end{pmatrix} = \begin{pmatrix} S_xR_x \\ S_xR_y \\ S_xR_z \\ 0 \\ \end{pmatrix}

ベクトルを回転させても大きさは変わりません。

そのため、取り出したベクトルの大きさはピタゴラスの定理を使って求められます。

x=(SxRx)2+(SxRy)2+(SxRz)2|\vec{x}| = \sqrt{(S_xR_x)^2+(S_xR_y)^2+(S_xR_z)^2}

これらをコードにすると以下のようになります。

float scaleRight = length(unity_ObjectToWorld._m00.m10_m20);
float scaleUp = length(unity_ObjectToWorld._m01.m11_m21);
float scaleForward = length(unity_ObjectToWorld._m02.m12_m22);

View行列をスキップしたやり方について

v2f vert(appdata v)
{
    v2f o;
    o.uv = v.uv;

    float4 vertex = float4(v.vertex.xyz, 1.0);
    vertex = mul(unity_ObjectToWorld, vertex);
    vertex = mul(UNITY_MATRIX_V, vertex);
    vertex = mul(UNITY_MATRIX_P, vertex);
    o.pos = vertex;
    return o;
}

上記のコードはView行列をスキップして、ビルボードを実装した例です。
こちらのコードを実行すると以下のようになります。

カメラの向きを左右にずらしたときに、ビルボードが動いてしまうことがわかります。
正しくない挙動なのですが、処理負荷が少なくそれっぽい動きをするので、使ってみるのも良いかも入れません。

ただし、プラットフォーム間の違いは対応する必要があります。

参考

Unity CGに使用される行列についての考察
Y軸ビルボードシェーダー
Unity シェーダーチュートリアル アルファとアルファテスト
回転行列(方向余弦行列)とは?定義・性質・3つの物理的な意味・公式のまとめ
その39 知っていると便利?ワールド変換行列から情報を抜き出そう

Table of contents


©️ 2026 went5. blog icon by

icons8