Post Lists

2018년 6월 24일 일요일

Getting Started - Camera (10) 번역

위의 자료를 번역한 내용입니다.
--------------------------------------

Camera
이전 튜토리얼에서 우리는 view matrix와 어떻게 scene 주변을 움직이기 위해 view matrix 사용할 수 있는지에 대해 이야기 했다. (우리는 조금 뒤로 갔었다.) OpenGL은 그 자체로 camera 개념과는 친숙하지 않지만, 우리는 반대 방향으로 scene에서 모든 오브젝트들을 움직여서 카메라를 시연할 수 있다. 이것은 우리가 움직이고 있다는 환영을 준다.

이 튜토리얼에서, 우리는 어떻게 OpenGL에서 카메라를 설정할지 이야기 할 것이다. 우리는 너가 3D 공간에서 너가 자유롭게 움직이도록 하는 FPS-스타일 카메라를 이야기할 것이다. 이 튜토리얼에서 우리는 또한 keyboard와 mouse input을 다루고, custom camera class로 마무리 할 것이다.

Camera/View space
우리가 카메라/view space에 대해 이야기 할 때, 우리는 scene의 origin으로서 카메라의 관점으로부터 보이는 vertex 좌표들에 대해 말하고 있는 것이다. view matrix는 모든 world 좌표들을 카메라 위치와 방향에 상대적인 view 좌표들로 변환한다. 카메라를 정의하기 위해서, 우리는 world space에서의 그것의 위치, 그것이 바라보고 있는 방향, 오른쪽을 향하는 벡터와 카메라로부터 위로 향하는 벡터를 필요로 한다. 세심한 독자는 우리가 실제로 원점으로서 카메라 위치와 함께 3개의 수직의 단위 축을 가진 좌표계를 만들려고 하는 것을 알아 챌 것이다.

1. Camera position
카메라 포지션을 얻는 것은 쉽다. 카메라 포지션은 기본적으로 카메라의 위치를 가리키는 world space에서의 vector이다. 우리는 이전 튜토리얼에서 카메라를 설정한 같은 위치에 카메라를 설정한다.

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

Green Box
양의 z-축이 스크린을 뚫고 너를 향한다는 것을 잃지 말아라. 그래서 만약 우리가 카메라를 뒤로 움직이길 원한다면, 우리는 양의 z축을 따라 움직인다.

2. Camera direction
요구되는 다음 벡터는 카메라의 방향이다. 예를들어 어떤 방향을 향하고 있는지에 대해. 지금, 우리는 카메라가 우리의 scene의 원점을 가리키도록 한다. : (0,0,0). 만약 우리가 서로 두 벡터를 뺀다면, 이러한 두 벡터의 차이인 벡터를 얻는 다는 것을 기억하는가? scene의 원점 벡터로부터 카메라 포지션 벡터를 빼는 것은 따라서 방향 벡터를 만들어 낸다. 카메라가 음의 z 방향을 가리키고 있다는 것을 알기 때문에, 우리는 방향벡터가 카메라의 양의 z-축을 가리키기를 원한다. 만약 우리가 뺄셈 순서를 바꾼다면, 우리는 이제 카메라의 양의 z-축 쪽을 향하는 벡터를 얻는다.

glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

Red Box
direction 벡터라는 이름은 가장 좋은 이름은 아니다. 왜냐하면 그것은 실제로 그것이 가리키는 것의 반대방향을 가리키고 있기 때문이다.

3. Right axis
우리가 필요한 다음 벡터는 right vector인데, 이것은 카메라 공간의 양의 x축을 나타낸다. right vector를 얻기위해, 우리는 (world space에서) 위쪽을 향하는 up vector를 처음에 명시하여 작은 트릭을 사용한다. 그런 후에 우리는 up vector와 step2의 방향 벡터에 외적을 한다. 외적의 결과는 두 벡터에 수직한 벡터이기 때문에, 우리는 양의 x축을 가리키는 벡터를 얻을 것이다. (만약 우리가 그 벡터들을 바꾼다면, 음의 x축을 가리키는 벡터를 얻을 것이다.)

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

