written by yechoi

[cub3d] 각도 기반 레이케스팅 - 텍스쳐 올리기 본문

Born 2 Code/C, C++

[cub3d] 각도 기반 레이케스팅 - 텍스쳐 올리기

yechoi 2021. 1. 1. 14:34
반응형

 

각도 기반 레이케스팅에서 텍스쳐 올리는 법을 알아본다. 이 튜토리얼은 레이케스팅 엔진을 구현해, 이미 위 그림처럼 민무늬의 벽을 그릴 수 있다는 것을 가정한다. 아직 민무늬 벽을 그려보지 못했다면, pikuma의 raycasting 튜토리얼을 따라해보거나, 벡터를 이해할 수 있다면 lodev의 튜토리얼을 보길 권장한다(벡터로 레이케스팅을 구현할 것이라면 지금 이 문서는 보지 않아도 된다).

 

다시 본론으로 돌아와 각도 기반 레이케스팅 엔진을 구현했다면, 우리는 각 레이(광선)가 벽에 맞은 위치를 알아낼 수 있다. 텍스쳐가 있는 벽을 만들기 위해 중요한 것은 '레이가 맞은 벽의 좌표''텍스쳐의 좌표'로 환산하는 것이다.

 

들어가기에 앞서 설명의 편의를 위해 다음과 같이 변수를 명명하겠다.

  • 플레이어의 위치는 player_pos
  • 광선이 맞은 지점은 ray_hit_pos
  • 텍스쳐 좌표는 tex_pos
  • 각각 x 좌표는 변수명.x y 좌표는 변수명.y ex) player_pos.x
  • 벽의 한변의 길이는 TILE_SIZE
  • 텍스쳐의 한변의 길이는 TEX_SIZE

 

 

텍스쳐의 x 좌표 구하기

 

벽상의 좌표 구하기

우선 경우를 두가지로 나눠볼 수 있다. 광선이 가로 축에 맞았을 때와 광선이 세로 축에 맞았을 때.

 

광선이 가로 축에 맞았을 때

 

우선 광선이 가로 축에 맞았을 때, 한 벽면을 기준으로 x좌표를 먼저 구해본다. 그림 상에서 A의 x좌표가 0, B의 좌표가 벽의 길이인 TILE_SIZE라고 할 때, 레이가 맞은 지점의 x좌표를 구하는 것이다.

 

ray_hit_pos.x를 벽의 길이로 나누었을 때, 나머지가 그 벽면에서 x좌표일 것이다.

fmod(ray_hit_pos.x, TILE_SIZE)

❗️ fmod는 float으로 나눴을 때 나머지를 구하는 함수다

 

광선이 세로 축에 맞았을 때

이때는 ray_hit_pos.x가 아닌 ray_hit_pos.y 를 기준으로, 벽면에서의 x 좌표를 구하면 된다.

fmod(ray_hit_pos.y, TILE_SIZE)

 

이 두 가지를 모두 아울러 표현한다면

가로 축에 맞았는지 세로 축에 맞았는지 확인할 수 있는 변수 wasHitVertical이 있다. 1이면 세로 축에 맞은 것이고 0 이면 가로 축에 맞은 것이다.

wasHitVertical ? fmod(ray_hit_pos.y, TILE_SIZE) : fmod(ray_hit_pos.x, TILE_SIZE)

 

 

벽상의 좌표를 텍스쳐 상의 좌표로 변환하기

이제는 이 좌표를 텍스쳐 상의 좌표로 변환해 보자. 벽의 한변 길이와 텍스쳐의 한변 길이의 비를 이용하면 된다.

위 예시에서 왼쪽이 벽 오른쪽이 텍스쳐라고 하자. 벽의 좌표가 (1,0)이라고 하면 텍스쳐상의 좌표는 2를 곱한 (2.0)이 된다. 즉 텍스쳐의 한변 길이(TEX_SIZE) / 벽의 한변 길이(TILE_SIZE)를 곱해주면 된다.

 

그래서 최종적으로 도출한 x좌표를 구하는 식은

tex_pos.x = wasHitVertical ? fmod(ray_hit_pos.y, TILE_SIZE) * TEX_SIZE / TILE_SIZE  : fmod(ray_hit_pos.x, TILE_SIZE) * TEX_SIZE / TILE_SIZE

 

 

 

텍스쳐의 y 좌표 구하기

