如何修复我的 raycaster 中的翘曲墙?

问题描述

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

Example 1

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

  • 我是这样编译的: clang `pkg-config --cflags --libs sdl2` raycaster.c
  • 要前进和后退,请按向上和向下键。按左右键进行扫射。您可以使用 as 键分别向左和向右转。

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

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

#define SET_COLOR(r,g,b) SDL_SetRenderDrawColor(renderer,r,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}
};

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,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,&window,&renderer);
    SDL_SetwindowTitle(window,"Raycaster");    

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

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

        draw_rectangle(the_ceiling,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,wall_height};
 
                 SDL_RenderFillRect(renderer,&column);
                 SDL_RenderDrawRect(renderer,&column);

发现了一些问题。

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

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

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

enter image description here

编辑。放 dist_step = 0.01

,

这段代码有两个主要问题。必须根据射线与墙壁的交点计算到墙壁的距离,而不是仅仅依赖于您的端点位于墙壁方块内。

C 有一个摄像头,通过从 C 投射光线穿过 A 和 B 之间足够多的点来解决失真问题,只需将该平面(您的屏幕)划分为同样宽的列(像素).

我对余弦校正相当悲观,因为据我所知,与列高相比,人们更可能用它来调整绘制的列宽或位置。

Geometry of the problem

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...