4. Up axis
x축과 z축 벡터 둘 다 가지고 있으니, 카메라의 양의 y축을 가리키는 벡터를 가져오는 것은 상대적으로 쉽다. 우리는 right vector와 direction vector의 외적을 취한다.

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

외적과 몇 가지 트릭의 도움으로, 우리는 view/camera space를 형성하는 모든 벡터들을 만들 수 있었다. 좀 더 수학에 관심이 있는 독자들에게, 이 과정은 선형대수에서 Gram-Schmidt라고 알려져 있따. 이러한 카메라 벡터들을 사용하여, 우리는 이제 카메라를 만드는데 매우 유용하다고 증명된 LookAt 행렬을 만들 수 있다.

LookAt
행렬에 관한 훌륭한 것은 만약 너가 3개의 수직의 축 (non-linear)을 사용하는 좌표 공간을 정의한다면, 너는 translation 벡터가 더해진 그러한 3개의 축을ㅇ 가진 행렬을 만들 수 있고, 너는 어떤 벡터를 그것에 이 행렬을 곱하여 그 좌표 공간으로 변환 할 수 있다. 이것은 정확히 LookAt 행렬이 하는 것이고, 우리가 카메라 공간을 정의하기위해 3개의 직교하는 축과 position vector를 가지고 있으니까, 우리는 우리 자신의 LookAt matrix를 만들 수 있다.

 



여기에서 R는 right vector, U는 up vector, D는 방향 벡터이고, P는 카메라의 포지션 벡터이다. position vector가 반대로 되었다는 것에 주목해라. 왜냐하면 결국에 우리는 우리가 가고자 하는 곳의 반대 방향으로 world를 움직이고 싶기 때문이다. 이 LookAt 매트릭스를 우리의 view matrix로 사용하는 것은 효과적으로 모든 world 좌표들을 우리가 정의한 view space로 바꾼다. LookAt 행렬은 그러고나서 정확히 그것이 말하는 것을 한다. 그것은 주어진 target을 바라보는 view matrix를 만든다.

운좋게도, GLM은 이미 우리를 위해 이 모든 작업을 한다. 우리는 오직 camera position, target position 그리고 world space에서 up vector를 나타내는 벡터만을 명시한다. (right vector를 계산하기 위해 우리가 사용했던 up vector). GLM은 그러고나서 우리의 view matrix로서 사용가능한 LookAt matrix를 만든다.

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.f, 0.f, 3.f), glm::vec3(0.f, 0.f, 0.f), glm::vec3(0.f, 1.f, 0.f));

glm::LookAt 함수는 position, target, 그리고 up vector를 각각 요구한다. 이것은 이전의 튜토리얼에서 사용했던것과 같은 view matrix를 만든다.

user input에 들어가기 전에, 카메라를 scene 주위를 회전시키는 멋진 것을 해보자. 우리는 scene 타겟을 (0,0,0)으로 유지한다.

우리는 매 프레임마다, 원의 한 점을 나타내는 x, z 좌표를 만드는 삼각법을 조금 사용한다. 그리고 우리는 우리의 카메라 포지션에 대해 이것들을 사용할 것이다. x, y좌표들을 다시 계산하여, 우리는 원에 있는 모든 점들을 가로지를 것이고, 따라서 그 카메라는 scene의 주변을 회전한다. 우리는 미리 정의된 radius로 이 원을 확장하고 GLFW의 glfwGetTime 함수를 사용하여 매 render 반복마다 새로운 view matrix를 만들어낸다.

float radius = 10.f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
view = glm::lookAt(glm::vec3(camX, 0.f, camZ), glm::vec3(0.f, 0.f, 0.f), glm::vec3(0.f, 1.f, 0.f));

만약 너가 이 코드를 실행한다면, 너는 이것과 같은 것을 얻을 것이다.

