第3章:ライティングとシェーディング

はじめに

光と影は、3Dシーンにリアリズムをもたらす重要な要素です。本章では、Phongシェーディングモデルと影の計算を学びます。

---

1. 光のモデル

1.1 光の種類

+--------------------------------------------------+
|                    光源の種類                     |
+--------------------------------------------------+
|                                                  |
|  点光源         平行光源        面光源           |
|    ●              ↓↓↓           □□□            |
|   /|\              |||           |||             |
|  / | \             |||           |||             |
| /  |  \            |||           |||             |
|                                                  |
| 1点から放射     平行な光線      面から放射       |
| (電球)          (太陽)          (蛍光灯)         |
+--------------------------------------------------+

miniRTでは主に点光源を使用します:

typedef struct s_light {
    t_vec3  position;
    double  intensity;  // 0.0 - 1.0
    t_color color;
} t_light;

1.2 環境光

環境光(Ambient Light)は、間接光の近似です:

typedef struct s_ambient {
    double  intensity;  // 0.0 - 1.0
    t_color color;
} t_ambient;

// 環境光の計算
t_color ambient_light(t_ambient *ambient, t_color object_color) {
    return color_scale(
        color_mul(ambient->color, object_color),
        ambient->intensity
    );
}

環境光なし:              環境光あり:
+----------+            +----------+
|   ○      |  Light     |   ○      |
|   |      |            |   |      |
|   ▼      |            |   ▼      |
| ■■■■     |            | ■■■■     |
| ████ ← 真っ黒         | ▓▓▓▓ ← 薄暗い
+----------+            +----------+

---

2. Phongシェーディング

2.1 歴史

1973年、Bui Tuong Phongはユタ大学で博士論文を発表しました。彼のシェーディングモデルは、50年経った今でもリアルタイムグラフィックスで広く使われています。

2.2 三つの成分

Phongモデルは3つの成分から構成されます:

I = I_ambient + I_diffuse + I_specular

I_ambient  : 環境光(一定)
I_diffuse  : 拡散反射(ランバート反射)
I_specular : 鏡面反射(ハイライト)

+--------------------------------------------------+
|           Phongシェーディングの成分               |
+--------------------------------------------------+
|                                                  |
|  環境光        拡散反射       鏡面反射           |
|  ▓▓▓▓         ████          ████                |
|  ▓▓▓▓         ▓▓██          ▓▓▓●                |
|  ▓▓▓▓         ░░▓▓          ░░▓▓                |
|  一定          明→暗         ハイライト          |
+--------------------------------------------------+
|                   合計                           |
|                   ████                           |
|                   ▓▓█●                           |
|                   ░░▓▓                           |
+--------------------------------------------------+

2.3 拡散反射(Diffuse)

ランバートの余弦法則:

I_diffuse = k_d * I_light * max(0, N · L)

k_d     : 拡散反射係数(物体の色)
I_light : 光の強度
N       : 法線ベクトル
L       : 光源方向ベクトル

t_color diffuse_light(t_hit *hit, t_light *light) {
    // 光源方向
    t_vec3 light_dir = vec3_normalize(
        vec3_sub(light->position, hit->point)
    );

    // ランバートの余弦
    double n_dot_l = vec3_dot(hit->normal, light_dir);

    if (n_dot_l <= 0)
        return color_new(0, 0, 0);

    // 拡散反射
    return color_scale(
        color_mul(light->color, hit->color),
        light->intensity * n_dot_l
    );
}

        Light ○
              \
               \ L
                \
                 \
           N ↑    ↘
             |   θ
    ─────────●──────── Surface
            Hit

cos(θ) = N · L

θ = 0°  → N · L = 1   (最大輝度)
θ = 90° → N · L = 0   (光が当たらない)
θ > 90° → N · L < 0   (裏面)

2.4 鏡面反射(Specular)

Phongの鏡面反射モデル:

I_specular = k_s * I_light * max(0, R · V)^n

k_s : 鏡面反射係数
R   : 反射ベクトル
V   : 視線ベクトル(カメラ方向)
n   : 光沢度(shininess)

// 反射ベクトルを計算
t_vec3 reflect(t_vec3 v, t_vec3 n) {
    // R = V - 2(V · N)N
    return vec3_sub(v,
        vec3_scale(n, 2.0 * vec3_dot(v, n))
    );
}

