본문 바로가기
Opengles 3.0 with Android

Chapter 3.2 Instancing 렌더링

by SimonLee 2025. 2. 24.

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

 

OpenGLES3.0_Example/Chapter_3/app/src/main/cpp/Scene/InstanceCube.cpp at main · dlgmlals3/OpenGLES3.0_Example

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

github.com

 

 

 

인스턴스 렌더링(Instanced Rendering)은 동일한 **메시(Mesh)**를 여러 개 렌더링할 때 성능 최적화를 위해 사용된다.

 

1000개의 동일한 물체를 렌더링을 가정하면

인스턴스 렌더링을 사용하지 않으면

- 각 물체마다 다른 MVP 행렬을 적용해야 하기 때문에

- 1000번의 draw call과, 1000번의 uniform 설정함수를 실행해야 한다.

 

반면 인스턴스 렌더링을 사용하게 되면,

- 1회의 draw call로 여러 객체를 처리가 가능하다.

- 변환 행렬이 유니폼이 아닌 버텍스 어트리뷰트로 사용한다.

 

📌 인스턴스 렌더링 vs 유니폼 변경 방식 비교

유니폼 변경

- 개별 객체마다 다른 변환 적용 가능
- 코드가 간단하고 구현이 쉬움
- glDrawElements() 호출이 많아지면 성능 저하
- CPU와 GPU 간의 호출 오버헤드 증가
- 렌더링할 객체 수가 적을 때 (수십 개 이하)
- 각 개체마다 완전히 다른 변환이 필요할 때
인스턴스 렌더링

- glDrawElementsInstanced() 한 번으로 여러 객체 처리 가능 (Draw Call 최소화)
- 대량 렌더링 시 성능이 크게 향상됨
- 변환 행령을 유니폼을 사용하지 않고 버텍스 어트리뷰트 값을 사용

- 대량 객체 렌더링 (수백 개 이상) 필요할 때
- 동일한 객체를 한꺼번에 그릴 때 (예: 군집 렌더링)

 

 

인스턴스 렌더링(Instanced Rendering) 주요 함수

  • glVertexAttribDivisor()를 이용해 인스턴스별 속성 설정 (변환 행렬 사용)
    • divisor = 0: 속성이 각 정점마다 갱신됩니다. 
    • divisor = 1: 속성이 각 인스턴스마다 갱신됩니다.
    • divisor = n: 속성이 매 n개의 인스턴스마다 갱신됩니다. 
  • glDrawElementsInstanced()로 한 번에 여러개의 메쉬를 렌더링

 

Code

1. 정점, 인덱스, 인스턴스 위치 데이터 준비

float cubeVerts[] = {
    // 정점 좌표 (X, Y, Z)
    -0.5f, -0.5f, -0.5f, 
     0.5f, -0.5f, -0.5f, 
     0.5f,  0.5f, -0.5f, 
    -0.5f,  0.5f, -0.5f, 
    -0.5f, -0.5f,  0.5f, 
     0.5f, -0.5f,  0.5f, 
     0.5f,  0.5f,  0.5f, 
    -0.5f,  0.5f,  0.5f
};

unsigned short cubeIndices[] = {
    0, 1, 2, 2, 3, 0, // 앞면
    4, 5, 6, 6, 7, 4, // 뒷면
    0, 1, 5, 5, 4, 0, // 밑면
    2, 3, 7, 7, 6, 2, // 윗면
    0, 3, 7, 7, 4, 0, // 왼쪽면
    1, 2, 6, 6, 5, 1  // 오른쪽면
};

glm::vec3 instanceOffsets[100]; // 변환 행렬

for (int i = 0; i < 100; i++) {
    float x = (i % 10) * 2.0f; // X축 정렬 (10 x 10)
    float y = ((i / 10) % 10) * 2.0f; // Y축 정렬
    float z = 0.0f; // 평면 배치
    instanceOffsets[i] = glm::vec3(x, y, z);
}

 

2. VBO, IBO, 인스턴스 변환행렬 VBO

GLuint vbo, ibo, instanceVBO;
glGenBuffers(1, &vbo);
glGenBuffers(1, &ibo);
glGenBuffers(1, &instanceVBO);

// 정점 및 색상 VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVerts), cubeVerts, GL_STATIC_DRAW);