이 작은 코드로, 카메라는 지금 시간에 따라 scene 주변을 회전한다. LookAt 행렬이 어떻게 작동하는지에 대해 감을 얻기위해 radius와 position/direction 파라미터로 실험을 자유롭게 해라. 또한, 만약 막힌다면 소스코드를 참고해라.

Walk around
scene 주변에서 카메라를 회전시키는건 재미있지만, 우리 스스로 움직이게 하는 것이 더 재미있다. 처음에 우리는 카메라 시스템을 설정할 필요가 있다. 그래서 우리의 프로그램의 맨 위에 카메라 변수들을 정의하는 것이 유용하다.

glm::vec3 cameraPos = glm::vec3(0.f, 0.f, 3.f);
glm::vec3 cameraFront = glm::vec3(0.f, 0.f, -1.f);
glm::vec3 cameraUp = glm::vec3(0.f, 1.f, 0.f);

LookAt 함수는 이제 이렇게 된다.

view = glm::LookAt(cameraPos, cameraPos + cameraFront, cameraUp);

처음에 우리는 이전에 정의된 cameraPos를 설정한다 . 그 방향은 현재 position과 우리가 방금 정의한 방향 벡터를 더한 것이다. 이것은 우리가 어떻게 움직이든, 카메라가 타겟 방향을 움직이도록 한다.  우리가 키를 눌렀을 때, cameraPos를 업데이트시켜서 이 변수들을 가지고 놀아보자.

우리는 이미 GLFW의 키보드 입력을 관리하는 processInput함수를 정의했었다. 그래서 체크할 몇 가지 새로운 키 커맨드를 더하자.


float cameraSpeed = 0.05f;
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
cameraPos += cameraSpeed * cameraFront;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
cameraPos -= cameraSpeed * cameraFront;
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;

우리가 WASD 키 중 하나를 누를 때 마다, 카메라의 위치는 그에 따라 업데이트 된다. 만약 우리가 앞으로 또는 뒤로 움직인다면, position 벡터로부터 방향벡터를 더하거나 뺀다. 만약 우리가 옆으로 가길 원한다면, right vector를 만들기 위해 외적을하고 right vector를 따라 움직인다. 이것은 카메를 사용할 때의 친숙한 strafe 효과를 만들어낸다.

Green Box
우리가 결과로 나타나는 right vector를 정규화하고 있다는 것에 주목해라. 만약 우리가 이 벡터를 정규화 하지 않는다면, 그 외적의 결과는 cameraFront 변수에 기반하여 다른 크기의 벡터를 반환할지도 모른다. 만약 우리가 그 벡터를 정규화 하지 않는다면, 우리는 일관된 움직임 속도 대신에 카메라의 방향을 따라서 느리거나 빠른 속도로 움직일 것이다.

이제, 너는 이미 카메라를 어느정도 움직였을 것이다. 비록 너가 cameraSpeed를 수정할 필요가 있는 시스템 특정한 속도였을지라도.

Movement speed
현재 우리는 걸어다닐 때 이동 속도로 상수값을 사용했다. 이론에서 이것은 괜찮아 보이지만, 실제로 사람들은 다른 연산력을 가진다. 그 결과로 몇 사람들은  다른 사람들보다 초당 더 많은 프레임을 그릴 수 있다. 한 사용자가 다른 사용자보다 더 많은 프레임을 그릴 수 있을 때 마다, 그는 또한 processInput을 종종 호출한다. 그 결과는 설정에 따라 몇 몇 사람들은 정말 빠르게 움직이고 몇몇은 정말 느리게 움직인 다는 것이다. 너의 프로그램을 보낼 때, 너는 그것이 같은 종류의 하드웨어에서 똑같이 작동하기를 보장하길 원한다.

