ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

Unity - Shader の書き方の基本

初めに

 Shader(シェーダー)は描画方法を記述したプログラムであり、1つの Material に1つのシェーダーを設定します。本記事では、Shader の書き方の基本をまとめます。



概要

 Unity のシェーダーを作成する際には、HLSL と ShaderLab という言語を使用します。

  • HLSL(High Level Shading Language)
    マイクロソフトで開発された DirectX で使用されているシェーダプログラム用言語
  • ShaderLab
    シェーダーオブジェクトを定義する Unity 固有の言語

docs.unity3d.com

Shader の書き方

 Assets 内で右クリック > Create > Shader から選択して、Shader を作ります。

(例:デフォルトの CustomRenderTexture)

Shader "CustomRenderTexture/MyShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex("InputTex", 2D) = "white" {}
     }

     SubShader
     {
        Blend One Zero

        Pass
        {
            Name "MyShader"

            CGPROGRAM
            #include "UnityCustomRenderTexture.cginc"
            #pragma vertex CustomRenderTextureVertexShader
            #pragma fragment frag
            #pragma target 3.0

            float4      _Color;
            sampler2D   _MainTex;

            float4 frag(v2f_customrendertexture IN) : COLOR
            {
                float2 uv = IN.localTexcoord.xy;
                float4 color = tex2D(_MainTex, uv) * _Color;

                // TODO: Replace this by actual code!
                uint2 p = uv.xy * 256;
                return countbits(~(p.x & p.y) + 1) % 2 * float4(uv, 1, 1) * color;
            }
            ENDCG
        }
    }
}
文構造

 Shader の構造は次のようになっています。

Shader "Examples/ShaderSyntax"  //Shaderの定義
{
    CustomEditor = "ExampleCustomEditor"  //CustomEditorの割り当て

    Properties
    {
        // プロパティの定義
    }
    SubShader
    {
        // SubShaderの残りの部分をここで定義

        Pass
        {
           // Passの定義
        }
    }

    Fallback "ExampleFallbackShader"
}

 Unity の Shader は、主に次の3つのブロックで構成されています。

ブロック 説明
Properties シェーダーのプロパティを定義する場所。プロパティには、float、vector、color、textureなどが含まれる。
SubShader 複数のプラットフォームで動作するシェーダーを定義する場所。複数のSubShaderブロックを使用することで、プラットフォームごとに異なるシェーダーを定義できる。
Pass シェーダーがレンダリングパイプラインにどのように組み込まれるかを定義する場所。通常、シェーダーには複数のPassブロックがあり、それぞれが異なるレンダプロパリングステージで使用される。

 CustomEditor の割り当てができます。これにより Inspector の表示を変更などができます。記述しなくても問題はありません。

 SubShader はプラットフォームごとに適切なものが選ばれます。適切な SubShader が見つからなかった場合、FallBack で割り当てた Shader を使用します。

docs.unity3d.com

Properties

 Properties では、float や color などプロパティを定義することができます。

Properties
{
     //_Colorが変数名, "Color"がInspectorへの表示, Color が型名, 右辺が初期値
    _Color ("Color", Color) = (1,1,1,1)
    _MainTex("InputTex", 2D) = "white" {}
}

 Material に Shader を割り当てたとき、Inspector からプロパティを変更することができます。

SubShader・Pass

 Shader 内には、1つ以上の SubShader を記述します。SubShader 内に Shader の設定を記述していきます。

 SubShader の中では、Pass や Tags 記述していきます。Pass 内に実際の処理を記述します。

(例:単色シェーダー)

