5) cub3D - Raycaster 구현 (2)

참고한 링크

lodev tutorial - wall

변수 선언 (t_wall_info)

typedef struct		s_wall_info
{
	double		camera_x;
	double		raydir_x;
	double		raydir_y;
	double		side_dist_x;
	double		side_dist_y;
	double		delta_dist_x;
	double		delta_dist_y;
	double		perp_wall_dist;
	double		wall_x;
	double		step;
	double		tex_pos;
	int		map_x;
	int		map_y;
	int		step_x;
	int		step_y;
	int		hit;
	int		side;
	int		line_height;
	int		draw_start;
	int		draw_end;
	int		tex_num;
	int		tex_x;
	int		tex_y;
	int		color;
	int		x;
	int		y;
}			t_wall_info;

set_wall()

(3) DDA algorithm 시작

t_info		*info;
t_wall_info	*wall;

while (wall->hit == 0)
{
	if (wall->side_dist_x < wall->side_dist_y)
	{
		wall->side_dist_x += wall->delta_dist_x;
		wall->map_x += wall->step_x;
		wall->side = 0;
	}
	else
	{
		wall->side_dist_y += wall->delta_dist_y;
		wall->map_y += wall->step_y;
		wall->side = 1;
	}
	if (info->map[wall->map_y][wall->map_x] == '1')
		wall->tex_num = texture_ewsn(wall);
}

1) 벽에 부딪힐 때까지while (hit == 0) 매번 한 칸씩 광선을 이동시키는 루프
2) side_dist가 작은 좌표에 따라 step_x, step_y 중 하나를 사용하면 x, y 방향으로 광선이 한 칸씩 점프
3) 만약 광선이 x축 방향과 일치, (y방향이 바뀌지 않을테니) 반복문을 돌 때 x방향으로만 한 칸씩 점프
4) 만약 광선이 y축 방향으로 아주 조금 기울어져 있으면 x방향으로 엄청 많이 점프하고나서야 y방향으로 1칸 점프
5) 만약 광선이 y축 방향과 일치, (x방향이 바뀌지 않을테니) 반복문을 돌 때 y방향으로만 한 칸씩 점프
6) 광선이 점프할 때마다 side_dist_x, side_dist_y 는 delta_dist_x, delta_dist_y 가 더해지면서 값이 업데이트
7) 광선이 점프할 때마다 map_x, map_y 는 step_x, step_y 가 더해지면서 값이 업데이트

벽에 부딪히면 루프가 종료된다.
이때 변수 side의 값이 0이면 벽의 x면에, 1이면 벽의 y면에 부딪혔음을 알 수 있고, 또 step_x, step_y로 어떤 방향의 벽에 부딪혔는지 알 수 있다.
( texture_ewsn()으로 동서남북을 구별해 줄 수 있다.)

(4) 광선의 시작점에서 벽까지의 이동거리 계산 (perp_wall_dist)

t_player 	*player;
t_wall_info 	*wall;

if (wall->side == 0)
	wall->perp_wall_dist = (wall->map_x - player->pos_x + (1 - wall->step_x) / 2) / wall->raydir_x;
else
	wall->perp_wall_dist = (wall->map_y - player->pos_y + (1 - wall->step_y) / 2) / wall->raydir_y;
wall->line_height = (int)(HEIGHT / wall->perp_wall_dist);
wall->draw_start = -wall->line_height / 2 + HEIGHT / 2;
if (wall->draw_start < 0)
	wall->draw_start = 0;
wall->draw_end = wall->line_height / 2 + HEIGHT / 2;
if (wall->draw_end >= HEIGHT)
	wall->draw_end = HEIGHT - 1;

1) 벽을 얼마나 높게 그릴지 알아내는데 사용
2) 어안렌즈 효과를 피하기 위해 플레이어 위치까지의 유클리드 거리 대신에, 카메라 평면 까지의 거리 (또는 카메라 쪽으로 플레이어에 투사된 지점의 거리)를 사용

위 그림은 플레이어까지의 거리 대신에 카메라 평면까지의 거리를 사용하는 이유를 보여준다.

플레이어 기준 왼쪽의 적색 선은 벽의 적중지점에서 플레이어까지의 유클리드 거리를 나타내는 광선이다. 플레이어 기준의 녹색 선은 플레이어가 아닌 카메라 평면으로 바로 이동하는 광선을 나타낸다. 이 녹색 선의 길이가 우리가 유클리드 거리(실제 거리) 대신 사용할 수직거리이다.

