Slide 1

Slide 1 text

古典的レイトレーサーの実装 2021/01/24 ~ 2021/02/21 OSK#p-ray Lecture02 ~ Lecture05

Slide 2

Slide 2 text

内容 • 画像が出来る仕組み • 古典的レイトレーサーについて • レイの表現 • レイと球体との交差計算 • レイとシーンとの交差計算 • ピンホールカメラ 理論編 実装編 • CMakeの使い方 • 分割コンパイルとヘッダオンリー • 古典的レイトレーサーの実装 • SSAA • OpenMPによる並列化 • Ambient Occlusion(AO)

Slide 3

Slide 3 text

表記と仮定 • Ԧ 𝑥でベクトルを表現し, 主に位置や方向を表すのに使用する • 方向ベクトルは長さ1に正規化されていると仮定する • ベクトルは縦ベクトルで表現する

Slide 4

Slide 4 text

理論編 画像が出来る仕組み

Slide 5

Slide 5 text

画像が出来る仕組み • カメラが3次元空間上に置かれているとする • カメラの各画素に届く光を計算すれば良い イメージセンサー レンズ

Slide 6

Slide 6 text

画像が出来る仕組み • 光は光源から画素に流れていくが、それを逆にして考える • 画素からシーン上の点にレイを飛ばし, その点から来る光の強さを計算する イメージセンサー ? シーン上の点からどれくらいの光が反射して画素に向かうのかを計算したい

Slide 7

Slide 7 text

補足: 光の強さとは? • 物理的には放射輝度に対応するが, それについては次回見ていく • CGでは光の強さはRGB(Red, Green, Blue)の3次元ベクトルで表されることが多い • RGBはそのまま画像として書き込める利点がある

Slide 8

Slide 8 text

補足: 現実世界はRGB…? • 現実世界では色の実体は波長ごとの光の強さの分布(Spectral Power Distribution) • 人間はそれを錯体細胞によってRGBのように認識していると考えられる • 波長を考慮したレンダリングをSpectral Renderingという https://en.wikipedia.org/wiki/Spectral_power_distribution

Slide 9

Slide 9 text

理論編 古典的レイトレーサー

Slide 10

Slide 10 text

古典的レイトレーサー • コンピューターグラフィクス黎明期の物理ベースではないレイトレーサー(e.g. Whitted Ray Tracing) An improved illumination model for shaded display, Whitted, 1979

Slide 11

Slide 11 text

古典的レイトレーサーの仕組み • 鏡, ガラス以外の面: まず光源から見えるかどうか確かめる ?

Slide 12

Slide 12 text

古典的レイトレーサーの仕組み 光源から見えた場合 𝜔𝑖 を入射方向ベクトル, 𝑛を法線とすると 光源から見えない場合 𝑎の部分は環境光と呼ばれる これは物理ベースではない, アドホックな計算方法 光の強さ𝐼は以下のように計算する

Slide 13

Slide 13 text

古典的レイトレーサーの仕組み • 鏡, ガラスの面: 反射, 屈折した先の光を持ってくる(再帰的) ?

Slide 14

Slide 14 text

レイの表現 • レイは始点位置と方向ベクトルの組( Ԧ 𝑜, Ԧ 𝑑)で表現できる 始点位置 方向ベクトル

Slide 15

Slide 15 text

レイとシーンの交差計算 • レイトレーサーを実装するにはレイとシーンの交差位置の計算が必要 • まずは球とレイの交差位置の計算を見ていく ? 交差位置の計算 交差判定

Slide 16

Slide 16 text

レイと球の交差計算 レイ上の点(𝑡はパラメーター) 半径𝑟, 中心Ԧ 𝑐の球の方程式( Ԧ 𝑝は球面上の点) レイ上の点が球面上にあると仮定し

Slide 17

Slide 17 text

レイと球の交差計算 を球の方程式に代入すると ノルムの2乗は内積で表せるので 展開して整理していくと

Slide 18

Slide 18 text