그래픽스 프로그램과 게임들에서 보통 마지막 프레임을 렌더링하는데 걸린 시간을 저장하는 deltatime 변수를 추적한다. 우리는 그러고나서 모든 속도를 이 deltaTime 값으로 곱한다. 그 결과는 우리가 한 프레임에서 큰 deltaTime을 가졌을 때, 마지막 프레임이 평균보다 오래 걸렸다는 의미를 하면서, 그 프레임에 대한 속도는 또한 그것의 밸런스를 맞추기 위해 더 높을것이다. 이 접근법을 사용할 때, 너가 빠르거나 느린 pc를 가진 것은 중요하지 않다. 카메라의 속도는 그에 따라 균형이 맞춰질 것이다. 그래서 각 사용자들은 같은 경험을 갖게 될 것이다.

deltaTime 값을 계산하기 위해 우리는 두 개의 전역 변수들을 추적한다.

float deltaTime = 0.f; // Time between current frame and last frame
float lastFrame = 0.f; // Time of last frame

각 프레임 내에서 우리는 나중에 사용하기 위해 새로운 deltaTime 값을 계산한다.

float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

우리는 deltaTime을 가졌으므로, 우리는 속도를 계산할 때 그것을 고려할 수 있다.

void processInput(GLFWwindow *window)
{
float cameraSpeed = 2.5f * deltaTime;
....
}

이전 섹션에서와 함께, 우리는 이제 scene주변을 이동하는데 좀 더 부드럽고 좀 더 일관된 카메라 시스템을 갖게 될 것이다.

Look around
오직 키보드를 사용하여 주변을 돌아다니는 것은 흥미롭지 않다. 특히 우리가 주변을 돌 수 없기 때문에, 움직임을 오히려 제한적으로 만든다. 이제 마우스가 들어올 때 이다.

scene을 둘러보기 위해서, 우리는 마우스의 input에 기반하여 cameraFront 벡터를 바꿔야만 한다. 그러나, 마우스 방향을 기반으로 방향 vector를 바꾸는 것은 조금 복잡하고 삼각법을 요구한다. 만약 너가 삼각법을 이해하지 못한다면, 걱정하지 말아라. 너는 코드 부분을 뛰어 넘을 수 있고, 너의 코드에 그것들을 복사할 수 있다; 너는 항상 너가 더 알기를 원한다면 나중에 항상 되돌아 올 수 있다.

Euler angles
오일러 각들은 1700년대에 어딘가에서 레온하르트 오일러에 의해 정의된 3D에서 어떤 회전을 나타내는 3가지 값들이다. 세 가지 오일러 각들이 있다 : pitch, yaw and roll. 다음의 이미지는 그것들에게 시각적인 의미를 준다:

pitch는 첫 번째 이미지에서 보여지듯이, 우리가 얼만큼 위 아래로 보고있는지를 묘사하는 각이다. 두 번째 이미지는 우리가 왼쪽과 오른쪽을 보고있는 크기를 나타내는 yaw value를 보여준다. roll은 우리가 space-flight camera에서 대개 사용되듯이, 얼마나 roll(구르는지)를 나타낸다. 각각의 오일러 각은 단일이ㅡ 값으로 나타내지고 그것들 모두 3가지 조합으로, 우리는 3D에서 어떤 회전 벡터를 계산할 수 있다.

우리의 카메라 시스템에 대해, 우리는 오직 yaw와 pitch 값만을 신경쓴다. 그래서 우리는 여기에서 roll value에 대해 이야기 하지 않을 것이다. pitch와 yaw value가 주어진다면, 우리는 그것들을 변환하여, 새로운 방향 벡터를 나타내는 3D 벡터로 변환할 수 있다. 한 방향 벡터에 대해 yaw와 pitch값을 변환하는 프로세스는 조금의 삼각법을 요구하고, 우리는 기본적인 케이스로 시작한다.

만약 우리가 빗변의 길이가 1로 정의한다면, 우리는 삼각법으로부터 인접한 변의 길이를 알 수 있고 반대 편의 길이를 알 수 있다. 이것은 주어진 각을 기반으로 우리에게 x, y 두 방향에서 길이를 알아내는 일반적인 공식을 준다. 방향 벡터의 요소를 계산하기 위해 이것을 사용해 보자.

