第4章:テクスチャマッピング
はじめに
本章では、壁にテクスチャを貼り付ける方法を学びます。
---
1. XPMファイル
1.1 XPM形式
XPM(X PixMap)は、C言語で直接読み込めるテキストベースの画像形式です。
/* XPM */
static char * example_xpm[] = {
"8 8 3 1", // 幅 高さ 色数 文字/色
" c #000000", // 黒
". c #FFFFFF", // 白
"# c #FF0000", // 赤
" ",
" .... ",
" ...... ",
" .####. ",
" .####. ",
" ...... ",
" .... ",
" "
};
1.2 miniLibXでの読み込み
typedef struct s_texture {
void *img;
char *addr;
int width;
int height;
int bpp;
int line_length;
int endian;
} t_texture;
int load_texture(t_game *game, t_texture *tex, char *path) {
tex->img = mlx_xpm_file_to_image(game->mlx, path,
&tex->width, &tex->height);
if (!tex->img)
return (error_exit("Failed to load texture"));
tex->addr = mlx_get_data_addr(tex->img, &tex->bpp,
&tex->line_length, &tex->endian);
return (0);
}
1.3 テクスチャからピクセル取得
unsigned int get_texture_pixel(t_texture *tex, int x, int y) {
char *dst;
if (x < 0 || x >= tex->width || y < 0 || y >= tex->height)
return (0);
dst = tex->addr + (y * tex->line_length + x * (tex->bpp / 8));
return (*(unsigned int *)dst);
}
---
2. テクスチャ座標の計算
2.1 壁のX座標
レイが壁に当たった位置のX座標(0.0〜1.0)を計算:
double calc_wall_x(t_ray *ray, t_player *player) {
double wallX;
if (ray->side == 0) {
// 東西の壁
wallX = player->posY + ray->perpWallDist * ray->dirY;
} else {
// 南北の壁
wallX = player->posX + ray->perpWallDist * ray->dirX;
}
wallX -= floor(wallX); // 小数部分のみ
return (wallX);
}
2.2 テクスチャのX座標
int calc_tex_x(double wallX, t_texture *tex, t_ray *ray) {
int texX = (int)(wallX * tex->width);
// 壁の向きによって反転
if (ray->side == 0 && ray->dirX > 0)
texX = tex->width - texX - 1;
if (ray->side == 1 && ray->dirY < 0)
texX = tex->width - texX - 1;
return (texX);
}
2.3 テクスチャのY座標
void draw_textured_wall(t_game *game, int x, t_ray *ray, t_draw *draw) {
double wallX = calc_wall_x(ray, &game->player);
t_texture *tex = get_wall_texture(game, ray);
int texX = calc_tex_x(wallX, tex, ray);
// テクスチャのステップ(1ピクセルあたりのテクスチャ移動量)
double step = (double)tex->height / draw->lineHeight;
double texPos = (draw->drawStart - game->screenHeight / 2 +
draw->lineHeight / 2) * step;
for (int y = draw->drawStart; y < draw->drawEnd; y++) {
int texY = (int)texPos & (tex->height - 1); // ビットANDで高速に
texPos += step;
unsigned int color = get_texture_pixel(tex, texX, texY);
// 影をつける(南北の壁)
if (ray->side == 1)
color = (color >> 1) & 0x7F7F7F;
put_pixel(game, x, y, color);
}
}
---
3. 4方向のテクスチャ
3.1 テクスチャの選択
t_texture *get_wall_texture(t_game *game, t_ray *ray) {
if (ray->side == 0) {
if (ray->stepX > 0)
return (&game->tex_east); // 東向きの壁
else
return (&game->tex_west); // 西向きの壁
} else {
if (ray->stepY > 0)
return (&game->tex_south); // 南向きの壁
else
return (&game->tex_north); // 北向きの壁
}
}
3.2 テクスチャの管理
typedef struct s_game {
// ...
t_texture tex_north;
t_texture tex_south;
t_texture tex_east;
t_texture tex_west;
} t_game;
int load_all_textures(t_game *game) {
if (load_texture(game, &game->tex_north, game->north_path))
return (1);
if (load_texture(game, &game->tex_south, game->south_path))
return (1);
if (load_texture(game, &game->tex_east, game->east_path))
return (1);
if (load_texture(game, &game->tex_west, game->west_path))
return (1);
return (0);
}
---
4. 床と天井の色
4.1 色の解析
// "220,100,0" → 0xDC6400
int parse_color(char *str) {
int r, g, b;
r = ft_atoi(str);
while (*str && *str != ',') str++;
if (*str) str++;
g = ft_atoi(str);
while (*str && *str != ',') str++;
if (*str) str++;
b = ft_atoi(str);
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255)
return (-1);
return ((r << 16) | (g << 8) | b);
}
4.2 描画
void draw_ceiling_floor(t_game *game, int x, t_draw *draw) {
int y;
// 天井
for (y = 0; y < draw->drawStart; y++)
put_pixel(game, x, y, game->ceiling_color);
// 床
for (y = draw->drawEnd; y < game->screenHeight; y++)
put_pixel(game, x, y, game->floor_color);
}
---
5. テクスチャの床と天井(ボーナス)
5.1 床のレイキャスティング
void draw_floor_ceiling(t_game *game) {
for (int y = game->screenHeight / 2 + 1; y < game->screenHeight; y++) {
// 現在の行のレイ方向
float rayDirX0 = game->player.dirX - game->player.planeX;
float rayDirY0 = game->player.dirY - game->player.planeY;
float rayDirX1 = game->player.dirX + game->player.planeX;
float rayDirY1 = game->player.dirY + game->player.planeY;
// 現在の行の高さ位置
int p = y - game->screenHeight / 2;
float posZ = 0.5 * game->screenHeight;
float rowDistance = posZ / p;
// 床の座標
float floorStepX = rowDistance * (rayDirX1 - rayDirX0) / game->screenWidth;
float floorStepY = rowDistance * (rayDirY1 - rayDirY0) / game->screenWidth;
float floorX = game->player.posX + rowDistance * rayDirX0;
float floorY = game->player.posY + rowDistance * rayDirY0;
for (int x = 0; x < game->screenWidth; x++) {
int cellX = (int)floorX;
int cellY = (int)floorY;
// テクスチャ座標
int tx = (int)(game->tex_floor.width * (floorX - cellX)) & (game->tex_floor.width - 1);
int ty = (int)(game->tex_floor.height * (floorY - cellY)) & (game->tex_floor.height - 1);
floorX += floorStepX;
floorY += floorStepY;
// 床
unsigned int color = get_texture_pixel(&game->tex_floor, tx, ty);
color = (color >> 1) & 0x7F7F7F; // 暗くする
put_pixel(game, x, y, color);
// 天井(反転)
color = get_texture_pixel(&game->tex_ceiling, tx, ty);
put_pixel(game, x, game->screenHeight - y - 1, color);
}
}
}
---
6. 色の操作
6.1 色の分解と合成
typedef struct s_color {
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
} t_color;
t_color int_to_color(unsigned int c) {
t_color color;
color.a = (c >> 24) & 0xFF;
color.r = (c >> 16) & 0xFF;
color.g = (c >> 8) & 0xFF;
color.b = c & 0xFF;
return (color);
}
unsigned int color_to_int(t_color c) {
return ((c.a << 24) | (c.r << 16) | (c.g << 8) | c.b);
}
6.2 明るさの調整
unsigned int darken_color(unsigned int color, double factor) {
t_color c = int_to_color(color);
c.r = (unsigned char)(c.r * factor);
c.g = (unsigned char)(c.g * factor);
c.b = (unsigned char)(c.b * factor);
return (color_to_int(c));
}
// 距離による減衰
unsigned int apply_fog(unsigned int color, double distance) {
double factor = 1.0 / (1.0 + distance * 0.1);
return (darken_color(color, factor));
}
---
まとめ
本章で学んだこと:
- XPMファイル: 形式と読み込み
- テクスチャ座標: 壁の位置からテクスチャ座標へ
- 4方向のテクスチャ: 壁の向きによる選択
- 床と天井: 色と テクスチャ
- 色の操作: 影、フォグ
次章では、マップのパースと実装を学びます。