レイと球の交差計算 これは𝑡に関する2次方程式になっているので, 解の公式を用いて解を計算できる として 交差判定: 交差位置: 求めた𝑡を に代入 これで交差位置が計算できた

Slide 19

Slide 19 text

演習: レイと平面の交差計算 先程の流れを参考にし, 平面とレイの交差計算の式を導出してみましょう 平面は中心位置Ԧ 𝑐と法線𝑛を用いて と表せる( Ԧ 𝑝は平面上の点)

Slide 20

Slide 20 text

レイとシーンの交差計算 • シーンが複数の球で構成されているとする • レイとシーンの交差計算は, 全ての球と交差計算を行って最も距離が近いものを返せば良い hit? hit? hit?

Slide 21

Slide 21 text

注意: レイとシーンの交差計算 • 下図のような場合だと, 数値計算誤差によりレイが床と交差してしまう場合がある • このようなケースを防ぐために, レイに許容最小交差距離𝑡𝑚𝑖𝑛 を導入する

Slide 22

Slide 22 text

補足: 空間分割法 • 先ほど説明した方法は全探索で𝑂(𝑛) • Kd-treeやBVHといった空間分割法を用いると𝑂(log 𝑛) ポリゴンを含むようなシーンでは空間分割法を用いないと計算時間がかかりすぎる

Slide 23

Slide 23 text

カメラモデル • これまでの流れでレイとシーンの交差計算が分かった • 最初のレイはどうやって作れば良いのか? ?

Slide 24

Slide 24 text

カメラモデル • 画素からレンズ内の屈折をシミュレーションし, シーンにレイを飛ばせば良い • ここでは最も簡単なピンホールカメラモデルを考える イメージセンサー レンズ

Slide 25

Slide 25 text

補足: Realistic Camera Model • 現実の写真レンズ内の屈折をシミュレーションすれば, そのレンズで撮った画像 を表現できる

Slide 26

Slide 26 text

ピンホールカメラ • ピンホールカメラ: 画素に入ってくる光は全てピンホールを通ってくる • ボケの無い完全な結像が得られる イメージセンサー ピンホール

Slide 27

Slide 27 text

ピンホールカメラによるレイの生成 • 単純に画素からピンホールに向けてレイを飛ばせば良い イメージセンサー ピンホール

Slide 28

Slide 28 text

余談: Camera Obscura • Camera Obscuraというピンホールカメラが古い時代から使われていた • Cameraは部屋, Obscuraは暗いを意味するラテン語 • ピンホールを通過する光量はとても少ないので撮影には時間がかかる De Radio Astronomica et Geometrica, Frisius, 1544

Slide 29

Slide 29 text

実装編 実装のための準備

Slide 30

Slide 30 text

実装方法について • 実装はhttps://github.com/TUS-OSK/lecture-classical-raytracer-2020をforkし, srcの中に実 装していきます • refにはリファレンス実装が入っています • プロジェクトはCMakeで管理しているため, それについて少し見ていきます • ヘッダオンリーで実装していきます(後述)

Slide 31

Slide 31 text

CMakeとは? • C/C++の開発でよく使われるビルド自動化ツール • プラットフォームの差異の吸収, 依存ライブラリのビルド管理が楽になる • 今回はCMakeを使ったビルド方法だけを紹介します

Slide 32

Slide 32 text

CMakeを使ったビルドの流れ プロジェクトルートにCMakeLists.txtがある場合, 以下でbuildの中に実行ファイルが生成される

Slide 33

Slide 33 text

演習: CMakeによるビルド • forkしたリポジトリをCMakeでビルドし, 生成された実行ファイルを実行してみ ましょう

Slide 34

Slide 34 text

分割コンパイル • C/C++ではヘッダーファイル(.h, .hpp)に宣言, ソースファイル(.c, .cpp)に実装を書くという書 き方をする • 各ソースファイルはオブジェクトファイルにコンパイルされる • 実行ファイルは各オブジェクトファイルをリンクすることで生成される func.h func.cpp

Slide 35

Slide 35 text