이 삼각형은 이전의 삼각형과 비슷해 보인다. 그래서 만약 우리가 xz면에 안자있고 y축을 향해서 바라본다고 시각화 한다면, 우리는 y 방향의 length / strength를 계산할 수 있다. (얼마나 많이 우리가 올려다보거나 내려다보는지를) 첫 번째 삼각형을 기반으로. 이 이미지로부터 우리는 주어진 pitck에 대한 결과 y 값이 sin(theta)와 같다는 것을 볼 수 있다

  direction.y = sin(glm::radians(pitch)); // Note that we convert the angle to radias first

여기에서 우리는 영향받은 y 값만은 업데이트하지만, 만약 너가 세심히 본다면, 너는 또한 x와 z 성분에 영향을 미칠 수 있다. 삼각형으로부터 우리는 그들의 값이 동일하다는 것을 알 수 있다.

  direction.x = cos(glm::radians(pitch));
  direction.z = cos(glm::radians(pitch));

yaw value에 대해서도 요구된 성분을 찾을 수 있는지 보자

pitch triangle 처럼, 우리는 x 요소가 cos(yaw) value 값에 의존하고, z 값이 또한 yaw value의 sin값에 의존한다는 것을 볼 수 있다. 이전의 값을 이것에 추가하는 것은 pitch와 yaw 값에 기반으로 한 최종 방향 벡터를 만든다.

  direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
  direction.y = sin(glm::radias(pitch));
  direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

이것은 우리에게 yaw와 pitch 값을 주위를 둘러볼 수 있도록 사용할 수 있는 3차원 방향벡터에 대해 바꿀 수 있는 공식을 준다. 너는 아마도 지금까지 궁금했었다 : 어떻게 우리는 이러한 yaw와 pitch 값들을 받는가?

Mouse input
yaw와 pitch값들을 마우스의 (또는 컨트롤러/조이스틱)의 움직임으로부터 얻어진다. 그리고 거기에서 수평 마우스 움직임은 yaw에 영향을 미치고, 수직 마우스 움직임은 pitch에 영향을 미친다. 그 아이디어는 마지막 프레임의 마우스 위치를 저장하고 현재 프레임에서 우리는 그 마우스값이 이전 프레임의 값과 비교하여 얼마나 바뀌었는지를 계산한다. 수평/수직 차이가 높으면 높을 수록, 우리는 pitch 또는 yaw값을 업데이트하고 그리고 따라서 그 카메라는 좀 더 움직여야 한다.

처음에 우리는 GLFW에게 그것이 커서를 숨기고 그것을 잡아야 한다고 말할 것이다. 커서를 capture하는 것은 그 프로그램이 focus를 가지기만 한다면, 마우스 커서가 윈도우 내에서 머무른다는 것을 의미한다. (만약 프로그램이 focus 또는 중단되지 않는다면). 우리는 이것을 한 가지 간단한 설정 호출로 할 수 있다:

 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

이 호출 후에, 우리가 마우스를 어디로 움직이든, 그것은 보이지 않을 것이고, 그것은 그것은 윈도우를 떠나지 않을 것이다. 이것은 FPS 카메라 시스템에 완벽한다.

pitch와 yaw값을 계산하기 위해서, 우리는 GLFW에게 마우스 움직임 event들을 듣도록 말할 필요가 있다. 우리는 이것을 다음의 프로토타입과 함께 callback function을 만들어서 한다.

  void mouse_callback(GLFWwindow* window, double xpos, double ypos);

여기에서 xpos와 ypos는 현재 마우스 위치를 나타낸다. 마우스가 움직일 떄 마다 우리가 GLFW로 callback 함수를 등록하자마자, 그 mouse_callback 함수가 호출된다:

 glfwSetCursorPosCallback(window, mouse_callback);

FPS 스타일 카메라를 위한 mouse input을 다룰 때, 결국에 방향 벡터를 가져오기전에  우리가 거쳐야 할 몇 가지 단계뜰이 있다.


  1. 지난 프레임 이후로 마우스의 이동량를 계산
  2. 카메라의 yaw와 pitch 값에 좌표 값을 더한다.
  3. 최대/최소 pitch 값에 대해 제약을 더한다.
  4. 방향 벡터를 계산한다.
