射线投射器特定角度与位置下墙体消失及无限走廊问题
问题描述
我正在开发一款射线投射器(raycaster),遇到了一个视觉故障:在特定条件下墙体消失并出现“无限走廊”效果。该问题出现在玩家方向与墙体完全垂直,且玩家位置恰好处于网格线(grid line)上时,具体触发条件如下:
- 玩家方向:方向(以弧度为单位)与墙体完全垂直
- 玩家位置:位置精确落在网格线上
满足这些条件时,相机正前方的墙体消失,侧墙延伸形成看似无限的走廊。以下两张图片展示了该问题:
- 垂直方向且玩家位置精确在网格线上
- 垂直方向但玩家位置不在网格线上
源代码采用C语言编写,仅允许使用标准库<math.h>,因此我自行实现了向量函数。射线投射算法参考了Lode Vandevenne的射线投射指南,但为了便于理解,我仅使用欧几里得角度而非相机平面。下面是核心代码部分:
t_vec angle_to_vec(double angle) { t_vec result; result.x = cos(angle); result.y = sin(angle); if (fabs(result.x) < 1e-5) result.x = 0; if (fabs(result.y) < 1e-5) result.y = 0; return (result); } t_vec vec_add(t_vec a, t_vec b) { t_vec result; result.x = a.x + b.x; result.y = a.y + b.y; return (result); } t_vec vec_scal_m(t_vec a, double scalar) { t_vec result; result.x = a.x * scalar; result.y = a.y * scalar; return (result); } t_vec_int vec_int_scal_d(t_vec a, double scalar) { t_vec_int result; result.x = (int)(a.x / scalar); result.y = (int)(a.y / scalar); return (result); } t_vec vec_inv(t_vec a) { t_vec result; if (fabs(a.x) < 1e-3) result = (t_vec){0, 1}; else if (fabs(a.y) < 1e-3) result = (t_vec){1, 0}; else { result.x = 1 / a.x; result.y = 1 / a.y; } return (result); } double deg_to_rad(double angle) { return (angle * M_PI / 180); } int is_zero(double value) { return (fabs(value) < 1e-10); } bool vertical_gridline_hit(t_vec *d) { return ((d->x < d->y && !is_zero(d->x)) || is_zero(d->y)); } int is_tile_wall(t_cub3d *cub3d, t_ray *ray, t_vec_int map, int wall_type) { ray->wall = cub3d->minimap->map[map.y][map.x]; if (ray->wall == 0) return (ray->wall); if (wall_type == 1) { if (ray->dir.x > 0) ray->side = EAST; else ray->side = WEST; } else if (wall_type == 0) { if (ray->dir.y > 0) ray->side = SOUTH; else ray->side = NORTH; } return (ray->wall); } t_vec_int stepping_direction(t_vec dir) { t_vec_int step; if (dir.x < 0) step.x = -1; else step.x = 1; if (dir.y < 0) step.y = -1; else step.y = 1; return (step); } t_vec calculate_initial_offset(t_cub3d *cub3d, t_ray *ray, t_vec delta_dist, t_vec_int map) { t_vec p; t_vec offset; p = cub3d->p->pos; if (ray->dir.x < 0) offset.x = (p.x - map.x * TILE_SIZE) * delta_dist.x; else offset.x = ((map.x + 1) * TILE_SIZE - p.x) * delta_dist.x; if (ray->dir.y < 0) offset.y = (p.y - map.y * TILE_SIZE) * delta_dist.y; else offset.y = ((map.y + 1) * TILE_SIZE - p.y) * delta_dist.y; return (offset); } void dda(t_cub3d *cub3d, t_ray *ray, t_vec delta_dist, t_vec_int map, t_vec *distance) { t_vec_int step; step = stepping_direction(ray->dir); while (true) { if (vertical_gridline_hit(distance)) { map.x += step.x; if (is_tile_wall(cub3d, ray, map, 1)) break ; distance->x += delta_dist.x * TILE_SIZE; } else { map.y += step.y; if (is_tile_wall(cub3d, ray, map, 0)) break ; distance->y += delta_dist.y * TILE_SIZE; } } } void cast_ray(t_cub3d *cub3d, t_ray *ray) { t_vec delta_dist; t_vec distance; t_vec_int map; delta_dist = vec_apply(vec_inv(ray->dir), fabs); map = vec_int_scal_d(cub3d->p->pos, TILE_SIZE); distance = calculate_initial_offset(cub3d, ray, delta_dist, map); dda(cub3d, ray, delta_dist, map, &distance); if (vertical_gridline_hit(&distance)) ray->distance = distance.x; else ray->distance = distance.y; ray->wall_hit = vec_add(cub3d->p->pos, vec_scal_m(ray->dir, ray->distance)); ray->distance *= cos(cub3d->p->angle - ray->angle); } void cast_fov(void *ptr) { t_cub3d *cub3d; t_ray ray; double fov_rad; double ray_inc; cub3d = (t_cub3d *)ptr; fov_rad = deg_to_rad(cub3d->p->fov); ray_inc = fov_rad / cub3d->width; cub3d = (t_cub3d *)ptr; ray.index = 0; while (ray.index < (int)cub3d->width) { ray.angle = cub3d->p->angle - fov_rad / 2 + ray.index * ray_inc; ray.dir = angle_to_vec(ray.angle); cast_ray(cub3d, &ray); render_vertical_stripe(cub3d, &ray); ray.index++; } render_sprite(cub3d); }
已尝试的解决方案
我知道这很可能是经典的“差一错误”(off by one)问题,但始终找不到关键条件来定位并解决根源。我已经尝试了以下方法:
- 检查double值是否接近零并将其置零:仅能正确渲染中间一小条垂直区域,无法阻止无限走廊效果
- 优化double到int的类型转换:导致射线投射过程中出现更大的计算错误及渲染问题,已回滚修改
感谢帮助:)
内容的提问来源于stack exchange,提问作者Botanist20
相关产品推荐
相关产品推荐

