본문 바로가기
Opengles 3.0 with Android

Chapter 2.2 VBO, EBO, VAO

by SimonLee 2025. 2. 21.

https://github.com/dlgmlals3/OpenGLES3.0_Example/blob/main/Chapter_2/app/src/main/cpp/Scene/ManyCubes.cpp

 

OpenGLES3.0_Example/Chapter_2/app/src/main/cpp/Scene/ManyCubes.cpp at main · dlgmlals3/OpenGLES3.0_Example

Contribute to dlgmlals3/OpenGLES3.0_Example development by creating an account on GitHub.

github.com

InitBufferObject(), RenderCube()

 

 정의

VBO
(Vertex Buffer Object)
GPU 메모리에 정점 데이터를 저장하는 버퍼 객체 정점 데이터(좌표, 색상, 노멀 등)를 CPU에서 GPU로 효율적으로 전송하여 렌더링 성능을 향상시킴
EBO
(Element Buffer Object)
GPU 메모리에 인덱스 데이터를 저장하는 버퍼 객체 인덱스를 사용하여 정점 데이터를 재사용함으로써 메모리 사용을 최적화하고, 드로우 콜 효율을 높임
VAO
(Vertex Array Object)
정점 관련 상태(바인딩된 VBO, EBO, 정점 속성 설정 등)를 캡슐화한 객체 복잡한 정점 속성 설정을 한 번에 저장 및 복원하여, 상태 관리와 렌더링 설정을 단순화시킴

 

 

> glVertexAttribPointer 사용

VBO
사용
바인딩된 버퍼 내 오프셋 (바이트 단위) glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer
(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
최적화됨:
정점 데이터는 glBufferData/glBufferSubData로 한 번 GPU에 업로드되어, 이후 드로우콜마다 GPU 메모리에서 직접 읽으므로 CPU→GPU 복사가 반복적으로 일어나지 않음.
클라이언트 사이드 배열 CPU 메모리 상의 실제 데이터 주소 glVertexAttribPointer
(0, 3, GL_FLOAT, GL_FALSE, 0, cubeVerts);
비최적화:
매번 드로잉 시 CPU 메모리에서 데이터를 읽어와 GPU로 복사해야 하므로, 오버헤드가 증가하고 성능이 저하됨.

 

클라이언트 사이드 배열

// 예시 데이터 (필요한 데이터로 채워주세요)
float cubeVerts[] = {
    // 4개의 정점, 각 정점 3개 좌표 (x, y, z)
     0.5f,  0.5f, 0.0f,  // 정점 0: 오른쪽 위
     0.5f, -0.5f, 0.0f,  // 정점 1: 오른쪽 아래
    -0.5f, -0.5f, 0.0f,  // 정점 2: 왼쪽 아래
    -0.5f,  0.5f, 0.0f   // 정점 3: 왼쪽 위
};

float cubeColors[] = {
    // 각 정점에 대한 컬러 (R, G, B)
    1.0f, 0.0f, 0.0f,  // 정점 0: 빨강
    0.0f, 1.0f, 0.0f,  // 정점 1: 초록
    0.0f, 0.0f, 1.0f,  // 정점 2: 파랑
    1.0f, 1.0f, 0.0f   // 정점 3: 노랑
};

unsigned short cubeIndices[] = {
    // 사각형을 두 개의 삼각형으로 구성하는 인덱스
    0, 1, 3,  // 첫 번째 삼각형
    1, 2, 3   // 두 번째 삼각형
};

// 렌더링 함수 (VBO/VAO/EBO 사용 없이, 클라이언트 메모리 배열 이용)
// glEnableClientState 대신 glEnableVertexAttribArray를 사용합니다.
void renderCube_Original_NoBuffers() {
    // 만약 혹시 바인딩된 버퍼 객체가 있다면 해제합니다.
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    // 정점 속성을 활성화합니다.
    // 여기서는 셰이더에서 정점 위치를 location 0, 컬러를 location 1로 사용한다고 가정합니다.
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    // 클라이언트 메모리에 있는 데이터를 직접 지정합니다.
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, cubeVerts);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, cubeColors);

    // 인덱스 데이터를 이용하여 도형(여기서는 두 개의 삼각형으로 구성된 사각형)을 그립니다.
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, cubeIndices);

    // 사용 후 정점 속성 비활성화 (상태 정리를 위해)
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
}

 

 

 

VBO + EBO

// 전역 데이터 (예시: cubeVerts, cubeColors, cubeIndices)
// cubeVerts: 정점 좌표 배열 (예: 24개의 float)
// cubeColors: 정점 컬러 배열 (예: 24개의 float)
// cubeIndices: 인덱스 배열 (예: 36개의 unsigned short)

