知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】レイマーチング入門【3】 #34

前回の成果

微分について理解した。

soramamenatan.hatenablog.com


今回やること

前回のレイマーチングの続きで、ライティングを理解していきます。

gurutaka-log.com


ソースコード

Shader "Unlit/lightingRaymarching" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _Radius("Radius", Range(0.0, 1.0)) = 0.3
    }
    SubShader {
        Tags { "Queue"="Transparent" "LightMode"="ForwardBase"}
        LOD 100

        Pass {
            ZWrite On
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 pos : POSITION1;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Radius;

            float sphere(float3 pos) {
                return length(pos) - _Radius;
            }

            float3 getNormal(float3 pos) {
                float d = 0.001;
                return normalize(float3(
                    sphere(pos + float3(d, 0, 0)) - sphere(pos + float3(-d, 0, 0)),
                    sphere(pos + float3(0, d, 0)) - sphere(pos + float3(0, -d, 0)),
                    sphere(pos + float3(0, 0, d)) - sphere(pos + float3(0, 0, -d))));
            }

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.pos = mul(unity_ObjectToWorld, v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                float3 pos = i.pos.xyz;
                float3 rayDir = normalize(pos.xyz - _WorldSpaceCameraPos);
                int stepNum = 30;

                for (int i = 0; i < stepNum; i++) {
                    float marcingDist = sphere(pos);
                    if (marcingDist < 0.001) {
                        float3 lightDir = _WorldSpaceLightPos0.xyz;
                        float3 normal = getNormal(pos);
                        float3 lightColor = _LightColor0;
                        fixed4 col = fixed4(lightColor * max(dot(normal, lightDir), 0), 1.0);
                        col.rgb += fixed3(0.2f, 0.2f, 0.2f);
                        return col;
                    }
                    pos.xyz += marcingDist * rayDir.xyz;
                }
                return 0;
            }
            ENDCG
        }
    }
}

もう一度添付しておきます。


偏微分

多変数関数に対して一つの変数のみに関する(それ以外の変数は定数として固定する)微分である

偏微分 - Wikipedia

多変数関数とは複数の変数によって値が決まるy = f(x_1, x_2, ... , x_n)のような関数のことです。

では、実際の数値を用いて偏微分をしてみます。


値を入れてみる

f(x, y) = x^2 + y^3 +5y + xy
偏微分していきます。

xについての偏微分 \displaystyle \frac{δf(x, y)}{δx}, \frac{δf}{δx}, f_xなどの記号で表されます。


微分nx^{n-1}なので、xについての偏微分の式は

 \displaystyle
\frac{δf}{δx} = x^2 + y^3 +5y + xy
\\ = x^2 + (y^3) +(5y) + xy
\\ = 2x + y

となります。
同じようにyも行うと

 \displaystyle
\frac{δf}{δx} = x^2 + y^3 +5y + xy
\\ = (x^2) + y^3 +5y + xy
\\ = 2y^2 + x + 5

となります。


偏微分の定義

前回の記事で、

soramamenatan.hatenablog.com

導関数の定義は、

 \displaystyle
f'(x) = \lim_{h \to 0}\frac{f(x + h) - f(x)}{h}

と表しました。

偏微分は一つの変数以外定数として固定するので、

 \displaystyle
f'(x) = \lim_{h \to 0}\frac{f(x + h, y) - f(x, y)}{h}

となります。


距離函数を使った法線の公式である以下の式と、

 \displaystyle
{t} = (\frac{∂f}{∂x}, \frac{∂f}{∂y}, \frac{∂f}{∂z})


x偏微分の定義をもう一度思い出してください。

 \displaystyle
\frac{∂f(x, y, z)}{∂x} := \lim_{h \to 0}\frac{∂f(x + h, y, z) - ∂f(x, y, z)}{h}


このことから、法線の公式にx偏微分を代入してあげると、getNormalと同じとなります。
なので、この関数で法線が取れます。


