matrix를 곱셈은 다시 말하지만 오른쪽에서 왼쪽 역순입니다. 이렇게 구한 clip vertex를 vertex shader에서 gl_Position에 할당하면 opengl이 perspective division과 clipping을 처리해줍니다.
opengl이 clip-space coordinates에 perspective division을 적용하고 NDC로 변환한 후, glViewport를 사용해서 NDC를 screen coordinates로 매핑합니다. 이제 각 좌표는 화면에 대응되서 나타내는데, 이걸 viewport transform이라고 합니다.
이제부터 실제로 코드에서 어떻게 활용되는지 알아봅시다.
Going 3D
이제 단순 2D가 아니라 3D object를 렌더링해봅시다.
먼저 model matrix를 생성합니다. object의 vertex를 global world spce로 변환하는 translation, scaling, rotation의 결합입니다. x축을 기준으로 약간 회전시켜봅니다.
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
model matrix를 곱해서 vertex coordinate를 world 좌표로 변환했고, 바닥쪽으로 기울어진 global world 평면이 되었습니다.
다음은 view matrix입니다. object를 카메라로부터 약간 뒤로 이동시키려고 합니다.
카메라를 뒤로 이동시키는 것과 scene 전체를 앞으로 당기는 것은 같습니다.
view matrix가 하는 일입니다. camera의 이동 방향과, 반대 방향으로 scene을 움직이는 것은 같습니다. 그래서 view가 뒤로 이동하는(오른손 좌표계 기준 z축 양의 방향) 효과를 위해 scene을 z축 기준으로 음의 방향으로 translation 합니다.
Right-handed system opengl은 right-handed system을 채용합니다. x축의 양의 방향이 오른쪽, y축의 양의 방향이 위, z축의 양의 방향이 뒤로 향합니다. z축의 경우 헷갈릴 수 있는데 뒤쪽이라는 것은 여러분 쪽으로 향하는 것입니다. 오른손 좌표계를 쉽게 이해하는 손 사용법입니다. 1. 오른팔을 양의 y축을 따라 위로 뻗습니다. 2. 엄지손가락을 오른쪽으로 가리킵니다. 3. 집게손가락을 위로 가리킵니다. 4. 가운데손가락을 90도 아래로 굽힙니다.
엄지는 x축, 검지는 y축, 중지는 z축을 가리킵니다. 왼손으로 하는 left-handed system도 있는데, 이건 DirectX에서 사용됩니다. 또 opengl의 NDC에서도 left-handed system을 사용합니다.(projection matrix가 방향을 바꿉니다)
scene 안에서 세부적으로 이동하는 법은 나중에 다루도록 하고, 여기서는 view matrix를 설정합니다.
glm::mat4 view = glm::mat4(1.0f);
// 우리가 이동하고자 하는 방향의 반대 방향으로 장면을 이동시키는 것을 주의하세요
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
마지막으로 projection matrix를 정의합니다. perspective projection을 하기 위해 다음과 같이 선언합니다.
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
그러고 이번에는 인덱스가 없이 glDrawArrays로 36개의 vertex를 그립니다.
glDrawArrays(GL_TRIANGLES, 0, 36);
결과는 이렇습니다.
cube가 보이긴 하는데 좀 이상하죠? 뭔가 뚫려있는 느낌이 나고 어떤 면은 다른 면 위에 나오는 현상이 생깁니다. 이건 opengl에서 삼각형을 fragment 로 그려낼 때 이미 그린 pixel 위에 덧붙혀 그려서 생기는 현상입니다. opengl draw call에서는 겹치는 pixel에 대해서 순서를 알아서 해주지 않습니다. 그래서 가려져야 하는 삼각형이 우선으로 그려지는 겁니다.
opengl에서 z-buffer를 사용해서 buffer에 depth 정보를 저장하는 방법으로 이를 해결합니다. pixel을 덮어쓸지 아니면 그리지 않을지에 대한 것을 결정할 수 있습니다. z-buffer를 이용하면 depth-testing이라는 것도 할 수 있습니다.
Z-buffer
opengl에서 depth에 대한 정보를 저장하는 z-buffer(depth buffer)가 있는데, GLFW는 자동으로 buffer를 생성해주긴 합니다. depth 정보는 fragment 마다 z값의 형태로 저장되고 출력할 때 z-buffer에 담긴 depth 값을 비교해서 현재 fragment가 다른 것보다 더 뒤에 있어서 가려져야하면 버려지고, 더 앞에 있다면 덮어씌웁니다. 이걸 opengl이 수행해주는데 depth testing이라고 합니다.
opengl이 depth testing을 하기 위해서 활성화 해주어야합니다. 기본적으로는 비활성화 되어있기 때문이죠. glEnable을 사용해서 depth testing을 활성화 합시다.
glEnable / glDisable로 opengl의 각종 기능을 활성화/비활성화합니다. GL_DEPTH_TEST를 활성화해줍시다.
glEnable(GL_DEPTH_TEST);
depth buffer를 사용하면 rendering roop를 돌 때 depth buffer를 초기화 해주어야합니다. 아니면 이전 frame의 정보가 buffer에 남기 때문입니다.