ヘッダオンリー • ヘッダファイルに実装を全て書くこともでき, そのようなライブラリはヘッダオンリーライブラ リと呼ばれる • #includeしたソースファイルに全ての実装が集約されるので, コンパイル時間が長くなる • ヘッダファイル中での関数の実装にはinlineをつける func.h

Slide 36

Slide 36 text

ヘッダファイルの作成 • インクルードガードと呼ばれるものを最初に必ず書く • これは同じヘッダファイルが1つのファイルに2回以上インクルードされるのを防ぐ インクルードガード

Slide 37

Slide 37 text

実装編 古典的レイトレーサーに必要な部品の実装

Slide 38

Slide 38 text

古典的レイトレーサーの実装に必要なもの 3次元ベクトル(Vec3) 球(Sphere) レイ(Ray) ピンホールカメラ(PinholeCamera) 画像(Image) 以下の要素をクラスとして実装していく シーン(Scene) 交差情報 (IntersectInfo)

Slide 39

Slide 39 text

Vec3クラス • 3次元ベクトルを表現するクラス • 位置や方向ベクトル, RGBを表現するのに使用する

Slide 40

Slide 40 text

演習: Vec3クラスの作成 • vec3.hを作成し, 以下のような機能を持つ3次元ベクトルクラスを作成する • ベクトル同士の加算, 減算 • スカラー倍 • ベクトル同士のアダマール積 • ノルム • 内積 • 外積 • 正規化

Slide 41

Slide 41 text

演習: Vec3クラスのテスト • Vec3クラスを作成できたら各種演算が正しいかテストしてみる

Slide 42

Slide 42 text

Imageクラス • 画像を表現するクラス • 画像はfloatの配列で各画素のRGBを表現することにする • RGBは[0,1]の範囲で表現する 1 2 3 … R G B R G B R G B … 画素1 画素2 画素3

Slide 43

Slide 43 text

画像の出力 • 画像はPPM画像で出力することにする • PPM画像は以下のようにテキストファイルで記述される 横, 縦の画素数 階調数 RGBデータ PPM画像であることを示すヘッダー

Slide 44

Slide 44 text

ガンマ補正 • 生RGBをそのまま画像にすると見た目があんまり良くない • そこで以下のような補正をかける(ガンマ補正)

Slide 45

Slide 45 text

演習: Imageクラスの作成 • 以下のような機能を持つImageクラスを作成する • 横, 縦の画素数を返す • 画素(i, j)にRGBをセットする • PPM画像の出力 • ガンマ補正

Slide 46

Slide 46 text

演習: テスト画像の出力 • 画像クラスを実装できたら, 以下のようなコードで画像を出力してみる

Slide 47

Slide 47 text

演習: テスト画像の出力 • 次のような画像が出たらOK

Slide 48

Slide 48 text

演習: Rayクラスの作成 • ray.hを作成し, 以下のような機能を持つRayクラスを作成する • 許容最小交差距離 • 始点から距離𝑡のレイ上の点を返す機能

Slide 49

Slide 49 text

PinholeCameraの実装 • レイの生成は画素上の点の位置と, ピンホールの位置が分かれば簡単に計算でき る 方向: 始点: ピンホール イメージセンサー

Slide 50

Slide 50 text

ピンホールの位置 • イメージセンサーの中心位置とカメラの前方向から計算できる • ピンホールまでの距離は今はとりあえず適当に1とする イメージセンサー ピンホール

Slide 51

Slide 51 text

画素上の点の位置 • イメージセンサーの横, 縦方向の長さが必要になる. ここではとりあえず(2, 2)としておく • 中心位置 + 相対位置を計算すれば良い イメージセンサー

Slide 52

Slide 52 text

画素上の点の位置 カメラの横方向: カメラの上方向: ここで(𝑢, 𝑣)は(𝑖, 𝑗)を −1,1 × [−1,1]の領域に含まれるように変換したもの

Slide 53

Slide 53 text