t_color specular_light(t_hit *hit, t_light *light, t_vec3 view_dir,
                       double shininess) {
    // 光源方向
    t_vec3 light_dir = vec3_normalize(
        vec3_sub(light->position, hit->point)
    );

    // 反射ベクトル
    t_vec3 reflect_dir = reflect(vec3_scale(light_dir, -1), hit->normal);

    // 鏡面反射
    double spec = pow(fmax(0, vec3_dot(reflect_dir, view_dir)), shininess);

    return color_scale(light->color, light->intensity * spec);
}

     Light ○        ● Camera
           \       /
          L \     / V
             \   /
              \ /
         R ←──●──→ R'
              ↑
              N

R = reflect(-L, N)
spec = (R · V)^n

n が大きい → 鋭いハイライト
n が小さい → 広いハイライト

2.5 Phongシェーディングの実装

t_color phong_lighting(t_scene *scene, t_hit *hit, t_ray *ray) {
    // 1. 環境光
    t_color result = ambient_light(&scene->ambient, hit->color);

    // 視線方向
    t_vec3 view_dir = vec3_scale(ray->direction, -1);

    // 2. 各光源からの寄与
    for (int i = 0; i < scene->light_count; i++) {
        t_light *light = &scene->lights[i];

        // 影のチェック
        if (in_shadow(scene, hit, light))
            continue;

        // 拡散反射
        t_color diffuse = diffuse_light(hit, light);
        result = color_add(result, diffuse);

        // 鏡面反射(ボーナス)
        t_color specular = specular_light(hit, light, view_dir,
                                          hit->shininess);
        result = color_add(result, specular);
    }

    return color_clamp(result);
}

---

3. 影の計算

3.1 シャドウレイ

影は、交差点から光源へのレイ(シャドウレイ)が遮られるかで判定します:

int in_shadow(t_scene *scene, t_hit *hit, t_light *light) {
    // 光源方向
    t_vec3 light_dir = vec3_sub(light->position, hit->point);
    double light_dist = vec3_length(light_dir);
    light_dir = vec3_normalize(light_dir);

    // シャドウレイを作成(自己交差を避けるためオフセット)
    t_vec3 origin = vec3_add(hit->point,
                             vec3_scale(hit->normal, EPSILON));
    t_ray shadow_ray = {origin, light_dir};

    // 遮蔽物を探す
    for (int i = 0; i < scene->object_count; i++) {
        t_hit shadow_hit = intersect_object(&scene->objects[i], &shadow_ray);

        // 光源より手前に遮蔽物がある
        if (shadow_hit.valid && shadow_hit.t < light_dist)
            return 1;
    }

    return 0;
}

     Light ○
           |
           |  Shadow Ray
           |
     ┌─────┤
     │Block│ ← 遮蔽物
     └─────┘
           |
           ↓
     ──────●────── Hit point
           影になる

3.2 自己交差問題

問題:
交差点から正確にシャドウレイを発射すると、
浮動小数点誤差で自分自身と交差してしまう

     Light
       |
       ↓
───────●──────
      ↑ ここで自己交差

解決策:
法線方向にわずかにオフセット

───────●──────
       ↑
       ●' ← オフセットした点から発射

3.3 ソフトシャドウ(ボーナス)

// 面光源によるソフトシャドウ
double soft_shadow(t_scene *scene, t_hit *hit, t_light *light, int samples) {
    int shadow_count = 0;

    for (int i = 0; i < samples; i++) {
        // 光源位置にランダムなオフセットを加える
        t_vec3 jittered_pos = vec3_add(
            light->position,
            random_in_disk(light->radius)  // ボーナス
        );

        t_vec3 light_dir = vec3_normalize(
            vec3_sub(jittered_pos, hit->point)
        );

        t_vec3 origin = vec3_add(hit->point,
                                 vec3_scale(hit->normal, EPSILON));
        t_ray shadow_ray = {origin, light_dir};

        if (is_occluded(scene, &shadow_ray, vec3_length(
                vec3_sub(jittered_pos, hit->point))))
            shadow_count++;
    }

    return 1.0 - (double)shadow_count / samples;
}

---

4. 距離減衰

4.1 逆二乗則

物理的に正確な光の減衰:

I = I_0 / (d²)

I_0 : 光源の強度
d   : 光源からの距離

double attenuation(t_vec3 hit_point, t_vec3 light_pos) {
    double distance = vec3_length(vec3_sub(light_pos, hit_point));

    // 0除算を防ぐ
    if (distance < 1.0)
        distance = 1.0;

    return 1.0 / (distance * distance);
}

4.2 実用的な減衰モデル