float cubeVerts[] = { /* 정점 좌표 데이터 */ };
float cubeColors[] = { /* 정점 컬러 데이터 */ };
unsigned short cubeIndices[] = { /* 인덱스 데이터 */ };

GLuint vbo, ebo;
GLsizei size = 24 * sizeof(float);              // 정점 데이터 또는 컬러 데이터 크기
GLsizei indexSize = 36 * sizeof(unsigned short);  // 인덱스 데이터 크기

// VBO와 EBO를 생성하고 데이터를 GPU에 업로드하는 초기화 함수 (VAO 미사용)
void initBuffers_NoVAO() {
    // VBO 생성 및 설정
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    // 전체 버퍼 크기는 정점 데이터 + 컬러 데이터
    glBufferData(GL_ARRAY_BUFFER, size + size, NULL, GL_STATIC_DRAW);
    // 버퍼의 첫 부분에 정점 데이터 업로드
    glBufferSubData(GL_ARRAY_BUFFER, 0, size, cubeVerts);
    // 버퍼의 두 번째 부분(오프셋 size)에 컬러 데이터 업로드
    glBufferSubData(GL_ARRAY_BUFFER, size, size, cubeColors);
    
    // EBO 생성 및 설정
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize, NULL, GL_STATIC_DRAW);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexSize, cubeIndices);
    
    // 바인딩 해제 (옵션)
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

// 렌더링 함수: VBO와 EBO를 직접 바인딩하여 정점 속성 설정 후 그리기
void renderCube_NoVAO() {
    // 1. VBO 바인딩 및 정점 속성 설정
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
    // 셰이더에서 정점 위치를 attribute location 0, 컬러를 location 1로 사용한다고 가정합니다.
    glEnableVertexAttribArray(0);
    // 정점 데이터: VBO의 오프셋 0부터 읽음 (각 정점 3개 float)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    
    glEnableVertexAttribArray(1);
    // 컬러 데이터: VBO의 오프셋 'size'부터 읽음 (각 정점 3개 float)
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)size);
    
    // 2. EBO 바인딩 (인덱스 데이터를 사용하기 위해)
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    
    // 3. 그리기: 인덱스 데이터를 사용하여 삼각형 36개로 구성된 도형(큐브) 렌더링
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, (void*)0);
    
    // 4. 사용한 정점 속성 비활성화 및 버퍼 바인딩 해제
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

 

VBO + EBO  + VAO

// 전역 데이터 (예시 데이터)
// cubeVerts: 정점 좌표 (예: 24개의 float = 8 vertices * 3)
// cubeColors: 정점 컬러 (예: 24개의 float)
// cubeIndices: 인덱스 데이터 (예: 36개의 unsigned short)
float cubeVerts[] = { /* ... 정점 좌표 데이터 ... */ };
float cubeColors[] = { /* ... 정점 컬러 데이터 ... */ };
unsigned short cubeIndices[] = { /* ... 인덱스 데이터 ... */ };

GLuint VAO, VBO, EBO;
GLsizei size = 24 * sizeof(float);            // 정점 데이터 또는 컬러 데이터 크기
GLsizei indexSize = 36 * sizeof(unsigned short); // 인덱스 데이터 크기

// 초기화: VAO, VBO, EBO 생성 및 데이터 업로드
void initBuffers() {
    // VAO, VBO, EBO 객체 생성
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    // VAO 바인딩: 이후의 설정이 VAO에 저장됨
    glBindVertexArray(VAO);

    // VBO 설정: 정점 데이터와 컬러 데이터를 하나의 버퍼에 연속적으로 저장
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 전체 크기: 정점 데이터 + 컬러 데이터
    glBufferData(GL_ARRAY_BUFFER, size + size, NULL, GL_STATIC_DRAW);
    // 첫 번째 부분에 정점 데이터 업로드
    glBufferSubData(GL_ARRAY_BUFFER, 0, size, cubeVerts);
    // 두 번째 부분(오프셋 size)에 컬러 데이터 업로드
    glBufferSubData(GL_ARRAY_BUFFER, size, size, cubeColors);

    // EBO 설정: 인덱스 데이터를 업로드
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize, NULL, GL_STATIC_DRAW);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexSize, cubeIndices);

    // 정점 속성 설정 (예제에서는 attribute 0: 위치, attribute 1: 컬러)
    glEnableVertexAttribArray(0);
    // 위치 데이터: VBO의 오프셋 0부터 읽어들이며 3개의 float씩
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);

    glEnableVertexAttribArray(1);
    // 컬러 데이터: VBO의 오프셋 'size'부터 읽어들이며 3개의 float씩
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)size);

    // VAO 설정 완료 후 해제 (EBO 바인딩 정보는 VAO에 포함됨)
    glBindVertexArray(0);
}