첫 번째 단계는 지난 프레임 이후로 마우스의 이동량를 계산하는 것이다. 우리는 처음에 프로그램에서 이전의 마우스 위치를 저장해야만 한다. 그리고 우리는 그것을 스크린의 중심으로 설정한다. 초기에 (스크린 사이즈는 800x600이다.)

  float lastX = 400, lastY = 300;

그런 후에, mouse callback 함수에서, 우리는 지난 프레임과 현재 프레임 사이의 위치 이동거리를 계산한다.

  float xoffset = xpos - lastX;
  float yoffset = lastY - ypos; // reversed since y-coordinates range from bottom to top
  lastX = xpos;
  lastY = ypos;
  
  float sensitivity = 0.05f;
  xoffset *= sensitivitiy;
  yoffset *= sensitivity;

우리가 이동량 값에 sensitivity 값을 곱한다는 것에 주목해라. 만약 우리가 이 곱을 빠뜨린다면, 마우스 움직임은 너무 강할 것이다; 너가 원하는대로 민감도 값을 가지고 놀아라.

다음에 우리는 전역 선언된 pitch와 yaw 값들에 대한 이동거리 값을 더한다.

  yaw += xoffset;
  pitch += yoffset;

세 번째 단계에서 우리는 카메라에 몇 가지 제약을 더하고 싶다. 그래서 사용자들은 이상한 카메라 움직임을 할 수 없을 것이다. (또한 몇 가지 이상한 문제들을 방지한다.) pitch는 사용자가 89도 보다 높게 볼 수 없는 방식으로 제약되어 질 것이다. (90도에서 그 view는 반대가 되어버리는 경향이 있다, 그래서 우리는 우리의 제한으로 89를 고수한다.) 그리고 또한 아래로도 -89도 아래로 가 아니게끔. 이것은 사용자가 하늘을 쳐다보고 그의 발을 내려다 볼 수 있도록 하지만 더 이상 나아가게는 못한다. 그 제약은 결과값이 제약은 위반할 때 마다 그 결과값을 제약 값으로 대체하여 작동한다 :

  if(pitch > 89.0f)
    pitfh = 89.0f;
  if(pitch < -89.0f)
    pitch = -89.0f;

우리가 yaw value값에 어떠한 제약도 설정하지 않았다는 것에 주목해라. 왜냐하면 우리는 수평 회전에서 사용자에게 제약을 두길 원하지 않기 때문이다. 그러나, 너가 그렇게 하고 싶다면, 또한 yaw에 대해 제약을 더하는 것은 쉽다.

네 번째이자 마지막 단계는 이전 부분에서 이야기 했듯이 결과 yaw, pitch 값으로 부터 실제 방향 벡터를 계산하는 것이다:

glm::vec3 front;
front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
front.y = sin(glm::radians(pitch));
front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
cameraFront = glm::normalize(front);

이 계산된 방향 벡터는 그러고나서 마우스 움직임으로부터 계산된 모든 회전들을 포함한다. cameraFront 벡터는 이미 glm'의 lookAt 함수에 포함되기 때문에 우리는 할 준비가 되어있다.


만약 너가 지금 코드를 작동시킨다면, 너는 윈도우가 처음에 너의 마우스 커서에 focus를 받을 때 마다 매우 큰 갑작스러운 점프를 한다는 것을 볼 것이다. 갑작스러운 점프에 대한 원인은 너의 커서가 윈도우에 들어가자마자, 마우스 callback 함수가 너의 마우스가 screen에 들어간 위치와 동일한 xpos와 ypos 위치와 함께 호추된다는 것이다. 이것은 보통 스크린의 중심으로부터 꽤 먼거리의 위치이다. 그래서 이것은 큰 이동량을 만들어내고 따라서 큰 움직임의 jump가 만들어진다. 우리는 이 문제를 boolean 전역변수를 정의하여 해결할 수 있다. 우리가 마우스 input을 처음 받는 것인지 아닌지를 검사하기 위해서이다. 만약 처음 받는다면, 우리는 초기 마우스 위치를 새로운 xpos와 ypos 값을 업데이트 시킨다. 그 결과적으로 마우스 움직임은 입력된 마우스 위치 좌표를 그것의 이동량을 계산하기 위해 사용할 것이다.

