第6章:高度な機能と最適化
はじめに
本章では、反射・屈折の実装、マルチスレッド最適化、その他のボーナス機能を学びます。
---
1. 反射(Reflection)
1.1 反射の物理学
鏡面反射では、入射角と反射角が等しくなります:
入射レイ 反射レイ
\ /
\ /
θi \ / θr (θi = θr)
\ /
──────●────── 表面
N ↑
法線
1.2 反射ベクトルの計算
// R = D - 2(D · N)N
t_vec3 reflect(t_vec3 direction, t_vec3 normal) {
double dot = vec3_dot(direction, normal);
return vec3_sub(
direction,
vec3_scale(normal, 2.0 * dot)
);
}
1.3 反射の実装
t_color trace_ray_with_reflection(t_scene *scene, t_ray *ray, int depth) {
if (depth >= MAX_DEPTH)
return color_new(0, 0, 0);
t_hit hit = find_closest_hit(scene, ray);
if (!hit.valid)
return background_color(ray);
// 基本色(Phongシェーディング)
t_color local_color = phong_lighting(scene, &hit, ray);
// 反射がない場合
if (hit.reflective <= 0)
return local_color;
// 反射レイを作成
t_vec3 reflect_dir = reflect(ray->direction, hit.normal);
t_vec3 reflect_origin = vec3_add(
hit.point,
vec3_scale(hit.normal, EPSILON)
);
t_ray reflect_ray = {reflect_origin, reflect_dir};
// 再帰的にトレース
t_color reflect_color = trace_ray_with_reflection(
scene, &reflect_ray, depth + 1
);
// 合成(フレネル近似)
return color_add(
color_scale(local_color, 1.0 - hit.reflective),
color_scale(reflect_color, hit.reflective)
);
}
1.4 フレネル効果
視野角によって反射率が変化する現象:
// Schlick近似
double fresnel_schlick(double cos_theta, double ior) {
double r0 = (1 - ior) / (1 + ior);
r0 = r0 * r0;
return r0 + (1 - r0) * pow(1 - cos_theta, 5);
}
t_color trace_with_fresnel(t_scene *scene, t_ray *ray, t_hit *hit, int depth) {
double cos_theta = -vec3_dot(ray->direction, hit->normal);
double reflectance = fresnel_schlick(cos_theta, 1.5);
t_color reflected = trace_reflected(scene, ray, hit, depth);
t_color refracted = trace_refracted(scene, ray, hit, depth);
return color_add(
color_scale(reflected, reflectance),
color_scale(refracted, 1 - reflectance)
);
}
---
2. 屈折(Refraction)
2.1 スネルの法則
n₁ sin(θ₁) = n₂ sin(θ₂)
n₁: 入射側の屈折率
n₂: 透過側の屈折率
θ₁: 入射角
θ₂: 屈折角
屈折率の例:
- 空気: 1.0
- 水: 1.33
- ガラス: 1.5
- ダイヤモンド: 2.42
2.2 屈折ベクトルの計算
t_vec3 refract(t_vec3 direction, t_vec3 normal, double eta_ratio) {
double cos_theta = fmin(-vec3_dot(direction, normal), 1.0);
t_vec3 r_perp = vec3_scale(
vec3_add(direction, vec3_scale(normal, cos_theta)),
eta_ratio
);
t_vec3 r_parallel = vec3_scale(
normal,
-sqrt(fabs(1.0 - vec3_length_squared(r_perp)))
);
return vec3_add(r_perp, r_parallel);
}
2.3 全反射
臨界角を超えると全反射が発生:
int can_refract(t_vec3 direction, t_vec3 normal, double eta_ratio) {
double cos_theta = -vec3_dot(direction, normal);
double sin_theta = sqrt(1.0 - cos_theta * cos_theta);
// sin(θ₂) > 1 なら全反射
return eta_ratio * sin_theta <= 1.0;
}
t_color trace_transparent(t_scene *scene, t_ray *ray, t_hit *hit, int depth) {
double eta_ratio = hit->front_face ? (1.0 / hit->ior) : hit->ior;
if (!can_refract(ray->direction, hit->normal, eta_ratio)) {
// 全反射
return trace_reflected(scene, ray, hit, depth);
}
// 屈折
t_vec3 refracted_dir = refract(ray->direction, hit->normal, eta_ratio);
t_vec3 refracted_origin = vec3_sub(
hit->point,
vec3_scale(hit->normal, EPSILON)
);
t_ray refracted_ray = {refracted_origin, refracted_dir};
return trace_ray(scene, &refracted_ray, depth + 1);
}
---
3. マルチスレッド最適化
3.1 pthread基本
#include <pthread.h>
typedef struct s_thread_data {
t_scene *scene;
t_mlx *mlx;
int start_y;
int end_y;
int thread_id;
} t_thread_data;
void *render_thread(void *arg) {
t_thread_data *data = (t_thread_data *)arg;
for (int y = data->start_y; y < data->end_y; y++) {
for (int x = 0; x < WIDTH; x++) {
double u = (double)x / (WIDTH - 1);
double v = (double)(HEIGHT - 1 - y) / (HEIGHT - 1);
t_ray ray = camera_get_ray(&data->scene->camera, u, v);
t_color color = trace_ray(data->scene, &ray, 0);
put_pixel(data->mlx, x, y, color_to_int(color));
}
}
return NULL;
}
3.2 スレッドプールの実装
#define NUM_THREADS 8
void render_parallel(t_data *data) {
pthread_t threads[NUM_THREADS];
t_thread_data thread_data[NUM_THREADS];
int rows_per_thread = HEIGHT / NUM_THREADS;
// スレッドを作成
for (int i = 0; i < NUM_THREADS; i++) {
thread_data[i].scene = &data->scene;
thread_data[i].mlx = &data->mlx;
thread_data[i].start_y = i * rows_per_thread;
thread_data[i].end_y = (i == NUM_THREADS - 1) ?
HEIGHT : (i + 1) * rows_per_thread;
thread_data[i].thread_id = i;
pthread_create(&threads[i], NULL, render_thread, &thread_data[i]);
}
// すべてのスレッドを待つ
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
}
3.3 スレッドセーフな乱数
#include <pthread.h>
// スレッドローカル乱数生成器
__thread unsigned int thread_seed;
void init_thread_random(int thread_id) {
thread_seed = time(NULL) ^ (thread_id * 12345);
}
double thread_random_double(void) {
thread_seed = thread_seed * 1103515245 + 12345;
return (double)(thread_seed & 0x7fffffff) / 0x7fffffff;
}
---
4. BVH(Bounding Volume Hierarchy)
4.1 概念
オブジェクトを階層的なバウンディングボックスで囲む:
Root AABB
+--------+
/ \
AABB1 AABB2
+----+ +----+
|○ ○| |○ |
+----+ | ○|
+----+
レイがRoot AABBに当たらなければ、
すべての子オブジェクトをスキップ
4.2 AABB構造体
typedef struct s_aabb {
t_vec3 min;
t_vec3 max;
} t_aabb;
// AABBの結合
t_aabb aabb_union(t_aabb a, t_aabb b) {
return (t_aabb){
.min = vec3_new(
fmin(a.min.x, b.min.x),
fmin(a.min.y, b.min.y),
fmin(a.min.z, b.min.z)
),
.max = vec3_new(
fmax(a.max.x, b.max.x),
fmax(a.max.y, b.max.y),
fmax(a.max.z, b.max.z)
)
};
}
4.3 BVHノード
typedef struct s_bvh_node {
t_aabb bounds;
struct s_bvh_node *left;
struct s_bvh_node *right;
t_object *object; // リーフノードの場合
} t_bvh_node;
t_bvh_node *build_bvh(t_object *objects, int start, int end) {
t_bvh_node *node = malloc(sizeof(t_bvh_node));
int count = end - start;
if (count == 1) {
// リーフノード
node->object = &objects[start];
node->bounds = object_bounds(node->object);
node->left = NULL;
node->right = NULL;
return node;
}
// 中心軸でソート
int axis = random_int(0, 2);
qsort_objects(objects + start, count, axis);
int mid = start + count / 2;
node->left = build_bvh(objects, start, mid);
node->right = build_bvh(objects, mid, end);
node->bounds = aabb_union(node->left->bounds, node->right->bounds);
node->object = NULL;
return node;
}
4.4 BVHトラバーサル
t_hit bvh_intersect(t_bvh_node *node, t_ray *ray) {
if (!aabb_intersect(&node->bounds, ray))
return hit_miss();
// リーフノード
if (node->object)
return intersect_object(node->object, ray);
// 両方の子をトラバース
t_hit left_hit = bvh_intersect(node->left, ray);
t_hit right_hit = bvh_intersect(node->right, ray);
if (!left_hit.valid)
return right_hit;
if (!right_hit.valid)
return left_hit;
return (left_hit.t < right_hit.t) ? left_hit : right_hit;
}
---
5. テクスチャマッピング
5.1 UV座標
// 球のUV座標
void sphere_uv(t_hit *hit, t_sphere *sphere) {
t_vec3 p = vec3_normalize(vec3_sub(hit->point, sphere->center));
double phi = atan2(-p.z, p.x) + M_PI;
double theta = acos(-p.y);
hit->u = phi / (2 * M_PI);
hit->v = theta / M_PI;
}
// 平面のUV座標
void plane_uv(t_hit *hit, t_plane *plane) {
// ローカル座標系を構築
t_vec3 u_axis = vec3_cross(plane->normal, vec3_new(0, 1, 0));
if (vec3_length(u_axis) < EPSILON)
u_axis = vec3_cross(plane->normal, vec3_new(1, 0, 0));
u_axis = vec3_normalize(u_axis);
t_vec3 v_axis = vec3_cross(plane->normal, u_axis);
hit->u = vec3_dot(hit->point, u_axis);
hit->v = vec3_dot(hit->point, v_axis);
}
5.2 チェッカーボードテクスチャ
t_color checkerboard_texture(double u, double v, double scale) {
int u_int = (int)floor(u * scale);
int v_int = (int)floor(v * scale);
if ((u_int + v_int) % 2 == 0)
return color_new(1.0, 1.0, 1.0);
else
return color_new(0.0, 0.0, 0.0);
}
5.3 画像テクスチャ
typedef struct s_texture {
unsigned char *data;
int width;
int height;
int channels;
} t_texture;
t_color sample_texture(t_texture *tex, double u, double v) {
// UV座標をピクセル座標に変換
int x = (int)(u * tex->width) % tex->width;
int y = (int)((1 - v) * tex->height) % tex->height;
if (x < 0) x += tex->width;
if (y < 0) y += tex->height;
int offset = (y * tex->width + x) * tex->channels;
return color_new(
tex->data[offset] / 255.0,
tex->data[offset + 1] / 255.0,
tex->data[offset + 2] / 255.0
);
}
---
6. 追加のプリミティブ
6.1 コーン(円錐)
typedef struct s_cone {
t_vec3 apex; // 頂点
t_vec3 axis; // 軸方向
double angle; // 半頂角(ラジアン)
double height; // 高さ
} t_cone;
t_hit cone_intersect(t_cone *cone, t_ray *ray) {
t_vec3 co = vec3_sub(ray->origin, cone->apex);
double cos_a = cos(cone->angle);
double sin_a = sin(cone->angle);
double dv = vec3_dot(ray->direction, cone->axis);
double cv = vec3_dot(co, cone->axis);
double a = dv * dv - cos_a * cos_a;
double b = 2 * (dv * cv - vec3_dot(ray->direction, co) * cos_a * cos_a);
double c = cv * cv - vec3_dot(co, co) * cos_a * cos_a;
// 二次方程式を解く...
// (省略)
}
6.2 ディスク(円板)
typedef struct s_disk {
t_vec3 center;
t_vec3 normal;
double radius;
} t_disk;
t_hit disk_intersect(t_disk *disk, t_ray *ray) {
// まず平面との交差
double denom = vec3_dot(ray->direction, disk->normal);
if (fabs(denom) < EPSILON)
return hit_miss();
t_vec3 p0_o = vec3_sub(disk->center, ray->origin);
double t = vec3_dot(p0_o, disk->normal) / denom;
if (t < EPSILON)
return hit_miss();
// 交差点が円内かチェック
t_vec3 p = ray_at(ray, t);
t_vec3 v = vec3_sub(p, disk->center);
double dist2 = vec3_dot(v, v);
if (dist2 > disk->radius * disk->radius)
return hit_miss();
t_hit hit;
hit.valid = 1;
hit.t = t;
hit.point = p;
hit.normal = disk->normal;
return hit;
}
---
7. 画像出力
7.1 PPM形式
void save_ppm(t_mlx *mlx, char *filename) {
FILE *fp = fopen(filename, "wb");
fprintf(fp, "P6\n%d %d\n255\n", WIDTH, HEIGHT);
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
unsigned int color = get_pixel(mlx, x, y);
unsigned char rgb[3];
rgb[0] = (color >> 16) & 0xFF;
rgb[1] = (color >> 8) & 0xFF;
rgb[2] = color & 0xFF;
fwrite(rgb, 1, 3, fp);
}
}
fclose(fp);
}
7.2 BMP形式
void save_bmp(t_mlx *mlx, char *filename) {
FILE *fp = fopen(filename, "wb");
// BMPヘッダー
unsigned char header[54] = {
'B', 'M', // マジックナンバー
0, 0, 0, 0, // ファイルサイズ(後で設定)
0, 0, 0, 0, // 予約
54, 0, 0, 0, // ヘッダーサイズ
40, 0, 0, 0, // DIBヘッダーサイズ
0, 0, 0, 0, // 幅(後で設定)
0, 0, 0, 0, // 高さ(後で設定)
1, 0, // プレーン数
24, 0, // ビット深度
0, 0, 0, 0, // 圧縮(なし)
0, 0, 0, 0, // 画像サイズ
0, 0, 0, 0, // 水平解像度
0, 0, 0, 0, // 垂直解像度
0, 0, 0, 0, // カラーパレット
0, 0, 0, 0 // 重要な色
};
int row_size = (WIDTH * 3 + 3) & ~3;
int file_size = 54 + row_size * HEIGHT;
// ファイルサイズを設定
header[2] = file_size & 0xFF;
header[3] = (file_size >> 8) & 0xFF;
header[4] = (file_size >> 16) & 0xFF;
header[5] = (file_size >> 24) & 0xFF;
// 幅を設定
header[18] = WIDTH & 0xFF;
header[19] = (WIDTH >> 8) & 0xFF;
// 高さを設定
header[22] = HEIGHT & 0xFF;
header[23] = (HEIGHT >> 8) & 0xFF;
fwrite(header, 1, 54, fp);
// ピクセルデータ(下から上へ、BGR順)
unsigned char padding[3] = {0, 0, 0};
int padding_size = row_size - WIDTH * 3;
for (int y = HEIGHT - 1; y >= 0; y--) {
for (int x = 0; x < WIDTH; x++) {
unsigned int color = get_pixel(mlx, x, y);
unsigned char bgr[3];
bgr[0] = color & 0xFF; // B
bgr[1] = (color >> 8) & 0xFF; // G
bgr[2] = (color >> 16) & 0xFF; // R
fwrite(bgr, 1, 3, fp);
}
fwrite(padding, 1, padding_size, fp);
}
fclose(fp);
}
---
まとめ
本章で学んだこと:
- 反射: 反射ベクトル、フレネル効果
- 屈折: スネルの法則、全反射
- マルチスレッド: pthreadによる並列化
- BVH: 空間分割による高速化
- テクスチャ: UV座標、チェッカーボード
- 追加プリミティブ: コーン、ディスク
- 画像出力: PPM、BMP形式
これでminiRTガイドは完了です。