각도 기반으로 레이케스팅 엔진을 구현한 사람이라면, 보여지는 벽의 높이를(wall_height) 구해 색깔을 칠하는 방식으로 벽을 그린다는 것을 알고 있을 것이다. 가물가물한 사람들을 위해 짧게 설명을 하자면, 레이가 맞은 지점의 거리에 따라 벽의 높이는 달리 보인다. 가깝게 맞았다면 벽의 높이는 클 것이고, 멀리 맞았다면 벽의 높이는 작아진다.

텍스쳐의 y좌표를 구할 때도 이 벽의 높이를 활용할 것이다.

아래의 그림에서 흰색 큰 사각형이 화면이고, 파란색 사각형이 우리가 그리고자 하는 벽이라고 하자. A좌표와 B좌표는 x좌표는 같고 y좌표는 다르다. 우리는 벽을 그릴 때, A좌표에서 B좌표까지 점을 찍는 방식으로 벽을 그릴 것이다. (그리고 x좌표를 증가시켜 선을 하나 그리고, 다시 x좌표 증가해 선을 그리고... 선들이 모여 면이 될 때까지 반복.)

 

 

 

벽상의 좌표 구하기

벽상의 x좌표를 구해줬듯, 이번엔 벽상의 y좌표를 구할 것이다. A와 B사이에 current_pos가 있다. 이는 화면상에서 찍히는 좌표다. current_pos의 y좌표를 벽상의 y좌표로 변환하는 것은 간단하다. 시작지점인 A의 y 좌표만 빼주면 된다. A지점의 y좌표는 화면 높이 절반(window_height/2)에서 벽의 높이 절반(wall_height/2)를 뺀 값과 같다.

current_pos.y - (window_height / 2 - wall_height / 2)

 

 

벽상의 좌표를 텍스쳐상의 좌표로 변환하기

앞서 벽의 크기와 텍스쳐의 크기의 비를 이용해 x좌표를 도출해 냈듯, 벽의 높이와 텍스쳐의 크기를 비교해 y좌표를 도출할 것이다.

간단한 예를 들어보자. 위 그림에서 높이 4인 벽이 있을 때, y좌표가 1인 지점이 있다고 하자. 이 좌표는 높이 8인 텍스쳐에서 y좌표 2로 변환할 수 있다.

 

즉 텍스쳐상의 좌표는 아까 구한 벽상의 좌표에 TEX_SIZE / wall_height 만큼 곱해주면 된다.

(current_pos.y - (window_height / 2 - wall_height)) * TEX_SIZE / wall_height

 

남은 것들은

  • current_pos를 반복문을 통해 A에서 시작해 B까지 도달하도록 만들기
  • 텍스쳐 상의 x와 y좌표를 통해 픽셀 색상을 추출한다.
  • 그 색상을 원래 좌표에 찍어준다.

 

 

아래는 내가 텍스쳐를 렌더링할 때 실제로 사용했던 코드다. 전체 코드 구성 참고용으로 덧붙인다.

/*
** pos[0] window size, pos[1] current pos, pos[2] tex pos
*/
void    render_a_stripe(t_game *game, t_config config, t_ray *ray, int rayid)
{
    int        i;
    t_pos    pos[3];
    int        color;
    double    dist_to_camera;
    double    wall_height;
    dist_to_camera = (config.width / 2) / tan(config.fov / 2);
    wall_height = TILE_SIZE * dist_to_camera / ray[rayid].distance;
    set_pos(&pos[0], config.width, config.height);
    set_pos(&pos[1], config.width / NUM_RAYS * rayid,
            MAX(0, config.height / 2 - wall_height / 2 - 1));
    pos[2].x = ray[rayid].wasHitVertical ?
    (int)fmod(ray[rayid].wallHit.y, TILE_SIZE) / TILE_SIZE * config.tex_width :
    (int)fmod(ray[rayid].wallHit.x, TILE_SIZE) / TILE_SIZE * config.tex_width;
    i = -1;
    while (++i < wall_height && ++pos[1].y < pos[0].y)
    {
        restrain_pos(&pos[1], &pos[0]);
        pos[2].y = (pos[1].y - (pos[0].y / 2 - wall_height / 2)) /
            wall_height * config.tex_height;
        color = get_tex_color(game,
            config.texture[ray[rayid].wallHitContent], &pos[2]);
        game->img.data[to_coord(pos[1].x, pos[1].y, game)] = color;
    }
}
반응형