如何修复我的 raycaster 中的翘曲墙? [英] How do I fix warped walls in my raycaster?

查看:66
本文介绍了如何修复我的 raycaster 中的翘曲墙?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用带有 C 的 SDL 库编写光线投射器.我已经处理鱼眼效应好几个星期了.对于像 60 度这样的视野,我将到墙壁的距离乘以相对角度的余弦(范围从 -30 到 30),但仍然得到相同的鱼眼.看起来像这样:

I am writing a raycaster using the SDL library with C. I have been dealing with the fisheye effect for many weeks now. For a field of view like 60 degrees, I multiply the distance to the wall by the cosine of the relative angle (ranging from -30 to 30), but still, I get the same fisheye. Here's what that looks like:

我现在不知道该怎么做,因为很多来源都推荐了余弦校正,但它并不能解决我的情况.

I don't know what to do at this point, given that so many sources have recommended cosine correction and it just does not fix the distortion in my case.

  • 我是这样编译的:clang `pkg-config --cflags --libs sdl2` raycaster.c
  • 要前进和后退,请按向上和向下键.按左右键进行扫射.您可以使用 as 键分别向左和向右转.
  • I am compiling like this: clang `pkg-config --cflags --libs sdl2` raycaster.c
  • To go forward and back, press the up and down keys. Press left and right to strafe. You can use the a and s keys to turn left and right respectively.

如果你想看一下,我的代码在下面.如果您设法弄清楚为什么我的引擎会出现扭曲的视角,请告诉我.

My code is below if you want to take a look. If you manage to figure out why I am getting a warped perspective in my engine, please let me know.

#include <SDL2/SDL.h>
#include <math.h>

#define SET_COLOR(r, g, b) SDL_SetRenderDrawColor(renderer, r, g, b, SDL_ALPHA_OPAQUE)

typedef struct {
    float x, y, prev_x, prev_y, angle, fov;
} Player;

enum {
    map_width = 12, map_height = 15,
    screen_width = 800, screen_height = 500
};

const float
    move_speed_decr = 0.08,
    angle_turn = 2.0,
    theta_step = 0.05,
    dist_step = 0.8,
    width_ratio = (float) screen_width / map_width,
    height_ratio = (float) screen_height / map_height;

const unsigned char map[map_height][map_width] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1},
    {1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1},
    {1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1},
    {1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};

SDL_Window* window;
SDL_Renderer* renderer;

float to_radians(float degrees) {
    return degrees * (M_PI / 180.0f);
}

void draw_rectangle(SDL_Rect rectangle, int r, int g, int b) {
    SET_COLOR(r, g, b);
    SDL_RenderFillRect(renderer, &rectangle);
    SDL_RenderDrawRect(renderer, &rectangle);
}

void raycast(Player player) {
    SET_COLOR(210, 180, 140);

    float
        half_fov = player.fov / 2,
        rel_x = player.x * width_ratio, rel_y = player.y * height_ratio;

    float screen_x = 0, step_x = (screen_width / player.fov) * theta_step;

    for (float theta = player.angle - half_fov; theta < player.angle + half_fov; theta += theta_step) {
        float rad_theta = to_radians(theta);
        float cos_theta = cos(rad_theta), sin_theta = sin(rad_theta);

        float dist = 0;
        while (dist += dist_step) {
            float
                new_x = cos_theta * dist + rel_x,
                new_y = sin_theta * dist + rel_y;

            if (map[(int) (new_y / height_ratio)][(int) (new_x / width_ratio)]) {
                dist *= cos(to_radians(theta - player.angle));
                float double_dist = 2 * dist;

                if (double_dist >= screen_height) break;
                SDL_Rect column = {screen_x, dist, step_x + 1, screen_height - double_dist};

                SDL_RenderFillRect(renderer, &column);
                SDL_RenderDrawRect(renderer, &column);
                break;
            }
        }
        screen_x += step_x;
    }
}

