Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

게임공장

[Learn OpenGL 번역] 2-9. 시작하기 - 카메라 본문

OpenGL

[Learn OpenGL 번역] 2-9. 시작하기 - 카메라

짱승_ 2018. 7. 15. 19:57

카메라

시작하기/카메라

  이전의 강좌에서 scene 주위를 움직이기(우리는 뒤로 약간 움직였었습니다) 위해 view 행렬을 사용하는 방법에 대해서 다루었습니다. OpenGL 자체는 카메라의 개념과 친숙하지 않습니다. 하지만 우리는 scene의 모든 오브젝트들을 반대 방향으로 이동시킴으로써 우리가 움직이는 것처럼 착시효과를 일으켜 시뮬레이션할 수 있습니다.


  이번 강좌에서 OpenGL에서 카메라를 세팅하는 방법을 다룰 것입니다. 3D scene을 자유롭게 이동할 수 있는 FPS 스타일의 카메라를 다룰 것입니다. 이번 강좌에서 키보드, 마우스 입력 또한 다룰 것이고 마지막으로 우리 임의의 카메라 클래스를 만드는 것으로 마칠 것입니다.

카메라/View space

  카메라/view space 에 애해서 언급했었을 때 우리는 scene의 원점에 있는 카메라의 시점에서 보이는 모든 vertex 좌표들에 대해서 이야기했었습니다. view 행렬은 카메라의 위치와 방향에 따라 world 좌표를 view 좌표로 변환합니다. 카메라를 정의하기 위해 world space에서 카메라의 위치가, 바라보고 있는 방향, 카메라의 오른쪽을 가리키는 벡터, 카메라의 위쪽을 가리키는 벡터가 필요합니다. 섬세한 독자들은 카메라의 위치를 원점으로 하고 3개의 수직인 축을 가지고 있는 좌표계를 만들 것임을 알아차릴 것입니다.


1. 카메라 위치

  카메라의 위치를 얻는 것은 쉽습니다. 카메라의 위치는 기본적으로 world space의 벡터입니다. 이 벡터는 카메라의 위치를 가리킵니다. 이전의 강좌에서 설정했던 카메라의 위치와 같은 위치로 설정합니다.


glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);  
  z 축의 양의 방향은 화면에서 여러분쪽으로 가리키는 것을 잊지 마세요. 그래서 카메라의 위치를 뒤로 옮기려면 z 축의 양의 방향쪽으로 이동시켜야 합니다.

2. 카메라 방향

  필요한 다음 벡터는 카메라의 방향입니다. 카메라가 가리키는 방향입니다. 우선은 카메라가 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);
  뱡향 벡터의 이름은 알맞지 않습니다. 실제로는 카메라가 보고있는 방향의 반대 방향을 가리키고 있기 때문입니다.

3. 오른쪽 축

  우리가 필요한 다음 벡터는 카메라 space에서 x 축의 양의 방향을 나타내는 오른쪽 벡터입니다. 오른쪽 벡터를 얻기 위해 먼저 (world space에서)위쪽을 가리키는 위쪽 벡터를 지정하여 약간의 트릭을 사용합니다. 그런 다음 위쪽 벡터와 2 번째 단계의 방향 벡터를 외적합니다. 외적의 결과는 두 벡터와 수직인 벡터이므로 x 축에 대해 양의 방향을 가리키는 벡터를 얻을 것입니다.


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

4. 위쪽 축

  이제 우리는 x 축 벡터와 z 축 벡터를 얻었습니다. 카메라에대해 y 축의 양의 방향을 가리키는 벡터를 찾는 것은 쉽습니다. 오른쪽 벡터와 방향 벡터를 외적하면 얻을 수 있습니다.


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

  외적과 약간의 트릭을 이용하여 view/카메라 space를 형성하는 모든 벡터들을 생성할 수 있었습니다. 수학적으로 좀 더 알고 싶은 독자들을 위해 이 과정은 선형대수학에서 Gram-Schmidt이라고 불립니다. 이 카메라 벡터들을 사용하여 우리는 이제 카메라를 생성하는 데에 매우 유용한 LookAt 행렬을 생성할 수 있습니다.