위의 그림을 보면, 플레이어는 벽을 정면으로 바라보고 있어 이 경우 벽의 윗선과 아랫선이 화면에서 완전히 수평을 이뤄야한다. 이때 적색 선(유클리드 거리)를 적용하면 광선의 길이가 다 다르기 때문에 벽의 높이가 일정하지 않게 되고 결국 벽이 둥글게 보이는 것이다. 반면, 오른쪽의 녹색 선(수직거리)은 모두 길이가 같아서 올바른 결과를 얻을 수 있다.

플레이어가 회전할 때(카메라 평면이 수평이 아니게 되고, 녹색선의 길이도 서로 달라지지만 서로 일정한 차이를 유지하면서 달라지는 상황)에도 동일하게 적용되어 벽은 화면에 대각선이긴 하지만 직선으로 보인다.

if (wall->side == 0)
	wall->perp_wall_dist = (wall->map_x - player->pos_x + (1 - wall->step_x) / 2) / wall->raydir_x;
else
	wall->perp_wall_dist = (wall->map_y - player->pos_y + (1 - wall->step_y) / 2) / wall->raydir_y;

1) 광선의 이동거리를 계산하면서, 어안렌즈 효과를 보정하는 코드를 따로 추가하지 않고도 간단히 방지할 수 있는 레이캐스팅 방법을 사용한다.

  • 이 수직거리를 구하는 방식은 실제 이동거리를 구하는 방식보다 훨씬 쉽고, 벽에 어느 위치에 정확히 부딪혔는지 몰라도 구할 수 있다.
  • (1 - step_x) / 2는 step_x가 -1이면 1, step_x가 1이면 0이 된다. 이는 raydir_x < 0일때 길이에 1을 더해주기 위한 코드이다. 이전에 side_dist_x의 초기값을 설정할 때 raydir_x의 방향에 따라 1을 더해주거나 말거나 했던 것과 같은 이유이다.

2) 수직거리를 계산하는 방법

  • 만약 광선이 처음으로 부딪힌 면이 x면이라면, map_x - pos_x + (1 - step_x) / 2 는 광선이 x방향으로 몇 칸이나 지나갔는지를 나타내는 수이다. (정수일 필요 없음)
  • 만약 광선의 방향이 x면에 수직이면 이미 정확한 수직거리의 값이지만, 대부분의 경우 광선의 방향이 있고 이 때 구해진 값은 실제 수직거리보다 큰 값이므로 raydir_x로 나누어준다.
  • y면에 부딪힌 경우에도 같은 방식으로 해준다.
  • map_x - pos_x가 음수이더라도 역시 음수인 raydir_x로 나누어 계산된 값은 항상 양수가 된다.


perp_wall_dist는 벽의 적중지점과 플레이어의 카메라 평면을 사용해서, 점에서 선까지의 거리를 구하는 공식을 적용해서 계산할 수도 있다. 하지만 이 공식은 앞의 더 간단한 공식보다 계산량이 많다.
위의 그림은 더 간단한 공식이 어떻게 도출되는지 보여준다.
이 설명은 y면에 부딪힌 경우(side == 1)를 보여준다. x면에 부딪힌 경우도 같은 원리로 설명할 수 있다.

계산한 거리(perp_wall_dist)로 화면에 그려야하는 선의 높이를 구할 수 있다.
wall->line_height = (int)(HEIGHT / wall->perp_wall_dist);
wall->draw_start = -wall->line_height / 2 + HEIGHT / 2;
if (wall->draw_start < 0)
	wall->draw_start = 0;
wall->draw_end = wall->line_height / 2 + HEIGHT / 2;
if (wall->draw_end >= HEIGHT)
	wall->draw_end = HEIGHT - 1;

1) perp_wall_dist를 역수로 취하고, 픽셀 단위로 맞춰주기 위해 픽셀 단위의 화면높이 height를 곱해서 구할 수 있다.
2) 벽을 더 높게 그리거나, 낮게 그리고 싶으면 2 * height와 같은 다른 값을 넣을 수도 있다.
3) height는 일정한 벽의 높이, 너비 및 깊이를 가진 박스처럼 보이게 하고, 값이 클수록 높이가 높은 박스를 만들어준다.
4) 이렇게 구한 line_height(화면에 그려야 할 수직선의 높이)에서 실제로 선을 그릴 위치의 시작 및 끝 위치를 알 수 있다.
5) 벽의 중심은 화면의 중심에 있어야하고, 이 중심점이 화면 범위 아래에 놓여있다면 0으로, 화면 범위 위에 놓여있다면 height - 1로 덮어 씌운다.

좋은 웹페이지 즐겨찾기