第1章:レイトレーシング入門
はじめに
miniRTは、42カリキュラムにおけるグラフィックス分野の高度なプロジェクトです。cub3Dのレイキャスティングから一歩進んで、物理的に正確な光のシミュレーションを行うレイトレーシングを実装します。
本章では、レイトレーシングの歴史と基本原理を学びます。
---
1. レイトレーシングの歴史
1.1 光学の歴史
レイトレーシングの理論的基盤は、古代ギリシャまで遡ります。
紀元前300年: ユークリッド「光学」
- 視線は目から対象物へ放射される(視線説)
- 幾何学的光学の基礎
11世紀: イブン・アル=ハイサム「光学の書」
- 光は光源から目へ進む(正しい理解)
- カメラ・オブスクラの原理
17世紀: スネルの法則、フェルマーの原理
- 光の屈折の数学的記述
- 光は最短時間経路を通る
19世紀: マクスウェル方程式
- 光は電磁波
- 波動光学の完成
1.2 コンピュータグラフィックスの黎明
1960年代:
├── 1963: Ivan Sutherland "Sketchpad"
│ └── 最初のインタラクティブCGシステム
├── 1968: Ray Appel
│ └── 隠面消去にレイキャスティングを使用
└── 1969: Arthur Appel
└── "Some Techniques for Shading Machine Renderings of Solids"
1970年代:
├── 1971: Henri Gouraud
│ └── Gouraudシェーディング(頂点補間)
├── 1973: Bui Tuong Phong
│ └── Phongシェーディング(法線補間)
├── 1975: Martin Newell
│ └── Utah Teapot(CGの標準テストモデル)
└── 1979: Kay & Greenberg
└── テクスチャマッピング
1.3 Turner Whittedの革命
1980年、Turner Whittedは画期的な論文を発表しました:
"An Improved Illumination Model for Shaded Display"
Whittedの主要な貢献:
1. 再帰的レイトレーシング
- 反射レイ:鏡面反射をシミュレート
- 屈折レイ:透明物体を通過
- 影レイ:光源への可視性判定
2. Whitted方程式:
I = I_local + k_r * I_reflected + k_t * I_transmitted
I_local: 局所照明(Phong)
I_reflected: 反射光
I_transmitted: 透過光
k_r, k_t: 反射・透過係数
Whittedの有名なレンダリング:
+------------------------------------------+
| ○ Light |
| |
| +-------+ ○ |
| |Mirror | (Glass |
| |Sphere | Sphere) |
| +-------+ |
| |
| ====== Checkerboard Floor ====== |
| |
+------------------------------------------+
特徴:
- 鏡面球が床を反射
- ガラス球が背景を屈折
- 球の影が床に落ちる
1.4 レイトレーシングの進化
1980年代:
├── 1984: Robert Cook
│ └── 分布レイトレーシング(モーションブラー、被写界深度)
├── 1986: James Kajiya
│ └── レンダリング方程式(物理的に正確な光輸送)
└── 1986: Constructive Solid Geometry (CSG)
1990年代:
├── 1995: Henrik Wann Jensen
│ └── フォトンマッピング(グローバルイルミネーション)
├── 1997: Eric Veach
│ └── 双方向パストレーシング
└── 1998: Mental Ray商用化
2000年代:
├── 2006: NVIDIA OptiX
├── 2010: Disney's Hyperion Renderer
└── 2018: NVIDIA RTX(リアルタイムレイトレーシング)
---
2. レイトレーシングの基本原理
2.1 光線の追跡
レイトレーシングは、光の進行を逆方向に追跡します:
実際の光の経路:
光源 → 物体 → 物体 → ... → 目
レイトレーシング:
カメラ → 物体 → 物体 → ... → 光源
なぜ逆方向?
- 光源から放射される光線の大部分は目に届かない
- カメラから追跡すれば、見える光線のみを計算
- 計算効率が大幅に向上
2.2 基本アルゴリズム
// 疑似コード
Color trace_ray(Ray ray, int depth) {
if (depth > MAX_DEPTH)
return BLACK;
// 1. 最も近い交差点を見つける
Intersection hit = find_closest_intersection(ray, scene);
if (!hit.valid)
return background_color;
// 2. 局所照明を計算
Color local = calculate_lighting(hit, scene);
// 3. 反射を計算(再帰)
Color reflected = BLACK;
if (hit.material.reflective > 0) {
Ray reflect_ray = create_reflection_ray(ray, hit);
reflected = trace_ray(reflect_ray, depth + 1);
}
// 4. 屈折を計算(再帰)
Color refracted = BLACK;
if (hit.material.transparent > 0) {
Ray refract_ray = create_refraction_ray(ray, hit);
refracted = trace_ray(refract_ray, depth + 1);
}
// 5. 最終色を合成
return local
+ hit.material.reflective * reflected
+ hit.material.transparent * refracted;
}
2.3 レイの定義
// レイ = 始点 + t * 方向
typedef struct s_ray {
t_vec3 origin; // 始点 O
t_vec3 direction; // 方向 D(正規化)
} t_ray;
// レイ上の点: P(t) = O + t * D
t_vec3 ray_at(t_ray *ray, double t) {
return vec3_add(ray->origin,
vec3_scale(ray->direction, t));
}
D (方向ベクトル)
↗
/
/
/
O ──────────────→ P(t)
始点 t=1の点
P(t) = O + t * D
t < 0: 始点の後ろ
t = 0: 始点
t > 0: 始点の前方
2.4 カメラモデル
typedef struct s_camera {
t_vec3 origin; // カメラ位置
t_vec3 lower_left; // 画面左下
t_vec3 horizontal; // 水平方向
t_vec3 vertical; // 垂直方向
} t_camera;
// ピクセル座標からレイを生成
t_ray camera_get_ray(t_camera *cam, double u, double v) {
t_vec3 direction = vec3_sub(
vec3_add(
vec3_add(cam->lower_left,
vec3_scale(cam->horizontal, u)),
vec3_scale(cam->vertical, v)
),
cam->origin
);
return (t_ray){cam->origin, vec3_normalize(direction)};
}
Viewport (仮想スクリーン)
+---------------------------+
| ↑ |
| vertical |
| | |
| | |
| ←───horizontal───→ |
| | |
+---------------------------+
lower_left
●
/ | \
/ | \
/ | \
/ | \
●─────────●─────────●
Camera focal_length
---
3. レイキャスティングとの違い
3.1 cub3D vs miniRT
| 特徴 | cub3D(レイキャスティング) | miniRT(レイトレーシング) | |------|---------------------------|--------------------------| | 次元 | 2.5D(擬似3D) | 真の3D | | 壁の形状 | 垂直な壁のみ | 任意の形状 | | 光線の挙動 | 1次レイのみ | 反射・屈折(再帰) | | 照明 | 距離減衰のみ | Phongモデル | | 影 | なし(または簡易) | 正確な影 | | 速度 | リアルタイム | オフラインレンダリング |
3.2 アルゴリズムの違い
レイキャスティング(cub3D):
// 各列で1本のレイ、DDAで壁を探す
for (int x = 0; x < width; x++) {
Ray ray = create_ray(player, x);
double dist = dda(ray, map); // 2Dグリッド走査
draw_column(x, dist);
}
レイトレーシング(miniRT):
// 各ピクセルで1本以上のレイ、3D交差判定
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Ray ray = camera_get_ray(cam, x, y);
Color color = trace_ray(ray, 0); // 再帰的追跡
set_pixel(x, y, color);
}
}
---
4. 3Dベクトル演算
4.1 ベクトルの定義
typedef struct s_vec3 {
double x;
double y;
double z;
} t_vec3;
// コンストラクタ
t_vec3 vec3_new(double x, double y, double z) {
return (t_vec3){x, y, z};
}
4.2 基本演算
// 加算
t_vec3 vec3_add(t_vec3 a, t_vec3 b) {
return vec3_new(a.x + b.x, a.y + b.y, a.z + b.z);
}
// 減算
t_vec3 vec3_sub(t_vec3 a, t_vec3 b) {
return vec3_new(a.x - b.x, a.y - b.y, a.z - b.z);
}
// スカラー倍
t_vec3 vec3_scale(t_vec3 v, double t) {
return vec3_new(v.x * t, v.y * t, v.z * t);
}
// 成分ごとの乗算(色の計算用)
t_vec3 vec3_mul(t_vec3 a, t_vec3 b) {
return vec3_new(a.x * b.x, a.y * b.y, a.z * b.z);
}
4.3 ベクトル積
// 内積(ドット積)
double vec3_dot(t_vec3 a, t_vec3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
// 外積(クロス積)
t_vec3 vec3_cross(t_vec3 a, t_vec3 b) {
return vec3_new(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
内積の意味:
a · b = |a| |b| cos(θ)
- θ = 0° → a · b > 0(同じ方向)
- θ = 90° → a · b = 0(直交)
- θ = 180°→ a · b < 0(反対方向)
外積の意味:
a × b = 両方に直交するベクトル
a × b
↑
|
|
a ──┼── b
|
右手の法則
4.4 長さと正規化
// 長さの二乗(平方根を避ける)
double vec3_length_squared(t_vec3 v) {
return v.x * v.x + v.y * v.y + v.z * v.z;
}
// 長さ
double vec3_length(t_vec3 v) {
return sqrt(vec3_length_squared(v));
}
// 正規化(単位ベクトル)
t_vec3 vec3_normalize(t_vec3 v) {
double len = vec3_length(v);
if (len == 0)
return vec3_new(0, 0, 0);
return vec3_scale(v, 1.0 / len);
}
---
5. シーンの構成
5.1 シーンファイル形式
miniRTでは、.rtファイルでシーンを定義します:
A 0.2 255,255,255
C -50,0,20 0,0,1 70
L -40,0,30 0.7 255,255,255
pl 0,0,0 0,1.0,0 255,0,225
sp 0,0,20 20 255,0,0
cy 50.0,0.0,20.6 0,0,1.0 14.2 21.42 10,0,255
5.2 要素の解析
// 識別子と意味
A - Ambient light(環境光)
C - Camera(カメラ)
L - Light(点光源)
pl - Plane(無限平面)
sp - Sphere(球)
cy - Cylinder(円柱)
5.3 データ構造
// シーン全体
typedef struct s_scene {
t_ambient ambient;
t_camera camera;
t_light *lights;
int light_count;
t_object *objects;
int object_count;
} t_scene;
// オブジェクト(共用体を使用)
typedef struct s_object {
t_obj_type type;
union {
t_sphere sphere;
t_plane plane;
t_cylinder cylinder;
} data;
t_vec3 color;
double specular; // ボーナス
double reflective; // ボーナス
} t_object;
---
6. 色の表現
6.1 RGB色空間
typedef struct s_color {
double r; // 0.0 - 1.0
double g;
double b;
} t_color;
// 色の作成
t_color color_new(double r, double g, double b) {
return (t_color){r, g, b};
}
// 色の加算
t_color color_add(t_color a, t_color b) {
return color_new(a.r + b.r, a.g + b.g, a.b + b.b);
}
// 色のスカラー倍
t_color color_scale(t_color c, double t) {
return color_new(c.r * t, c.g * t, c.b * t);
}
// クランプ(0-1の範囲に制限)
t_color color_clamp(t_color c) {
return color_new(
fmin(1.0, fmax(0.0, c.r)),
fmin(1.0, fmax(0.0, c.g)),
fmin(1.0, fmax(0.0, c.b))
);
}
6.2 整数への変換
// 浮動小数点色 → 整数色
unsigned int color_to_int(t_color c) {
c = color_clamp(c);
unsigned char r = (unsigned char)(c.r * 255.999);
unsigned char g = (unsigned char)(c.g * 255.999);
unsigned char b = (unsigned char)(c.b * 255.999);
return (r << 16) | (g << 8) | b;
}
// 整数色 → 浮動小数点色
t_color int_to_color(unsigned int color) {
return color_new(
((color >> 16) & 0xFF) / 255.0,
((color >> 8) & 0xFF) / 255.0,
(color & 0xFF) / 255.0
);
}
---
まとめ
本章で学んだこと:
- レイトレーシングの歴史: 光学からCGへ、Whittedの革命
- 基本原理: 光の逆追跡、再帰的アルゴリズム
- レイの定義: 始点と方向、パラメトリック表現
- 3Dベクトル演算: 内積、外積、正規化
- シーン構成: .rtファイル形式、データ構造
次章では、3D幾何学と交差判定アルゴリズムを学びます。