void handle_input(const Uint8* keys, Player* player) {
    SDL_Event event;

    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_QUIT) {
            SDL_DestroyWindow(window);
            SDL_DestroyRenderer(renderer);
            exit(0);
        }

        else if (event.type == SDL_KEYDOWN) {
            float radian_theta = to_radians(player -> angle);
            float move_x = cos(radian_theta) * move_speed_decr,
                move_y = sin(radian_theta) * move_speed_decr;

            // handle arrow keys
            if (keys[SDL_SCANCODE_UP]) player -> x += move_x, player -> y += move_y;
            if (keys[SDL_SCANCODE_DOWN]) player -> x -= move_x, player -> y -= move_y;
            if (keys[SDL_SCANCODE_LEFT]) player -> x += move_y, player -> y -= move_x;
            if (keys[SDL_SCANCODE_RIGHT]) player -> x -= move_y, player -> y += move_x;

            // handle 'a' and 's' for angle changes
            if (keys[SDL_SCANCODE_A]) player -> angle -= angle_turn;
            if (keys[SDL_SCANCODE_S]) player -> angle += angle_turn;

            // safeguards for invalid positions and angles
            if (player -> x < 0) player -> x = 0;
            else if (player -> x > screen_width) player -> x = screen_width;

            if (player -> y < 0) player -> y = 0;
            else if (player -> y > screen_height) player -> y = screen_height;

            // move the player to their previous coordinate if they're in a wall
            if (map[(int) player -> y][(int) player -> x])
                player -> y = player -> prev_y, player -> x = player -> prev_x;

            if (player -> angle > 360) player -> angle = 0;
            else if (player -> angle < 0) player -> angle = 360;

            player -> prev_y = player -> y, player -> prev_x = player -> x;
        }
    }
}

int main() {
    SDL_CreateWindowAndRenderer(screen_width, screen_height, 0, &window, &renderer);
    SDL_SetWindowTitle(window, "Raycaster");    

    Player player = {5, 5, 0, 0, 0, 60};
    SDL_Rect the_ceiling = {0, 0, screen_width, screen_height / 2};
    SDL_Rect the_floor = {0, screen_height / 2, screen_width, screen_height};
    const Uint8* keys = SDL_GetKeyboardState(NULL);

    while (1) {
        handle_input(keys, &player);

        draw_rectangle(the_ceiling, 96, 96, 96);
        draw_rectangle(the_floor, 255,69,0);

        raycast(player);

        SDL_RenderPresent(renderer);
        SDL_UpdateWindowSurface(window);
    }
}

推荐答案

您需要应用以下差异:

diff --git a/so33.c b/so33.c
index e65cff8..b0f6d8a 100644
--- a/so33.c
+++ b/so33.c
@@ -56,7 +56,7 @@ void raycast(Player player) {
 
     float
         half_fov = player.fov / 2,
-        rel_x = player.x * width_ratio, rel_y = player.y * height_ratio;
+        rel_x = player.x, rel_y = player.y;
 
     float screen_x = 0, step_x = (screen_width / player.fov) * theta_step;
 
@@ -70,12 +70,12 @@ void raycast(Player player) {
                 new_x = cos_theta * dist + rel_x,
                 new_y = sin_theta * dist + rel_y;
 
-            if (map[(int) (new_y / height_ratio)][(int) (new_x / width_ratio)]) {
+            if (map[(int) (new_y)][(int) (new_x)]) {
                 dist *= cos(to_radians(theta - player.angle));
-                float double_dist = 2 * dist;
-
-                if (double_dist >= screen_height) break;
-                SDL_Rect column = {screen_x, dist, step_x + 1, screen_height - double_dist};
+               float wall_height = screen_height / dist;
+               if (wall_height > screen_height)
+                       wall_height = screen_height;
+                SDL_Rect column = {screen_x, screen_height/2 - wall_height/2, step_x + 1, wall_height};
 
                 SDL_RenderFillRect(renderer, &column);
                 SDL_RenderDrawRect(renderer, &column);

发现了一些问题.

  1. 系数 width_ratioheight_ratio 似乎将地图空间中的坐标与屏幕空间中的坐标混合在一起.这是毫无意义的.此外,它通过沿特定轴更快地移动来破坏导航.

  1. Coefficients width_ratio and height_ratio seems to mix coordinates in the map space with coordinates in the screen space. It is pointless. Moreover, it breaks navigation by moving faster along specific axis.

在将 dist 投影到通过屏幕中心投射的光线后 (dist *= cos(...),您必须将简单透视应用于计算墙的高度(变量wall_height)

After projecting dist to the ray cast through the center of the screen (dist *= cos(...) you have to apply simple perspective to compute height of the wall (variable wall_height)

最后,围绕中间水平线绘制一个高度为 wall_height 的矩形.

Finally, draw a rectangle of height wall_height around the middle horizontal line.

编辑.放dist_step = 0.01

Edit. Set dist_step = 0.01

这篇关于如何修复我的 raycaster 中的翘曲墙?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