カメラの横方向, 上方向の生成 • カメラ指定はカメラ位置(イメージセンサー中心)とカメラの前方向だけで行いたい • カメラの横方向, 上方向は前方向だけから以下の式で生成する(正規直交基底の生成)

Slide 54

Slide 54 text

演習: PinholeCameraクラスの作成 • pinhole-camera.hを作成し, 以下のような機能を持つPinholeCameraクラスを実 装する • 与えられた(𝑢, 𝑣)からレイを生成する

Slide 55

Slide 55 text

演習: レイの生成のテスト • PinholeCameraが実装できたら以下のようなコードを試してみる

Slide 56

Slide 56 text

演習: レイの生成のテスト • 次のような画像が出たらOK

Slide 57

Slide 57 text

IntersectInfo シーンとの交差計算では以下のものもセットで計算しておくと良い • 交差位置までの距離 • 交差位置 • 交差位置における法線 これらをIntersectInfoという構造体にまとめることにする

Slide 58

Slide 58 text

演習: IntersectInfoの作成 • intersect-info.hを作成する

Slide 59

Slide 59 text

Sphereクラス • Sphereクラスには球の中心位置, 半径だけではなく, その球がどのような材質なのかを表すイン デックスを持たせる • 材質はミラー, ガラス, その他の3種類 • 材質の色も持たせる

Slide 60

Slide 60 text

演習: Sphereクラスの作成 • sphere.hを作成し, 以下のような機能を持つSphereクラスを実装する • 与えられたレイとの交差判定・交差計算を行う

Slide 61

Slide 61 text

演習: 交差計算のテスト • Sphereが実装できたら球の法線を表示してみる

Slide 62

Slide 62 text

演習: 交差計算のテスト • 次のような結果が出たらOK

Slide 63

Slide 63 text

Sceneクラス • Sceneクラスはシーン全体(今回の場合球の集合)を表現するクラス • シーン全体とレイの交差計算を行う

Slide 64

Slide 64 text

演習: Sceneクラスの作成 • scene.hを作成し, 以下のような機能を持つSceneクラスを実装する • 球の追加 • 与えられたレイとの交差判定・交差計算を行う

Slide 65

Slide 65 text

演習: シーンとの交差計算のテスト • Sceneクラスが実装できたら, 複数の球を追加した状態で法線を表示してみる

Slide 66

Slide 66 text

演習: シーンとの交差計算のテスト • 次のような画像が出たらOK(手前にあるものがちゃんと手前に来ているか注意する)

Slide 67

Slide 67 text

実装編 古典的レイトレーサーの実装

Slide 68

Slide 68 text

古典的レイトレーサーの実装 • これで古典的レイトレーサーを実装するための部品が全て揃った • ここからは理論編で紹介したアルゴリズムを実装していく

Slide 69

Slide 69 text

注意: 古典的レイトレーサーの実装 • 古典的レイトレーサーの実装には交差した球のマテリアル情報が必要になる • 1つの解決策: IntersectInfoに交差した球へのポインタを追加 Mirror?

Slide 70

Slide 70 text

演習: IntersectInfoの拡張 • IntersectInfoを拡張し, 交差した物体のマテリアル情報を取得できるようにする

Slide 71

Slide 71 text

反射ベクトルの計算 • 反射: 法線に関して入射角と出射角が等しい

Slide 72

Slide 72 text

演習: 反射ベクトルの計算 • 入射方向 Ԧ 𝑣, 法線𝑛から反射方向Ԧ 𝑟を計算する式を導出する • ベクトルを面と水平な方向・直交する方向に分割して考え、入射角と出射角の関係を適用してみ る

Slide 73

Slide 73 text

屈折ベクトルの計算 • 屈折は以下のスネルの法則によって記述される • 𝑛1 , 𝑛2 は屈折率 • 入射角によっては全反射があることに注意

Slide 74

Slide 74 text

演習: 屈折ベクトルの計算 • 反射ベクトルの場合と同様に水平方向, 垂直方向に分けてスネルの法則を適用

Slide 75

