第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
---
まとめ
本章で学んだこと:
次章では、線形代数の基礎を学びます。