Computer Graphics/OpenGL

OpenGL 정리 - 11. 큐브 그리기, Depth Buffer, 여러 큐브 그리기

surkim 2024. 9. 13. 20:04

OpenGL을 사용하여 3D 큐브를 그리는 방법과 좌표 변환, 깊이 버퍼, 그리고 여러 개의 큐브를 동시에 그리는 방법에 대해 배웠다. 기본적인 개념 설명과 함께 실제 코드 예시를 통해 어떻게 구현할 수 있는지 살펴보자.


1. 좌표계와 변환

좌표계는 local space, world space, view space, 그리고 clip space로 나뉜다. 물체는 처음에는 local space에서 정의되지만, 화면에 그려지기 위해서는 다른 좌표계로 변환되어야 한다. 이 변환 과정은 아래와 같다.

  • Local Space -> World Space: 모델 변환 행렬을 통해 물체를 세계 좌표계로 변환한다.
  • World Space -> View Space: 카메라의 위치와 방향을 반영하여 세계 좌표계를 카메라 좌표계로 변환한다.
  • View Space -> Clip Space: 원근 투영 또는 직교 투영을 통해 화면에 그려질 좌표로 변환한다.
  • Clip Space -> Screen Space: 화면 좌표로 변환하여 픽셀로 그린다.

이를 Model-View-Projection(MVP) 매트릭스라고 부르며, 모든 변환 과정을 하나의 행렬 곱으로 처리할 수 있다.


2. 큐브 그리기

큐브를 그리기 위해서는 먼저 큐브의 정점 데이터를 정의해야 한다. 각 정점은 위치와 텍스처 좌표를 포함하며, 총 36개의 인덱스 값을 가지는 삼각형을 사용하여 큐브의 6면을 그린다.

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, 0.0f, 1.0f,
  // 생략: 나머지 면
};

uint32_t indices[] = {
   0,  2,  1,  2,  0,  3, // 앞면
   4,  5,  6,  6,  7,  4, // 뒷면
   // 생략: 나머지 면
};

이 정점 데이터를 Vertex Buffer Object(VBO)Index Buffer Object(IBO)에 저장한 뒤, OpenGL에서 삼각형으로 큐브를 그릴 수 있다.


3. 깊이 버퍼 (Depth Buffer)

3D 물체를 그릴 때, 화면에 먼저 그려진 물체가 뒤에 있는 물체를 덮어 그리는 문제가 발생할 수 있다. 이를 해결하기 위해 Depth Buffer(깊이 버퍼)를 사용한다. 깊이 버퍼는 각 픽셀의 깊이 값을 저장하고, 깊이 테스트를 통해 화면에 그려질 픽셀을 결정한다.

glEnable(GL_DEPTH_TEST); // 깊이 테스트 활성화
glDepthFunc(GL_LESS);    // 기본 깊이 비교 함수 설정

깊이 버퍼를 사용하면, 화면에 가까운 물체가 우선적으로 그려지고, 더 멀리 있는 물체는 가려진다.


4. 변환 행렬 (Transformation Matrices)

물체를 회전하거나 크기를 조절하는 등의 변환을 적용하려면, Model Matrix, View Matrix, Projection Matrix를 사용하여 변환을 수행해야 한다. GLM 라이브러리를 사용하면 이러한 변환을 쉽게 구현할 수 있다.

auto model = glm::rotate(glm::mat4(1.0f), glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); // x축 회전
auto view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f)); // 카메라 이동
auto projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, 0.01f, 100.0f); // 원근 투영

이 변환들을 모두 곱하여 최종적으로 Model-View-Projection(MVP) Matrix를 만든다.

auto transform = projection * view * model;
m_program->SetUniform("transform", transform);

5. 여러 개의 큐브 그리기

하나의 큐브만 그리는 것이 아니라, 여러 개의 큐브를 그리려면 각 큐브의 위치를 다르게 설정해줘야 한다. 이를 위해, glm::translateglm::rotate를 사용하여 각 큐브의 위치와 회전을 지정할 수 있다.

std::vector<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),
    // 생략: 나머지 큐브 위치
};

auto projection = glm::perspective(glm::radians(45.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.01f, 20.0f);
auto view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));

for (size_t i = 0; i < cubePositions.size(); i++) {
    auto model = glm::translate(glm::mat4(1.0f), cubePositions[i]);
    model = glm::rotate(model, glm::radians((float)glfwGetTime() * 120.0f + 20.0f * (float)i), glm::vec3(1.0f, 0.5f, 0.0f));
    auto transform = projection * view * model;
    m_program->SetUniform("transform", transform);
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}

여러 큐브를 동시에 그리면서, 각 큐브는 고유의 위치와 회전 상태를 가질 수 있다.