如何在我的 raycaster 中修复扭曲的墙壁?
How do I fix warped walls in my raycaster?
我正在使用 SDL 库和 C 编写一个 raycaster。我已经处理鱼眼效果好几个星期了。对于像 60 度这样的视野,我将到墙的距离乘以相对角度的余弦值(范围从 -30 到 30),但仍然得到相同的鱼眼。这是它的样子:
我现在不知道该怎么做,因为有这么多来源推荐了余弦校正,但它并没有解决我的失真问题。
- 我是这样编译的:
clang `pkg-config --cflags --libs sdl2` raycaster.c
- 要前进和后退,请按向上和向下键。按左和右扫射。您可以使用
a
和 s
键分别向左和向右转。
我的代码在下面,如果你想看的话。如果您设法弄清楚为什么我的引擎中出现扭曲的视角,请告诉我。
#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);
}
}
这段代码有两个主要问题。必须计算光线与墙的交点到墙的距离,而不是仅仅依赖于端点位于墙正方形内。
在 C 有一个摄像头,通过从 A 和 B 之间足够数量的点投射来自 C 的光线来解决失真,只需将这个平面(您的屏幕)划分为同样宽的列(像素)。
我对余弦校正相当悲观,因为据我所知,与列高相比,应该更有可能调整绘制的列宽或位置。
您需要应用以下差异:
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);
发现了一些问题。
系数 width_ratio
和 height_ratio
似乎混合了地图中的坐标 space 和屏幕中的坐标 space。这是毫无意义的。此外,它通过沿特定轴移动得更快来破坏导航。
将 dist
投影到通过屏幕中心投射的光线后(dist *= cos(...)
你必须应用简单的透视来计算墙的高度(变量 wall_height
)
最后,围绕中间水平线画一个高度wall_height
的矩形。
编辑。放
dist_step = 0.01
我正在使用 SDL 库和 C 编写一个 raycaster。我已经处理鱼眼效果好几个星期了。对于像 60 度这样的视野,我将到墙的距离乘以相对角度的余弦值(范围从 -30 到 30),但仍然得到相同的鱼眼。这是它的样子:
我现在不知道该怎么做,因为有这么多来源推荐了余弦校正,但它并没有解决我的失真问题。
- 我是这样编译的:
clang `pkg-config --cflags --libs sdl2` raycaster.c
- 要前进和后退,请按向上和向下键。按左和右扫射。您可以使用
a
和s
键分别向左和向右转。
我的代码在下面,如果你想看的话。如果您设法弄清楚为什么我的引擎中出现扭曲的视角,请告诉我。
#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);
}
}
这段代码有两个主要问题。必须计算光线与墙的交点到墙的距离,而不是仅仅依赖于端点位于墙正方形内。
在 C 有一个摄像头,通过从 A 和 B 之间足够数量的点投射来自 C 的光线来解决失真,只需将这个平面(您的屏幕)划分为同样宽的列(像素)。
我对余弦校正相当悲观,因为据我所知,与列高相比,应该更有可能调整绘制的列宽或位置。
您需要应用以下差异:
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);
发现了一些问题。
系数
width_ratio
和height_ratio
似乎混合了地图中的坐标 space 和屏幕中的坐标 space。这是毫无意义的。此外,它通过沿特定轴移动得更快来破坏导航。将
dist
投影到通过屏幕中心投射的光线后(dist *= cos(...)
你必须应用简单的透视来计算墙的高度(变量wall_height
)最后,围绕中间水平线画一个高度
wall_height
的矩形。
编辑。放 dist_step = 0.01