Look At

  행렬에 대한 좋은 점은 만약 3개의 직각(또는 비선형)인 축을 사용하여 좌표 space를 만들면 3개의 축과 이동 벡터와 함께 행렬을 만들 수 있고 어떠한 벡터든지 이 행렬과 곱하여 이 좌표 space로 변환할 수 있다는 것입니다. 이것이 정확히 LookAt 행렬이 수행하는 것입니다. 그리고 이제 우리는 카메라 space를 정의하기 위한 3개의 직각인 축과 위치 벡터를 가지고 있으므로 우리만의 LookAt 행렬을 만들 수 있습니다.



  R은 오른쪽 벡터, U 는 위쪽 벡터, D는 방향 벡터, P 는 카메라의 위치 벡터입니다. 위치 벡터가 반대로 되어있음을 알아두세요. 우리는 결국 world를 우리가 이동할 곳의 반대 방향으로 이동시키기를 원하기 때문입니다. 이 LookAt 행렬을 우리의 view 행렬로서 사용하여 효과적으로 모든 world 좌표들을 우리가 방금 정의한 view space로 변환할 수 있습니다. 그런 다음 이 LookAt 행렬은 정확히 주어진 타겟을 바라보고(look) 있는 view 행렬을 생성합니다.


  우리에게 운좋게도 GLM은 이미 우리를 위해 이 작업들을 수행했습니다. 우리는 단지 카메라 위치와 타겟 위치, world space의 위쪽을 나타내는 벡터(오른쪽 벡터를 계산해내기 위해 사용)를 지정해주기만 하면 됩니다. 그러면 GLM이 우리가 view 행렬로서 사용할 수 있는 LookAt 행렬을 생성해줍니다.


glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), 
  		   glm::vec3(0.0f, 0.0f, 0.0f), 
  		   glm::vec3(0.0f, 1.0f, 0.0f));

  glm::LookAt 함수는 위치, 타겟, 위쪽 벡터를 각각 원합니다. 이 것은 이전의 강좌에서 사용했던 것과 같은 view 행렬을 생성합니다.


  유저의 입력을 다루기 전에 우리의 scene 주위를 카메라가 도는 조금 펑키한 것을 해봅시다. scene의 타겟은 (0,0,0)으로 유지합니다.


  우리는 약간의 삼각법을 사용하여 각 프레임에서 원을 따라 가리키는 x, z 좌표를 생성합니다. 우리는 이 좌표를 카메라 위치로 사용할 것입니다. x, z 좌표를 재계산함으로써 원에대한 모든 지점을 가로질러 카메라가 scene 주위를 돌 수 있습니다. 우리는 이 원을 미리 정의된 radius을 사용하여 확장시킬 수 있고 렌더링 루프가 돌때마다 GLFW의 glfwGetTime 함수를 사용하여 새로운 view 행렬을 생성할 수 있습니다.


float radius = 10.0f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
glm::mat4 view;
view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));  

  이 코드를 실행하면 다음과 같은 결과를 볼 수 있을 것입니다.



  이 짧은 코드로 이제 카메라는 시간이 지남에 따라 scene 주위를 돌게 됩니다. radius와 위치/뱡향 파라미터를 수정하여 이 LookAt 행렬이 어떻게 동작하는지 느껴보세요. 또한 문제가 소스 코드를 확인해보세요.

돌아다니기

  카메라가 scene 주위를 도는 것은 재밌습니다. 하지만 모든 움직임을 우리 스스로 할 수 있는 것이 좀 더 재미있습니다. 먼저 우리는 카메라 시스템을 세팅해야 합니다. 그래서 프로그램의 맨 위에 카메라 변수들을 정의해 놓은 것이 유용합니다.


glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f);

  이제 LookAt 함수는 다음과 같습니다.


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

  먼저 전에 정의한 cameraPos로 카메라 위치를 설정합니다. 방향은 현재 위치 벡터 + 방금 정의한 방향 벡터입니다. 이는 우리가 움직이더라도 카메라는 타겟 방향을 바라보도록 유지합니다. 키를 눌렀을 때 cameraPos 백터를 수정해봅시다.


  우리는 이미 GLFW의 키보드 입력을 관리하기 위해 processInput 함수를 정의하였습니다. 그래서 확인할 새로운 키 커맨드를 추가해봅시다.