Shader "Unlit/SingleColor"
{
    Properties
    {
        // マテリアルインスペクターの Color プロパティ、デフォルトを白に
        _Color ("Main Color", Color) = (1,1,1,1)
    }
    SubShader  //SubShaderの定義
    {
        Pass  //レンダリングを行う
        {
            //CGPROGRAMからENDCGまでが実際の処理の記述
            CGPROGRAM

            //関数をどのシェーダーに利用するか記述
            #pragma vertex vert  //vert関数をvertex(頂点)シェーダーとしてコンパイル
            #pragma fragment frag  //frag関数をfragmentシェーダーとしてコンパイル
            
            // 頂点シェーダー
            // 今回は、 "appdata" 構造体の代わりに、入力を手動で書き込みます
            // そして v2f 構造体を返す代わりに、1 つの出力
            // float4 のクリップ位置だけを返します
            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                // クリップスペースへの変換位置
                // (モデル*ビュー*プロジェクション行列で乗算)
                return mul(UNITY_MATRIX_MVP, vertex); //mulは行列の掛け算, UNITY_MATRIX_MVPは現在のモデルビュー行列×射影行列
            }
            
            // マテリアルからのカラー
            fixed4 _Color;

            // ピクセルシェーダー、入力不要
            fixed4 frag () : SV_Target
            {
                return _Color; // 単に返します
            }
            ENDCG
        }
    }
}

 Pass 内に CGPROGRAM / ENDCG を記述し、その中にシェーダーコードを記述します。CGPROGRAM / ENDCGブロック内のコードは、HLSL言語(Cg言語)で記述されており、GPUに最適化されたコードにコンパイルされます。

〇 #pragma:プラグマディレクティブ

 #pragma を使って、関数をどのシェーダーとして利用するか、指定します。

 //関数をどのシェーダーに利用するか記述
#pragma vertex vert  //vert関数をvertex(頂点)シェーダーとしてコンパイル
#pragma fragment frag  //frag関数をfragmentシェーダーとしてコンパイル

シェーダー 説明
vertex 頂点シェーダー。3D モデルの各頂点で実行されるプログラムです。多くの場合、それは特に何もせず、頂点の位置を「クリップスペース」に変換し、渡すのみです。クリップスペースは、GPU が画面上のオブジェクトをラスタライズするために使用するものです。 また、入力テクスチャ座標を変更しないで渡します。フラグメントシェーダーのテクスチャをサンプリングするために、これが必要になるようです。
fragment フラグメントシェーダー。オブジェクトが画面上で占めるすべてのピクセルでそれぞれ実行されるプログラムで、通常、各ピクセルの色を計算して出力するために使用されます。通常、画面上には何百万ものピクセルがあり、フラグメントシェーダはそれらのすべてに対して実行されます。

 上の単色シェーダーでは、次の処理をしたことになります。

(処理の内容)

  • vert 関数が各頂点をクリップスペースに変換し、そのまま頂点の座標を返した
  • frag 関数で画面上にオブジェクトが表示されているピクセルに _Color を返した

〇Tags
 SubShader では、タグを使用して、いつどのようにしてレンダリングするか指定することができます。

(タグの書き方)

Tags { "TagName1" = "Value1" "TagName2" = "Value2" }

(例)

Shader "Transparent Queue Example"
{
     SubShader
     {
        Tags { "Queue" = "Transparent" }
        Pass
        {
            // シェーダーのボディの残りの部分...
        }
    }
}

 Queue (キュー)によりオブジェクトを描画する順番を判定することに使われます。Queue のほかにも、RenderType タグや IgnoreProjector タグなどがあります。詳細は下のドキュメントをご覧ください。
docs.unity3d.com

(補足)
 Shader には、上記コードの UNITY_MATRIX_MVP のように定義済みの値があります。(通常のコードでは、Vector3.left や Mathf.PI のようなものが定義済みの値)下のドキュメントにまとめてあります。
docs.unity3d.com

参考になるかもしれない過去記事

fineworks-fine.hatenablog.com

最後に

 本記事のまとめ

  • Shader は Properties、SubShader、Pass で構成されている
  • Properties には、Inspector に表示されるプロパティを記述
  • SubShader の Pass 内に基本的な処理を記述する

 ShaderGraphを使って、コードを書かずに、Shader を作る機能もあります。機会があれば紹介します。