float3 getNormal(float3 pos) {
    float d = 0.001;
    return normalize(float3(
        sphere(pos + float3(d, 0, 0)) - sphere(pos + float3(-d, 0, 0)),
        sphere(pos + float3(0, d, 0)) - sphere(pos + float3(0, -d, 0)),
        sphere(pos + float3(0, 0, d)) - sphere(pos + float3(0, 0, -d))));
}



_WorldSpaceLightPos0

これはライトの位置になります。
詳しくはこちらを参照してください。

soramamenatan.hatenablog.com


_LightColor0

ライトの色です。


ライティングの処理

ここの部分で処理を行っています。

fixed4 col = fixed4(lightColor * max(dot(normal, lightDir), 0), 1.0);
col.rgb += fixed3(0.2f, 0.2f, 0.2f);

1行目はランバート反射の計算をしています。

ランバート反射とは噛み砕いて説明すると、
光源(黄色の矢印)があると、暗くなる部分(ピンクの矢印)が生まれる反射のことです。

f:id:soramamenatan:20200109104451p:plain

[Unity]シェーダでランバート反射 - Qiita:より引用


ランバート反射は

ランバート反射 = ライトの色 * dot(面の法線 * ライトのベクトル) + 環境光

で表すことができるので、

fixed4 col = fixed4(lightColor * max(dot(normal, lightDir), 0), 1.0);
col.rgb += fixed3(0.2f, 0.2f, 0.2f);

で表現できます。

maxを使用しているのは、暗くなりすぎないようにしているからで好みです。

max使用時

f:id:soramamenatan:20200109105005p:plain

max 非使用時

f:id:soramamenatan:20200109105015p:plain


結果

ライティングが出れば成功です。

f:id:soramamenatan:20191227124920p:plain


ソースコードにコメントを付与

Shader "Unlit/lightingRaymarching" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _Radius("Radius", Range(0.0, 1.0)) = 0.3
    }
    SubShader {
        Tags { "Queue"="Transparent" "LightMode"="ForwardBase"}
        LOD 100

        Pass {
            ZWrite On
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 pos : POSITION1;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Radius;

            // 中心との距離から円を描画
            float sphere(float3 pos) {
                return length(pos) - _Radius;
            }

            // 法線を取得
            float3 getNormal(float3 pos) {
                // δ
                float d = 0.001;
                // 法線の公式より、各変数の偏微分から計算
                return normalize(float3(
                    sphere(pos + float3(d, 0, 0)) - sphere(pos + float3(-d, 0, 0)),
                    sphere(pos + float3(0, d, 0)) - sphere(pos + float3(0, -d, 0)),
                    sphere(pos + float3(0, 0, d)) - sphere(pos + float3(0, 0, -d))));
            }

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.pos = mul(unity_ObjectToWorld, v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                float3 pos = i.pos.xyz;
                // レイのベクトル
                float3 rayDir = normalize(pos.xyz - _WorldSpaceCameraPos);
                int stepNum = 30;

                for (int i = 0; i < stepNum; i++) {
                    // レイを進める距離
                    float marcingDist = sphere(pos);
                    // 衝突検知
                    if (marcingDist < 0.001) {
                        float3 lightDir = _WorldSpaceLightPos0.xyz;
                        float3 normal = getNormal(pos);
                        float3 lightColor = _LightColor0;
                        // 内積によって色を変化させる
                        fixed4 col = fixed4(lightColor * max(dot(normal, lightDir), 0), 1.0);
                        // 環境光のオフセット
                        col.rgb += fixed3(0.2f, 0.2f, 0.2f);
                        return col;
                    }
                    // レイを進める
                    pos.xyz += marcingDist * rayDir.xyz;
                }
                // stepNum回レイを進めても衝突しなかったらピクセルを透明にする
                return 0;
            }
            ENDCG
        }
    }
}

今回は以上となります。
ここまでご視聴ありがとうございました。