Slide 75 text

演習: 反射・屈折ベクトルの計算 • 反射・屈折ベクトルを計算する関数をvec3.hに追加する

Slide 76

Slide 76 text

演習: 古典的レイトレーサーの実装 • 理論編のスライドを参考に, 古典的レイトレーサーの処理を実装する • 光源の方向は適当に与える • 再帰またはfor文で処理を実装できる. 好きな方で実装してみよう

Slide 77

Slide 77 text

演習: 古典的レイトレーサーのテスト • 実装できたら複数の球を追加して画像を生成してみる

Slide 78

Slide 78 text

演習: 古典的レイトレーサーのテスト • 次のような画像が生成される

Slide 79

Slide 79 text

課題: 古典的レイトレーサーの拡張 • カメラの視野角を設定できるようにする • 球だけではなく, 複数の形状に対応させる(e.g. 平面) • 点光源を試してみる(光の強さは距離の2乗に反比例) • 複数の光源に対応させる • 物体の色をテクスチャで表現してみる

Slide 80

Slide 80 text

実装編 並列化・AA・AO

Slide 81

Slide 81 text

エイリアシング • 物体や影のエッジに注目するとギザギザが見える • これはエイリアシングと呼ばれる • 連続的な光の分布を画素で離散化しているために発生する

Slide 82

Slide 82 text

アンチエイリアス • エイリアシングを防ぐために、アンチエイリアス(AA)という処理を施す • ここではSSAAといって、画素内で場所を変えながら複数回色を評価し、その平均を取る処理を施す イメージセンサー ピンホール 平均を書き込む

Slide 83

Slide 83 text

乱数の生成 • SSAAを実装するためには画素内でランダムに点を取りたい → 乱数生成が必要 • PCG32というアルゴリズムを用いて乱数生成を行う

Slide 84

Slide 84 text

PCG32による乱数生成 • pcg32_random_tが乱数生成器の状態を表す • pcg32_random_rを呼ぶことでuint32_tの一様乱数が返ってくる • uint32_tのままでは扱いにくいので, [0, 1]のfloatの一様乱数に変換したい • RNGクラスでfloatの一様乱数を生成させる

Slide 85

Slide 85 text

演習: RNGの実装 • rng.hを作成し, 以下のような機能を持つRNGクラスを実装する • シード値の設定 • [0,1]の範囲のfloatの一様乱数の生成

Slide 86

Slide 86 text

演習: 乱数生成のテスト • 実装したRNGクラスを用いて乱数を生成し, std::coutしてみる

Slide 87

Slide 87 text

演習: SSAAの実装 • 作成したRNGクラスを用いてSSAAを実装してみる

Slide 88

Slide 88 text

演習: SSAAのテスト • SSAAによってエイリアシングが軽減されているか確かめる

Slide 89

Slide 89 text

並列化 • SSAAのサンプル数 or 画像の大きさを増やしていくと計算時間が増えてくる • 計算時間を削減するために並列化が重要 CPU1 CPU2 CPU3 CPU4 CPU1 CPU2 CPU3 CPU4 並列化 未並列化 Task2 Task1 Task3 Task1 Task2 Task3

Slide 90

Slide 90 text

並列化 • レイトレは各画素で同じ処理を行うため、並列化との相性が良い • ここではOpenMPを使って手軽に並列化を行ってみる 並列化できる

Slide 91

Slide 91 text

OpenMPによる並列化 • OpenMPでfor文を並列化するには以下のようにする • #pragmaの部分が並列化を指定している部分

Slide 92

Slide 92 text

並列化の注意点 • ある変数を複数のスレッドが同時に読み書きすると値がおかしくなる場合がある(読み込みだけな ら問題ない) • そのため乱数生成器は各画素ごとに用意する(各スレッドごとでも良い) x CPU1 CPU2 1 2 1 or 2?

Slide 93

Slide 93 text

演習: OpenMPによる並列化 • 実装したレイトレーサーをOpenMPで並列化し, 計算スピードが上がったことを確認する • を用いて計算時間を計測し, 比較すると良い

