본문 바로가기
Opengles 3.0 with Android

Chapter 3.4 Instance 렌더링

by SimonLee 2024. 10. 30.
#define MATRIX1_LOCATION 2
#define MATRIX2_LOCATION 3
#define MATRIX3_LOCATION 4
#define MATRIX4_LOCATION 5

void Cube::InitModel()
{
    // Create VBO
    size = 24*sizeof(float);
    glGenBuffers(1, &vId);
    glBindBuffer( GL_ARRAY_BUFFER, vId );
    glBufferData( GL_ARRAY_BUFFER, size + size, 0, GL_STATIC_DRAW );
    glBufferSubData( GL_ARRAY_BUFFER, 0,			size,	cubeVerts );
    glBufferSubData( GL_ARRAY_BUFFER, size,			size,	cubeColors );


    // Create VBO for transformation matrix
    glGenBuffers(1, &matrixId);
    glBindBuffer( GL_ARRAY_BUFFER, matrixId );


    glm::mat4 transformMatrix[dimension][dimension][dimension];
    glBufferData(GL_ARRAY_BUFFER, sizeof(transformMatrix) , 0, GL_DYNAMIC_DRAW);

    // Create IBO
    unsigned short indexSize = sizeof( unsigned short )*36;
    glGenBuffers(1, &iId);
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, iId );
    glBufferData( GL_ELEMENT_ARRAY_BUFFER, indexSize, 0, GL_STATIC_DRAW );
    glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, 0, indexSize,	cubeIndices );

    glGenVertexArrays(1, &Vertex_VAO_Id);
    glBindVertexArray(Vertex_VAO_Id);

    // Create VBO  and set attribute parameters
    glBindBuffer( GL_ARRAY_BUFFER, vId );
    glEnableVertexAttribArray(VERTEX_LOCATION);
    glEnableVertexAttribArray(COLOR_LOCATION);
    glVertexAttribPointer(VERTEX_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glVertexAttribPointer(COLOR_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, (void*)size);

    // Create VBO for transformation matrix and set attribute parameters
    glBindBuffer( GL_ARRAY_BUFFER, matrixId );
    glEnableVertexAttribArray(MATRIX1_LOCATION);
    glEnableVertexAttribArray(MATRIX2_LOCATION);
    glEnableVertexAttribArray(MATRIX3_LOCATION);
    glEnableVertexAttribArray(MATRIX4_LOCATION);

    glVertexAttribPointer(MATRIX1_LOCATION, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 0));
    glVertexAttribPointer(MATRIX2_LOCATION, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 4));
    glVertexAttribPointer(MATRIX3_LOCATION, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 8));
    glVertexAttribPointer(MATRIX4_LOCATION, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 12));

    glVertexAttribDivisor(MATRIX1_LOCATION, 1);
    glVertexAttribDivisor(MATRIX2_LOCATION, 1);
    glVertexAttribDivisor(MATRIX3_LOCATION, 1);
    glVertexAttribDivisor(MATRIX4_LOCATION, 1);

    // Bind IBO
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, iId );


    // Make sure the VAO is not changed from outside code
    glBindVertexArray(0);

    return;
}

 

glVertexAttribPointer(MATRIX1_LOCATION, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 0));

세번째 인자는 stride 이고, 4번째는 offset 이다.

stride는 정점마다 간격이 얼마나 되는지 써줘야 하므로 sizeof(mat4)가 맞고,

offset은 mat4의 첫번째열 두번째열 세번째열 네번째열로 바인딩 해야 하기 떄문에,

0, 16, 32. 48 로 해주면된다.

glVertexAttribDivisor는 인스턴스 당 행렬을 갱신해야 되므로, 두번째 인자를 1로 준다.

 

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

void glVertexAttribDivisor(GLuint index, GLuint divisor);

 

  • index: 속성의 위치로, glVertexAttribPointer로 지정한 속성 인덱스와 동일합니다.
  • divisor: 이 속성이 얼마나 자주 갱신될지를 나타내는 값입니다.

 

