レイトレーサーの制作: 方法と学び
イントロ
今年に入って、さまざまな事情で実際には手をつけられなかったいくつかの個人プロジェクトに、余暇の一部を充てることにしました。
この特定のプロジェクトの目標は、クールな画像を生成できるシンプルなレイトレーサー・ソフトウェア・ツールを作成することです。
レイ・トレーシングは、3次元モデルから2次元画像を生成するために使用される技術であり、レイトレーサーは、レイ・トレーシングの計算を実行するソフトウェアです。
レイ・トレーシング全体は、これらの日々ますます人気を集めています。なぜなら、一般的なグラフィックカードがリアルタイムでレイ・トレーシングを行う能力を持ち、画像の品質を向上させることができるからです。
私は、ゼロからスクラッチでレイトレーサーを作成するまでのプロセスを紹介します。
個人プロジェクト
このプロジェクトのアイデアは、実は昨年浮かんできました。
当時、友人がPS1のエミュレータを作っていて、ソフトウェアでグラフィカルなパイプラインを実装する(つまり、GPUに助けを求めずに画像をレンダリングする)といった面白い話をしていました。
その話を聞いて、昔に作ったレイトレーサーを思い出しました(古いiMac G4のハードドライブにまだあるかもしれません)。 かなりクレイジーだったことを覚えていますが、それ以上に実験する機会がありませんでした。
個人プロジェクトとしてもう一度レイトレーサーを作るのは、面白いんじゃないかと思いました。
そう思って、気まぐれでコーディングを始めました。 しかし、他の用事が忙しくて、プロジェクトを数回のコミットの後に中断せざるを得ませんでした。
ほぼ1年が経ち、プロジェクトは私のGitHubアカウントにぽつんと残っていましたが、もう一度チャンスを与えることにしました。
今回は、もっと構造的に進めるつもりです。 まず計画せずに飛び込んでしまうと、プロジェクトを放棄してしまう可能性が高くなると感じています。
また、より効果的な学習経験にするために、このプロジェクトは第一原理から取り組むつもりです。つまり、レイ・トレーシングの基礎となる理論を研究し、完全に理解する必要があります。
プロジェクトを結論に導くために、4ヶ月間(6月から9月)を予定しています。 4ヶ月間は、主題を探求するには十分な時間でありながら、先延ばしにするのを避けるために十分に短い期間です。
始める前に
レイトレーサーの作り方に関するチュートリアルや本はたくさんありますが、過去には、数学の説明をスキップしてコードに直行することが多いペイント・バイ・ナンバーズ風の本を使ってみたことがあります。 しかし、レイトレーサーがどのように動作するかを理解していない場合、基本的にはコードをコピーして貼り付けるだけで、十分に理解していない状態になります。 それらを参考にすることもできましたが、私は数年前にすでにレイトレーサーを書いていたので、今回はもっと深く掘り下げたいと思いました。
まずは、その背後にある数学から始めることにしました。
私がコンピュータグラフィックス(主にOpenGLの固定機能パイプライン)を始めたばかりの頃、線型代数を勉強したことを覚えているので、それが良い出発点だと思いました。 自分自身にその主題についてしっかりと復習する必要がありました。
オンラインで検索して、Saint Michael’s Collegeの数学教授Jim Hefferon氏による素晴らしい無料の教科書、「Linear Algebra」(線型代数)を見つけました。 この教科書は無料でダウンロードできます。 また、ウェブサイトにはLaTeXソース、すべての演習問題の解答、授業用スライド、一連のビデオ講義が利用できます。
私が使った2冊目の本は、「An Introduction to Ray Tracing」(レイ・トレーシング入門)という本です。 これは、その分野の古典的な本で、本に含まれる技術の多くはやや古いものもありますが、レイ・トレーシングの基本を理解する上でまだ基本的なものです。 私は昔、この本を物理的な形で購入し、非常に分かりやすく理解しやすいことを覚えています。 この本も無料で入手可能です。
アプリケーション
このような学習プロジェクトでは、ネイティブアプリケーションではなく、ウェブブラウザで実行できるものを作成することに興味があります。その理由は、プロジェクトがブラウザで実行される場合、将来の雇用主などに共有や紹介する際に摩擦が少なくなるからです。
サイドプロジェクトがネイティブアプリでしかアクセスできない特定の機能を必要としない限り、私は常にウェブアプリを選ぶようにしています。 唯一の問題は、現代のウェブプロジェクトでは通常、JavaScriptとNode.jsのエコシステムを使わなければならないことです。 Nodeには何の抵抗もありませんが、JavaScriptの最大のファンではありません。 誤解しないでください、それは立派なプログラミング言語ですが、私は静的型チェックをサポートしている言語を使う方が個人的に生産的です。
そしてここでTypeScriptが救いの手を差し伸べてくれます! TypeScriptは、JavaScriptにトランスパイルされるプログラミング言語です。 TypeScriptは、JavaScriptの欠点を補うために開発され、オプションの静的型付けをサポートするようになりました。それが、このプロジェクトで選んだ理由です。
線型代数を勉強する
線型代数は広範な分野です。
結局、本全体を勉強し、レイ・トレーシングに必要な数学だけを調べていた場合には出会わなかっただろう多くの興味深いトピックを学びました。 また、この本は各章の最後に線型代数の実世界での応用例も紹介しています。 それによって、自分が勉強していることが現実にどのように関係しているかがよくわかります。
全体的に、この本はとても楽しめました。 証明や演習問題を解くのは、学生時代の記憶よりも楽しかったです。
An Introduction to Ray Tracingを勉強する
Linear Algebraを終えた直後に、An Introduction to Ray Tracingの勉強を始めました。
本書は、レイ・トレーシングに関する包括的な導入部から始まります。
様々なイラストが導入部に添えられており、読者がプロセスを視覚化しやすくなっています。 レイ・トレーシングは視覚的な問題なので、テキストと視覚的な形で説明されるのは素晴らしいことです。 私は、書かれた説明よりも、それらに頼ることが多くなりました。
導入部の後には、本書で最も重要な章のひとつに入ります。 適切にEssential Ray Tracing Algorithms(基本的なレイ・トレーシング・アルゴリズム)と題されたこの章では、シーンのオブジェクトとの交差点を計算するという、古典的なレイ・トレーシングの本質を説明しています。
ノートを取る
私はすべての作業を紙に書きました。
紙に書くことで集中力が高まります。 その方が摩擦が少ないのです。 コンピュータでは、次の誘惑が新しいタブを開くだけで待っています。 また、紙に情報を書き留めると、後で情報を思い出すのが簡単になると感じます。
手書きで書くことが記憶力を向上させ、効率的に学習できることを示す研究を読んだことがあります。 脳は、キーボードでタイピングするのではなく、ペンや鉛筆で書くことでより刺激され、気が散りにくくなると言われています。 これが長期記憶の形成に役立ちます。
また、私は紙のノートをデジタルのノートよりもよく見直す傾向があることに気づきました。 机の上に物理的なものが常に置いてあって、進捗状況を思い出させてくれる方が、一度書いてしまうと忘れてしまうtext fileに入れておくよりも良いです。 私はこれを知っています。なぜなら、私は何年もの間にたくさんのメモをたくさんのデバイスやクラウドサービスに散らばって忘れてしまったからです。
学んだことの復習
各章を終えるごとに、フラッシュカードに興味深い説明や定義を書き留めました。 フラッシュカードやSRSが学習コミュニティで物議を醸す話題であることは知っていますが、私はこれらのツールを長年使ってきて、常に役立ってきました。 確かに、カードの作成と復習には非常に時間がかかりますが、時間の投資に見合うと思います。 特に定義を覚えるのに役立ちます。
繰り返しが何かを定着させるための鍵です。
繰り返しは知識を新鮮に保つ簡単な方法です。 Spaced repetitionソフトウェアは、カードの復習に役立つ繰り返しを使用します。 カードの答えを正しく答える回数が多いほど、カードが表示される頻度が減ります。
私の選択のSRSはAnkiです。 私は長年これを使っており、Ankiアカウントにかなりの数のフラッシュカードが蓄積されています。 このソフトウェアは非常に使いやすいです。 例えば、定理を証明するように求めるカードがあります。 答えを終えた後(前述のように、手でしっかり書くことで記憶を呼び起こすのに役立つため)、カードの裏に書かれた答えと自分の答えを比較します。 次に、答えを評価する時間です。 カードに答えるのがどれだけ難しかったかによって、Easy、Good、またはAgainを選択できます。 Againを選択すると、Ankiは答えを間違いとしてフラグ付けし、それらのカードをより頻繁に表示して覚えるのを助けます。
レイトレーシングアルゴリズム
これは、すべてのレイトレーサーの中核であり、数学とコードがうまく機能する交差点です。
人々がレイトレーシングについて説明するとき、通常、現実世界のカメラの仕組みとの類似性を引き合いに出します。
環境からの光線がレンズからカメラに入り、カメラの背面にあるセンサー(アナログの場合はフィルム)に当たります。 センサーは光の色を捉え、カメラは最終的な画像ファイルを生成します。
レイトレーシングは、実際のカメラとほぼ同じ方法で動作しますが、センサーの代わりに画像平面があります。
レイトレーサーの画像平面はカメラのセンサーのように機能します。 基本的に、レイトレーサーは画像平面の各ピクセルにどの色を塗るかを決定することで画像を生成します。 たとえば、シーンの中心に青い球がある場合、画像平面上の対応するピクセルも青色になります。
しかし、レイトレーサーはそれらのピクセルにどの色を塗るかどうやって知るのでしょうか? 結局のところ、3Dシーンの中には実際の光がなく、ただのデータの束です。 ここで、レイトレーシングの巧妙な直感が登場します。 現実世界のカメラが機能するのと同じ原理を使って、光線をシミュレートすることができます。
まず、視点(アイまたはカメラとも呼ばれる)から画像平面の各ピクセルの中心に向かって光線を生成します。
光線はシーンのオブジェクトと交差することもあれば、しないこともあります。 交差する場合は、交差したオブジェクトに基づいて、ピクセルを色付けするだけです。 たとえば、光線が赤い立方体と交差する場合、最終的な画像でそのピクセルを赤色にすることを検討できます。
これは、実際のカメラが機能するのと同じように、視点の前に画像平面が配置され、カメラのレンズの背後にセンサーまたはフィルムがあるということです。
これを行うには、光線が3Dシーンに配置されたオブジェクトとどこで交差するかを検出するアルゴリズムが必要です。
幾何学的プリミティブ(球、平面、三角形など)に応じて、さまざまな光線オブジェクト交差アルゴリズムがあります。 異なるアルゴリズムは、効率性と洗練度に違いがあります。
理解して実装しやすいものの1つは、光線球交差アルゴリズムです(これが、レイトレーサーで生成された多くの画像に球が含まれている理由です)。
ただし、シーン内のオブジェクトのローカルカラーで各ピクセルをシェーディングするだけでなく、レイトレーサーでさらに多くのことができます。
たとえば、影をレンダリングできます。 影は、コンピュータで生成された画像をより信頼できるものにする上で非常に重要な役割を果たします。影はオブジェクトの形状に強力な視覚的手がかりを提供するからです。
交差アルゴリズムが設定されたら、影がどこにあるかを計算するのは簡単です。 すでに光線が球に当たる表面上の点を持っているのでしょう? その点から、光源に向かって別の光線(シャドウレイまたは照明レイとも呼ばれる)をトレースできます。 このシャドウレイが途中で物体に当たり、光源に達する前にその物体をブロックすると、この物体がシャドウレイが生成される点に影を投影すると結論付けることができます。
影があるので、影を投影するための平らな平面があるといいですね。 これが、レイトレーサーにも平面と光線の交差テストを実装した理由です。
平面が非常に平らに見えるため、チェッカーボードテクスチャも追加しました。 レンダリングされると通常のテクスチャのように見えますが、平面のUV座標を参照して手続き型で生成されます。
最後に、すべてを少し滑らかに見せるために、アンチエイリアシング(AAとも呼ばれる)を追加しました。
開発環境
このプロジェクトの開発環境のセットアップは、Visual Studio Code、npm、およびwebpackを組み合わせて使用することで非常に簡単に行えました。ライブリロード機能により、アプリケーションの作成とテストが簡単になりました。
このプロジェクトでは、レイトレーシングアルゴリズムとベクトル数学ライブラリを両方ともゼロからコーディングしました。すべての可能なエッジケースをカバーするテストユニットを作成しました。これは、コードをリファクタリングする際に特に役立ちました。変更後もアプリケーションが意図した通りに動作することを確認する必要がありました。テストフレームワークとしては、Jestを使用しました。そのシンプルさが大好きです! うまく機能します!
プロジェクトのCI/CDとして、通常はTravis CI(またはCircleCI)を使用しています。どちらもオープンソースプロジェクト向けの無料プランがあるためですが、今回はGitHub Actionsを使用しました。初めての利用でしたが、大きな問題なくすべて順調に進みました。
ビルドとテストが終わったら、プロジェクト全体が自動的にGitHub Pagesにデプロイされ、すぐに使用可能になります。
将来の改善の可能性
もし再びこのレイトレーサーに取り組む機会があれば、追加したい機能のリスト:
シーンエディタ
シーンを操作する簡単な方法を追加したかったですが、現在はソースコード内にハードコーディングされています。スペキュラハイライト
照らされた際に光沢のある表面に見られる明るい光のスポット。反射
鏡や水面のような反射材をレンダリングするための表面からの光の反射。屈折
光が物体を通過する際に曲がる現象。 水の中にストローを入れたときにストローが曲がって見える効果などがこれに該当します。テクスチャ
テクスチャマッピングの実装、およびノーマルマップに対応して、詳細なレンダリングができるようにする。スカイボックス
キューブマップを使用したスカイボックスで、シーンをより活気あるものにし、光沢のある素材に良い反射を得られるようにする。カラーライト
純白の光は特に屋外のシーンでは人工的に感じるため、カラーライトを導入します。加速構造
バウンディングボリュームや階層のようなもので、シーンの複雑さが増した場合でもレイトレーサーがパフォーマンスを維持できるようにする。さらなるプリミティブ
球 と 平面 以外のプリミティブもレンダリングできるようにする。 3Dモデリングソフトウェア(Maya や Blender)からエクスポートしたものをレンダリングする場合、三角形が一般的な選択肢です。
締めくくりの言葉
これはとても学ぶ価値のあるトピックでした。 今後も4ヶ月ごとにサイドプロジェクトを行う形式を続けたいと思います。 このプロジェクトを時間をかけて完成させたことに満足しており、数学とソフトウェアエンジニアリングの両方を包括する素晴らしい学びの経験でした。 近いうちにレイトレーサーの機能をさらに拡張していくことができればと思います。