Slide 94

Slide 94 text

Ambient Occlusion(AO) • 周辺から入ってくる光が周りの物体によってどれだけ遮蔽されるかを計算すること • 遮蔽割合に応じて環境光の強さを変化させれば, よりリアルに見える • SSAO, HBAOなど様々な計算方法があるが、今回はレイトレによる計算方法を実装してみる

Slide 95

Slide 95 text

RTAO(Ray Traced Ambient Occlusion) • 半球面上で一様に点をサンプリングし, その方向にレイを飛ばす • レイが物体に衝突せずに一定距離以上まで進めるかどうかの割合を計算 45%

Slide 96

Slide 96 text

半球面上の点の一様サンプリング • ワールド座標系で考えると法線の向きを考慮する必要があり, 話がややこしくなる • そこで, 注目している点の接空間で方向のサンプリングを行う • 最後にサンプリングした方向を接空間からワールド空間に変換すれば良い ワールド空間 接空間

Slide 97

Slide 97 text

接空間 • 基底が接線(Tangent), 法線(Normal), 従法線(Binormal)で与えられるベクトル空間を接空間 (Tangent Space)という • 物体上の点での方向サンプリングを考える際には接空間で考えると法線の向きを考慮しなくて良 くなるので楽になる

Slide 98

Slide 98 text

接線, 従法線の計算 • カメラの横方向・上方向の計算と同じことをすれば良い(当該スライド参照) • 法線が(0, 1, 0)に近い場合は計算が破綻するので, 別のベクトルを代わりに使う

Slide 99

Slide 99 text

補足: 接線, 従法線の計算 • 物体表面の位置が𝑓(𝑢, 𝑣)で表現される場合, 𝜕𝑓 𝜕𝑢 が接線, 𝜕𝑓 𝜕𝑣 が従法線に対応する • 球の場合はこれを解析的に計算できるので, 興味ある人はやってみよう

Slide 100

Slide 100 text

半球面一様サンプリング • 接空間上で半球面上の点の一様サンプリングは以下の式で行える(導出は今回は 省略) • 𝑢, 𝑣は[0,1]の一様乱数

Slide 101

Slide 101 text

接空間からワールド空間への変換 ワールド空間から接空間への変換行列は次で与えられる 接空間からワールド空間への変換は逆行列を考えれば良いが, 𝑇が直交行列であることに注意すると 具体的には接空間上のベクトル Ԧ 𝑣のワールド座標系への変換は以下のように書ける

Slide 102

Slide 102 text

演習: RTAOの実装 • RTAOを実装し, 1.0-遮蔽割合を画像にしてみる

Slide 103

Slide 103 text

演習: RTAOの活用 • 実装した古典的レイトレーサーにRTAOを組み込み, 環境光の強さを遮蔽割合に応 じて変化させてみる

Slide 104

Slide 104 text

補足: 超一様乱数列(準乱数) • 一様乱数はサンプル数が少ないと, 一様に分布しているようには見えない • Halton列, Sobol列などの超一様分布列(準乱数)を用いることで少ないサンプル数でも一様に分布 させることができる • RTAOの計算でこれを使うとノイズを低減できる 一様乱数 Sobol列

Slide 105

Slide 105 text

古典的レイトレーサー再考 AOを導入しても写実的には見えない. 何が足りないのだろうか? 足りないと考えられる要素 • 本来であれば光源から直接来る光(直接照明)だけではなく, 隣接する他の物体から反射した光の 影響も受ける(間接照明) • 物体上での光の強さの計算が物理的に正しくない • 本来であれば空全体から光が入ってくるはず 次回からはこれらの要素を古典的レイトレーサーに取り込んでいく事を行います

Slide 106

Slide 106 text

次回以降(予定) • 物理ベースレンダリング理論 • 確率論の基礎 • レンダリング方程式のモンテカルロ積分 理論が中心のお話になります パストレーシングは今回+次回以降の内容ですぐに実装できます!