// 인덱스 IBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cubeIndices), cubeIndices, GL_STATIC_DRAW);

// 인스턴스 오프셋 VBO
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(instanceOffsets), instanceOffsets, GL_STATIC_DRAW);

 

 

3. VAO 설정 ( Vertex Attribute 설정 정보 저장 & 로딩 용이)

GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

// 1. 정점 속성 설정
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);

// 2. 인스턴스 위치 속성 설정
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glVertexAttribDivisor(1, 1); // 인스턴스마다 1번만 업데이트

// 3. 인덱스 바인딩
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

// VAO 보호
glBindVertexArray(0);

 

4. 셰이더 설정

 

#version 300 es
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aOffset;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;

void main() {
    vec3 finalPosition = aPos + aOffset; // 인스턴스별 위치 적용
    gl_Position = projection * view * model * vec4(finalPosition, 1.0);
}

 

5. 렌더링

glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0, 100);

/*
// 인스턴스 사용하지 않는 경우.
for (int i = 0; i < 100; i++) {
    glm::mat4 model = glm::translate(glm::mat4(1.0f), instanceOffsets[i]);
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0);
}
*/

 

주요 함수 설명

void glVertexAttribDivisor(GLuint index, GLuint divisor);

인스턴스 렌더링(instance rendering) 시에 각 속성이 얼마나 자주 갱신되어야 하는지 지정하는 함수입니다. 이 함수는 여러 인스턴스를 동시에 렌더링할 때, 각 인스턴스에 대해 정점 속성(예: 위치, 색상, 변환 행렬 등)을 다르게 설정할 수 있도록 합니다.

매개변수 설명

  • index: 속성의 위치로, glVertexAttribPointer로 지정한 속성 인덱스와 동일합니다.
  • divisor: 이 속성이 얼마나 자주 갱신될지를 나타내는 값입니다.
    • divisor = 0: 속성이 각 정점마다 갱신됩니다. 이는 일반적인 VBO처럼 작동하여, 하나의 정점마다 속성 데이터가 변경됩니다.
    • divisor = 1: 속성이 각 인스턴스마다 갱신됩니다. 예를 들어, 변환 행렬처럼 각 인스턴스마다 다르게 설정할 필요가 있는 속성에 사용합니다.
    • divisor = n: 속성이 매 n개의 인스턴스마다 갱신됩니다. 예를 들어, divisor = 2라면 2개의 인스턴스마다 속성이 한 번씩 갱신됩니다.
void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount);

OpenGL에서 **인스턴스 렌더링(instance rendering)**을 수행하는 함수로,

하나의 드로우 호출로 여러 인스턴스를 한꺼번에 렌더링할 수 있게 해줍니다. 이 함수는 각 인스턴스가 동일한 정점 데이터를 사용하지만, 위치, 크기, 회전과 같은 변환이 인스턴스마다 다를 때 유용합니다.

매개변수 설명

  • mode: 그리기 모드를 지정합니다. 렌더링할 기본 도형을 정의하는 값으로, 다음 중 하나를 사용할 수 있습니다.
    • GL_TRIANGLES: 삼각형을 사용해 도형을 그림
    • GL_LINES: 선을 사용해 도형을 그림
    • GL_POINTS: 점을 사용해 도형을 그림
    • 그 외에 GL_TRIANGLE_STRIP, GL_LINE_STRIP 등 다양한 도형이 있습니다.
  • count: 각 인스턴스에서 사용할 정점 인덱스의 개수입니다.
    • 예를 들어, 삼각형으로 구성된 큐브는 36개의 정점 인덱스를 사용하므로 count = 36을 지정합니다.
  • type: 인덱스 배열에 있는 값의 자료형을 지정합니다.
    • 주로 GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT 중 하나를 사용합니다.
      예를 들어, 인덱스 배열이 unsigned int 타입이라면 GL_UNSIGNED_INT로 설정합니다.
  • indices: 인덱스 배열의 시작 위치에 대한 포인터입니다.
    • 인덱스 버퍼 객체(IBO)에서 데이터를 사용할 경우, 0으로 설정하고 IBO를 미리 바인딩합니다.
  • instancecount: 생성할 인스턴스의 개수입니다.
    • 예를 들어, 1000개의 큐브 인스턴스를 렌더링하고 싶다면 instancecount = 1000으로 설정합니다.
728x90