夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

ゼロから始めるUnityShader Unityでビルボードなシェーダーを書く

本日はShader学習枠です。

今回は実用的なシェーダーとして板ポリゴンに適応することで常にユーザの向きを向くビルボードなシェーダーを構築します。

〇ビルボード

 ビルボードは3DCGゲームで容量をできるだけ削減しながらも多くのものを描画する手法として広く使用されているもんです。

 実態のポリゴンとしては1枚の四角ポリゴンのみで、プレイヤーに常に正面を向くように回転することで立体的に見せる手法です。

 

〇回転

頂点シェーダーを用いてのオブジェクトの回転は以前の記事で作っていました。

redhologerbera.hatenablog.com

このシェーダーをベースに考えていきます。

Shader "HoloMoto/Billboard"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _Rotation("Rotation", Range(0, 360)) = 0
        _Pivot("Pivot", Vector) = (0.5, 0.5, 0)
    }
      SubShader
    {
        Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
        Cull Off
        LOD 100
       Pass{
           CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
   ・・・
        sampler2D _MainTex;
        float _Rotation;
        float3 _Pivot;

        v2f vert(appdata v)
        {
            v2f o;

            // ピボットを中心に回転行列を計算します
            float3 worldPos = v.vertex.xyz - _Pivot;
            float rad = _Rotation * (3.14159265 / 180.0);
            float2x2 rotationMatrix = float2x2(cos(rad), -sin(rad), sin(rad), cos(rad));

            // 頂点座標を回転行列で変換します
            worldPos.xy = mul(worldPos.xy, rotationMatrix);
            worldPos.xy += _Pivot.xy;

            o.vertex = UnityObjectToClipPos(worldPos);
            o.uv = v.uv;
            return o;
        }

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

このシェーダーでは回転はオブジェクトのローカル座標軸でシェーダーのRotationパラメータの値に応じて回転を行っていました

このパラメータと回転方向をユーザーのカメラに対して設定していきます。

〇カメラ位置の取得と回転

オブジェクトの回転を行うためには回転するための行列が必要です。今回の場合Unityの座標を使用して回転するためワールド座標を取得する必要があります。

world座標の取得は4次元の float4 worldPos = mul(unity_ObjectToWorld, v.vertex);を用いることが一般的です。

これは頂点の位置、回転、スケール情報を保持しています。

しかし今回はビルボードを作成するにあたってオブジェクトの位置は移動する必要がなく回転とスケールのみを操作する必要があるため3次元の行列を使用します。

float3 vpos = mul((float3x3)unity_ObjectToWorld, v.vertex.xyz);

vposではUnityが提供する行列であるuinty_ObjectToWorldから位置を無視して回転とスケールのみを変換します。

次にオブジェクトのワールド座標に対しての位置を取得します。

float4 worldCoord = float4(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23, 1);

こちらはunity_ObjectToWorldを使用していますがunity_ObjectToWorld._mXYを使用することで行列の成分を取得することができます。

ここではm03, m13, _m23を各成分に取得しており、これはそれぞれ1行4列、2行4列、3行4列の成分になりこちらがローカル座標の値になります。

最後にカメラ位置を取得するにはワールド座標に対してUnityの提供するマクロを使用して変換を行います。

以下の処理になります。

             float4 viewPos = mul(UNITY_MATRIX_V, worldCoord) + float4(vpos, 0);

UNITY_MATRIX_Vはビュー行列で、座標を入力することでカメラ位置を取得できます。

最後にプロジェクション行列を使用し3D空間から2D空間への変換を行います。

         float4 outPos = mul(UNITY_MATRIX_P, viewPos);

このシェーダーを実行することで以下のような結果を得ることができます。

以上でカメラ位置に対して追従するビルボードなシェーダーができました。

〇コード一覧

Shader "Custom/TransparentRotatedArea"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _Rotation("Rotation", Range(0, 360)) = 0
        _Pivot("Pivot", Vector) = (0.5, 0.5, 0)
    }

    SubShader
    {
        Tags
        {
            "Queue" = "Transparent" "RenderType" = "Transparent"
        }
        Cull Off
        LOD 100
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 pos : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv.xy;

                // billboard mesh towards camera  
                float3 vpos = mul((float3x3)unity_ObjectToWorld, v.vertex.xyz);
                float4 worldCoord = float4(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23,
                                           1);
                float4 viewPos = mul(UNITY_MATRIX_V, worldCoord) + float4(vpos, 0);
                float4 outPos = mul(UNITY_MATRIX_P, viewPos);

                o.pos = outPos;

                UNITY_TRANSFER_FOG(o, o.vertex);
                return o;
            }

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


                return col;
            }
            ENDCG
        }
    }
}