知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】レイマーチング入門【2】 #33

前回の成果

レイマーチング入門を行った。

soramamenatan.hatenablog.com


今回やること

前回はただ白くしただけだったので、そこにライティングを加えていきます。

gurutaka-log.com


事前準備

前回と同様に、Scene上にQuadをおいてください。

f:id:soramamenatan:20191223125602p: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;
                }
                return 0;
            }
            ENDCG
        }
    }
}

前回のソースにライティングの処理を加えました。


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))));
}

今回の記事はここがキモとなっています。
微分から自分は学びました


先に結論

先ほどのgetNormal関数は言ってしまえばこちらの距離函数を使った法線の公式 を求めたものになります。

 \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}

getNormal関数で求められるわけですが、理解できなかったので一から説明していこうと思います。

参考サイト様 hackerslab.aktsk.jp


偏微分を理解するには、まず微分からだと思うので簡単に解説していきます。


微分

微分は一言でいうと、グラフの傾きのことです。

ここにy = x^2のグラフがあります。

f:id:soramamenatan:20191226101901p:plain

微分とは何か? - 中学生でも分かる微分のイメージ:より引用


次に上のグラフを0.5付近で拡大したものを用意しました。

f:id:soramamenatan:20191226101442p:plain

微分とは何か? - 中学生でも分かる微分のイメージ:より引用

この拡大したグラフで見て欲しいところは

  • 曲線であるy = x^2のグラフの一部を拡大すると直線に見える
  • x = 0.5で拡大した際のy = x^2の傾きはだいたい1である

以上の二つです。
直線にみえるやだいたい1といった数学らしくない曖昧なところがありますが、そこは後で説明します。


x = 1 の時の傾き

x = 1で拡大した際のy = x^2のグラフになります。

f:id:soramamenatan:20191226122216p:plain

微分とは何か? - 中学生でも分かる微分のイメージ:より引用

xが1増えると、yが2増えているので傾きは2となります。


傾きを実際に出してみる

グラフだけだとわかりにくい部分があるので、計算してみます。

例えば、y = x^2のグラフにおけるx = 1.0 から x = 1.1までの変化の割合は

 \displaystyle
 {変化の割合} = \frac{{yの増加量}}{{xの増加量}} 
\\ = \displaystyle \frac{1.1^2 - 1.0^2}{1.1 - 1.0} \\ = \displaystyle \frac{0.21}{0.1} \\ \displaystyle = 2.1

となるので、傾きは2.1となります。


次に、2点間の距離を縮めてみます。
y = x^2のグラフにおけるx = 1.00 から x = 1.01までの変化の割合は

 \displaystyle
 {変化の割合} = \frac{{yの増加量}}{{xの増加量}} 
\\ = \displaystyle \frac{1.01^2 - 1.0^2}{1.01 - 1.0} \\ = \displaystyle \frac{0.0201}{0.01} \\ \displaystyle = 2.01

となるので、傾きは2.01になります。
これを先ほど出したx = 1の時の傾きである2に近づけるため、増加量を減らしていきます。


傾きの証明

次は2点間の距離をhとして、変化の割合を求めます。

 \displaystyle
 {変化の割合} = \frac{{yの増加量}}{{xの増加量}} 
\\ = \displaystyle \frac{(1 + h)^2 - 1^2}{(1 + h) - 1} \\ \displaystyle = \frac{(1 + 2h + h^2) - 1}{(1 + h) - 1} \\ 
 \displaystyle = \frac{2h + h^2}{h} \\ 
 \displaystyle = 2 + h

hに0.1を入れると2.1となり、hに0.01を入れると2.01となるので、傾きを実際に出してみるの見出しで出しているものが証明されました。


y = x^2微分する

一般のxの点における傾きを求めます

 \displaystyle
 {変化の割合} = \frac{{yの増加量}}{{xの増加量}} 
\\ \displaystyle = \frac{(x + h)^2 - x^2}{(x + h) - x} \\ = \displaystyle \frac{(x^2 + 2hx + h^2) - x^2}{(x + h) - x} \\ \displaystyle = \frac{2hx + h^2}{h} \\ \displaystyle = 2x + h

hを限りなく0にすると、この式は2xとなります。
これにより、 y = x^2微分すると2xになることが証明されました。


記号で表す

ここまで実数で表していたので、記号で数学っぽく表します。
今回のグラフはy = f(x)と定義します。


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

先ほど、hを限りなく0にすると表現したのですが、それはlim_{h \to 0}で表されています。


この

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

導関数の定義と呼びます。
この数式、どこかで見覚えがありませんか?
最初に説明した、xの偏微分

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

に似ています。
この偏微分に関しては次回説明します。

これで微分は終了です。
だいぶ噛み砕いて説明したので、言葉足らずの部分があるかもしれないのですが、ご了承ください。


参考サイト様

sci-pursuit.com

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