第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ガイドは完了です。