divisor 값의 의미

  • divisor = 0: 속성이 각 정점마다 갱신됩니다. 이는 일반적인 VBO처럼 작동하여, 하나의 정점마다 속성 데이터가 변경됩니다.
  • divisor = 1: 속성이 각 인스턴스마다 갱신됩니다. 예를 들어, 변환 행렬처럼 각 인스턴스마다 다르게 설정할 필요가 있는 속성에 사용합니다.
  • divisor = n: 속성이 매 n개의 인스턴스마다 갱신됩니다. 예를 들어, divisor = 2라면 2개의 인스턴스마다 속성이 한 번씩 갱신됩니다.

glVertexAttribDivisor의 용도

glVertexAttribDivisor는 인스턴스별로 고유한 속성을 지정할 때 필수적입니다. 예를 들어, 100개의 큐브 인스턴스를 각각 다른 위치와 회전으로 렌더링하려는 경우, 각 인스턴스마다 고유한 model 변환 행렬을 제공해야 합니다.

이런 상황에서 glVertexAttribDivisor를 통해 속성이 인스턴스마다 갱신되도록 설정하면, glDrawElementsInstanced나 glDrawArraysInstanced와 같은 함수 호출을 한 번으로 여러 인스턴스를 렌더링할 수 있습니다.

 

 

// Shader
#version 300 es

layout(location = 0) in vec4 VertexPosition;
layout(location = 1) in vec4 VertexColor;
layout(location = 2) in mat4 MODELVIEWPROJECTIONMATRIX;

out vec4 Color;

void main() 
{
  gl_Position = MODELVIEWPROJECTIONMATRIX * VertexPosition;

  Color = VertexColor;
}

 

셰이더에서는 이 네 개의 vec4가 자동으로 하나의 mat4로 조합됩니다.

예를 들어, 셰이더 코드에서 layout(location = 2) in mat4 MODELVIEWPROJECTIONMATRIX;로 선언하면

location = 2부터 location = 5까지의 4개의 연속된 vec4가 하나의 mat4로 매핑됩니다.

 

layout (location = 2)  layout (location = 3)  layout (location = 4) layout (location = 5)

설정한 4개의 vec4 속성은 GPU가 각 인스턴스마다 고유의 mat4로 해석하고,

이를 MODELVIEWPROJECTIONMATRIX로 사용할 수 있게 됩니다.

 

void Cube::RenderCube()
{
    glBindBuffer( GL_ARRAY_BUFFER, matrixId );
    glm::mat4* matrixBuf = (glm::mat4*)glMapBufferRange( GL_ARRAY_BUFFER, 0, sizeof(glm::mat4*)*(dimension*dimension*dimension), GL_MAP_WRITE_BIT);
    static float l = 0;
    TransformObj->TransformRotate(l++, 1, 1, 1);
    TransformObj->TransformTranslate(-distance*dimension/4,  -distance*dimension/4, -distance*dimension/4);
    glm::mat4 projectionMatrix  = *TransformObj->TransformGetProjectionMatrix();
    glm::mat4 modelMatrix       = *TransformObj->TransformGetModelMatrix();
    glm::mat4 viewMatrix        = *TransformObj->TransformGetViewMatrix();
    int instance = 0;
    LOGI("Show matrix buf size : %d", sizeof(matrixBuf));

    for ( int i = 0; i < dimension; i++ )
    {
        for ( int j = 0; j < dimension; j++ )
        {
            for ( int k = 0; k < dimension; k++ )
            {
                matrixBuf[instance++] = projectionMatrix * viewMatrix
                        * glm::translate(modelMatrix,glm::vec3( i*distance , j*distance, k*distance))
                        * glm::rotate( modelMatrix, l, glm::vec3(1.0, 0.0, 0.0));
            }
        }
    }
    glUnmapBuffer ( GL_ARRAY_BUFFER );

    glBindVertexArray(Vertex_VAO_Id);
    glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, (void*)0, dimension*dimension*dimension);
}

 

인스턴스 오브젝트 1000개를 그리려면 1000개의 변환 행렬이 필요하다.

1000개의 행렬을 저장할 메모리를 glMapBufferRange를 통해 할당하자.

glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, (void*)0, dimension*dimension*dimension);

 

glDrawElementsInstanced는 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으로 설정하고 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iId);와 같이 IBO를 미리 바인딩합니다.
  • instancecount: 생성할 인스턴스의 개수입니다.
    예를 들어, 1000개의 큐브 인스턴스를 렌더링하고 싶다면 instancecount = 1000으로 설정합니다.