初歩レイトレーシングレンダラー
学部2年の白嶋です。今回でブログAizuVRのブログを書くのが2回目になります。
目次
概要
今回は、Unityとは全く関係がない内容になりますが、最近流行りのレイトレーシングの初歩を学ぼう! という事でC++でレイトレーシング レンダラーを作って行きたいと思います。
レイトレーシングとは?
最近、RTXがリアルタイムレイトレーシングが出来ると話題になっていますよね。 大体のゲームは、ラスタライズという方法で描画していましたが、RTXではラスタライズと一部レイトレーシングで描画しています。 一部でしかレイトレーシングが使われないのは、本来1フレームを描画するのは数時間、何日もかかる処理なので、それをリアルタイムで描画するのはまだ今の性能では無理な話です。なので、ガラスや水面などレイトレーシングが適してる所だけレイトレーシングで描画しているので、なので一部レイトレーシングなのです。
レイトレーシング ON OFF比較
RTXデモ動画
Battlefield V: Official GeForce RTX Trailer Battlefield V, NVIDIA RTX Ray Tracing, And GeForce RTX Combine To Deliver Next-Gen Graphics in Games – See It In Action In Our Exclusive Trailer
レイトレーシングの描画流れ
現実世界では、光源から光が放射して物体(以下、「オブジェクト」と言う。)などに反射、屈折などをして、人間の目に光が入り光の周波数によって色を認識しています。 レイトレーシングはその逆の流れで計算をしています。理由は、光源の光のごく一部しか目に入らないので、光源の光を全部計算すると無駄が多くなってしまうからです。
レイトレーシング実装
今回のブログでは、レイトレーシング法で簡単な反射のみ実装していきます。
1.レイの生成
std::vector<Ray> rays; for (int i = 0; i < getScreen.w * getScreen.h; i++) { double x = getScreen.GetWidth(i) - getScreen.w * 0.5; double y = getScreen.GetHigh(i) - getScreen.h * 0.5; rays.push_back(Ray(getScreen.cameraRay.o, getScreen.cameraRay.d + V(x * getScreen.pov, y * getScreen.pov, 0))); }
raysにこれから計算するレイをプッシュバックしてます。 x,yは for文の i に対してスクリーン上のピクセルかを計算しています。x,yでカメラの方向をズラし、それをレイとしてプッシュバックしています。 例) iが getScreen.w * 0.5 + getScreen.h * 0.5、つまりシーンの中心の時、x、yは0になります。その時はカメラの方向をそのままレイとしてプッシュバックします。
応用として、視野角も後々考えないといけませんね。
2.レイの反射
光源とオブジェクト間での反射を考えます。
std::optional<HitInfo> RayHit(Screen &getScreen, const Ray &getRay, double rayPower) { //再帰中止 if (rayPower < 0.01) { return std::nullopt; } for (int s = 0; s < getScreen.spheres.size(); s++) { double dotA = Dot(getRay.d, getRay.d); double dotB = -2 * Dot(getRay.d ,getScreen.spheres[s].p - getRay.o); double dotC = Dot(getScreen.spheres[s].p - getRay.o, getScreen.spheres[s].p - getRay.o) - std::pow(getScreen.spheres[s].r,2); double D_4 = (std::pow(dotB, 2) - 4 * dotA*dotC) / 4; double t1 = (-dotB / 2 + std::pow(D_4, 0.5)) / dotA; double t2 = (-dotB / 2 - std::pow(D_4, 0.5)) / dotA; if (t1 >= DBL_EPSILON) { V Q1 = V(getRay.o + t1 * getRay.d); HitInfo hit; hit.hitObject = getScreen.spheres[s]; hit.position = Q1; hit.hitObjectNormal = Normalize(Q1 - getScreen.spheres[s].p); hit.dot = Dot(hit.position, hit.hitObject.p) / Magnitude(hit.position) / Magnitude(hit.hitObject.p); hit.ray = Ray(hit.position, Normalize(getRay.d) + hit.hitObjectNormal); //光源の場合、直ちに光を反映 if (hit.hitObject.emission.Power() > 0) { hit.color = hit.dot* hit.hitObject.emission; return hit; } auto hitRecursive = RayHit(getScreen , Ray(hit.position, Normalize(getRay.d) + hit.hitObjectNormal),rayPower*hit.dot); if (hitRecursive) { if (hitRecursive.value().hitObject.emission.Power() > 0) { hit.color = hitRecursive.value().dot * hitRecursive.value().hitObject.emission; } else { hit.color = hitRecursive.value().color * hit.dot; } } else { //色の影響がごくわずかである //あるいは、rayが接触しなかった hit.color = Color(0,0,0); } return hit; } } //結果衝突を検出出来なかった return std::nullopt; }
反射を繰り返し、ピクセルの色を決定する時の色の影響率が低いレイは再帰の途中で停止してます。
3.画像ファイルの書き出し
今回はPPMファイルを出力しています。PPMファイルの中身はテキストで書かれています。全てのピクセルがテキストで表現できるので、余計なライブラリを使わずに簡単に書き出しが出来ます。
計算結果
上手くいきました👍