// 렌더링 함수 (VBO/EBO 방식)
// 필요시 glBufferSubData를 이용하여 GPU 버퍼의 데이터를 업데이트할 수 있음.
void renderCube_VBO() {
    // 예: 만약 애니메이션 등으로 정점 데이터를 업데이트하고 싶다면...
    // float newCubeVerts[] = { ... 업데이트된 데이터 ... };
    // glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // glBufferSubData(GL_ARRAY_BUFFER, 0, size, newCubeVerts);
    // glBindBuffer(GL_ARRAY_BUFFER, 0);

    // 셰이더 프로그램 활성화, MVP 행렬 uniform 업데이트 등은 생략하고 VAO 바인딩 후 드로우 콜
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, (void*)0);
    glBindVertexArray(0);
}

 

 

Function >

0. glBufferSubData

할당된 버퍼 객체 내의 특정 부분을 업데이트하는 함수입니다.
즉, 한 번 glBufferData로 전체 버퍼의 저장소를 할당한 후, 그 저장소 중 일부를 새 데이터로 덮어쓰고 싶을 때 사용합니다.

void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
  • 매개변수 설명:
    • target:
      업데이트할 버퍼의 용도를 지정합니다. 예를 들어, 정점 데이터의 경우 GL_ARRAY_BUFFER, 인덱스 데이터의 경우 GL_ELEMENT_ARRAY_BUFFER를 사용합니다.
    • offset:
      버퍼 저장소 내에서 데이터를 업데이트할 시작 오프셋(바이트 단위)입니다. 즉, 버퍼의 어느 위치부터 새 데이터를 덮어쓸지를 지정합니다.
    • size:
      업데이트할 데이터의 크기를 바이트 단위로 지정합니다.
    • data:
      새로 업데이트할 데이터를 가리키는 포인터입니다. 이 데이터는 버퍼 내의 해당 오프셋부터 size 바이트 만큼 복사됩니다.

 

1. glBufferData

OpenGL에서 버퍼 객체(예: VBO, EBO 등)의 데이터 저장소를 생성하고 초기화하는 함수입니다. CPU 메모리의 데이터를 GPU 메모리에 복사하거나, 단순히 저장소를 할당하는 역할을 합니다.

 

void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
  • 매개변수 설명:
    • target:
      버퍼의 용도를 지정합니다. 예를 들어, 정점 데이터의 경우 GL_ARRAY_BUFFER, 인덱스 데이터의 경우 GL_ELEMENT_ARRAY_BUFFER를 사용합니다.
    • size:
      할당할 버퍼 저장소의 크기를 바이트 단위로 지정합니다.
    • data:
      초기화할 데이터를 가리키는 포인터입니다. 만약 NULL을 지정하면, 지정된 크기만큼의 저장소만 할당되고 초기 데이터는 채워지지 않습니다.
    • usage:
      버퍼 데이터의 사용 패턴에 대한 힌트입니다. 예를 들어,
      • GL_STATIC_DRAW: 데이터가 거의 변경되지 않을 때
      • GL_DYNAMIC_DRAW: 데이터가 자주 변경될 때
        등의 값을 사용하여 드라이버에게 최적화를 위한 정보를 제공합니다.

 

2. glGetAttribLocation()

glGetAttribLocation은 셰이더 프로그램에서 속성(attribute) 변수의 위치(인덱스)를 얻는 함수입니다. 버텍스 데이터를 셰이더의 특정 속성에 연결하기 위해 해당 속성의 위치를 알아야 하며, 그 위치는 컴파일된 셰이더 프로그램에서만 결정됩니다.

GLint glGetAttribLocation(GLuint program, const GLchar *name);
  • program: 셰이더 프로그램의 ID입니다. (glCreateProgram으로 생성한 프로그램)
  • name: 셰이더 속성(attribute) 변수의 이름입니다. 이 이름은 셰이더 소스 코드에서 정의한 in 변수를 가리킵니다.
  • 셰이더 프로그램 내에서 속성의 인덱스 위치를 반환합니다. 이 값은 glVertexAttribPointer나 glEnableVertexAttribArray에서 속성의 인덱스로 사용됩니다.
  • 반환값이 -1이면 해당 이름을 가진 속성이 셰이더에서 존재하지 않거나 최적화로 인해 제거된 것입니다.

 

