お米 is ライス

C#やらUnityやらを勉強していて、これはメモっといたほうがええやろ、ということを書くつもりです

【Cg Programming/Unity】Basic Lighting ~ Diffuse Reflection【順番にやっていく】

今回はこちら。
en.wikibooks.org
この節ではDiffuse Reflection、つまり「拡散反射」というものの描画方法について学ぶ。

拡散反射

拡散反射とは何か。例に上がっているのは月面の反射である。
月の表面は非常に凹凸が激しいがゆえに、入射した光は入射時の角度の記憶を失い、完全にランダムな方向へ反射される。
つまり、一定の光線が入射している面をどの角度から見ようと、その明るさは変わらないというわけだ。言い換えれば前節において_WorldSpaceCamraPosとして使用したような視線方向には関わらない描画となる。

一方で、ある面が感じる光の強さというものは、その面に対して垂直に光線が入射するほど大きい。つまり車のヘッドライトを横から見ると別に何ともないが、真正面から浴びせられると目が潰れてしまうほど強く光を感じるのと同様である。
面とベクトルが垂直であるかはそのベクトルと、面の法線ベクトルの内積の絶対値が1に近いかどうかで判別できるのだった。
そして一定の光量下で単純な拡散反射を考えた場合、物体が見える明るさは光線のベクトルと物体の面の法線ベクトルとの内積によってのみ決まる。簡単に書くと以下のようにあらわせる。

物体の明るさ = dot(面の法線ベクトル, 光線の方向ベクトル)

ここまでわかれば後はシェーダで書くだけだ。

コード

Shader "Custom/DiffuseReflection"
{
    SubShader
    {
        pass
        {
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            #include "UnityCG.cginc"

            struct v2f
            {
                float4 sv_position : SV_POSITION;
                float4 color : TEXCOORD0;
            };

            v2f v(float4 vertexPos : POSITION, float3 normal : NORMAL)
            {
                v2f output;
                output.sv_position = UnityObjectToClipPos(vertexPos);

                float4 worldPos = mul(unity_ObjectToWorld, vertexPos);  //頂点のワールド座標
                float3 worldNormal = UnityObjectToWorldNormal(normal);  //面の法線(UnityCG.cgincの関数を私用)

                //光源への向きを計算
                float3 lightDirection = _WorldSpaceLightPos0;   //Directional Light(太陽光のようにすべての空間に一定の方向で光が降り注ぐ)の場合はこう
                //float3 lightDirection = normalize((_WorldSpaceLightPos0 - worldPos).xyz);   //Point Light(点光源、つまり一点から等方的に光を発している)場合はこう
                

                //反射の度合いを計算
                //光源への向きと面の法線が同じ方向を向いているほど大きくなる
                float reflection = dot(lightDirection, worldNormal);    

                output.color = float4(float3(1, 1, 1) * max(reflection, 0), 1);

                return output;
            }

            float4 f(v2f input) : COLOR
            {
                return input.color;
            }
            ENDCG
        }

        pass
        {
            Tags { "LightMode" = "ForwardAdd" }
            Blend One One
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            #include "UnityCG.cginc"

            struct v2f
            {
                float4 sv_position : SV_POSITION;
                float4 color : TEXCOORD0;
            };

            v2f v(float4 vertexPos : POSITION, float3 normal : NORMAL)
            {
                v2f output;
                output.sv_position = UnityObjectToClipPos(vertexPos);

                float4 worldPos = mul(unity_ObjectToWorld, vertexPos);  //頂点のワールド座標
                float3 worldNormal = UnityObjectToWorldNormal(normal);  //面の法線(UnityCG.cgincの関数を私用)

                //光源への向きを計算
                //Directional Lightかどうかは_WorldSpaceLightPos0のwを見て判別する
                float3 lightDirection;
                if(_WorldSpaceLightPos0.w == 0)
                {
                    lightDirection = _WorldSpaceLightPos0;   //Directional Light(太陽光のようにすべての空間に一定の方向で光が降り注ぐ)の場合はこう
                }
                else
                {
                    lightDirection = normalize((_WorldSpaceLightPos0 - worldPos).xyz);   //Point Light(点光源、つまり一点から等方的に光を発している)場合はこう
                }

                //反射の度合いを計算
                //光源への向きと面の法線が同じ方向を向いているほど大きくなる
                float reflection = dot(lightDirection, worldNormal);    

                output.color = float4(float3(1, 1, 1) * max(reflection, 0), 1);
                //output.color = float4(lightDirection, 1);
                return output;
            }

            float4 f(v2f input) : COLOR
            {
                return input.color;
            }
            ENDCG
        }
    }
}

_WorldSpaceLightPos

光源のワールド座標は_WorldSpaceLightPos0というuniform変数で取得できる。
末尾に”0”が入っていることからわかるように、複数の光源がある場合は_WorldSpaceLightPos1のようにインデックスを変えた変数名でアクセスする。

LightModeの指定

さて、ここで注意しておきたいのがpassの先頭でTags { "LightMode" = "ForwardBase/ForwardAdd" }と指定している点である。
ライティングの最適化等の観点から、優先して描画すべきライトと、簡略化してもよいライトに分けられているようです。
(簡略化というのは例えば、複数のDirectional Lightを1つの行列で表してしまうもの(多分。。。))
何もしていなければ1つ目のDirectional LightはForwardBaseで描画され、2番目以降のDirectional LightやPoint LightはForwardAddで描画される?
(ちょっとこの辺どうなってるかはわからんのでそのうち詳しく調べることにする)
とにかく、ForwardBaseと指定したpassではDirectional Lightの描画処理を、ForwardAddと指定したpassにはどっちが来るかわからんので_WorldSpaceLightPosのw座標に入っている値で判別するらしい。

そしてpassを2つ以上通るため、2つ目のpassではBlend One Oneを指定して1つ目のpassの結果に加算するように描画している。

Directional LightとPoint Lightを1つずつ用意して描画したものは以下のようになる。
f:id:spi_8823:20200508012333p:plain

passを2つ以上書き始めるといよいよコードが長くなってきましたな。

次回はこちら。
spi8823.hatenablog.com