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

射线投射器变更地图尺寸后墙体渲染异常问题求助

射线投射器地图尺寸变更后的墙体渲染异常问题

我正在用C++编写一个射线投射器,使用GDI/GDI+进行渲染。我清楚GDI并非渲染图形的最优选择,原本应该使用OpenGL、SFML等库,但这个射线投射器不需要高级实时图形,GDI足以满足需求;另外我要在学校展示这个项目,安装OpenGL会非常麻烦。

实际遇到的问题是:当地图网格从8x8改为8x16这类尺寸时,部分墙体的渲染方式变得异常:
正常渲染的墙体
异常渲染效果

若有人能解释该问题的成因,我将非常感激,以便排查代码问题。

main.cpp

/*
 * Pseudo-code of the void renderer():
 *  Horizontal gridline check:
 *   Set horizontal distance to a pretty high value, horizontal coordinates to camera coordinates
 *   Calculate negative inverse of tangent
 *   Set DOF variable to 0
 *   If ray angle is bigger than PI calculate ray Y-coordinate to be as close as possible to the gridline position and subtract 0.0001 for precision, calculate ray X-coordinate and offset coordinates for the ray moovement over the gridline
 *   If ray angle is smaller than PI do the same as if ray angle < PI but add whatever the size of the map is to ray Y-coordinate
 *   If ray angle is straight up or down set ray coordinates to camera coordinates and DOF to map size
 *   Loop only if DOF is smaller than map size:
 *    Calculate actual gridline coordinates
 *    If the grid cell at [X, Y] is a wall break out from the loop, save the current ray coordinates, calculate the distance between the camera and the wall
 *    Else update ray coordinates with the earlier calculated offsets
 *
 * Vertical gridline check:
 *  Set vertical distance to a pretty high value, vertical coordinates to camera coordinates
 *   Calculate inverse of tangent
 *   Set DOF variable to 0
 *   If ray angle is bigger than PI / 2 and smaller than 3 * PI / 2 calculate ray X-coordinate to be as close as possible to the gridline position and subtract 0.0001 for precision, calculate ray Y-coordinate and offset coordinates for the ray moovement over the gridline
 *   If ray angle is smaller than PI / 2 or bigger than 3 * PI / 2 do the same as if ray angle > PI / 2 && < 3 * PI / 2 but add whatever the size of the map is to ray X-coordinate
 *   If ray angle is straight left or right set ray coordinates to camera coordinates and DOF to map size
 *   Loop only if DOF is smaller than map size:
 *    Calculate actual gridline coordinates
 *    If the grid cell at [X, Y] is a wall break out from the loop, save the current ray coordinates, calculate the distance between the camera and the wall
 *    Else update ray coordinates with the earlier calculated offsets
 *
 * If the vertical distance is smaller than the horizontal one update ray coordinates to the horizontal ones and set final distance to the horizontal one
 * Else update ray coordinates to the vertical ones and set final distance to the vertical one
 * Fix fisheye effect
 * Add one radian to the ray angle
 * Calculate line height by multiplying constant integer 400 by the map size and dividing that by the final distance
 * Calculate line offset (to make it more centered) by subtracting half of the line height from constant integer 400
 * Draw 8-pixels wide column at [ray index * 8, camera Z-offset + line offset] and [ray index * 8, camera Z-offset + line offset + line height] (the color doesn't matter i think)
*/

#include "../../LIB/wsgl.hpp"
#include "res/maths.hpp"
#include <memory>

using namespace std;

const int window_x = 640, window_y = 640;

float camera_x = 256, camera_y = 256, camera_z = 75;
float camera_a = 0.001;
int camera_fov = 80;

int map_x;
int map_y;
int map_s;
shared_ptr<int[]> map_w;

void controls()
{
    if(wsgl::is_key_down(wsgl::key::w))
    {
        int mx = (camera_x + 30 * cos(camera_a)) / map_s;
        int my = (camera_y + 30 * sin(camera_a)) / map_s;
        int mp = my * map_x + mx;

        if(mp >= 0 && mp < map_s && !map_w[mp])
        {camera_x += 15 * cos(camera_a); camera_y += 15 * sin(camera_a);}
    }
    
    if(wsgl::is_key_down(wsgl::key::s))
    {
        int mx = (camera_x - 30 * cos(camera_a)) / map_s;
        int my = (camera_y - 30 * sin(camera_a)) / map_s;
        int mp = my * map_x + mx;

        if(mp >= 0 && mp < map_s && !map_w[mp])
        {camera_x -= 5 * cos(camera_a); camera_y -= 5 * sin(camera_a);}
    }
    
    if(wsgl::is_key_down(wsgl::key::a_left))
    {camera_a = reset_ang(camera_a - 5 * RAD);}
    
    if(wsgl::is_key_down(wsgl::key::a_right))
    {camera_a = reset_ang(camera_a + 5 * RAD);}

    if(wsgl::is_key_down(wsgl::key::a_up))
    {camera_z += 15;}

    if(wsgl::is_key_down(wsgl::key::a_down))
    {camera_z -= 15;}
}