// より制御しやすい減衰モデル
double attenuation_practical(double distance,
                            double constant,
                            double linear,
                            double quadratic) {
    return 1.0 / (constant + linear * distance +
                  quadratic * distance * distance);
}

// 典型的な値
// constant = 1.0
// linear = 0.09
// quadratic = 0.032

---

5. 複数光源

5.1 光の加算性

光は加算的です:

t_color calculate_lighting(t_scene *scene, t_hit *hit, t_ray *ray) {
    t_color total = ambient_light(&scene->ambient, hit->color);

    // 全ての光源からの寄与を合計
    for (int i = 0; i < scene->light_count; i++) {
        t_light *light = &scene->lights[i];

        if (!in_shadow(scene, hit, light)) {
            total = color_add(total, diffuse_light(hit, light));
            total = color_add(total, specular_light(hit, light,
                                                    ray->direction));
        }
    }

    return color_clamp(total);
}

5.2 色付き光源

// 光源の色を考慮
t_color diffuse_with_color(t_hit *hit, t_light *light) {
    t_vec3 light_dir = vec3_normalize(
        vec3_sub(light->position, hit->point)
    );

    double n_dot_l = fmax(0, vec3_dot(hit->normal, light_dir));

    // 光の色 × 物体の色 × 強度 × 角度
    return color_scale(
        color_mul(light->color, hit->color),
        light->intensity * n_dot_l
    );
}

赤い光 + 青い物体 = 暗い紫

     ● Red Light
     |
     ▼
   ┌───┐
   │Blue│ → 赤と青の共通成分のみ反射
   └───┘

---

6. マテリアルプロパティ

6.1 マテリアル構造体

typedef struct s_material {
    t_color albedo;      // 基本色
    double  ambient;     // 環境光係数 (0-1)
    double  diffuse;     // 拡散反射係数 (0-1)
    double  specular;    // 鏡面反射係数 (0-1)
    double  shininess;   // 光沢度 (1-256+)
    double  reflective;  // 反射率 (0-1) ボーナス
} t_material;

// プリセット材質
t_material material_matte(t_color color) {
    return (t_material){
        .albedo = color,
        .ambient = 0.1,
        .diffuse = 0.9,
        .specular = 0.0,
        .shininess = 1,
        .reflective = 0.0
    };
}

t_material material_shiny(t_color color) {
    return (t_material){
        .albedo = color,
        .ambient = 0.1,
        .diffuse = 0.6,
        .specular = 0.4,
        .shininess = 32,
        .reflective = 0.0
    };
}

t_material material_mirror(void) {
    return (t_material){
        .albedo = color_new(1, 1, 1),
        .ambient = 0.0,
        .diffuse = 0.0,
        .specular = 1.0,
        .shininess = 256,
        .reflective = 1.0
    };
}

6.2 マテリアルを使用したシェーディング

t_color shade_with_material(t_scene *scene, t_hit *hit, t_ray *ray) {
    t_material *mat = &hit->material;
    t_color result = color_new(0, 0, 0);

    // 環境光
    result = color_add(result,
        color_scale(mat->albedo, mat->ambient * scene->ambient.intensity)
    );

    t_vec3 view_dir = vec3_scale(ray->direction, -1);

    for (int i = 0; i < scene->light_count; i++) {
        t_light *light = &scene->lights[i];

        if (in_shadow(scene, hit, light))
            continue;

        t_vec3 light_dir = vec3_normalize(
            vec3_sub(light->position, hit->point)
        );
        double n_dot_l = fmax(0, vec3_dot(hit->normal, light_dir));

        // 拡散反射
        t_color diffuse = color_scale(
            color_mul(mat->albedo, light->color),
            mat->diffuse * light->intensity * n_dot_l
        );
        result = color_add(result, diffuse);

        // 鏡面反射
        if (mat->specular > 0) {
            t_vec3 reflect_dir = reflect(
                vec3_scale(light_dir, -1), hit->normal
            );
            double spec = pow(
                fmax(0, vec3_dot(reflect_dir, view_dir)),
                mat->shininess
            );
            t_color specular = color_scale(
                light->color,
                mat->specular * light->intensity * spec
            );
            result = color_add(result, specular);
        }
    }

    return color_clamp(result);
}

---

まとめ

本章で学んだこと:

  • 光のモデル: 点光源、環境光
  • Phongシェーディング: 拡散反射、鏡面反射
  • 影の計算: シャドウレイ、自己交差問題
  • 距離減衰: 逆二乗則
  • 複数光源: 光の加算性
  • マテリアル: 様々な材質の表現

次章では、カメラモデルと投影を学びます。