Mugichoko's blog

Mugichoko’s blog

しがない研究者のプログラミングを中心としたメモ書き.

最近分かってきたUnity Cg/HLSLシェーダのあれこれ

Unityを触り始めて2週間ほど,Cg/HLSLシェーダを触り始めて1週間ほどが経った.このあたりで,だんだん分かってきたことをまとめようと思う.

モチベーション

OpenGLを主として実装に使っていた者の感想としてUnityが何を想定していて,何を意識しないで/意識するように実装されているのかを掴むのになかなか時間がかかっている.こういった人は結構多いはずなのに「Unityのシェーダってこういうもんだよね」という感じのまとまった説明の仕方をしてくれているサイトがなかなか見つからないのが残念.

何かに行き詰まりググり始めると,大抵はUnity ForumsかStack Overflowに辿り着き,以下のように有益な情報が得られないことが多々あった.

  • 自分とは違うバージョンのバグの情報だった
  • 誰も答えを導けないで中途半端に終わったスレッドだった
  • 質問への答えになっていない見当違いな回答だった

とは言え,本家Unity Forumsの場合,Unity Technologiesからの直々の回答に幸運にも巡り合うことがある.この場合,とても役に立つ情報が得られる.また,役に立ちそうなコードを発見して喜んでいると,結局は使えないという以下のような場合もあった.

  • 前後が抜けているコードだった
    • こなれた感じのブログに多い
  • 欲しいところが抽出しきれない分量だった
    • ハイレベルなことを紹介しているブログに多い

こういう記事を読んでいると,みんな長くUnityに慣れ親しんでいて,最早語ってくれない部分があるように思えてきたし,実際そうだあと思う.そして,私のような初心者は「そこじゃないんだよ!もっと最初の辺りが分からないんだよ!」と言いたくなってくる.

かく言う私も少しづつUnityの決まり事を把握し始めている.ということで,初心者である内に「何だそういうことか」と思ったことをまとめておきたいと思った次第.今回は,タイトルの通り,Cg/HLSLシェーダの書き方についてのまとめです.

尚,経験不足でよく分からずに書いている部分があるので,しばらくしたらまたこの記事に戻ってきて適宜更新していきたいと思っています.

Cg/HLSLシェーダのあれこれ

基本構造 (ShaderLab)

まず,Unityの独自ShaderはShaderLabという構造をとっている.何もコードを書いていないとShaderLabは以下のように見えるはず.

Shader "ShaderName"
{
    Properties
    {
        // write me
    }
    SubShader
    {
        Tags
        {
            // write me
        }

        // write me

        Pass
        {
            // write me
        }
    }
}

具体例(テクスチャマッピング

前回の記事に書いたテクスチャマッピングのシェーダを例にコメントを追加していってみる.

Shader "Custom/TextureMapping"    // Inspector内でShaderを選択する際にプルダウン内に現れる表記
{
    Properties
    {
        // Inspectorから見えるデータ
        // 例えば,変数を置いたり,以下の様にテクスチャを置いたりする

        // "My Texture"がInspector内での表記
        // 何も設定しないと真っ白 "white" テクスチャ
        _MainTex("My Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags
        {
            "Queue" = "Geometry"  // レンダリングされる順序を決める優先順位
            "RenderType" = "Opaque"   // シェーダが適用されるオブジェクトの種類
        }

        CGINCLUDE   // ここからENDCGまでにメインの処理や変数を置く
        #include "UnityCG.cginc" // 基本的には入れておくUnity提供の便利ツール

        struct vsIn    // Vertex Shaderへの入力(個人的にこの呼び名が分かりやすくて好き)
        {
            // モデルの情報を入力する方法
            // 1. C#スクリプトで自分で定義する(過去の記事参照)か,
            // 2. Unityにモデルを読み込めが勝手に入力される
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
            // モデルによっては法線や接線方向が入力されるようなので必要に応じて追加
        };

        struct fsIn    // Fragment Shaderへの入力
        {
            float4 vertex : SV_POSITION;
            float2 uv : TEXCOORD0;
        };

        UNITY_DECLARE_TEX2D(_MainTex);   // Properties内にあったテクスチャの実体
        // sampler2D _MainTex でも構わない様子
        // ただ,このマクロを使った方がマルチプラットフォームに上手く対応できるとか
        // 以下のマクロも同様の理由で利用

        fsIn vert(vsIn input)
        {
            fsIn output;
            output.vertex = UnityObjectToClipPos(input.vertex);  // MVP変換を行うUnity提供のマクロ
            output.uv = input.uv;
            return output;
        }

        fixed4 frag(fsIn input) : SV_Target  // SV_Targetは色を出力するという意味.SV_Depth等もある様子
        {
            return UNITY_SAMPLE_TEX2D(_MainTex, input.uv); // テクスチャの色を取り出すUnity提供のマクロ
        }

        // ちなみに,ZTestの有効/無効の切り替えなどが必要な場合は以下のPassの前に書く

        Pass    // 実際に実行される関数の順番
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
    }
    
    FallBack "Diffuse"   // 上のシェーダが上手く実行されない場合のバックアッププラン
    // ここでは,Unity提供デフォルトのDiffuseシェーダを置く
    // これでデバッグ中にミスがあっても何等か表示される
}

このシェーダをどうやってオブジェクトに反映させるか,上のシェーダがInspector上でどう見えるのか,といったUnity Editorとのつながりに関しては,前回の記事とそこからつながる短い過去記事を1つか2つ辿れば分かると思います.

まとめ

今回はUnityでシェーダ書く上で捉えておかなければならないShaderLabの構造を,前回の実装例を交えてまとめた.必須でない項目もあるものの,基本的に書かないといけない内容は以下の通り.

  • Properties:Inspectorから見える&設定できる項目を書く
  • SubShader:以下の項目を囲う
  • Tags:レンダリングの優先順位などを書き込む
  • Unity独自のマクロ:マルチプラットフォームに対応できて安全
  • FallBack:最後に念のためのシェーダを書いておく

他にも,Unityが事前に用意してくれている画像処理用に特化したVertex Shadervert_imgを使うことで,画像処理を行う場合はVertex Shaderを丸々書かずに済むといった方法もあるようなので,こういったこともおいおいまとめていきたいと思っています.