基于MinilibX的Raycasting纹理垂直拉伸及距离异常问题排查
MinilibX Raycasting游戏纹理垂直拉伸问题修复方案
问题背景
在使用MinilibX开发基于Raycasting算法的小游戏时,墙体渲染位置、尺寸正确,但64x64规格的纹理出现垂直方向拉伸扭曲(spaghettified)现象。将line_height计算从float line_height = (S_H / wall_dist * 64);改为float line_height = (S_H / wall_dist);后,拉伸问题解决,但墙体显示距离玩家过远,需要调整。
原渲染函数代码
void draw_line(t_player *player, t_game *game, float start_x, int i) { float ray_dir_x = cos(start_x); float ray_dir_y = sin(start_x); float map_x = player->p_x; float map_y = player->p_y; double delta_dist_x = fabs(1 / ray_dir_x); double delta_dist_y = fabs(1 / ray_dir_y); double side_dist_x; double side_dist_y; int step_x; int step_y; if (ray_dir_x < 0) { step_x = -1; side_dist_x = (player->p_x - map_x) * delta_dist_x; } else { step_x = 1; side_dist_x = (map_x + 1.0 - player->p_x) * delta_dist_x; } if (ray_dir_y < 0) { step_y = -1; side_dist_y = (player->p_y - map_y) * delta_dist_y; } else { step_y = 1; side_dist_y = (map_y + 1.0 - player->p_y) * delta_dist_y; } int side; while (!touch(map_x, map_y, game)) { if (side_dist_x < side_dist_y) { side_dist_x += delta_dist_x; map_x += step_x; side = 0; } else { side_dist_y += delta_dist_y; map_y += step_y; side = 1; } } double wall_dist; double wall_x; if (side == 0) wall_dist = (map_x - player->p_x + (1 - step_x) / 2) / ray_dir_x; else wall_dist = (map_y - player->p_y + (1 - step_y) / 2) / ray_dir_y; //clear float line_height = (S_H / wall_dist * 64); // Correct line height calculation // Adjust line height based on distance and projection plane //float proj_plane_dist = (float)S_W / (2 * tan(FOV / 2)); // Projection plane distance //float line_height = (proj_plane_dist / wall_dist); // Use consistent scaling int draw_start = -(int)line_height / 2 + S_H / 2; if (draw_start < 0) draw_start = 0; int draw_end = line_height / 2 + S_H / 2; if (draw_end >= S_H) draw_end = S_H - 1; if (side == 0) wall_x = (player->p_y) + wall_dist * ray_dir_y; else wall_x = (player->p_x) + wall_dist * ray_dir_x; // wall_x -= floor(wall_x); // Ensure it's a fractional part between 0 and 1 //printf("wall_x: %f\n", wall_x); // Select the correct texture based on ray direction int *texture; if (side == 0) { if (ray_dir_x > 0) texture = game->texture_e.text_value; // East else texture = game->texture_w.text_value; // West } else { if (ray_dir_y > 0) texture = game->texture_s.text_value; // South else texture = game->texture_n.text_value; // North } printf("p_x: %f, p_y: %f, wall_x: %f\n", player->p_x,player->p_y, wall_x); int tex_x = (int)(wall_x * 64); // Adjust texture X tex_x = tex_x % 64; // Ensure tex_x is within bounds (0-63) // Draw the texture column for (int y = draw_start; y < draw_end; y++) { int tex_y = (int)((y - draw_start) * 64.0 / line_height) % 64; // Adjust texture Y // Debug output // printf("tex_x: %d, tex_y: %d, wall_x: %f\n", tex_x, tex_y, wall_x); int color = texture[tex_x + tex_y * 64]; // Access the texture pixel put_pixel(i, y, color, game); // Draw the pixel on the screen } //i++; }
调试输出
p_x: 760.554504, p_y: 268.357147, wall_x: 280.284412 p_x: 760.554504, p_y: 268.357147, wall_x: 280.252147 p_x: 760.554504, p_y: 268.357147, wall_x: 280.219890 p_x: 760.554504, p_y: 268.357147, wall_x: 280.187640 p_x: 760.554504, p_y: 268.357147, wall_x: 280.155396 Window Closed
核心问题分析
- 鱼眼效应未修正:当前
wall_dist是射线实际长度,而非垂直于玩家投影平面的距离,导致边缘墙体距离被高估,修改line_height后显示过远。 - line_height计算错误:原代码多乘64导致纹理Y采样间隔异常,造成拉伸;去掉64后又缺少地图格子尺寸的缩放,导致墙体比例失调。
- wall_x未取小数部分:注释掉的
wall_x -= floor(wall_x);导致纹理X坐标逻辑错误,虽暂时用%64凑出正确值,但不符合Raycasting采样逻辑。
修复方案
1. 修正wall_dist的鱼眼效应
在计算完wall_dist后添加角度修正:
// 修正鱼眼效应,start_x为当前射线角度,player->angle为玩家朝向角度 double angle_diff = start_x - player->angle; if (angle_diff > M_PI) angle_diff -= 2*M_PI; if (angle_diff < -M_PI) angle_diff += 2*M_PI; wall_dist *= cos(angle_diff);
2. 正确计算line_height
结合地图格子尺寸(64)和修正后的距离:
// 替换原line_height计算 float line_height = (64.0 * S_H) / wall_dist;
3. 恢复wall_x小数部分提取
取消注释该行代码:
wall_x -= floor(wall_x); // 提取0~1的小数部分,用于纹理X坐标采样
4. 修正tex_x计算
基于小数部分计算纹理X坐标:
int tex_x = (int)(wall_x * 64.0); tex_x = tex_x % 64; // 可选,防止浮点精度溢出
修复后关键代码片段
// ... 射线检测代码不变 ... double wall_dist; double wall_x; if (side == 0) wall_dist = (map_x - player->p_x + (1 - step_x) / 2) / ray_dir_x; else wall_dist = (map_y - player->p_y + (1 - step_y) / 2) / ray_dir_y; // 新增:修正鱼眼效应 double angle_diff = start_x - player->angle; if (angle_diff > M_PI) angle_diff -= 2*M_PI; if (angle_diff < -M_PI) angle_diff += 2*M_PI; wall_dist *= cos(angle_diff); // 正确计算line_height float line_height = (64.0 * S_H) / wall_dist; int draw_start = -(int)line_height / 2 + S_H / 2; if (draw_start < 0) draw_start = 0; int draw_end = line_height / 2 + S_H / 2; if (draw_end >= S_H) draw_end = S_H - 1; if (side == 0) wall_x = (player->p_y) + wall_dist * ray_dir_y; else wall_x = (player->p_x) + wall_dist * ray_dir_x; // 恢复提取小数部分 wall_x -= floor(wall_x); // ... 纹理选择代码不变 ... // 修正tex_x计算 int tex_x = (int)(wall_x * 64.0); tex_x = tex_x % 64; // 绘制纹理列 for (int y = draw_start; y < draw_end; y++) { int tex_y = (int)((y - draw_start) * 64.0 / line_height) % 64; int color = texture[tex_x + tex_y * 64]; put_pixel(i, y, color, game); }
修复逻辑说明
- 鱼眼修正:确保所有墙体使用垂直于投影平面的距离计算高度,避免边缘墙体比例失真。
- line_height缩放:用地图格子尺寸(64)适配屏幕高度,保证不同距离的墙体显示比例一致。
- 纹理采样逻辑:提取wall_x小数部分,确保从墙体表面的相对位置采样纹理,符合Raycasting映射规则。
内容的提问来源于stack exchange,提问作者Adam2475
相关产品推荐
相关产品推荐

