[cub3d]레이 캐스팅

JH Bang·2022년 12월 10일
0

42 Seoul

목록 보기
8/9
post-thumbnail

본 과제는 lodev.org의 그래픽 튜토리얼을 기초로 수행했다. 이 글을 보기에 앞서 해당 튜토리얼을 대략적으로 살펴보면 도움이 될 것이다.

레이캐스팅 vs 레이트레이싱

레이캐스팅은 3D모델을 점근선 등의 요소를 활용해 2D 화면으로 구현하는 것이다. 한 점에서 광선을 여러개 쏜 뒤 맞는 지점까지의 거리 등을 계산해 화면에 그려주는 그래픽 방법론이다. 반면 레이트레이싱은 빛 반사와 관련한 질감을 구현하는 것이다. 두개가 합쳐져서 물리엔진을 이룬다고 보면 된다. cub3d는 이 중 레이캐스팅과 관련돼 있다. 해당과제는 42에서 제공하는 그래픽 엔진인 mlx를 이용해 구현했다.

mlx 함수 정리 참고-> 정확한 내용은 solong 정리 참고

💡 mlx_init() = mlx가 적용된 프로세스와 디스플레이를 연결하고 초기화를 담당하는 함수
💡 mlx_new_window() = 해당 프로세스에서 윈도우 화면을 띄워주는 함수
💡 mlx_new_image() = 이미지 화면을 만들어 주는 함수(윈도우 화면 내 도화지라고 생각하면 됨)
💡 mlx_get_data_addr() = 해당 이미지 영역을 픽셀 단위로 조작할 수 있도록 도와주는 함수(mlx_new_image()와 같이 쓰임)
💡 mlx_xpm_file_to_image() = xpm 파일 이미지를 받아오는 함수.
💡 mlx_new_image + mlx_get_data_addr가 적용됐다고 보면 이해하기 쉬움.
💡 mlx_put_image_to_window() = 만들어진 image를 윈도우 화면에 그려주는 함수
💡 mlx_hook() 함수 = 이벤트에 해당하는 콜백함수를 호출해주는 함수
💡 mlx_loop_hook() 함수 = 이벤트가 없어도 계속 수행되는 콜백함수를 호출해주는 함수
💡
mlx_loop() 함수 = 무한루프로 콜백함수를 실행해주는 함수

레이트레이싱 개념

좌표 설정

우선 컴퓨터는 벡터의 방향이 y축의 경우 모니터의 좌측 상단 모서리를 0점으로 아래쪽을 양의 방향으로 설정해 둔다는 점을 염두에 둬야 한다.

이 상태에서 플레이어가 있는 지점(눈알의 중심)까지의 벡터를 POS->, 상이 맺히는 화면, 카메라평면(망막)까지의 벡터를 dir-> 이라고 하면 각 광선들은 다음과 같이 표현된다.

| dir | : | plane | = 1 : 0.66 이 1인칭 슈팅게임의 화각으로 알려져 있다. 만약 화면을 줌인 한다면 카메라평면까지의 거리가 길어지는 것이고, 줌아웃하면 짧아지는 것이다.

이 때 cameraX는 카메라평면의 길이를 1로 만들었을 때 중심부로부터의 거리다.

한편 mapX, mapY는 다음과 같다.

mapX, mapY는 정수의 2차원 배열로 맵을 만들고 나온 정수 좌표 개념이다.
다음과 같은 맵을 파싱해 그려주기 위한 것이다.

{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 0, 0, 0, 1, 0, 0, 0, 0, 1,
  1, 0, 1, 1, 1, 0, 1, 1, 0, 1,
  1, 0, 0, 0, 1, 0, 0, 1, 0, 1,
  1, 0, 0, 0, 1, 0, 0, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }

벽면까지의 거리

레이트레이싱의 개념에 따라 광선이 벽면을 맞았을 때 그 거리를 측정하려고 한다. 벽까지의 거리는 해당 벽의 높이를 어느정도로 표현할 것인지를 결정하기 때문이다.

하지만 컴퓨터에게 벽까지의 거리를 계산시키기 전에 광선이 벽을 hit 했다는 것을 보장할 필요가 있다. 컴퓨터는 픽셀로 이뤄졌기 때문이다.

만약 일정한 간격으로 특정 영역이 벽임을 확인한다면, 벽을 뚫고 지나가는 문제가 발생할 수 있다. 이를 위해 필요한 것이 DDA 또는 "Digital Differential Analysis"이다.

이는 정사각형의 영역 별로 벽임을 체크하는 간격을 달리하면서 영역 전체를 확인하여 벽을 hit하는 것을 보장하는 방법이다.

시작점부터 첫 X 정수 좌표의 수직선분(Y축과 수평)까지의 거리를 DistX, Y정수 좌표의 수직선분(X축과 수평)까지의 거리를 DistY라고 하고, 다음 수직선분까지의 거리변화를 델타로 둔다. 이를 계속 더해 갱신하면서, 해당 지점이 벽면인지를 체크해줄 것이다.

이를 실제 수식으로 옮기기 위해 광선의 x성분인 rayDirx-> 와 rayDirY->가 양수, 음수일때 일반식을 계산하도록 한다.

distX, distY에 대한 변화량은 아래와 같이 유도할 수 있다.

하지만 이대로 플레이어의 위치에서 측정된 벽면까지의 거리를 계산하게 되면 다음과 같이 어안렌즈 현상이 발생하게 된다.

벽면을 마주했을 때 벽면의 높이가 같도록 렌더링해야 하는 경우, 위치를 중심으로 계산했기 때문에 가장자리로 갈수록 멀게 계산됐기 때문이다. 따라서 pos-> 벡터를 지나는 dir->에 수직한 선분으로부터의 수직거리를 일괄 적용해주는 보정이 필요하다.

수직거리는 아래와 같이 계산된다.

렌더링

지금까지 렌더링할 물제의 크기를 결정했다. 이제부터는 텍스쳐를 입히는 작업을 해야한다. 광선을 쐈을 때 벽면의 텍스쳐는 광선의 방향에 따라 달리 입혀줘야 한다. 그렇지 않으면 실제 원하는 텍스쳐에서 뒤집힌 모습으로 렌더링 될 수 있다.

벡터 좌표계에서는 외적이라는 개념이 존재하는데, 이는 convoultion, 회전에 대한 좌표 개념이다. x축 양의 방향에서 y축 양의 방향으로 회전할때를 양의 방향으로 정의한다.

이에 따라 입히고자 하는 텍스쳐의 방향은 시계방향이 되는데, 계산값은 각 면에 따라 같은 방향일수도, 반대 방향일 수도 있다. 우리가 구하려는 임의의 0점에서 텍스트상 그려줄 텍스트 지점까지의 거리를 textX라고 하고, 각 면에 따라 각각 케이스를 나눠 계산해 준다.

textX는 텍스처 파일상의 거리였다. 우리가 그려주는 지점은 컴퓨터 모니터 화면상의 거리를 계산해야 하므로, 텍스쳐상의 textX와 대응되는 맵상의 거리를 wallX라고 한다. wallX는 다음과 같이 유도된다.

이동/회전

앞뒤로 이동시에는 x,y좌표를 그만금 더하거나 빼면 되고 회전시에는 삼각함수의 성질에 의해 회전각에 따라 x,y 좌표를 업데이트 시켜준다.

참고자료 = https://lodev.org/cgtutor/raycasting.html#Introduction

profile
의지와 행동

0개의 댓글