[OpenGL] VBO와 VAO

김가은·2026년 2월 21일

OpenGL

목록 보기
2/6

Tony Kim님의 OpenGL 삼각형 그리기learnopengl.com, ThinMatrix Youtube를 공부하며 작성한 글입니다.

앞서 작성한 그래픽스 파이프라인 게시물에서 좌표 정보를 셰이더에 전달할 때, VBO(Vertex Buffer Object)에 저장된다고 기술하였다.
오늘은 VBOVAO에 대해 배워보자~~!!


OpenGL Object와 State Machine

VBO와 VAO에 대해 설명하기 앞서, 기본적으로 OpenGL은 State Machine이다. 간단하게 설명하자면, 우리가 일련의 과정들을 통해 OpenGL의 state를 설정하면, OpenGL 함수들은 현재 설정된 state에 따라서 작동한다. 그리고 이 state를 바꾸지 않는 한 OpenGL 함수들은 그 경향을 계속 유지한다. 물론 이 state를 우리는 계속해서 바꿀 것이고, OpenGL 함수들도 가장 최근 설정된 state에 따라 작동하는 것이다.
쉽게 말해 상태 머신이란, 가장 최근에 설정된 값으로 돌아가는 기계이다.

OpenGL Object는 이러한 state를 담고 있는 구조체와 같은 것이다. 그리고 이런 OpenGL Object을 OpenGL context에 bind하면 OpenGL 함수들은 context에 연결된 state를 current state로 인식하고, 이를 바탕으로 작동한다. 예컨대, 우리가 A라는 삼각형의 꼭짓점 정보를 담은 VBO를 GL_ARRAY_BUFFER이라는 OpenGL context에 bind하면 이 연결을 깨기 전까진 OpenGL 함수들이 A 삼각형의 꼭짓점 정보를 바탕으로 작동한다. 참고로, GL_ARRAY_BUFFER과 같은 특정한 OpenGL context 지점을 target이라고도 한다.


VBO (Vertex Buffer Object)

이러한 target들은 실제로는 unsigned int 값이지만, 비유적으로 본다면 주소와도 같은 것이고, 이 주소에 VBO라는 값을 저장한다.

OpenGL에서 삼각형을 그린다면, 꼭짓점 정보를 Vertex Buffer Object(VBO)란 OpenGL Object에 저장하는 것이다.


VBO 생성 코드

unsigned int VBO;
glGenBuffers(1, &VBO);

VBO binding 코드

glBindBuffer(GL_ARRAY_BUFFER, VBO);

VBO가 GL_ARRAY_BUFFER이라는 target과 bind된 것이다.

이 순간부터 “GL_ARRAY_BUFFER 관련 작업은 이 VBO에 적용하겠다”라는 뜻이다.
물론 오픈지엘은 state machine이기 때문에 그 다음 정보가 업뎃되기 전까지를 의미한다.


정점 데이터 복사 코드

glBufferData(
    GL_ARRAY_BUFFER,
    sizeof(vertices),
    vertices,
    GL_STATIC_DRAW
);

glBufferData 함수는 CPU 메모리에서 GPU 메모리로 데이터 복사를 의미한다.
현재 bind된 Buffer Object에 데이터를 복사하여 담을 수 있게 하는 것이다.

GL_ARRAY_BUFFER → 현재 바인딩된 VBO 대상
sizeof(vertices) → 데이터 크기 (바이트 단위)
vertices → 실제 정점 데이터 주소
GL_STATIC_DRAW → 사용 방식 힌트 (usage hint)


Vertex Attributes

이제 삼각형의 꼭짓점 정보가 GL_ARRAY_BUFFER이라는 OpenGL context에 연결(bind)된 VBO에 복사, 저장된 것이다. 하지만 이 정보들로만은 OpenGL이 도형을 그릴 수 없다.
꼭짓점의 색, 좌표, 법선 등의 다양한 정보를 포함한 설명서를 GPU에 알려주어야 한다.

위 좌표, 색, 법선과 같은 꼭짓점에 대한 다양한 정보를 Vertex Attribute이라고 한다. Vertex Attribute를 GPU가 어떻게 해석할 지 설명서를 glVertexAttribPointer이라는 함수로 전달한다.

glVertexAttribPointer(
                          attributeNumber,
                          coordinateSize,
                          GL_FLOAT,
                          GL_FALSE,
                          0,
                          (void*)0
                          );
                          
glEnableVertexAttribArray(attributeNumber);

glBindBuffer(GL_ARRAY_BUFFER, 0);

attributeNumber
→ 구성하고자 하는 Vertex Attribute의 번호, 셰이더에서는 Vertex Attribute의 location라고 한다.

coordinateSize
→ 구성하고자 하는 Vertex Attribute의 크기, 만약 좌표라면 x, y, x이기 때문에 3일 것이다.

GL_FLOAT → 데이터의 자료형
GL_FALSE → 데이터를 정규화(Normalize) 할 것인지에 대한 참, 거짓 값

0
→ stride, 같은 Vertex Attribute 번호의 다음 요소까지의 거리 또는 크기를 바이트로 나타낸 값. 즉, 좌표값과 다음 꼭짓점의 좌표값 사이의 거리를 의미한다. 위 경우처럼 모든 값들이 빈 공간 없이 붙어있을 땐(tightly packed) stride에 0을 전달하여도 무방하다.

