【Unity】【レイトレ】Unityでレイトレーシング入門② 複数の球を描画する

レイトレーシングの概念を理解するためにUnityでレイトレしてみるシリーズ第二弾。
今回は複数の球体を描画してみます。

Unity2018.4

この記事は第一回の続きです

このシリーズはほとんど、以下のとても分かりやすいレイトレのチュートリアルを参考にさせていただき、
自分の中でしっかり理解することを目的としてUnityで実装しなおしただけのものです。

qiita.com

上の記事の方がしっかり解説されていますのでUnityのコードが必要ない方は上の記事を読むことをお勧めします。

さて、前回はレイトレーシングで球体を描画してライティングまで行いました。

light11.hatenadiary.com

本記事では前回の実装を元に実装を行いますので、必要に応じて前回の記事を参照してください。

衝突判定の関数を修正する

さてそれでは複数の球を描画するための実装をしていきます。
まず、前回作った衝突判定用の関数に手を加えます。

struct Intersection {
    bool hit;
    float3 hitPoint;
    float3 normal;
    float distance;
    float3  color;
};

// レイと球との当たり判定
void intersectSphere(Ray ray, Sphere sphere, inout Intersection i)
{
    float3 center = sphere.center;
    center -= ray.origin;

    float a = dot(ray.direction, ray.direction);
    float b = dot(ray.direction, center);
    float c = dot(center, center) - sphere.radius * sphere.radius;

    if (a == 0.0f) {
        // レイの長さがゼロ
        return;
    }

    float s = b * b - a * c;
    if (s < 0.0f) {
        // 衝突していない
        return;
    }

    s = sqrt(s);
    float a1 = (b - s) / a;

    if (a1 < 0.0f) {
        // レイの反対側で衝突
        return;
    }

    if (a1 >= i.distance)
    {
        // 既にヒットしている位置より遠い場合
        return;
    }

    i.hit = true;
    // レイの射出点から近いほうの交点
    i.hitPoint = ray.origin + a1 * ray.direction;
    // 法線
    i.normal = i.hitPoint - sphere.center;
    i.normal = normalize(i.normal);
    i.distance = a1;
    i.color = sphere.color;

    return;
}

変更点として、intersectSphereの返り値をvoidにしてinout修飾子を付けたIntersection構造体を引数として追加しています。
このIntersection構造体を複数の球の衝突判定に使いまわすことで、複数の球体との衝突結果を取得します。

具体的には、Intersection構造体に新たに定義したdistanceという変数に視点と衝突との距離を示す値を入れます。
衝突判定時にこれを使って、すでに衝突判定が行われている点よりも遠い点で衝突しても無視するような実装にします。

複数の球を描画する

あとは複数の球を定義して前節の衝突判定関数でレイとの衝突判定を行うだけです。
以下にシェーダを全文記載しておきます。

Shader "RayTracing"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            float3 lightDir = float3(1.0, 1.0, 1.0);

            struct Ray {
                float3 origin;
                float3 direction;
            };

            struct Sphere {
                float radius;
                float3  center;
                float3  color;
            };

            struct Intersection {
                bool hit;
                float3 hitPoint;
                float3 normal;
                float distance;
                float3  color;
            };

            // レイと球との当たり判定
            void intersectSphere(Ray ray, Sphere sphere, inout Intersection i)
            {
                float3 center = sphere.center;
                center -= ray.origin;

                float a = dot(ray.direction, ray.direction);
                float b = dot(ray.direction, center);
                float c = dot(center, center) - sphere.radius * sphere.radius;

                if (a == 0.0f) {
                    // レイの長さがゼロ
                    return;
                }

                float s = b * b - a * c;
                if (s < 0.0f) {
                    // 衝突していない
                    return;
                }

                s = sqrt(s);
                float a1 = (b - s) / a;

                if (a1 < 0.0f) {
                    // レイの反対側で衝突
                    return;
                }

                if (a1 >= i.distance)
                {
                    // 既にヒットしている位置より遠い場合
                    return;
                }

                i.hit = true;
                // レイの射出点から近いほうの交点
                i.hitPoint = ray.origin + a1 * ray.direction;
                // 法線
                i.normal = i.hitPoint - sphere.center;
                i.normal = normalize(i.normal);
                i.distance = a1;
                i.color = sphere.color;

                return;
            }

            void vert(float4 vertex : POSITION, out float4 position : SV_POSITION)
            {
                position = UnityObjectToClipPos(vertex);
            }

            fixed4 frag(UNITY_VPOS_TYPE vpos : VPOS) : SV_Target
            {
                float3 lightDir = normalize(float3(1.0, -1.0, 1.0) * -1);
                // 縦横のうち短いほうが-1~1になるような値を計算する
                float2 pos = (vpos.xy * 2.0 - _ScreenParams.xy) / min(_ScreenParams.x, _ScreenParams.y);
                // プラットフォームの違いを吸収
               #if UNITY_UV_STARTS_AT_TOP
                    pos.y *= -1;
               #endif

                // レイと球を定義
                Ray ray;
                ray.origin = float3(0.0, 0.0, -35.0);
                ray.direction = normalize(float3(pos.x, pos.y, 5.0));
                Sphere sphere1;
                sphere1.radius = 3.0;
                sphere1.center = float3(-6.0, 0.0, 0.0);
                sphere1.color = float3(1.0, 0.2, 0.2);
                Sphere sphere2;
                sphere2.radius = 2.0;
                sphere2.center = float3(0.0, 0.0, 0.0);
                sphere2.color = float3(0.2, 1.0, 0.2);
                Sphere sphere3;
                sphere3.radius = 3.0;
                sphere3.center = float3(6.0, 0.0, 0.0);
                sphere3.color = float3(0.2, 0.2, 1.0);

                float3 color = float3(0.1, 0.1, 0.1);

                // 当たり判定
                Intersection i = (Intersection)0;
                i.distance = 1.0e+30; // 大きい値を入れておく

                intersectSphere(ray, sphere1, i);
                intersectSphere(ray, sphere2, i);
                intersectSphere(ray, sphere3, i);
                if (i.hit) {
                    // 当たっていたらDiffuseを計算
                    float diffuse = max(0, dot(lightDir, i.normal));
                    color = i.color * diffuse;
                }

                return float4(color, 1.0);
            }
            ENDCG
        }
    }
}

フラグメントシェーダで球を3つ定義して順番に衝突判定しています。

結果

レンダリング結果は次のようになります。

f:id:halya_11:20190716001807p:plain

正常に描画されていることが確認できました。

関連

light11.hatenadiary.com

参考

qiita.com