レイトレーシングの概念を理解するためにUnityでレイトレしてみるシリーズ第三弾。
今回は前回描画した複数の球体に加えて平面を描画してみます。
Unity2018.4
この記事は第二回の続きです
このシリーズはほとんど、以下のとても分かりやすいレイトレのチュートリアルを参考にさせていただき、
自分の中でしっかり理解することを目的としてUnityで実装しなおしただけのものです。
上の記事の方がしっかり解説されていますのでUnityのコードが必要ない方は上の記事を読むことをお勧めします。
さて、前回はレイトレーシングで複数の球体を描画しました。
本記事では前回の実装を元に実装を行いますので、必要に応じて前回の記事を参照してください。
レイと平面の衝突判定
まずレイと平面の衝突判定関数を作ります。
struct Plane { float3 position; float3 normal; float3 color; }; // レイと平面との当たり判定 void intersectPlane(Ray ray, Plane plane, inout Intersection i) { float h = dot(plane.normal, plane.position); float t = (h - dot(plane.normal, ray.origin)) / dot(plane.normal, ray.direction); if (t < 0) { return; } if (t >= i.distance) { return; } i.hit = true; i.hitPoint = ray.origin + normalize(ray.direction) * t; i.normal = plane.normal; i.distance = t; i.color = plane.color; }
衝突判定のアルゴリズムは下記のサイトを参考にさせていただいています。
平面を描画する
あとはこれを使って平面を定義し、球と同様に衝突判定を行うだけです。
以下にシェーダの全文を記載します。
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 Plane { float3 position; float3 normal; 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 intersectPlane(Ray ray, Plane plane, inout Intersection i) { float h = dot(plane.normal, plane.position); float t = (h - dot(plane.normal, ray.origin)) / dot(plane.normal, ray.direction); if (t < 0) { return; } if (t >= i.distance) { return; } i.hit = true; i.hitPoint = ray.origin + normalize(ray.direction) * t; i.normal = plane.normal; i.distance = t; i.color = plane.color; } 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); // 平面を定義 Plane plane; plane.position = float3(0.0, -3.0, 0.0); plane.normal = float3(0.0, 1.0, 0.0); plane.color = float3(0.5, 0.5, 0.5); 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); intersectPlane(ray, plane, i); if (i.hit) { // 当たっていたらDiffuseを計算 float diffuse = max(0, dot(lightDir, i.normal)); color = i.color * diffuse; } return float4(color, 1.0); } ENDCG } } }
前回からの差分としては単純に平面の定義と衝突判定を追加しただけです。
結果
レンダリング結果は次のようになります。
角度的にも色的にも非常にわかりづらくなってしまいましたが、、
グレーの平面が床のように描画されていることが確認できました。