지난 글에서는 VBO 와 VAO 를 사용하여 삼각형을 그리는 방법에 대해 정리하였다.
이번 글에서는 EBO를 사용하여 정점 데이터를 중복으로 사용하지 않도록 하는 과정을 정리한다.
먼저 지난 예제를 토대로 사각형을 그리는 예제를 아래와 같이 작성하였다.
#include <glad/glad.h>
#include <GLFW/glfw3.h>
unsigned int createAndCompileShader(GLenum shaderEnum, const char* shaderSourceCode);
unsigned int createShaderProgramWithShaders(unsigned int vertexShader, unsigned int fragmentShader);
const char* VertexShaderSourceCode =
"#version 330 core \n"
"layout(location = 0) in vec3 aPos; \n"
"void main() { \n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); \n"
"} \n";
const char* FragmentShaderSourceCode =
"#version 330 core \n"
"out vec4 FragColor; \n"
"void main() { \n"
" FragColor = vec4(0.5f, 0.5f, 0.5f, 1.0f); \n"
"} \n";
float vertices[] = {
-0.5f, -0.5f, 1.0f, // 왼쪽 아래
+0.5f, -0.5f, 1.0f, // 오른쪽 아래
-0.5f, +0.5f, 1.0f, // 왼쪽 위 (여기까지 삼각형 1)
+0.5f, +0.5f, 1.0f, // 오른쪽 위
+0.5f, -0.5f, 1.0f, // 오른쪽 아래
-0.5f, +0.5f, 1.0f, // 왼쪽 위 (여기까지 삼각형 2) => (삼각형 2개로 사각형 생성)
};
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Window 생성
GLFWwindow* window = glfwCreateWindow(800, 600, "title", NULL, NULL);
glfwMakeContextCurrent(window);
// GLAD 로드 (Window 생성 이후에 실행해야 렉걸리지 않음)
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
// Vertex Shader 생성
unsigned int vertexShader;
vertexShader = createAndCompileShader(GL_VERTEX_SHADER, VertexShaderSourceCode);
// Fragment Shader 생성
unsigned int fragmentShader;
fragmentShader = createAndCompileShader(GL_FRAGMENT_SHADER, FragmentShaderSourceCode);
// Shader Program 생성
unsigned int shaderProgram = createShaderProgramWithShaders(vertexShader, fragmentShader);
// VBO 생성 후, 정점 데이터 복사
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// VAO 생성 후, vertex attribute 생성
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
glEnableVertexAttribArray(0);
// Redering Loop
while(!glfwWindowShouldClose(window)) {
glClearColor(.5f, .5f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
}
unsigned int createAndCompileShader(GLenum shaderEnum, const char* shaderSourceCode) {
unsigned int shader = glCreateShader(shaderEnum);
glShaderSource(shader, 1, &shaderSourceCode, NULL);
glCompileShader(shader);
return shader;
}
unsigned int createShaderProgramWithShaders(unsigned int vertexShader, unsigned int fragmentShader) {
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}
지난 코드에서 좀 더 직관적으로 기능을 이해할 수 있도록, 셰이더 생성, 셰이더 프로그램 생성 부분은 커스텀 함수로 빼냈다.
위 코드의 실행결과는 아래와 같다.
이렇게 사각형을 그려낸다.
중복된 정점의 사용
그런데 한 가지 아쉬운점이 있다.
현재 사각형을 그릴 때, 삼각형 2개를 그리는 방식으로 사각형을 그리고 있다.
그런데 위 배열을 보면 알 수 있듯, 오른쪽 아래 정점과 왼쪽 위 정점이 2개로 겹치고 있다.
렌더링 루프에서는 0번째 정점부터 6개 정점을 그리고 있으므로, 2개의 정점을 중복해서 사용하는 상황이라고 볼 수 있다.
물론 이렇게 0번째 정점부터 3개, 1번째 정점부터 3개를 그리도록 하면 위와 똑같은 사각형이 나오므로 4개 정점만 가지고 사각형을 그리는 것도 가능하다.
하지만 이렇게 하려면 정점의 순서를 잘 배치해야 한다.
glDrawsArrays 함수는 시작 인덱스로부터 연속된 count 개의 정점을 활용해 데이터를 그리는 함수이다.
따라서 내가 필요한 데이터들이 연속되어 있어야 한다는 제약사항이 있다.
그렇다면 내가 사용하려는 정점 데이터들을 어딘가에 보관해두고 매번 불러와서 사용하도록 할 수는 없을까?
EBO (Element Buffer Object)
이를 위해 사용하는 것이 바로 EBO이다.
EBO는 내가 사용하려는 정점 데이터가 배열에 들어있을 때, 어떤 정점들을 어떤 순서대로 골라 그릴지 그 인덱스 순서를 저장하고 있는 버퍼이다.
그래서 EBO 를 '인덱스 버퍼' 라고 부르기도 한다.
그래서 중복되는 정점을 제거하고, 4개 정점만을 기술한 다음, 실제로 데이터를 그릴 때는 어떤 element 를 사용하여 그릴지 기술한 EBO 데이터를 토대로 화면에 그림을 그린다.
한가지 주의할 점은, 정점을 나열할 때, 반시계 방향으로 정점을 나열하도록 해야한다고 한다.
그렇게 그리지 않으면 그리는 면의 방향이 반대 (즉, 뒷면) 을 그리게 되어 화면에 그린 면이 보이지 않을 수도 있다.
(지금은 당장 중요하지 않다.)
이제 위에서 작성한 사각형 그리기 예제를 EBO를 사용하여 리펙토링해보자.
EBO도 VBO와 동일한 버퍼 오브젝트이므로, 그 생성 과정은 비슷하다.
(복사해서 넣는 데이터가 인덱스 데이터냐, 정점 데이터냐의 차이)
우선 기존 코드에서는 왜 문제없이 작동했는지 의문이지만... VAO를 먼저 Context에 바인딩 해준다.
// VAO 생성 (VBO, EBO는 이 VAO에 바인딩 되므로 먼저 생성해야 함. 안 그러면 삼각형이 안그려짐!)
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
그리고 사용할 인덱스 정보 배열을 생성하고, EBO 에 배열 내용을 복사하여 넣는다.
int indices[] = {
0, 1, 2,
1, 2, 3
};
// EBO 생성 후, 인덱스 데이터 복사
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
렌더링 루프에서는 아래 코드를 사용하여 EBO에 저장된 인덱스 순서대로 정점을 그린다.
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
이 함수는 무엇을 그릴지 (현재는 삼각형), 몇 개의 인덱스 정보를 사용하는지, 인덱스의 데이터 타입은 무엇인지, 인덱스의 오프셋은 얼마인지를 필요로 한다.
지금까지의 내용을 도식화하면 위와 같다.
이로써 정점 데이터를 토대로 셰이더를 활용해 화면에 그림을 그리는 큰 과정을 정리하였다.
다음 글에서는 셰이더 프로그램을 작성하는데 사용하는 GLSL에 대해 조금 더 자세히 정리한다.
'CS > HCI 윈도우즈프로그래밍' 카테고리의 다른 글
[OpenGL] 9. Shader (5) - 직사각형에 실시간으로 변하는 색상 입히기 (GLFW, GLAD) (0) | 2024.04.09 |
---|---|
[OpenGL] 8. Shader (4) - GLSL (0) | 2024.04.09 |
[OpenGL] 6. Shader (2) - VAO (1) | 2024.04.07 |
[OpenGL] 5. Shader (1) - Shader, VBO, Shader Program (0) | 2024.04.05 |
[OpenGL] 4. Graphics Pipeline (1) | 2024.04.04 |