3. glVertexAttribPointer()

glVertexAttribPointer는 버텍스 데이터를 셰이더 속성에 연결하는 함수입니다.

이 함수는 OpenGL에 버텍스 버퍼에 저장된 데이터의 형식과 구조를 알려줍니다.

즉, 메모리에서 데이터를 어떻게 읽어야 할지 정의합니다.

void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
  • index: 셰이더에서 속성의 인덱스. (glGetAttribLocation으로 얻은 값)
  • size: 속성의 요소 개수. 예를 들어, vec3는 3, vec4는 4가 됩니다.
  • type: 버텍스 데이터의 타입. 보통 GL_FLOAT를 사용합니다.
  • normalized: 정수형 데이터를 정규화할지 여부를 결정합니다. (GL_TRUE 또는 GL_FALSE)
  • stride: 버텍스 사이의 간격(바이트 단위). 같은 속성의 데이터가 연속되어 있으면 0으로 설정합니다.
  • pointer: 버퍼 내에서 속성 데이터의 시작 위치입니다. 보통 NULL 또는 0으로 설정해 첫 번째 데이터를 가리키게 합니다.

이 함수는 중요한 특징이 있다.

glVertexAttribPointer는 GPU에서 버퍼를 읽어 올 수도 있고, CPU에서 읽어올 수 도 있다.

 

> VBO를 사용하는 경우:

glBindBuffer(GL_ARRAY_BUFFER, vbo);

VBO를 바인딩한 상태에서 pointer에 들어가는 값은 버퍼에서 해당 속성의 데이터가 시작하는 위치를 가리킵니다.

이 값은 버퍼에서의 오프셋이 됩니다.

일반적으로는 NULL 또는 0으로 첫 번째 데이터 요소부터 시작하게 합니다.

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);

 

> VBO를 사용하지 않는 경우:

만약 버퍼가 아닌 CPU 메모리에 있는 데이터를 직접 참조할 경우(예: 배열),

pointer는 그 배열의 메모리 주소가 됩니다.

이때는 GPU에 데이터가 전송되지 않고, CPU 메모리에서 직접 버텍스 데이터를 읽습니다.

일반적으로는 이 방법보다 VBO를 사용하는 것이 더 효율적입니다.

아래 예제 에서도 VBO를 사용하지 않는다.

float vertices[] = { ... };  // CPU 메모리에 있는 버텍스 데이터
glBindBuffer(GL_ARRAY_BUFFER, 0);  // VBO 사용 안 함
glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);

 

4. glEnableVertexAttribArray()

glEnableVertexAttribArray는 **셰이더에서 특정 속성(attribute)**을 활성화하는 함수입니다.

이 함수가 호출되면 해당 속성의 데이터를 읽을 수 있게 됩니다.

이 함수가 호출되지 않으면, OpenGL은 해당 속성 데이터를 읽어들이지 않습니다.

void glEnableVertexAttribArray(GLuint index);

index: 활성화하려는 속성의 인덱스. (glGetAttribLocation으로 얻은 값)

 

 

5. glGetUniformLocation

glGetUniformLocation은 특정 셰이더 프로그램에서 uniform 변수의 위치(인덱스)를 가져오는 함수입니다.

이 위치를 알면 나중에 해당 변수에 값을 설정할 수 있습니다.

GLint glGetUniformLocation(GLuint program, const GLchar *name);
  • program: uniform 변수를 가져올 셰이더 프로그램의 ID입니다. (glCreateProgram으로 생성한 프로그램)
  • name: uniform 변수의 이름입니다. 이 이름은 셰이더 소스 코드에서 정의된 uniform 변수의 이름과 일치해야 합니다.
  • 지정한 uniform 변수의 인덱스 위치를 반환합니다. 이 값은 나중에 glUniform* 함수를 호출할 때 사용됩니다.
  • 반환값이 -1이면 해당 이름을 가진 uniform 변수가 셰이더에서 존재하지 않거나 최적화로 인해 제거된 것입니다.

 

6. glUniform1f

glUniform1f는 float 타입의 uniform 변수를 설정하는 함수입니다.

이 함수를 사용하여 셰이더에서 정의된 uniform 변수의 값을 업데이트할 수 있습니다.

void glUniform1f(GLint location, GLfloat v0);
  • location: 설정할 uniform 변수의 위치. (glGetUniformLocation으로 얻은 값)
  • v0: uniform 변수에 설정할 float 값입니다.

정리하면, 유니폼 위치를 가져와서 유니폼 값을 설정합니다.