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
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);
}
}
You need to apply following diff:
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);
A few issues were identified.
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.
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
)
Finally, draw a rectangle of height wall_height
around the middle horizontal line.
Edit. Set dist_step = 0.01