void processInput(GLFWwindow *window)
{
    ...
    float cameraSpeed = 0.05f; // adjust accordingly
    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 키 중 하나를 누를때마다 카메라의 위치는 그에 따라 수정됩니다. 우리가 앞이나 뒤로 가기를 원할 때 위치 벡터에서 방향벡터를 더하거나 뺍니다. 옆으로 이동하고 싶을 때는 외적하여 오른쪽 벡터를 생성하고 오른쪽 벡터를 따라 이동합니다. 이렇게하면 카메라 사용시 익숙한 strafe가 만들어집니다.

  결과 오른쪽 벡터를 정규화한다는 것을 알아두세요. 이 벡터를 정규화하지 않으면 cameraFront 변수 때문에 외적의 결과가 다른 크기의 벡터가 될 수 있습니다. 벡터를 정규화하지 않으면 같은 속도가 아니라 카메라의 방향에 따라서 다르게 느리거나 빠르게 이동하게 됩니다.

  이제 여러분은 시스템에 따라 다른 속도이기는 하지만 이미 카메라를 움직일 수 있을 것입니다. 하지만 여러분은 cameraSpeed를 조정하여 움직이는 속도를 조정할 수 있습니다.

이동 속도

  현재 우리는 움질일 때 이동 속도로 상수값을 사용했습니다. 이론상으로는 괜찮아보이지만 실제로는 사람들은 각자 다른 프로세싱 파워를 가지고 있고 그 결과 어떠한 사람들은 다른 사람보다 더 많은 프레임을 그릴 수 있게됩니다. 유저가 다른 유저보다 더 많은 프레임을 그릴 때마다 processInput 함수를 더 자주 호출하게 됩니다. 그 결과 그들의 성능에 따라서 어떠한 사람들은 정말 빠르게 이동할 것이고 어떠한 사람들이 정말 느리게 이동할 것입니다. 응용 프로그램을 배포할 때 여러분은 모든 종류의 하드웨어에서 동일하게 실행되도록 해야합니다.


  그래픽 응용 프로그램과 게임은 일반적으로 마지막 프레임을 렌더링는 데에 걸리는 시간을 저장하는 deltaTime 변수를 기록합니다. 그런 다음 우리는 모든 속도들에 이 deltaTime 값을 곱합니다. 그 결과 프레임에서 긴 deltaTime을 가질 때(즉, 마지막 프레임이 렌더링하는 데에 걸린 시간이 평균보다 길었을 때) 프레임에서의 속도는 균형을 맞추기 위해 약간 증가합니다. 이 방법을 사용하면 여러분이 아주 느리거나 빠른 pc를 가지고 있어도 카메라의 속도는 균형을 맞추어 다른 유저들과 같은 경험을 할 수 있도록 할 것입니다.


  deltaTime 값을 계산하기 위해 2개의 전역 변수를 기록합니다.


float deltaTime = 0.0f;	// 마지막 프레임과 현재 프레임 사이의 시간
float lastFrame = 0.0f; // 마지막 프레임의 시간

  각 프레임마다 우리는 새로운 deltaTime 값을 계산할 수 있습니다.


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

  이제 우리는 속도들을 계산할 때 고려할 수 있는 deltaTime을 가지고 있습니다.


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

&nbps; 이전 섹션과 함께 이제 우리는 scene을 돌아다님에 있어서 좀 더 부드럽고 일관된 카메라 시스템을 볼수 있습니다.



  이제 우리는 모든 시스템에서 똑같은 빠르기로 걸어다니고 볼 수 있는 카메라를 가지고 있습니다. 문제가 생겼다면 소스 코드를 확인해보세요. 모든 움직임에 관해서 deltaTime 값이 자주 나오는 것을 볼 수 있을 것입니다.

둘러보기

  키보드를 이용하여 움직이기만 하는 것을 그다지 흥미롭지 않습니다. 특히 주위를 둘러볼 수 없기 때문에 움직임이 다소 제한적입니다. 여기에 마우스를 추가해봅시다!


  scene의 주위를 둘러보기 위해 우리는 마우스 입력에 따라 cameraFront 벡터를 수정해야합니다. 하지만 마우스 회전에 따라 방향 벡터를 수정하는 것은 약간 복잡하며 약간의 삼각법 지식을 요구합니다. 삼각볍을 이해하지 못했다면 걱정하지 마세요. 코드 섹션을 넘기고 여러분에 코드에 붙여넣으세요. 여러분이 좀더 알고 싶을 때마다 언제든지 돌아와서 다시 볼 수 있습니다.

오일러 각(Euler angles)

  오일러 각은 1700년대에 Leonhard Euler에 의해서 정의된 3D 상에서의 모든 회전을 나타낼 수 있는 3개의 값입니다. 여기에 3개의 오일러 각이 있습니다: pitch, yaw, roll. 다음 이미지는 이들이 시각적으로 의미하는 것을 나타냅니다.



pitch는 첫 번째 이미지에서 볼 수 있듯이 우리가 위나 아래를 어느 정도만큼 둘러볼 것인가에 대해 묘사합니다. 두 번째 이미지는 yaw 값을 보여주는데 이 값은 우리가 왼쪽이나 오른쪽을 둘러보는 정도를 나타냅니다. roll은 space-flight 카메라에서 주로 사용되는 roll(예를 들어 비행기가 바닥이 하늘을 향하도록 회전)을 얼마나 할 것인지에 대해 나타냅니다. 각각의 오일러 각은 하나의 값으로 나타나고 3개의 값을 사용하여 3D 상의 모든 회전 벡터를 계산할 수 있습니다.


  우리 카메라 시스템을 위해 우리는 yaw와 pitch 값만 고려할 것이므로 여기서 roll 값은 다루지 않겠습니다. pitch와 yaw가 주어지면 우리는 그들을 새로운 방향 벡터를 나타내는 3D 벡터로 변환할 수 있습니다. yaw와 pitch 값을 방향 벡터로 변환하는 것은 약간의 삼각법을 필요로 합니다. 아주 기본적인 예제로 시작해보도록 하겠습니다.



  우리가 빗변의 길이를 1로 정의했다면 우리는 삼각법에 의해서 인접한 변의 길이가 cos  x/h=cos x/1=cos  x 이고 반대편 변의 길이는 sin  y /h=sin  y /1=sin  y 임을 알 수 있습니다. 이는 우리에게 각에 의해서 x 방향과 y 방향의 길이를 구할 수 있는 일반적인 공식을 줍니다. 방향 벡터의 요소들을 계산하기 위해 이 공식을 써봅시다.



  이 삼각형은 이전의 삼각형과 비슷합니다. 우리가 xz 평면위에 앉아있고 y 축을 향하여 바라보고 있다고 생각해보세요. 이 때 우리는 첫 번째 삼각형을 기반으로 하여 y 방향의 길이와 크기(우리가 위 아래를 둘러보는 정도)를 계산할 수 있습니다. 위 이미지로부터 우리는 y 값이 주어진 pitch에 대한 sin  θ 와 같다는 것을 알 수 있습니다.


direction.y = sin(glm::radians(pitch)); // 먼저 각을 radian으로 변환해야 한다는 것을 알아두세요.

  여기에서 우리는 y 값만 수정하지만 주의 깊게 살펴보면 x, z 요소 또한 수정할 수 있습니다. 이 삼각형으로 부터 이들의 값은 다음과 같습니다.


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

  yaw 값을 위해 필요한 요소들을 찾을 수 있는지 살펴봅시다.



  pitch 삼각형과 마찬가지로 우리는 x 요소가 cos(yaw) 값에 따라 다르다는 것을 볼 수 있고 z 값은 yaw 값의 sin 값에 따라 다르다는 것을 알 수 있습니다. 이전의 결과 값에서 이 값들을 추가하여 pitch와 yaw 값에 의한 최종 방향 벡터를 구할 수 있습니다.


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

  이는 yaw와 pitch 값을 주변을 둘러볼 수 있도록 해주는 3차원 방향 벡터로 변환해주는 공식입니다. 이제 이 yaw와 pitch 값을 어떻게 얻는지가 궁금할 것입니다.

마우스 입력

  yaw와 pitch 값은 마우스(또는 컨트롤러/조이스틱) 움직임에서 얻을 수 있습니다. 수평에 대한 마우스 움직임은 yaw에 영향을 끼치고 수직에 대한 마우스 움직임은 pitch에 영향을 끼칩니다. 아이디어는 마지막 프레임의 마우스 위치를 저장하고 현재 프레임에서 마지막 프레임에서의 값과 현재 프레임에서의 값을 비교하여 마우스의 값이 얼마나 많이 바뀌었는지를 계산하는 것입니다. 수평/수직으로 더 많이 높아졌을 수록 pitch나 yaw 값이 더 크게 업데이트됩니다. 따라서 카메라는 더욱 더 많이 움직여야 합니다.


  먼저 우리는 GLFW에게 커서를 숨기고 capture해야 한다고 말할 것입니다. 커서를 Capture한다는 것은 응용 프로그램이 포커스되면 마우스 커서를 윈도우 창 안에 가두어 놓는 것을 의미합니다(응용 프로그램이 종료되거나 포커스를 잃을때까지). 우리는 이를 하나의 간단한 설명 명령으로 수행할 수 있습니다.


glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);  

  이 명령을 호출한 후에 우리가 마우스를 움직일 때마다 보이지 않고 윈도우 창을 떠날 수 없을 것입니다. 이는 완벽히 FPS 카메라 시스템을 위한 것입니다.


  pitch와 yaw 값을 계산하기 위해 GLFW에게 마우스 움직임 이벤트를 듣고 있으라고 말해야합니다. 다음 과같은 프로토타입의 콜백 함수를 생성함으로써 이를 수행할 수 있습니다.


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

  여기에서 xposypos는 현재 마우스의 위치를 나타냅니다. GLFW에 이 콜백 함수를 등록핳기만 하면 마우스가 움직일 때마다 mouse_callback 함수가 호출됩니다.


