第1章:レイキャスティングの歴史と理論

はじめに

cub3Dでは、レイキャスティング技術を使用して、1992年のWolfenstein 3Dスタイルのグラフィックエンジンを構築します。

---

1. 3Dグラフィックスの歴史

1.1 初期の3Dゲーム

3Dグラフィックスの歴史は1970年代に遡ります。

重要なマイルストーン

  • 1973: Maze War(最初のFPSゲーム)
  • 1980: Battlezone(ベクターグラフィックス)
  • 1987: Dungeon Master(疑似3D RPG)
  • 1992: Wolfenstein 3D(レイキャスティング)
  • 1993: DOOM(BSPツリー)
  • 1996: Quake(真の3Dエンジン)

1.2 John CarmackとWolfenstein 3D

John Carmackは、id SoftwareでWolfenstein 3Dのエンジンを開発しました。当時のPCでは、真の3Dレンダリングは不可能でした。

Carmackの革新的なアイデア: > 「2Dマップから3D風の視点を生成すれば、十分にリアルに見える」

これがレイキャスティング技術です。

1.3 レイキャスティング vs レイトレーシング

| 特徴 | レイキャスティング | レイトレーシング | |------|------------------|------------------| | レイの本数 | 画面幅分(320本など) | ピクセル数(1920×1080など) | | 計算量 | O(n) | O(n²)以上 | | 反射/屈折 | なし | あり | | 影 | 単純 | 精密 | | リアルタイム | 1992年のPCで可能 | 2018年以降のGPU |

---

2. レイキャスティングの基本原理

2.1 2Dマップから3D視点へ

2Dマップ:
#######
#.....#
#..P..#
#.....#
#######

3D視点:
+------------------------+
|     天井(青)          |
|------------------------|
|  壁  | 壁  | 壁  | 壁  |
|------------------------|
|     床(緑)            |
+------------------------+

2.2 レイの発射

プレイヤーの視点から、画面の各列に対して1本のレイを発射します:

        視野角 (FOV = 60度)
            /|\
           / | \
          /  |  \
         /   |   \
        /    |    \
       /     |     \
      /      |      \
     /       |       \
    /_______P_________\

P = プレイヤー位置
各線 = レイ(画面幅分発射)

2.3 距離と壁の高さ

壁までの距離が遠いほど、壁は低く描画されます:

距離と壁の高さの関係:
wallHeight = screenHeight / perpWallDist

近い壁: |||||||||||||||||||||||
        |||||||||||||||||||||||
        |||||||||||||||||||||||

遠い壁:     |||||||||||
            |||||||||||

---

3. 座標系と角度

3.1 座標系

cub3Dでは、以下の座標系を使用します:

Y軸(北)
  ^
  |
  |
  +-------> X軸(東)

角度:
  0度 = 東 (+X方向)
 90度 = 北 (+Y方向)
180度 = 西 (-X方向)
270度 = 南 (-Y方向)

3.2 ラジアン変換

#define PI 3.14159265358979323846

// 度からラジアンへ
double degToRad(double degrees) {
    return degrees * PI / 180.0;
}

// ラジアンから度へ
double radToDeg(double radians) {
    return radians * 180.0 / PI;
}

3.3 方向ベクトル

プレイヤーの向いている方向をベクトルで表現:

typedef struct s_player {
    double posX;      // X座標
    double posY;      // Y座標
    double dirX;      // 方向ベクトルX
    double dirY;      // 方向ベクトルY
    double planeX;    // カメラ平面X
    double planeY;    // カメラ平面Y
} t_player;

// 東向き (0度)
dirX = 1.0;
dirY = 0.0;
planeX = 0.0;
planeY = 0.66;  // FOV約66度

// 北向き (90度)
dirX = 0.0;
dirY = -1.0;
planeX = 0.66;
planeY = 0.0;

---

4. DDAアルゴリズム

4.1 DDAとは

DDA(Digital Differential Analyzer)は、レイとグリッドの交点を効率的に見つけるアルゴリズムです。

Bresenhamのアルゴリズムに似ているが、
浮動小数点を使用してより精密

4.2 アルゴリズムの概要

レイの方程式:
point = origin + t * direction

t: パラメータ (0から始まり、壁に当たるまで増加)
origin: プレイヤー位置
direction: レイの方向

