Instancing

Date:     Updated:

카테고리:

태그:

Main Reference
- Learn OpenGL
- Rinthel Kwon - OpenGL Lecture


Instancing

for (unsigned int i = 0; i < amount_of_models_to_draw; i++) {
  DoSomePreparations(); // bind VAO, bind textures, set uniforms etc.
  glDrawArrays(GL_TRIANGLES, 0, amount_of_vertices);
}

만약 배경에 존재하는 풀이나 총알과 같이 동일한 오브젝트를 위치만 다르게 그려하 하는 상황이라고 해보자. 이때 오브젝트마다 VBO, VAO 바인딩하고, transform matrix 보내주고, shader로 그리고.. 하면 엄청난 비용이 발생할 것이다. 심지어 driver를 통해 GPU와 통신하면서 하는 작업이라 개념이 비슷한 메모리 풀링이랑은 차원이 다르다. 이런 경우 OpenGL은 CPU, GPU간 통신을 최소화하고자 한 번의 draw call로 여러 오브젝트를 그리는 Instancing 기능이 존재한다.


Example

m_grassPos.resize(10000);
for (size_t i = 0; i < m_grassPos.size(); i++) {
    m_grassPos[i].x = ((float)rand() / (float)RAND_MAX * 2.0f - 1.0f) * 5.0f;
    m_grassPos[i].z = ((float)rand() / (float)RAND_MAX * 2.0f - 1.0f) * 5.0f;
    m_grassPos[i].y = glm::radians((float)rand() / (float)RAND_MAX * 360.0f);
}

m_grassInstance = VertexLayout::Create();
m_grassInstance->Bind();
m_plane->GetVertexBuffer()->Bind();
m_grassInstance->SetAttrib(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
m_grassInstance->SetAttrib(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), offsetof(Vertex, normal));
m_grassInstance->SetAttrib(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), offsetof(Vertex, texCoord));

m_grassPosBuffer = Buffer::CreateWithData(GL_ARRAY_BUFFER, GL_STATIC_DRAW, m_grassPos.data(), sizeof(glm::vec3), m_grassPos.size());
m_grassPosBuffer->Bind();
m_grassInstance->SetAttrib(3, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), 0);
glVertexAttribDivisor(3, 1);
  • m_grassPos : vec3 배열. -1 ~ 1 사이의 랜덤 값 지정
  • m_grassInstance : grass용 VAO 생성 & 바인딩
    • m_plane 오브젝트를 이용할 것이기 때문에, 그대로 pos, normal, texCoord 순으로 attribute 지정
  • m_grassPosBuffer : grass instance를 위한 position VBO 생성 & 바인딩
  • m_grassInstance 3번 layout에 지정
  • glVertexAttribDivisor(3, 1); : 바인딩된 VAO의 3번 attribute(layout=3)은 1개의 인스턴스마다 업데이트


// vertex shader
#version 330 core
 
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoord;
layout (location = 3) in vec3 aOffset;
out vec2 texCoord;
 
uniform mat4 transform;
 
void main() {
    float c = cos(aOffset.y);
    float s = sin(aOffset.y);
    mat4 offsetMat = mat4(
        c, 0.0, -s, 0.0,
        0.0, 1.0, 0.0, 0.0,
        s, 0.0, c, 0.0,
        aOffset.x, 0.0, aOffset.z, 1.0);
    gl_Position = transform * offsetMat * vec4(aPos, 1.0);
    texCoord = aTexCoord;
}
  • layout (location=1) : normal은 사용하지 않아서 뺌
  • 모든 풀들이 동일한 회전 값을 가지면 어색하므로 인스턴스 마다 다른 offset으로 간단한 회전 행렬 생성
    • 앞서 실행한 glVertexAttribDivisor(3, 1) 함수 때문
  • 회전 시킨 후 MVP 변환해서 gl_Position으로 넘겨주기


// Render()..
glEnable(GL_BLEND);
m_grassProgram->Use();
m_grassProgram->SetUniform("tex", 0);
m_grassTexture->Bind();
m_grassInstance->Bind();
modelTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.5f, 0.0f));
transform = projection * view * modelTransform;
m_grassProgram->SetUniform("transform", transform);
glDrawElementsInstanced(GL_TRIANGLES,
    m_plane->GetIndexBuffer()->GetCount(),
    GL_UNSIGNED_INT, 0,
    m_grassPosBuffer->GetCount());
void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount)
  • Instancing 기법을 이용하지 않으면 풀 10000개를 그려야 하는데, 풀 마다 m_plane을 draw 해야 함
  • glDrawElementsInstanced 함수는 기존에 사용하던 glDrawElements와 매우 유사. 끝에 instance count 추가해주면 됨
  • draw call 한 번으로 끝


034829



맨 위로 이동하기

OpenGL 카테고리 내 다른 글 보러가기

댓글 남기기