if(firstMouse) // this bool variable is initially set to true
{
   lastX = xpos;
   lastY = ypos;
   firstMouse = false;
}

최종 코드는 그런 후에 이렇게 된다.


void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse) // this bool variable is initially set to true
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos;
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.05f;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw += xoffset;
    pitch += yoffset;

    if (pitch > 89.0f)
        pitch = 89.0f;
    else if (pitch < -89.0f)
        pitch = -89.0f;

    glm::vec3 front;
    front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
    front.y = sin(glm::radians(pitch));
    front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
    cameraFront = glm::normalize(front);
}

해냈어! 회전을 해보아라 그러면 너는 우리가 이제 자유롭게 우리의 3D scene을 관통하여 이동할 수 있다는 것을 볼 것이다.

Zoom
카메라 시스템의 작은 엑스트라로서, 우리는 zooming 인터페이스를 구현할 것이다. 이전 튜토리얼에서 우리는 Field Of Viwe 또는 fov가 우리가 얼마나 많은 scene을 볼 수 있는지를 정의한다고 말했었다.  fov가 더 작아질 때, scene의 사영된 공간은 더 작아지고, 확대되는 환영을 준다. zoom in하기 위해서, 우리는 마우스 스크롤 휠을 사용할 것이다. 마우스 움직임과 키도브 입력과 유사하게, 우리는 마우스 스크롤링에 대한 callback function을 가질 것이다.

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    if (fov >= 1.0f && fov <= 45.0f)
        fov -= yoffset;
    if (fov <= 1.0f)
        fov = 1.0f;
    if (fov >= 45.0f)
        fov = 45.0f;
}

스크롤링 할 때, yoffset 값은 우리가 수직으로 스크롤링 한 양을 나타낸다. scrool_callback 함수가 호출 되었을 때, 우리는 전역변수인 fov의 양을 바꾼다. 45.0f가 기본 fov value이기 때문에, 우리는 zoom level을 1.0과 45.0 사이에서 제약하길 원한다.

우리는 이제 각 render iteration마다 perspective projection matrix를 GPU에 업로드 해야한다. 그러나 이번에 그것의 field of view로서 fov 변수와 함께:

  projection = glm::perspective(glm::radians(fov), 800.0f, 600.0f, 0.1f, 100.0f);

그리고 마지막으로 스크롤 callback function을 등록하는 것을 잊지마라:

  glfwSetScrollCallback(window, scroll_callback);


그리고 너는 이제 해냈다. 우리는 3D 환경에서 자유로운 움직임을 하게 하는 간단한 카메라 시스템을 구현했다.

자유롭게 실험을해보아라. 그리고 만약 너가 막혔다면 너의 코드와 소스 코드를 비교해라.

  Green Box
   오일러 각을 사용한 카메라 시스템은 여전히 완벽한 시스템은 아니다. 너의 제얀과 설정에 따라, 너는 여전히 Gimbal lock을 만들어낼 수 있다. 가장 좋은 카메라 시스템은 quaternions을 이용하여 개발될 것이다. 그러나 우리는 그것을 나중에 주제로 남겨둘 것이다.

