You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

射线投射器特定角度与位置下墙体消失及无限走廊问题

问题描述

我正在开发一款射线投射器(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)问题,但始终找不到关键条件来定位并解决根源。我已经尝试了以下方法:

  1. 检查double值是否接近零并将其置零:仅能正确渲染中间一小条垂直区域,无法阻止无限走廊效果
  2. 优化double到int的类型转换:导致射线投射过程中出现更大的计算错误及渲染问题,已回滚修改

感谢帮助:)


内容的提问来源于stack exchange,提问作者Botanist20

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 02:07:04