簡體   English   中英

我猜我的光線投射器會根據 map 尺寸以一種非常奇怪的方式渲染牆壁

[英]My raycaster renders walls in a really weird way depending on the map size I guess

我一直在 C++ 中編寫光線投射器並渲染我使用 GDI/GDI+ 的東西。 我知道使用 WGDI 來渲染圖形並不是世界上最好的主意,我應該使用 OpenGL、SFML 等。但是這個 raycaster 不涉及任何超高級實時圖形,所以在這種情況下 WGDI 可以工作。 此外,我可能會在我的學校展示這個並安裝 OpenGL 會有巨大的痛苦。

好的,所以我想討論的實際問題是,每當我將 map 網格從 8x8 更改為 8x16 時,某些牆壁的渲染方式非常奇怪: 正常渲染的牆壁 然后發生這種情況

如果有人能解釋為什么會出現這樣的問題,我會很高興發現我的代碼有什么問題。

主文件

/*
 * 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();}
}

數學.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 那只是我對一些 WGDI 例程等的包裝庫。

我認為問題出在這里:

        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;

您需要更改操作順序:

        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;

在您當前的實現中,您首先截斷ray_xray_y ,然后除以 map_s (這可能應該是一個浮點值,但在您當前的實現中是一個 integer ),然后再次截斷為 integer 值。 您當前的實現不必要地犧牲了精度,並且對於小的map_s值將是不可預測的。

此外, map_s似乎不正確。 您將map_s設置為代表 map 的總面積,但在上面的代碼中,您使用它就像它是 map 的邊長一樣。

正確的是,您需要類似的東西

#include <cmath>
map_x_pos = (int)(ray_x / sqrtf(map_s));
map_y_pos = (int)(ray_y / sqrtf(map_s));
map_cell = map_y_pos * map_x + map_x_pos;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM