OpenGL Coordinate Systems 코드 구현 cube 큐브 그리기 [Learn OpenGL 쉬운 번역]

728x90
반응형
※ 본 포스팅은 learnopengl.com을 번역 및 가감한 포스팅이며, learnopengl에서는 번역 작업 참여를 적극 장려하고 있습니다!
아래 링크에서 원문을 확인할 수 있습니다.

 

Putting it all together

여태까지 다루었던 각 단계에 해당하는 transform matrix들(model, view, projection)을 생성해야합니다. 그러고 나서 vertex에 적용하여 clip coordination으로 변환시킵니다.

 

$$V_{clip} = M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}$$

 

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을 하기 위해 다음과 같이 선언합니다.

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

transformation matrix를 생성했으면 이제 shader에 전달합시다. uniform으로 matrix를 선언하고 vertex coordinate와 곱합니다.

#version 330 core
layout (location = 0) in vec3 aPos;
...
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    // 행렬 곱셈을 오른쪽에서 왼쪽으로 읽는다는 것을 주의하세요
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    ...
}

이제 opengl에서 matrix를 shader로 전달하는 코드를 작성합니다(frame마다 호출해야합니다)

int modelLoc = glGetUniformLocation(ourShader.ID, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
... // View Matrix와 Projection Matrix도 동일하게 처리

이제 vertex coordinate가 model, view, projection (MVP) matrix로 transform되어 object가 다음 상태가 되었습니다.

  1. 바닥 쪽으로 약간 기울어짐.
  2. 우리로부터 조금 멀어짐.
  3. 원근법이 적용되어 표시됨(정점이 멀어질수록 더 작아짐).

이제 결과가 실제로 이러한 요구 사항을 충족하는지 확인해 봅시다

 

 

 

3D 평면이 바닥에 놓여있는 걸로 보입니다. 안된다면 코드와 비교하세요.

 

More 3D

이제 3D cube를 렌더링 해봅시다. cube는 vertex 36개(6 plane * 2 triangle* 3 vertex per tri)로 렌더링합니다. cube의 정점 데이터는 이걸 사용하세요.

float vertices[] = {
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

 

시간에 따라 회전하는 기능도 구현해봅시다.

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에 남기 때문입니다. 

 

glClear 함수에서 DEPTH_BUFFER_BIT 플래그로 depth buffer를 지웁니다.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

이제 프로그램을 실행해봅시다.

 

 
 
 
 
 
 

 

 

 

More cubes!

더 많은 cube를 그려봅시다. 10개 cube를 각기 world 공간의 다른 위치에 두고 회전을 다르게 줍니다. cube의 레이아웃은 동일한 것을 여러번 사용하기 때문에 attribute를 추가하지 않아도 됩니다. 다르게 할 것은 model matrix입니다.

 

cube 각각의 world 공간에서의 위치를 지정하는 translation vector를 정의합시다. glm::vec3 배열로 각 cube 위치를 만듭니다.

glm::vec3 cubePositions[] = {
    glm::vec3( 0.0f,  0.0f,  0.0f), 
    glm::vec3( 2.0f,  5.0f, -15.0f), 
    glm::vec3(-1.5f, -2.2f, -2.5f),  
    glm::vec3(-3.8f, -2.0f, -12.3f),  
    glm::vec3( 2.4f, -0.4f, -3.5f),  
    glm::vec3(-1.7f,  3.0f, -7.5f),  
    glm::vec3( 1.3f, -2.0f, -2.5f),  
    glm::vec3( 1.5f,  2.0f, -2.5f), 
    glm::vec3( 1.5f,  0.2f, -1.5f), 
    glm::vec3(-1.3f,  1.0f, -1.5f)  
};

이제 render roop에서 glDrawArrays를 10번 호출해야하지만, 호출할 때마다 10개의 model matrix를 각각 수정해서 vertex shader에 전달합니다.

glBindVertexArray(VAO);
for(unsigned int i = 0; i < 10; i++)
{
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, cubePositions[i]);
    float angle = 20.0f * i; 
    model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
    ourShader.setMat4("model", model);

    glDrawArrays(GL_TRIANGLES, 0, 36);
}

이제 cube가 새로 그려질 때마다 model matrix를 업데이트하고 10번 그리기 때문에 각기 다르게 transform된 cube들을 볼 수 있습니다.

 

 

이제 다양하게 퍼져있는 cube들이 보입니다. 잘 안된다면 코드를 보고 고쳐보세요!

728x90
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유