Camera class
다가오는 튜토리얼들에서, 우리는 항상 scene을 쉽게 둘러보기 위해서 카메라를 사용할 것이고 모든 각도로부터 결과들을 볼 것이다. 그러나, 카메라는 꽤 각 튜토리얼에서 어떤 공간을 차지하기 때문에, 우리는 세부사항으로부터 조금 추상화하고 몇몇 꽤 멋있는 엑스트라 기능과 함께 우리를 위해 대부분의 일을 하는 우리 자신의 카메라를 만들 것이다. 쉐이더 튜토리얼과 다르게 우리는 카메라 클래스를 만드는 것을 지도하지 않을 것이지만, 너에게 (완전히 주석이 달아진) 소스코드를 제공할 것이다. 만약 너가 내부 작동을 알기를 원한다면.

Shader object 처럼 우리는 그것을 전적으로 single header 파일에서 만든다. 너는 카메라 object를 here에서 찾을 수 있다. 너는 모든 코드를 지금 이해할 수 있어야 한다. 적어도 이것과 같은 카메라 객체를 너가 어떻게 만들 수 있는지 ㅇ라기 위해 클래스를 한 번 체크하는 것이 충고되어진다.

  Red Box
  우리가 도입한 카메라 시스템은 FPS같은 카메라인데 대부분의 목적에 부합하고 오일러 각과 잘 작동하지만, 비행 시뮬레이션 카메라 같은 다른 카메라 시스템을 만들 때는 주의해라. 각 카메라 시스템은 그것 자신의 까다로운 점을 가지고 있고 별난 점을 가지고 있다. 그래서 그것들을 읽어보도록 해라. 예를들어, 이 FPS 카메라는 pitch values가 90도 이상이 되는 것을 허용하지 않고, (0,1,0)의 static up vector가 우리가 roll value에 대해 고려할 떄는 작동하지 않는다.

새로운 카메라 object를 사용한 소스코드의 업데이트된 버전은 여기에서 볼 수 있다.

Exercise
  • 너가 날아다닐 수 없는 진짜 fps camera가 되도록 카메라 class를 바꿀 수 있는지 보아라; 너는 오직 xz 면에 머무르면서 주위를 둘러 볼 수 있다.
  • void chanCamera::ProcessKeyboard(Camera_Movement direction, float deltaTime)
    {
        float velocity = MovementSpeed * deltaTime;
        
        if (direction == Camera_Movement::FORWARD)
            Position += Front * velocity;
    
        if (direction == Camera_Movement::BACKWARD)
            Position -= Front * velocity;
    
        if (direction == Camera_Movement::LEFT)
            Position -= Right * velocity;
    
        if (direction == Camera_Movement::RIGHT)
            Position += Right * velocity;
    
        Position.y = 0;
    }
    

  • 이 튜토리얼의 시작부분에서 토론되었듯이, 너가 직접 view matrix를 만드는 너만의 LookAt function을 만들려고 해라. glm의 Lookat function을 너의 구현으로 대체하고, 그것이 여전히 똑같이 행동하는지 보아라


glm::mat4 chanCamera::GetViewMatrix()
{
    glm::vec3 zaxis = Position - (Position + Front);
    glm::vec3 xaxis = glm::normalize(glm::cross(WorldUp, zaxis));
    glm::vec3 yaxis = glm::cross(zaxis, xaxis);

    glm::mat4 result(1);
    result[0][0] = xaxis.x;
    result[1][0] = xaxis.y;
    result[2][0] = xaxis.z;
    result[0][1] = yaxis.x;
    result[1][1] = yaxis.y;
    result[2][1] = yaxis.z;
    result[0][2] = zaxis.x;
    result[1][2] = zaxis.y;
    result[2][2] = zaxis.z;
    result[3][0] = -glm::dot(xaxis, Position);
    result[3][1] = -glm::dot(yaxis, Position);
    result[3][2] = -glm::dot(zaxis, Position);

    return result;
    // return glm::lookAt(Position, Position + Front, WorldUp);
}

댓글 2개:

  1. 잘읽고갑니다.
    글씨폰트가 조금 더 컸었으면 가독성이 좋을꺼같네요.

    답글삭제
    답글
    1. 안녕하세요. 구글 블로거가 이용하기에 많이 어렵더라구요. 다음 블로그는 markdown 언어가 가능한 걸로 옮길 예정입니다

      삭제