夜風のMixedReality

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

Unity ShaderLabでコードを描くテクニック

本日はUnityShader勉強枠です。

筆者はUnity Shaderの勉強を独学で初めて1年ほどたちますが、ほかの方がGitHubに上げているコードを読んでいると次のようなコードを目にすることが多くありました。

Shader "HoloMoto/GeometryFirst"
{
    Properties
    {
  ...
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 100
        Pass
        {
            HLSLPROGRAM
           #include  "sample.hlsl"
            ENDHLSL
        }
    }
}

[HLSLPRPGRAM~ENDHLSL]を見てみるとhlslファイルを読み込んでいることがわかります。

#include  ●●.hlsl

は[UnityCg.cginc]や[Core.hlsl]などのようにShaderで定義済みの変数を使用するために多く使用されます。

UnityC#でいうところの[Monobehavior]のような感じでしょうか?

今まではなぜShaderのコードをわざわざ別のファイルに処理を記述するのか理解できていませんでしたが、最近になってようやく意味を理解できるようになったため記事に残します。

〇なぜShaderlab内で処理を丸ごと外部hlslに記述して読み込んでいるのか?

結論から先に記述すると『コードエディタでの補完・校正機能を使用してコーディングの正確性や速さを高めるため』です。

Unity ShaderLabではShaderコード内で2種類のプログラム言語を使用しています。

[ShaderLab]言語の中の[HLSLPROGRAM~ENDHLSL(CGPROGRAM~ENDCG)]の間はcg/hlsl言語で記述されます。

//ShaderLab
{
    Properties
    {
  ...
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 100
        Pass
        {
            HLSLPROGRAM//←ここからcg/hlsls言語

           #include  "sample.hlsl"
           ENDHLSL//←ここまでcg/hlsl言語
          
        }
    }
}

cg/hlsl言語ではC系の言語のためコードの末尾に[;]がつけられているため見分けがつきます。

この場合問題となることがIDEでコードのチェックや校正のサポートが受けられなくなる点です。

[VisualStudio]や[JetBrain Rider]など様々なIDEがありますが、いずれもデフォルトではShaderLabに対応していない問題があります。

しかしhlsl言語はコーディングのサポートを受けることができます。

そのためcg/hlsl言語の処理である[HLSLPROGRAM~ENDHLSL(CGPROGRAM~ENDCG)]の間の処理をhlslの別ファイルで記述して[#include]で読み込むことでShaderLab言語にまとめるよりも効率的にコーディングが行えます。

f:id:Holomoto-Sumire:20211123201553p:plain

〇サンプルコード

Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #include  "TestHLSL.hlsl"
            ENDHLSL
        }
    }
}

●TestHLSL.hlsl

#pragma vertex vert
#pragma  fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"

struct appdata_t
{
    float4 vertex:POSITION;
    float2 uv : TEXCOORD0;
    half3 normal: NORMAL;
};

struct v2f
{
    float4 vertex : SV_POSITION;
    float2 uv : TEXCOORD0;
    float3 normalWS : TEXCOORD1;
};

v2f vert (appdata_t v)
{
    v2f o;
    o.vertex = TransformObjectToHClip(v.vertex);
    VertexNormalInputs normal = GetVertexNormalInputs(v.normal);
    o.normalWS = normal.normalWS;
    return  o;
}
float4 frag(v2f i):SV_TARGET
{
    Light lt = GetMainLight();
    float4 col = (1,1,1,1);
    float strength = dot(lt.direction, i.normalWS);
    float4 lightColor = float4(lt.color, 1);
    return col* lightColor*strength;
}

ここではShaderとhlslファイルを同一階層に置いていますが、違う階層にある際は

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

などAssets以下の階層を記述する必要があります。

筆者がShaderを学び始めた際躓いた点の一つがIDEの校正のサポートが受けられず、大文字小文字含めタイプミスに気が付けない点がありました。

また二つの言語に関しても理解が及ぶのに時間がかかりました。最初から別ファイルにしておくことで理解も深まりそうです。