お米 is ライス

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

【Cg Programming/Unity】ライティング基礎 ~ 鏡面ハイライト②「環境光」【【順番にやっていく】

f:id:spi_8823:20200517095233p:plain
前回に引き続きこちらのチュートリアルをやっていきますわ!
en.wikibooks.org
前回は「鏡面反射」で、今回学ぶのは「環境光」になりますのよ。

環境光

え?環境光とは何かですって?
またですの?もう、しょうがありませんわね!
高貴なるものの義務としてわたくしがじっくり丁寧に教えて差し上げますのよ!

環境光とは

前節でやった拡散反射や、前回の鏡面反射については覚えていらして?
三歩進めば忘れてしまう鳥頭のあなたにもわかるようにそれぞれ描画した画像を再掲して差し上げますわ!そのダチョウみたいに大きなお目目でしっかりと見ておきなさい!
これが拡散反射↓
f:id:spi_8823:20200508012333p:plain
こっちが鏡面反射↓ですのよ。
f:id:spi_8823:20200517064927p:plain

ダチョウ並みの脳みそでも見ればわかるでしょうけど、どちらも光の当たっている部分は真っ黒になっていますの。
でも考えてみなさい?現実では光源から直接光が当たっていなくてもモノが見えるわ。ほら、電気を消した部屋の中でも光源はないけれどちゃんとモノが見えているでしょう?
そんな場合には太陽光が一度だけ反射した光ではなく、外の建物の壁や地面に反射したうえで、例えば窓から入った光がさらに部屋の壁、天井と何度も複雑に反射した光が私たちの目に届いているの。これを「環境光」と言いますのよ。

そんなのどうやって計算するのか、ですって?あなたにしては珍しくまともな質問をするのね。
ええ、そうですわ。複雑に反射してカメラに到達する光を簡単に計算する方法は無いですのよ。これを計算するには、光源からたくさんのRayを飛ばして起こりうるすべての反射を計算するRay Tracingというものをする必要がありますの。だけどそんなに重たい処理を気楽にさせられるほど計算資源は潤沢でないというのが庶民の悲しいところですわね。みんながみんな、わたくしのように個人用のスーパーコンピュータを持っているというわけでもないでしょうし……。

環境光の近似

一様な環境光

そこで一番初めに思いつくのがどの場所、どの角度でも一様な環境光が当たっていると考える近似方法ですの。
この場合、環境光の色をそのまま出力に足してあげればいいので簡単ですわね。
Unityでは環境光の値は[Window]->[Rendering]->[Lighting]で開いたウィンドウの[Environment]タブの中にある[Environment Lighting]という項目で設定することができるんですのよ。
今考えている「一様な環境光」は[Environment Lighting]->[Source]を「Color」にして設定できる[Ambient Color]から変更できますの。
f:id:spi_8823:20200517090736p:plain
ここで指定した値はシェーダのコードの中からUNITY_LIGHTMODEL_AMBIENTで取得できるんですのよ。

グラデーションする環境光

でも一様な環境光というのはそこまで現実的ではないんですのよ。だってほら、空は青いし、地面は茶色みたいなくすんだ色をしていることが多いじゃない。だから上を向いている面には青い環境光が、下を向いている面には茶色い環境光が差し込むとしたほうがよりそれらしく見えるわ。
こんな風に面の角度によってグラデーションする環境光も[Environment Lighting]から設定できますのよ。
[Source]を「Gradient」にすればほら、[Sky Color], [Equator Color], [Ground Color]が設定できるでしょう?これはそれぞれ「空の色」「水平線の色」「地面の色」を表しているの。これら3色を面の向きに合わせてグラデーションさせてあげれば、よりそれっぽく見えますの。
f:id:spi_8823:20200517091857p:plain
シェーダからはそれぞれunity_AmbientSky, unity_AmbientEquator, unity_AmbientGroundで取得できますわ。

Skyboxによる環境光

環境光にはSkyboxも指定できるんですの。
これを設定するとまさしく空の色として使っているSkyboxの色が環境光になるようですのよ。
デフォルトのシェーダではこちらを使って綺麗に描画してるのですけれど。ごめんなさい、これをシェーダから使用する方法は見つけられなかったわ。

というわけで今回は2番目の「グラデーションする環境光」を使った描画をすると次のようになりますのよ!
少しずつ「リッチ」な見た目に近づいてきたんじゃありませんこと?もちろん、わたくしのおうちのおリッチさには到底及びもしませんけれどね!おほほほほ!!
f:id:spi_8823:20200517095233p:plain

ついでにDiffuse Reflectionも上乗せした描画結果も置いておきますわ!
よくある見た目になったんじゃありませんこと?
f:id:spi_8823:20200517131023p:plain

コード

グラデーションする方法についてはコード中に詳しく書いてあるのでしっかりと感謝しながら読むんですのよ!
前回の鏡面反射のコードも一緒に書いてあるので気を付けて読むといいのですわ。具体的には頂点シェーダの後半部分が今回の「環境光」の部分ですのよ。

Shader "Custom/SpecularHighlights"
{
    Properties
    {
        _SpecularRatio ("SpecularRatio", float) = 1
        _AmbientRatio ("AmbientRatio", float) = 0.5 //環境光の度合い(大きいほど濃く環境光が描画されますわ)
    }
    SubShader
    {
        pass
        {
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            #include "UnityCG.cginc"

            uniform float _SpecularRatio;
            uniform float _AmbientRatio;

            struct v2f
            {
                float4 sv_position : SV_POSITION;
                float4 vertexPos : TEXCOORD0;
                float3 normal : TEXCOORD1;
                float4 ambientColor : TEXCOORD2;
            };

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

                //環境光の計算ですわ!
                float4 upAmbient = lerp(unity_AmbientEquator, unity_AmbientSky, normal.y);  //空の色と地平線の色を法線の上向き加減で補間しますの
                float4 downAmbient = lerp(unity_AmbientEquator, unity_AmbientGround, -normal.y);    //地面の色と地平線の色を法線の下向き加減で補完しますの

                //法線が上を向いている時はupAmbientを、下を向いているときはdownAmbientを使いますの。
                //シェーダでは並列計算を行っているのでif文はあまり推奨されませんの。その代わりにstep関数を使えば同じようなことができますのよ!
                output.ambientColor = upAmbient * step(0, normal.y) + downAmbient * step(normal.y, 0);
                output.ambientColor = output.ambientColor * _AmbientRatio;  //環境光の度合いをかけてるだけですのよ

                return output;
            }

            float4 f(v2f input) : COLOR
            {
                float4 worldPos = mul(unity_ObjectToWorld, input.vertexPos);
                float3 cameraDirection = (_WorldSpaceCameraPos - worldPos).xyz;
                cameraDirection = normalize(cameraDirection);

                float4 incidentVector = normalize(-_WorldSpaceLightPos0); 
                float isValid = step(dot(incidentVector, input.normal), 0);
                float3 specularDirection = incidentVector.xyz - input.normal * 2 * dot(input.normal.xyz, incidentVector.xyz);
                specularDirection = normalize(specularDirection) * isValid;

                float intensity = dot(cameraDirection, specularDirection.xyz);
                intensity = max(0, intensity);
                intensity = pow(intensity, _SpecularRatio);
                float4 color = float4(intensity * float3(1, 1, 1) + input.ambientColor, 1);

                return color;
            }
            ENDCG
        }
    }
}