본문 바로가기

OpenGL Study

OpenGL "Camera"

[CAMERA]

1. Camera / View Space

- view matrix는 카메라의 위치와 방향에 따라 world coordinate를 view coordinate로 변환한다

- 위 이미지는 카메라를 정의히기 위해 필요한 world space에서의 카메라 위치/ 카메라가  바라보고 있는 방향/카메라의 오른쪽/카메라의 위쪽을 가리키는 vector를 시각화한 것이다

: 카메라의 위치를 원점으로, 3개의 수직인 축을 가지는 좌표계를 구축한다

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

- 카메라의 위치를 설정하는 코드로, 이전 장에서 설정했던 카메라의 위치와 동일하게 설정한다

: z축 양의 방향은 화면에서 사용자쪽을 가리키므로, 카메라를 뒤로 옮기려면 z축 양의 방향으로 이동시켜야한다

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

- 카메라의 방향을 설정하는 코드로, 카메라가 보고 있는 방향의 반대 방향을 가리킨다

: 우선 카메라가 scene의 원점 (0, 0, 0)을 가리키게 한다

: 카메라 위치 vector에서 scene의 원점 vector를 빼 z축 양의 방향을 가리키는 방향 vector를 계산한다

(OpenGL 기본 연산은 scene의 원점 vector에서 카메라 위치 vector를 빼 z축 음의 방향을 가리키지만, View matrix에서는 z축 양의 방향을 사용한다)

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

- 카메라의 오른쪽 축을 설정하는 코드로, camera space에서 x축 양의 방향을 가리키는 vector이다

: 먼저 world space에서 위쪽을 가리키는 vector를 지정하여 계산한다

: 위쪽  vector와 방향 vector를 외적하여 x축 양의 방향을 가리키는 vector를 구한다

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

- 카메라의 위쪽 축을 설정하는 코드로, camera space에서 y축 양의 방향을 가리키는 vector이다 

: 오른쪽 축 vector와 방향 vector를 외적하여 계산한다

 

2. Look At

- LookAt matrix의 역할

: 3개의 직각(or 비선형)인 축을 사용하여 coordinate space를 만들면 3개의 축과 transform vector를 사용해 행렬을 만들 수 있다

: 어떠한 vector든지 위 행렬과 곱하면 해당 coordinate space로 변환할 수 있다

=> matrix의 장점

- About LookAt matrix

: R은 오른쪽  vector, U는 위쪽 vector, D는 방향 vector, P는 카메라의 위치 vector이다 (위치 vector는 반대로 되어있다)

: LookAt matrix를 사용자의 View matrix로서 사용하여 효과적으로 모든 world coordinate를 View space로 변환할 수 있다

: 이후 LookAt matrix는 지정된 target을 바라보고 있는 View matrix를 생성한다

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(위치 vector, 타겟 vector, 위쪽vector)

: GLM의 내장 함수인 lookAt을 사용하여 LookAt matrix를 생성한다

const 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 주위로 카메라가 돌게 하는 코드이다

: scene의 target은 (0, 0, 0)으로 유지한다

: 원 위의 점을 나타내는 각각의 frame을 x coordinate와 z coordinate로 만들기 위해 삼각법을 사용한다 (이 coordinate들을 카메라 위치로 사용한다)

:  시간이 지남에 따라 x coordinate와 z coordinate를 다시 계산함으로써 원에 대한 모든 지점을 가로질러 카메라가 scene 주위를 돌 수 있게 한다

: 이 원은 미리 정의된 radius를 사용하여 확장시킬 수 있으며, render loop가 돌 때마다 glfwGetTime( ) 함수를 사용하여 각 frame마다 새로운 View matrix를 생성한다

 

[WALK AROUND]

1. Camera System Setting

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);

- camera system setting을 편리하게 하기 위해 프로그램의 맨 위에 카메라 변수들을 정의한다

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

- 위에서 선언한 변수들을 사용해 LookAt 함수를 정의한다

: cameraPos 변수로 카메라 위치를 설정한다

: cameraPos + cameraFront 연산을 통해 카메라의 방향을 설정한다 (사용자가 이동하더라도 카메라는 target을 바라보도록 유지한다)

: cameraUp 변수로 카메라의 위쪽을 설정한다

void processInput(GLFWwindow *window)
{
    ...
    const 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;
}

- 이전에 사용했던 GLFW의 키보드 입력을 관리하는 함수인 processinput을 통해 새로운 key command를 추가한다

- W/A/S/D key 중 하나를 누를 때마다 카메라의 위치가 그에 맞게 수정된다

: 앞/뒤로 이동할 때에는 위치 vector에서 방향 vector를 더하거나 빼고, 옆으로 이동할 때에는 외적하여 오른쪽 vector를 생성하고 이에 따라 이동한다

=> 이 과정들은 카메라를 사용할 때 익숙한 strafe effect를 만든다