glfwSetCursorPosCallback(window, mouse_callback);  

  FPS 스타일 카메라를 위해 마우스 입력을 관리할 때 방향 벡터를 구하기 전에 우리가 거쳐야할 여러 단계들이 있습니다.

  1. 마지막 프레임부터의 마우스 offset을 계산합니다.
  2. 카메라의 yaw와 pitch 값에 offset 값을 더합니다.
  3. pitch 값에 최댓값/최솟값을 설정합니다.
  4. 방향 벡터를 계산합니다.

  첫 번째 단계는 마지막 프레임으로부터의 마우스 offset을 구하는 것입니다. 먼저 응용 프로그램에서의 마지막 마우스 위치를 저장해야 합니다. 마우스의 위치는 처음에 화면의 중앙(화면 크기는 800 x 600)으로 초기화시킵니다.


float lastX = 400, lastY = 300;

  그런 다음 마우스의 콜백 함수에서 마지막 프레임과 현재 프레임 사이의 움직임 offset을 계산합니다.


float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // y 좌표의 범위는 밑에서부터 위로가기 때문에 반대로 바꿉니다.
lastX = xpos;
lastY = ypos;

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

  offset 값을 sensitivity 값과 곱한다는 것을 알고 계세요. 우리가 이작업을 하지 않으면 마우스 움직임이 아주 커질 것입니다. 마음에 드는 감도로 주위를 둘러보기 위함입니다.


  그런 다음 우리는 전역으로 선언한 pitch, yaw 값에 offset 값을 더합니다.