DDAの手順:
1. 最初のグリッド境界までの距離を計算
2. 次のグリッド境界までの距離を計算
3. XまたはY方向の近い方を選択
4. 壁に当たるまで繰り返し

4.3 可視化

+---+---+---+---+
|   |   |   | # |
+---+---*---+---+  * = 交点
|   |   |\  | # |  # = 壁
+---+---+-\-+---+
|   | P |  \| # |  P = プレイヤー
+---+---+---*---+
|   |   |   | # |
+---+---+---+---+

レイは各グリッド境界で「ステップ」
壁セルに入ったら停止

---

5. テクスチャマッピング

5.1 壁のテクスチャ

壁にテクスチャを貼り付けるには、壁のどの位置にレイが当たったかを計算します:

// 壁のX座標(0.0-1.0)
double wallX;
if (side == 0) {  // 東西の壁
    wallX = posY + perpWallDist * rayDirY;
} else {          // 南北の壁
    wallX = posX + perpWallDist * rayDirX;
}
wallX -= floor(wallX);

// テクスチャのX座標
int texX = (int)(wallX * texWidth);

5.2 テクスチャの伸縮

壁の高さに応じてテクスチャを伸縮:

// 描画する壁の各ピクセルに対して
for (int y = drawStart; y < drawEnd; y++) {
    // テクスチャのY座標
    int texY = ((y - drawStart) * texHeight) / lineHeight;
    int color = texture[texY * texWidth + texX];
    putPixel(x, y, color);
}

---

6. 課題の要件

6.1 必須機能

  • マップファイル(.cub)の解析
  • 4方向の壁テクスチャ(北、南、東、西)
  • 床と天井の色
  • プレイヤーの移動と回転
  • ウィンドウ管理(ESCで終了、X閉じるボタン)
  • 6.2 マップファイル形式

    NO ./path_to_north_texture.xpm
    SO ./path_to_south_texture.xpm
    WE ./path_to_west_texture.xpm
    EA ./path_to_east_texture.xpm
    
    F 220,100,0
    C 225,30,0
    
            1111111111111111111111111
            1000000000110000000000001
            1011000001110000000000001
            1001000000000000000000001
    111111111011000001110000000000001
    100000000011000001110111111111111
    11110111111111011100000010001
    11110111111111011101010010001
    11000000110101011100000010001
    10000000000000001100000010001
    10000000000000001101010010001
    11000001110101011111011110N0111
    11110111 1110101101111010001
    11111111 1111111111111111111
    

    6.3 ボーナス機能

  • 壁との衝突判定
  • ミニマップ
  • ドアの開閉
  • アニメーションスプライト
  • マウスで視点回転
  • ---

    7. 開発環境

    7.1 miniLibX

    miniLibXは42が提供するグラフィックスライブラリです:

    #include "mlx.h"
    
    void *mlx;
    void *win;
    void *img;
    char *addr;
    int bits_per_pixel;
    int line_length;
    int endian;
    
    // 初期化
    mlx = mlx_init();
    win = mlx_new_window(mlx, WIDTH, HEIGHT, "cub3D");
    img = mlx_new_image(mlx, WIDTH, HEIGHT);
    addr = mlx_get_data_addr(img, &bpp, &ll, &endian);
    
    // ピクセル描画
    void put_pixel(int x, int y, int color) {
        char *dst = addr + (y * line_length + x * (bpp / 8));
        *(unsigned int*)dst = color;
    }
    
    // 画像表示
    mlx_put_image_to_window(mlx, win, img, 0, 0);
    
    // イベントループ
    mlx_loop(mlx);
    

    7.2 コンパイル

    CC = cc
    CFLAGS = -Wall -Wextra -Werror
    MLX_FLAGS = -lmlx -framework OpenGL -framework AppKit
    
    # Linux
    # MLX_FLAGS = -lmlx -lXext -lX11 -lm
    

    ---

    まとめ

    本章で学んだこと:

  • レイキャスティングの歴史: Wolfenstein 3Dと John Carmack
  • 基本原理: 2Dマップから3D視点の生成
  • 座標系: 方向ベクトルと角度
  • DDAアルゴリズム: 効率的なレイ追跡
  • テクスチャマッピング: 壁への画像貼り付け

次章では、線形代数の基礎を学びます。