+) 결과 오른쪽 vector를 정규화하지 않으면 cameraFront 변수 때문에 외적의 결과가 다른 크기의 vector를 반환할 수 있다. 또한 이동 속도가 일정하지 않고 카메라 방향에 따라 다른 속도로 이동할 수 있다

 

2. Movement Speed

- 그래픽 응용 프로그램과 게임은 일반적으로 마지막 frame을 render하는 데에 걸리는 시간을 저장하는 delta Time 변수를 기록한다

: 그 후 모든 속도들에 delta Time 값을 곱해 카메라 속도의 균형을 맞춘다

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

- delta Time 값을 계산하기 위해 2개의 전역 변수를 선언한다

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

- 각 frame마다 새로운 delta Time 값을 계산한다

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

- 카메라 속도에 delta Time 값을 곱해 속도를 일정하게 맞춰준다

: 이 코드를 통해 카메라는 초당 2.5단위의 일정한 속도로 움직인다

 

[LOOK AROUND]

1. Euler Angles (오일러 각)

- 오일러 각은 3D 상에서의 모든 회전을 나타낼 수 있는 3개의 값이다

: 위 이미지는 오일러 각 중 pitch, yaw, roll 3가지를 각각 시각화한 것이다

- Pitch

: 사용자가 위나 아래를 보고있는 정도를 나타내는 각도이다

- Yaw 

: 사용자가 왼쪽이나 오른쪽을 보고있는 크기를 나타내는 각도이다

- Roll

: space-flight camer에서 주로 사용되는 roll, 예를 들면 비행기의 바닥이 하늘을 향하도록 회전하는 정도를 나타내는 각도이다

=> 각각의 오일러 각은 단일 값으로 표현되고, 3개의 값을 사용하여 3D 상의 모든 회전 vector를 계산할 수 있다

 - camera system의 경우 yaw와 pitch값만 고려하므로, roll 값은 제외하고 설명한다

: yaw와 pitch 값이 주어지면 새로운 방향 vector를 나타내는 3D vector로 변환할 수 있다

- yaw와 pitch 값을 방향 vector로 변환하는 과정은 약간의 삼각법을 필요로 한다

- 사용자가 빗변의 길이를 1로 정의했다면

  삼각법을 통해 인접한 변의 길이는  cos x/h = cos x/1 = cos x이고,

  반대편 변의 길이는 sin y/h = sin y/1 = sin y이다

: 이를 통해 주어진 각도에 따라 직각 삼각형의 x 축과 y 축의 길이를 구하는 일반적인 공식을 얻을 수 있다

: 이 공식들을 사용해 방향 vector의 요소들을 계산한다

 

 

 

- yaw 각도를 x축에서 시작하는 시계 반대 방향 각도로 시각화하면 x축의 길이가 cos(yaw)와 관련이 있음을 알 수 있다

: 마찬가지로 z축의 길이가 sin(yaw)와 관련이 있음을 알 수 있다

이 삼각법 지식과 주어진 yaw 값을 사용해 camera diection vector를 만들 수 있다

 

 

 

glm::vec3 direction;
direction.x = cos(glm::radians(yaw)); // Note that we convert the angle to radians first
direction.z = sin(glm::radians(yaw));

- Yaw 값을 이용해 3D direction vector를 구하는 코드이다

 

 

 

- Yaw 값을 이용해 3D direction vector를 구할 수 있지만, pitch 값도 포함되어야한다

: 위 이미지를 통해 y 값은 주어진 pitch 값에 대한 sin θ와 같다는 것을 알 수 있다 

 

 

 

 

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

 

- 이 코드는 사용자가 주변을 둘러볼 때 사용할 수 있는 3D directino vector로 Yaw와 Pitch 값을 변환하는 공식을 제공한다

: Yaw 값과 Pitch 값을 이용해 오일러 각도에서 변환된 최종 direction vector를 얻을 수 있다

yaw = -90.0f;

- 지금까지 모든 것이 음의 z축 방향으로 배치되도록 scene world를 설정했지만, x와 z Yaw 삼각형을 통해 θ가 0이면 카메라의 direction vector가 양의 x축을 가리키고 있다는 것을 알 수 있다

: 카메라가 기본적으로 음의 z축을 가리키도록 하기 위해 Yaw에 시계 방향으로 90도 회전하는 기본값을 지정한다

: +각(양의 값)은 시계 반대 방향으로 회전하므로 기본 Yaw 값을 위와 같이 설정한다

 

2. Mouse Input

- Yaw와 Pitch의 값은 마우스 움직임에서 얻을 수 있다

: 수평에 대한 움직임은 Yaw 값에 영향을 끼치고, 수직에 대한 움직임은 Pitch 값에 영향을 끼친다

: 마우스 값이 얼마나 변경되었는지를 계산하기 위해, 마지막 frame의 마우스 위치를 저장하고 현재 frame에서 마지막 frame에서의 값과 현재 frame에서의 값을 비교한다 (이동한 크기가 클 수록 Yaw/Pitch 값이 커진다)

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