void renderer()
{
    int map_x_pos, map_y_pos, map_cell, dof;
    float ray_x, ray_y, ray_a = reset_ang(camera_a - deg_to_rad(camera_fov / 2));
    float x_offset, y_offset, tangent, distance_h, distance_v, h_x, h_y, v_x, v_y;
    float final_distance, line_height, line_offset;

    wsgl::clear_window();
    
    for(int i = 0; i < camera_fov; i++)
    {
        distance_h = 1000000, h_x = camera_x, h_y = camera_y;
        tangent = -1 / tan(ray_a);
        dof = 0;

        if(ray_a > PI)
        {ray_y = (((int)camera_y / map_s) * map_s) - 0.0001; ray_x = (camera_y - ray_y) * tangent + camera_x; y_offset = -map_s; x_offset = -y_offset * tangent;}

        if(ray_a < PI)
        {ray_y = (((int)camera_y / map_s) * map_s) + map_s; ray_x = (camera_y - ray_y) * tangent + camera_x; y_offset = map_s; x_offset = -y_offset * tangent;}

        if(ray_a == 0 || ray_a == PI)
        {ray_x = camera_x; ray_y = camera_y; dof = map_s;}

        for(dof; dof < map_s; dof++)
        {
            map_x_pos = (int)(ray_x) / map_s;
            map_y_pos = (int)(ray_y) / map_s;
            map_cell = map_y_pos * map_x + map_x_pos;

            if(map_cell >= 0 && map_cell < map_s && map_w[map_cell])
            {dof = map_s; h_x = ray_x; h_y = ray_y; distance_h = distance(camera_x, camera_y, h_x, h_y);}
            else
            {ray_x += x_offset; ray_y += y_offset;}
        }


        distance_v = 1000000, v_x = camera_x, v_y = camera_y;
        tangent = -tan(ray_a);
        dof = 0;

        if(ray_a > PI2 && ray_a < PI3)
        {ray_x = (((int)camera_x / map_s) * map_s) - 0.0001; ray_y = (camera_x - ray_x) * tangent + camera_y; x_offset = -map_s; y_offset = -x_offset * tangent;}

        if(ray_a < PI2 || ray_a > PI3)
        {ray_x = (((int)camera_x / map_s) * map_s) + map_s; ray_y = (camera_x - ray_x) * tangent + camera_y; x_offset = map_s; y_offset = -x_offset * tangent;}

        if(ray_a == PI2 || ray_a == PI3)
        {ray_x = camera_x; ray_y = camera_y; dof = map_s;}

        for(dof; dof < map_s; dof++)
        {
            map_x_pos = (int)(ray_x) / map_s;
            map_y_pos = (int)(ray_y) / map_s;
            map_cell = map_y_pos * map_x + map_x_pos;

            if(map_cell >= 0 && map_cell < map_s && map_w[map_cell])
            {dof = map_s; v_x = ray_x; v_y = ray_y; distance_v = distance(camera_x, camera_y, v_x, v_y);}
            else
            {ray_x += x_offset; ray_y += y_offset;}
        }


        if(distance_v < distance_h)
        {ray_x = v_x; ray_y = v_y; final_distance = distance_v;}
        else
        {ray_x = h_x; ray_y = h_y; final_distance = distance_h;}

        final_distance *= cos(reset_ang(camera_a - ray_a));
        ray_a = reset_ang(ray_a + RAD);
        line_height = (map_s * 400) / final_distance;
        line_offset = 200 - line_height / 2;

        wsgl::draw_line({i * 8, camera_z + line_offset}, {i * 8, camera_z + line_offset + line_height}, {0, 255 / (final_distance / 250 + 1), 0}, 8);

        if(i == camera_fov / 2)
        {wsgl::draw_text({0, 0}, {255, 255, 255}, L"Final distance: " + to_wstring(final_distance) + L" Line height: " + to_wstring(line_height) + L" X: " + to_wstring(camera_x) + L" Y: " + to_wstring(camera_y));}
    }

    wsgl::render_frame();
}

void load_map(wsgl::wide_str wstr, int cell_size = 1)
{
    shared_ptr<wsgl::bmp> map = shared_ptr<wsgl::bmp>(wsgl::bmp::FromFile(wstr.c_str(), true));
    map_x = map->GetWidth();
    map_y = map->GetHeight();
    map_s = map_x * map_y;
    map_w = shared_ptr<int[]>(new int[map_s]);
    wsgl::color color;

    for(int y = 0; y < map_y; y += cell_size)
    {
        for(int x = 0; x < map_x; x += cell_size)
        {
            map->GetPixel(x, y, &color);

            if(color.GetR() == 255 && color.GetG() == 255 && color.GetB() == 255)
            {*(map_w.get() + ((y / cell_size) * map_x + (x / cell_size))) = 0;}
            else
            {*(map_w.get() + ((y / cell_size) * map_x + (x / cell_size))) = 1;}
        }
    }
}

int main()
{
    wsgl::session sess = wsgl::startup(L"raycaster", {window_x, window_y}); 
    load_map(L"res/map.png");

    while(true)
    {controls(); renderer();}
}

maths.hpp

#include <cmath>

const float PI = 3.14159265359;
const float PI2 = PI / 2;
const float PI3 = 3 * PI2;
const float RAD = PI / 180;

float deg_to_rad(float deg)
{return deg * RAD;}

float distance(float ax, float ay, float bx, float by)
{
    float dx = bx - ax;
    float dy = by - ay;
    
    return sqrt(dx * dx + dy * dy);
}

float reset_ang(float ang)
{
    if(ang < 0)
    {ang += 2 * PI;}

    if(ang > 2 * PI)
    {ang -= 2 * PI;}

    return ang;
}

注:wsgl.hpp是我基于GDI routines封装的自定义库。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 18:54:23