yaw   += xoffset;
pitch += yoffset;  

  세 번째 단계에서 우리는 카메라에 제한사항을 추가하여 사용자가 이상한 카메라 움직임을 할 수 없게 할 것입니다(또한 이상한 문제들을 예방). pitch는 89도 이상으로 위를 볼 수 없도록 제한할 것입니다(90도 에서는 시점이 반대로 돌아갈 수 있습니다). 또한 -89도 밑으로도 허용하지 않을 것입니다. 이는 사용자가 위로 하늘을 볼 수 있고 밑으로 자신의 발을 볼 수 있지만 그 이상은 보지 못하게 합니다. 제한 작업은 이 제한조건을 위반하였을 때마다 위반한 값을 대체하는 것으로 수행될 수 있습니다.


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

  수평으로의 회전은 제한을 두고 싶지 않기 때문에 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 함수에 포함되어 있기 때문에 세팅하러 가봅시다.


  지금 코드를 실행해본다면 여러분의 마우스 커서 포커스를 윈도우가 처음 받을때마다 카메라가 갑자기 크게 점프 하는 것을 볼 수 있을 것입니다. 이 것의 원인은 여러분의 커서가 윈도우 창에 들어가자마자 화면에서의 마우스 위치가 마우스 콜백 함수의 xpos, ypos 파라미터로 들어가기 때문입니다. 이는 일반적으로 화면의 중앙과 멀리 떨어져있는 위치이기 때문에 큰 offset을 생성하여 크게 점프하는 것입니다. 간단히 전역 bool 변수를 선언하여 이 문제를 피할 수 있습니다. 이 변수는 마우스 입력이 처음으로 들어온 것인지 확인하고 그렇다면 먼저 마우스 위치의 초기값을 새로운 xpos, ypos 값으로 수정합니다. 결과적으로 마우스 움직임의 offset을 계산하기 위해 윈도우 창에 들어온 마우스 위치 좌표를 사용할 수 있습니다.


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)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
  
    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; 
    lastX = xpos;
    lastY = ypos;

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

    yaw   += xoffset;
    pitch += yoffset;

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

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