- 위 코드를 통해 GLFW에게 커서를 숨기고 capture해야한다고 알린다

: 커서를 capture한다는 것은 응용프로그램이 focus 되면 응용 프로그램이 종료되거나 foucs를 잃기 전까지 마우스 커서가 window 창 중앙에 머무르는 것을 의미한다

- 명령어가 호출된 후에는 사용자가 마우스를 이동할 때마다 마우스가 보이지 않으며, window창을 떠나지 않는다

: FPS 카메라 시스템에 적합하다

-> FPS : Frames Per Second의 약자로, 1초에 화면이 얼마나 다시 그려지는가의 단위로 사용되며 초당 프레임을 의미하기도 한다

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

- Yaw/Pitch 값을 계산하기위해 GLFW에 mouse-movement events를 듣고 있으라고 알려야한다

: 위 callback function을 사용하여 이 작업을 수행한다

- 이 코드에서 xpos와 ypos는 현재 마우스의 위치를 나타낸다

glfwSetCursorPosCallback(window, mouse_callback);

- GLFW에 이 callback 함수를 등록하기만 하면 마우스가 움직일 때마다 mouse_callback 함수가 호출된다

 

3. Fly Style Camera

- Fly 스타일 카메라를 위해 마우스 입력을 처리할 때, direction vector를 계산하기 전에 거쳐야할 여러 단계가 있다 (아래에서 순서대로 설명)

01] 마지막 frame 이후의 마우스 offset을 계산한다 

float lastX = 400, lastY = 300;

 

- application의 마지막 마우스 위치를 저장해야한다

: 마우스 위치는 처음에 화면 중앙으로 초기화하여 배치한다 (화면 크기 = 800x600)

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

const float sensitivity = 0.1f;
xoffset *= sensitivity;
yoffset *= sensitivity;

- 마우스의 callback 함수에서 마지막 frame과 현재 frame 사이의 움직임 offset을 계산한다

: offset 값을 sensitivity 값과 곱해야한다 (원하는 감도로 주위를 둘러보기 위함이며, 이를 수행하지 않으면 마우스 움직임이 매우 커진다)

02] 카메라의 Pitch/Yaw 값에 offset 값을 더한다

yaw   += xoffset;
pitch += yoffset;

- 위 코드를 통해 수행한다

03] Pitch 값의 최대/최소 값을 설정한다

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

- 카메라에 제한 사항을 추가하여 사용자가 이상한 카메라 움직임을 할 수 없도록 한다 (또한 이상한 오류를 예방한다)

- Pitch는 89도 이상으로 볼 수 없게 제한한다

: 90도에서 LookAt flip이 발생한다

- Pitch는 -89도 이하로 볼 수 없게 제한한다

-=> 제한 작업은 이 제한 조건을 벗어났을 때마다 벗어난 값을 대체하는 것으로 수행된다

04] Direction Vector를 계산한다

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

- 위 코드를 통해 계산된 directino vector는 마우스 움직임으로부터 계산된 모든 회전을 포함한다

: cameraFront vector가 이미 GLM의 LookAt 함수에 포함되어있음으로 이를 setting할 수 있다

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

- 지금까지의 코드만 실행한다면 window가 마우스 커서 포커스를 처음 받을 때마다 카메라가 크게 점프할 것이다

: 이는 커서가 window 창에 들어가자마자 화면에서의 마우스 위치가 마우스 callback 함수의 xpos, ypos parameter로 들어가기 때문이다

- 위와 같은 현상을 예방하기 위해 전역 bool 변수를 선언하여 사용한다

: 이 변수는 마우스 입력이 처음으로 들어온 것인지 확인하고, 맞다면 먼저 마우스 위치의 초기 값을 새로운 xpos, ypos 값으로 수정한다

- 결과적으로 마우스 움직임의 offset을 계산하기 위해 window 창에 들어온 마우스 위치 좌표를 사용한다

<CODE>

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.1f;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw   += xoffset;
    pitch += yoffset;

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

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

 

4. Zoom

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

- zoom in을 하기 위해 마우스 스크롤 휠을 사용한다

- 스크롤할 때 yoffset 값은 우리가 수직으로 스크롤한 정도를 나타낸다

- scroll_callback 함수가 호출되면 전역으로 선언한 fov 변수의 내용을 변경한다

: 45.0f가 기본 fov 값이므로 zoom level을 1.0f ~ 45.0f 사이로 제한한다

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

- loop가 돌 때마다 GPU에 perspective projection matrix를 upload 해야한다

: fow 변수를 field of view의 값으로서 사용한다

glfwSetScrollCallback(window, scroll_callback);

- scroll callback 함수를 등록한다

 

'OpenGL Study' 카테고리의 다른 글

OpenGL "Colors"  (0) 2023.01.06
OpenGL "Textures"  (0) 2023.01.06
OpenGL "Coordinate Systems"  (1) 2022.11.30
OpenGL "Transformations"  (1) 2022.11.22
OpenGL "Shaders"  (1) 2022.11.17