Unity
シェーダ

UNITY 古典シェーダ Part2 Ambient~複数ライト

Part1 https://qiita.com/YukiMiyatake/items/f71aa766de336fbc6781

Ambient

https://github.com/YukiMiyatake/UnityLesson/tree/Shader_5

物理ベースだと GI(グローバルイルミネーション)を使う
これは光の回析などを計算したライトモデル
ただし古典シェーダでは一律に明るさをあたえる Ambientライト

設定

設定はメニューの
Window -> Lighting -> Setting

9.png

Environment Lightingで設定します
Sourceを Colorにすると Ambient色も変更可能

シェーダ

シェーダからは UNITY_LIGHTMODEL_AMBIENT として取得できる
基本的にAmbientは 他のカラーに足せば良い

前回の続きで、ディフューズ、スペキュラーに足してみる

             float3 frag(v2f i) : SV_Target
                {
                    float3 L = normalize(_WorldSpaceLightPos0.xyz);
                    float3 V = normalize(_WorldSpaceCameraPos - i.vertexW.xyz);
                    float3 N = i.normal;
                    float3 H = normalize(L + V);


                    //Ambient
                    float3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;

                    // texture albedo
                    float4 tex = tex2D(_MainTex, i.uv);

                    // Diffuse(HalfLambert)
                    float3 NdotL = dot(N, L);
                    float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb;

                    // Speculer
                // float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz;  // reflection
                    float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz;  // Half vector


                    return (ambient + diffuse) * tex + specular;
                }

これで、Ambientが反映される

ライトの強さ

今まではライトの角度のみで強さに関しては無視していたので
ライトの強度を反映させる
LIGHT_ATTENUATION を使うために AutoLight.cgincを includeする

float3 lightCol = _LightColor0.rgb * LIGHT_ATTENUATION(i);

float3 diffuse = (NdotL*0.5 + 0.5) * lightCol;

float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz * lightCol;  // Half vector

こんな感じで、光源色と強さをdiffuseやSpecularに乗算すればいい

複数ライト

https://github.com/YukiMiyatake/UnityLesson/tree/Shader_6

UNITYでは複数のライトを扱うことが出来る
Forwardレンダリングモードでは
一番影響の高いディレクショナルライトのみが ForwardBaseで処理され
その他のライトは ForwardAdd で処理される

今の状態では ForwardBaseしか書いていないので、2つ目のライトを与えても
反映されないので
ForwardAddパスを追加する

シェーダーの中身は ForwardBaseと同じでよい
ただし Ambientライトは1回しか加えてはいけないので ForwardAddでは加算しない
ブレンドモードも OneOne などにしなければうまく加算されないので ブレンドモードも気をつけること

BlendModeなし
10.png

ForwardBaseのみ
11.png

ForwardAdd追加(赤いライト追加)
12.png

これで複数ライトの影響を与えられるようになった

Vertexシェーダでのライト計算

https://github.com/YukiMiyatake/UnityLesson/tree/Shader_7

今までは 補間された法線をつかい、フラグメントシェーダでピクセル単位でライト計算をしていた
頂点数にくらべ ピクセル数のほうが圧倒的に多く、可能であれば計算は頂点シェーダにまかせたい

ので、試しに
ForwardBaseは今までどおりの フラグメントシェーダでのライティングを行い
ForwardAddでは 頂点シェーダで頂点ごとライティング計算を行い補間を行い フラグメントシェーダではライト計算を行わない事にした

ただし、スペキュラのような ピーキーなライトでは、頂点による補間ではよい結果が出ないことが想定されるので
あくまでテスト

ForwardAddの構造体

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

    struct v2f
    {
        float4 vertex : SV_POSITION;
        float2 uv     : TEXCOORD1;

        float3 diffuse : TEXCOORD2;
        float3 specular : TEXCOORD3;
    };

フラグメントシェーダではもうライティング計算をおこなわないので、ワールド座標も法線も不要
頂点シェーダで diffuseとspecularを計算して 補完し送るだけだ

ここでセマンティクスを TEXCOORD2、TEXCOORD3にしているのは あまり理由がない
たしか今のHLSLでは TEXCOORDとCOLORには明確な違いがなくなったと思ってる
ので 多分どちらを使っても良いと思う

ForwardAddシェーダーの中身

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        float4 vertexW = mul(unity_ObjectToWorld, v.vertex);

        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        float3 normal = UnityObjectToWorldNormal(v.normal);


        float3 L = normalize(_WorldSpaceLightPos0.xyz);
        float3 V = normalize(_WorldSpaceCameraPos - vertexW.xyz);
        float3 N = normal;
        float3 H = normalize(L + V);
        float3 lightCol = _LightColor0.rgb * LIGHT_ATTENUATION(i);
        float3 NdotL = dot(N, L);

        o.diffuse = (NdotL*0.5 + 0.5) * lightCol;
        o.specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz * lightCol;  // Half vector

        return o;
    }

    float3 frag(v2f i) : SV_Target
    {

        // texture albedo
        float4 tex = tex2D(_MainTex, i.uv);

        return i.diffuse * tex + i.specular;
    }

今まで行っていたライト計算を 頂点シェーダにもってきただけだ
それにより フラグメントシェーダが 非常にコンパクトになり
そうとう速度が速くなったと思われる

肝心のレンダリング結果

通常
13.png

軽量
14.png

予想以上に変化がなかった
もちろん モデルのポリゴン数やライトの状況によっては 全く使えない事もあるし
そもそも 法線マッピングは メインライト以外影響しなくなるので
限定的ではあるが 覚えておいて損はないかと。

私はパフォーマンスチューニングで結構使います