됐습니다! 이제 3D scene 에서 자유롭게 돌아다닐 수 있습니다!

줌(확대 / 축소)

  카메라 시스템의 확장으로서 줌 인터페이스를 구현할 수 있습니다. 이전의 강좌에서 Field of view 혹은 fov이 scene에서 우리가 얼마나 볼 수 있는지를 정의하는 것이라고 언급했었습니다. field of view가 작아지면 scene projected space는 작지기 때문에 zoom in 되는 것같은 착시효과를 줄 수 있습니다. zoom in을 하기 위해 마우스 스크롤 휠을 사용할 것입니다. 마우스 움직임, 키 입력과 마찬가지로 마우스 스크롤에 대한 콜백 함수가 있습니다.


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 값이 우리가 수직으로 스크롤한 정도를 나타냅니다. scroll_callback 함수가 호출될 때 전역으로 선언된 fov 변수의 내용을 수정합니다. 45.0f가 디폴트 fov 값이기 때문에 우리는 zoom level을 1.0f45.0f 사이로 제한할 것입니다.


  이제 우리는 루프가 돌때마다 GPU에 perspective prjection 행렬을 업로드 해야합니다. 이번에는 fov 변수를 field of view 값으로서 사용할 것입니다.


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

  그리고 마지막으로 스크롤 콜백 함수를 등록하는 것을 잊지마세요.


glfwSetScrollCallback(window, scroll_callback); 

  이제 됐습니다. 3D 환경에서 자유롭게 움직일 수 있는 간단한 카메라 시스템을 구현했습니다.



  여러가지로 실험해보세요. 그리고 문제가 생겼다면 소스 코드를 확인해 보세요.

  오일러 각을 사용하는 카메라 시스템은 여전히 완벽한 시스템이 아닙니다. 제한 조건이나 여러분의 세팅에 따라 Gimbal lock 문제를 야기시킬 수 있습니다. 최고의 카메라 시스템은 사원수(quaternions)를 사용하여 개발된 것이지만 나중에 다루도록 하겠습니다.

카메라 클래스

  다가오는 강좌에서 우리는 항상 scene을 쉽게 둘러보고 결과를 모든 각도에서 볼 수 있도록 카메라를 사용합니다. 하지만 카메라는 각 강좌마다 약간의 공간을 차지하기 때문에 세부사항을 약간 추상화하여 우리만의 카메라 오브젝트를 만들어 대부분의 일을 약간의 추가사항과 함께 깔끔하게 수행할 것입니다. shader 강좌와는 다르게 카메라 클래스를 생성하는 것에 관해 상세히 설명드리지 않을 것이지만 내부적으로 어떠한 작업이 이루어지는지 알 수 있도록 (주석이 완벽히 달린) 소스 코드를 제공할 것입니다.


  shader 객체와 마찬가지로 전체적인 하나의 헤더파일을 생성합니다. 카메라 객체는 여기에서 확인할 수 있습니다. 이제 이 모든 코드를 이해할 수 있어야 합니다. 카메라 오브젝트를 이런식으로 생성하는 방법을 확인하기 위해 최소한 한번은 살펴보는 것을 권장합니다.

  우리가 설명한 카메라 시스템은 FPS 비슷한 카메라입니다. 이는 대부분 오일러 각과 함께 대부분의 목적과 작업에 맞습니다. 하지만 비행기 시뮬레이션 카메라와 같은 다른 카메라 시스템을 만들 때는 주의하세요. 각각의 카메라 시스템은 그들만의 트릭과 별난 점을 가지고 있으므로 많은 것을 공부해야 합니다. 예를 들어 이 FPS 카메라는 pitch 값을 90 각도 이상으로 높일 수 없고 roll 값을 고려할 때 위쪽 벡터 (0,1,0)가 동작하지 않습니다.

  새로운 카메라 오브젝트를 사용한 업데이트된 버전의 코드는 여기에서 볼 수 있습니다.

연습

  • 하늘을 날 수 없는 진짜 fps 카메라를 만들 수 있는지 확인하세요. 여러분은 항상 xz평면 위에 있어야 합니다: 해답
  • 여러분만의 LookAt 함수를 만들어 보세요. 이 함수는 수동으로 이 강좌의 처음에 다룬 view 행렬을 만듭니다. glm의 LookAt 함수를 여러분이 구현한 것으로 대체하여 동일하게 동작하는지 확인해보세요: 해답.



출처 : https://learnopengl.com, Joey de Vries

반응형