(void*)0
→ void 포인터형이어야하며, 해당 Vertex Attribute의 값이 버퍼에서 얼마큼의 offset 후에 시작되는지를 바이트값으로 받는다. 좌표는 버퍼의 첫 시작부터 그 값이 시작되니 0이다.

glVertexAttribPointer를 호출한 이후에는 glEnableVertexAttribArray 함수에 Vertex Attribute 번호를 인자로 전달하여 해당 Vertex Attibute을 직접 설정한대로 인식하라고 OpenGL에게 요청한다.

또한, OpenGL context는 순간 순간 필요에 따라 다른 OpenGL Object와 연결되기 때문에 구성을 마친 후에는 glBindBuffer(GL_ARRAY_BUFFER, 0);를 통해 unbind 해주는 습관이 중요하다.
인자로 OpenGL Object의 ID 대신 0을 전달하면 현재 OpenGL context와 bind되어 있는 OpenGL Object의 연결고리가 끊어진다.
unbind를 통해 의도치 않게 다른 곳에서 state가 오염되는 것을 방지해준다.


VAO (Vertex Array Object)

셰이더만 있다면 매 프레임마다 삼각형 그리는 작업을 반복하면서 그릴 수 있다. 하지만 매 프레임마다 GL_ARRAY_BUFFER에 VBO를 bind하고, 데이터를 저장하고, Vertex Attribute들을 설정하는 것은 비효율적이다.

그렇기 때문에 바로 위의 모든 작업을 하나의 state로 묶어서 OpenGL Object에 저장하는 마지막 작업이 필요하다. 그렇게 된다면 우리는 위의 작업을 한 번만 하고, 삼각형을 그리기 전에 위 작업을 하나로 묶은 state를 current state로 만들어주기만 하면 된다.

이 때 필요한 OpenGL Object가 VAO(Vertex Array Object) 이다. 위 과정을 진행하기 전에 VAO를 만들고 이를 OpenGL context에 bind 해놓기만 하면 알아서 위 과정들이 VAO에 저장된다. 그리고 나중에 위 과정으로 만든 삼각형을 그리고 싶을 때, 즉 매 프레임에서 여기서 만들어놓은 VAO를 OpenGL context에 bind 해주기만 하면 된다.


VAO 생성 코드

// Loader.c

GLuint Loader::createVAO() {
    GLuint vaoID;
    glGenVertexArrays(1, &vaoID);
    vaos.push_back(vaoID);
    glBindVertexArray(vaoID);
    return vaoID;
}

위의 모든 작업을 하나의 state로 묶어서 OpenGL Object에 저장하는 마지막 작업이다 즉 VAO를 생성하는 작업이다.


VAO binding 코드

// Loader.c

RawModel Loader::loadToVAO(const std::vector<float>& positions) {
    GLuint vaoID = createVAO();
    // 3D 좌표이므로 coordinateSize는 3
    storeDataInAttributeList(0, 3, positions);
    unbindVAO();
    
    return RawModel(vaoID, static_cast<int>(positions.size()) / 3);
}
// MainGameLoop.c
    
	RawModel model = loader.loadToVAO(vertices);
    
    ...
    
    while (!DisplayManager::isCloseRequested()) {
        renderer.prepare();
        renderer.render(model);
        DisplayManager::updateDisplay();
    }

loader.loadToVAO(vertices) 함수 내의 glBindVertexArray(vaoID);를 통해 만들어 놓은 VAO를 OpenGL context에 binding 했으며,
그렇기 때문에 이후 while문에서 current state를 계속 실행하며 삼각형을 그릴 수 있는 것이다.


총정리

순서
VAO bind
→ VBO bind
→ glBufferData
→ glVertexAttribPointer
→ glEnableVertexAttribArray

단계실행 코드 (예시)역할 및 비유
1. VAO BindglBindVertexArray(vao);빈 도면(파일) 열기
지금부터 설정하는 모든 정보(VBO 연결, Vertex Attribute 설정 등)를 이 VAO라는 도면 파일에 저장하겠다고 OpenGL에 알리는 단계이다.
2. VBO BindglBindBuffer(GL_ARRAY_BUFFER, vbo);데이터 박스 연결
“이제부터 사용할 정점 데이터는 이 VBO 박스에 들어 있다”고 OpenGL 상태 머신에 알려주는 단계이다.
3. 데이터 업로드glBufferData(GL_ARRAY_BUFFER, size, data, usage);데이터 채우기
CPU 메모리에 존재하던 실제 정점 좌표 데이터를 GPU 메모리(방금 바인딩한 VBO)에 복사하는 단계이다.
4. Attribute 설정glVertexAttribPointer(...);데이터 해석 지침서 작성
“이 VBO 안의 데이터는 3개씩 끊어서(x, y, z) 읽으며, 자료형은 float이다”와 같은 해석 방법을 지정한다.
이 설정 정보는 현재 바인딩된 VAO에 저장된다.
5. Attribute 활성화glEnableVertexAttribArray(0);스위치 ON
앞에서 설정한 Vertex Attribute(0번 location)를 실제로 사용하겠다고 활성화하는 단계이다.

GPU는
VAO 보고
연결된 VBO에서
정점 데이터를 꺼내
Vertex Shader로 흘려보낸다~!!!!!

0개의 댓글