Post Lists

2018년 8월 8일 수요일

Advanced OpenGL - Instancing (10)

https://learnopengl.com/Advanced-OpenGL/Instancing

Instancing
너가 많은 모델들을 그리고 있는 scene을 가진다고 가정해보자. 거기에서 대부분의 이러한 모델들은 vertex data의 같은 집합을 포함한다. 하지만, 다른 world transformations를 가진다. 잔디 잎으로 채워진 scene을 생각해보아라: 각 grass leave는 오직 몇 개의 삼각형으로 구성된 작은 모델이다. 너는 아마도 그것들 몇 개를 꽤 그리길 원할 것이다. 그리고 너의 scene은 너가 매 frame마다 렌더링 할 필요가 있는 수십만개의 잔디들로 채워질지도 모른다. 각 잎들이 오직 몇 개의 삼각형으로만 구성되기 때문에, 그 잎은 거의 끊임없이 렌더링 되지만, 너가 만들어야 할 모든 그러한 수천개의 렌더링 호출들은 급속도로 성능을 떨어뜨릴 것이다.

만약 우리가 그러한 많은 양의 오브젝트들을 렌더링 하려고 한다면, 그것은 코드에서 이것처럼 보일 것이다:


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);
}

이 처럼 너의 모델들의 많은 instances를 그리려고 할 때, 너는 많은 draw calls 때문에 성능 병목에 빠르게 도달할 것이다. 실제 정점들을 렌더링 하는 것과 비교하여, GPU에게 glDrawArrays 또는 glDrawElements와 같은 함수들로 정점 데이터를 렌더링하라고 말하는 것은 꽤 성능을 먹어치우게 한다. 왜냐하면 OpenGL은 그것이 너의 정점 데이터를 그릴 수 있게 하기 전에 필수적인 준비들을 해야만 하기 때문이다. (GPU에게 어떤 버퍼가 데이터를 읽어들일지, 어디에서 정점 attributes를 찾을지 그리고 상대적으로 느린 CPU에서 GPU가는 버스에 대한 이 모든 것들). 그래서 너의 정점들을 렌더링하는 것이 매우 빠를지라도, 너의 GPU 렌더링 명령어를 주는 것은 그렇지 않다.

만약 우리가 GPU에게 한 번에 몇 데이터를 보내고 OpenGL에게 이 데이터를 사용하여 한 번의 drawing call로 수 많은 오브젝트들을 그리도록 하는 것은 좀 더 편리해질 것이다.

Instancing은 한 번의 render call로 많은 오브젝트를 그리는 기법이다. 이것은 한 오브젝트를 렌더링할 때 마다 CPU -> GPU의 의사소통을 줄여준다; 이것은 오직 한 번만 되어야 한다. instancing을 사용하여 렌더링하기 위해서, 우리가 할 필요가 있는 모든 것은 render calls glDrawArrays와 glDrawElements를 glDrawArraysInstanced와 glDrawElementsInstanced로 각각 바꾸는 것이다. 이러한 고전 렌더링 함수의 instanced 버전들은 우리가 렌더링 하길 원하는 인스턴스들의 개수를 설정하는 instance count라고 불리는 추가 인자를 받는다. 따라서 우리는 모든 요구되는 데이터를 GPU에게 오직 한 번 보내고, 그러고나서 GPU에게 어떻게 그것이 모든 이러한 인스턴스들을 한 번의 call로 그려야 할지를 말해준다. 그 GPU는 그러고나서 CPU와 지속적으로 통신할 필요 없이 모든 이러한 인스턴스들을 렌더링한다.

그 자체로 이 함수는 조금 쓸모 없다. 같은 오브젝트를 수 천번 그리는 것은 우리에게 쓸모 없다. 왜냐하면 그 렌더링 된 오브젝트들 가각은 정확히 같게 렌더링 되고, 따라서 또한 같은 위치에 있기 때문이다; 우리는 오직 한 개의 오브젝트를 볼 것이다! 이러한 이유로 GLSL는 gl_InstanceID라고 불리는 vertex shader에서 또 다른 내장 변수를 넣었다.

인스턴스화된 렌더링 호출중 하나를 통해 draw할 때, gl_InstanceID는 렌더링 될 각 인스턴스에 대해 0부터 시작하여 증가된다. 만약 우리가 예를들어 43번째 인스턴스를 렌더링한다고 하면, gl_InstanceID는 vertex shader에서 42의 값을 갖을 것이다. 인스턴스마다 고유 값을 갖는 것은 우리가 이제 예를들어 world에서 각 인스턴를 다른 위치에 배치하기 위해 position values의 큰 배열에 인덱싱 할 수 있다는 것이다.

인스턴스화된 draw에 대한 느낌을 얻기위해, 우리는 NDC에서 백개의 2D quads를 렌더링하는 간단한 예를 보여줄 것이다. 우리는 100개의 offset vectors의 uniform array에 인덱싱하여 각 인스턴스화된 quad에 작은 offset을 추가하여 이것을 얻는다. 그 결과는 전체 윈도우를 채우는 단정히 구성된 quads들의 그리드이다:

각 쿼드는 총 6개의 정점으로 2개의 삼각형으로 구성된다. 각 정점은 2D NDC 위치 벡터와 컬러 벡터를 포함한다. 아래에서 이 예제에 대해 사용된 vertex data가 있다. - 그 삼각형은 많은 양일 때 스크린에 적절히 맞기위해 꽤 작다:


float quadVertices[] = {
    // positions     // colors
    -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
     0.05f, -0.05f,  0.0f, 1.0f, 0.0f,
    -0.05f, -0.05f,  0.0f, 0.0f, 1.0f,

    -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
     0.05f, -0.05f,  0.0f, 1.0f, 0.0f,   
     0.05f,  0.05f,  0.0f, 1.0f, 1.0f        
};  

그 쿼드들의 컬러들은 vertex shader로부터 넘겨진 color vector를 받아서 그것의 color output을 설정하는 fragment shader로 얻어진다.

지금 까지 새로운 것은 없지만, vertex shader에서 흥미로운 것이 생긴다:


#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out vec3 fColor;

uniform vec2 offsets[100];

void main()
{
 vec2 offset = offsets[gl_InstanceID];
 gl_Position = vec4(aPos + offset, 0.0, 1.0);
 fColor = aColor;
}

여기에서 우리는 총 100개의 offset vectors들을 포함하는 offsets이라고 불리는 uniform array를 정의했다. vertex shader내에서, 우리는 gl_InstanceID를 사용하여 offsets 배열에 인덱싱하여 각 인스턴스에 대해 offset vector를 얻는다. 만약 우리가 인스턴스화된 drawing을 사용하여 100개의 quads를 그리려고 한다면, 우리는 이 vertex shader로 다른 위치에 있는 100개의 쿼드들을 얻을 것이다.

우리는 실제로 게임 루프에 들어가기전에 중첩 for loop에서 계산한 offset positions을 설정할 필요가 있다:


glm::vec2 translations[100];
int index = 0;
float offset = 0.1f;
for (int y = -10; y < 10; y += 2)
{
 for (int x = -10; x < 10; x += 2)
 {
  glm::vec2 translation;
  translation.x = (float)x / 10.0f + offset;
  translation.x = (float)y / 10.0f + offset;
  translations[index++] = translation;
 }
}

여기에서 우리는 10x10 grid에서 모든 포지션들에 대해 translation vector를 포함하는 100개의 translation vector 집합을 만든다.translations 배열을 생성하는 것 외에도, 우리는 데이터를 vertex shader의 uniform 배열에 전송할 필요가 있다:


instanceShader.use();
for (unsigned int i = 0; i < 100; ++i)
{
 std::stringstream ss;
 std::string index;
 ss << i;
 index = ss.str();
 instanceShader.setVec2(("offsets[" + index + "]").c_str(), translations[i]);
}

이 코드 조각 내에서, 우리는 for-loop counter i를 우리가 동적으로 uniform location을 쿼리를 위한 location string을 만들기 위해 사용할 string으로 변환한다. offsets uniform array에 있는 각 항목에 대해, 우리는 그러고나서 대응되는 translation vector를 설정한다.

모든 준비가 끝났으니 우리는 그 쿼드들을 렌더링할 수 있다. instanced rendering을 통해 draw하기 위해서, 우리는 glDrawArraysInstanced 또는 glDrawElementsInstanced를 호출한다. 우리는 element index buffer를 사용하고 있지 않아서, 우리는 glDrawArrays 버전을 호출할 것이다:


instanceShader.use();
glBindVertexArray(insquadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);


glDrawArraysInstanced의 인자들은 정확히 glDrawArrays와 같다. 우리가 그리고 싶은 인스턴스의 개수를 설정하는 마지막 인자를 빼고. 우리는 10x10 grid에서 100개의 quads들을 보이고 싶기 때문에, 우리는 그것을 100으로 설정한다. 코드를 작동시키는 것은 너에게 친숙한 100개의 컬러풀한 쿼드들의 이미지를 줄 것이다.

Instanced arrays
이전의 구현이 이 특정한 use case에 작동할지라도, 우리가 100개 이상의 인스턴스들을 렌더링하기 원할 때마다 (꽤 흔한) 우리는 결국에는 우리가 쉐이더들에 보낼 수 있는 uniform data의 양에 제한에 걸리게 된다. 또 다른 대안은 instanced arrays라고 불려지는데, 그것은 vertex shader가 새로운 instance를 렌더링 할 때 마다 업데이트되는 vertex attribute로서 정의되는 것이다. (이것은 우리가 좀 더 많은 데이터를 저장하도록 해준다.)

vertex attributes와 함께, vertex shader의 각각의 작동은 GLSL가 현재 vertex에 속하는 vertex attributes의 다음 set를 가져오도록 한다. 그러나 vertex attribute를 한 instanced array로 설정할 때, vertex shader는 오직 정점마다 대신에, instance마다 vertex attribute의 내용을 업데이트 해야 한다. 이것은 우리가 정점마다 데이터에 대한 기준 vertex attribute를 사용하도록 하고, instance마다 고유의 데이터를 저장한 instanced array를 사용하게 한다.

instanced array의 한 예를 주기 위해, 우리는 이전의 예를 가져와서 offset uniform array를 instanced array로 나타낼 것이다. 우리는 또 다른 vertex attribute를 추가하여 vertex shader를 업데이트 할 것이다:


#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;

out vec3 fColor;

void main()
{
 gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
 fColor = aColor;
}

우리는 더 이상 gl_InstanceID를 사용하지 않고, 처음에 큰 uniform array에 인덱싱하지 않고 직접적으로 offset attribute를 사용할 수 있다.

instanced array가 vertex attribute이기 때문에, position과 color 변수들 처럼, 우리는 또한 그것의 내용을 vertex buffer object에 저장할 필요가 있고, 그것의 attribute pointer를 설정할 필요가 있다. 우리는 처음에 translations 배열을 새로운 버퍼 오브젝트에 저장할 것이다:

unsigned int instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

그러고나서 우리는 또한 그것의 vertex attribute pointer를 설정하고 vertex attribute를 활성화 할 필요가 있다:


glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribDivisor(2, 1);

이 코드를 흥미롭게 만드는 것은 마지막 라인인데, 거기에서 우리는 glVertexAttribDivisor를 호출한다. 이 함수는 OpenGL에게 vertex attribute의 내용을 언제 다음 element로 업데이트 할지를 말해준다. 그것의 첫 번째 인자는 vertex attribute이고, 두 번째 parameter는 attribute divisor이다. 기본적으로 attribute divisor는 0으로 설정되어있고, 이것은 OpenGL에게 vertex shader의 매 반복마다 vertex attribute의 내용을 업데이트하라고 말하는 것이다. 이 attribute를 1로 설정하여, 우리는 OpenGL에게 새로운 인스턴스를 렌더링하기 시작할 때 그 vertex attribute의 내용을 업데이트하길 원한다고 말하는 것이다. 그것을 2로 설정하여, 우리는 2개의 인스턴스마다 그 내용을 업데이트한다. 등등. 그 attribute divisor를 1로 설정하여, 우리는 효과적으로 attribute location 2인 vertex attribute가 instanced array라고 말하는 것이다.

만약 우리가 이제 그 쿼드들을 다시 glDrawArraysInstanced를 이용하여 렌더링 한다면, 우리는 다음의 결과를 얻는다:


이것은 정확히 이전의 예시외 같지만, 이번에 instanced arrays를 이용하여 얻어진 것이다. 그 instanced arrays는 우리가 많은 데이터를 (메모리가 우리에게 허용하는 만큼) vertex shader로 instanced drawing을 위해 넘기는 것을 가능하게 한다.

재미를 위해서, 우리는 천천히 각 쿼드를 오른쪽 위에서 왼쪽 아래로 크기를 줄일 수 있다. gl_InstanceID를 다시 사용해서.


void main()
{
 vec2 pos = aPos * (gl_InstanceID / 100.0);
 gl_Position = vec4(pos + aOffset, 0.0, 1.0);
 fColor = aColor;
}

그 결과는 그 쿼드들의 첫 번째 인스턴스들은 매우 작게 그려지고, 우리가 그 인스턴스를 그리는 과정에서 더 나아갈수록, gl_InstanceID는 100에 가까워지고, 따라서 좀 더 많은 쿼드들이 그것들의 원래 사이즈를 되찾는다. gl_InstanceID로 이렇게 instanced arrays를 함께 사용하는 것은 완벽히 합법적이다.

만약 너가 어떻게 instanced rendering이 작동하는지 확신이 안서거나, 모든 것이 어떻게 확실히 들어맞는지 보길 원한다면, 여기에서 프로그램의 전체 소스코드를 볼 수 있다.

재미있지만, 이러한 예제들은 instancing의 정말 좋은 예들이 아니다. 그래, 그것들은 너에게 어떻게 instancing이 작동하는지에 대한 쉬운 개관을 제공하지만, instancing은 우리가 지금까지 정말 하지 않았던 유사한 오브젝트들의 수 많은 양을 그리려할 때 매우 유용하다.

An asteroid field
큰 소행성 고리의 중심에 있는 하나의 큰 행성을 가진 scene을 상상해보아라. 그러한 소행성 고리는 수십개의 바위 지형을 포함할 수 있고, 빠르게 어느 멋진 그래픽카드에서도 렌더링 될 수 없다. 이 시나리오는 그 자체로 특히 instanced rendering에 유용하다는 것을 보여준다. 왜냐하면 모든 소행성들은 단일의 모델을 이용하여 나타내어지기 때문이다. 각 단일의 소행성은 그러고나서 각 소행성에 고유한 transformation matrix를 이용하여 사소한 변형을 포함한다.

instanced rendering의 영향을 보여주기 위해, 우리는 instaned rendering 없이 한 행성 주위에 날아다니는 소행성의 한 scene을 렌더링 할 것이다. 그 scene은 여기에서 다운로드 될 수 있는 큰 행성 모델과 우리가 적절히 행성 주위에 배치할 소행성 바위들의 큰 집합을 포함할 것이다. 그 소행성 바위 모델은 여기에서 다운로드 되어질 수 있다.

코드 샘플 내에서, 우리는 model loading tutorials에서 이전에 정의한 model loader를 사용해서 models들을 불러온다.

우리가 찾는 그 효과를 얻기 위해서, 우리는 그것들의 model matrix로서 사용할 각 소행성에 대한 transformation matrix를 만들 것이다. 그 transformation matrix는 처음에 소행성 고리에 바위를 어딘가에 이동하여 만들어진다. - 우리는 또한 그 고리가 좀 더 자연스럽게 보이기 위해 몇 가지 랜덤 변위 값을 이 offset에 추가할 것이다. 그러고나서 우리는 random scale과 random rotation을 회전 벡터에 적용할 것이다. 그 결과는 행성 주변에서 각 소행성을 transform하는 transformation matrix이다.  또한 이것은 좀 더 자연스럽고 다른 소행성들과 비교하여 고유한 외관을 준다. 그 결과는 각 소행성이 서로에게 달라보이는 소행성들로 꽉찬 고리이다.


unsigned int amount = 10000;
glm::mat4* modelMatrices;
modelMatrices = new glm::mat4[amount];
srand(glfwGetTime()); // initialize random seed
float radius = 50.0f;
float offset = 2.5f;
for (unsigned int i = 0; i < amount; ++i)
{
 glm::mat4 model(1.0);
 // 1. translation: displace along circle with 'radius' in range [-offset, offset]
 float angle = (float)i / (float)amount * 360.0f;
 float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
 float x = sin(angle) * radius + displacement;
 displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
 float y = displacement * 0.4f; // keep the height of the filed smaller compared to width of x and z
 displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
 float z = cos(angle) * radius + displacement;
 model = glm::translate(model, glm::vec3(x, y, z));

 // 2. scale: Scale between 0.05 and 0.25f;
 float scale = (rand() % 20) / 100.0f + 0.05f;
 model = glm::scale(model, glm::vec3(scale));

 // 3. rotation: add random rotation around a (semi) randomly picked rotation axis vector
 float rotAngle = (rand() % 360);
 model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));

 modelMatrices[i] = model;
}

이 코드는 주늑들게 하는 것처럼 보이지만, 우리는 기본적으로 radius로 정의된 반지름이 있는 한 원을 따라 소행성의 x와 z의 위치로 transform 하고, 랜덤하게 각 소행성을 원 주위에 -offset and offset의 범위로 배치한다. 우리는 y 변위에 좀 더 평평한 소행성 고리를 만들기 위해 영향을 덜 준다. 그러고나서 우리는 scale과 회전 transformation을 적용하고, 크기가 amount인 modelMatrices에 최종 transformation matrix를 저장한다. 여기에서 우리는 소행성마다 한 개씩 총 100개의 모델 행렬를 생성한다. (나는 10000개로 했다.)

행성과 바위 모델들을 불러오고 쉐이더들을 컴파일 한 후에, 그 렌더링 코드는 이렇게 보일 것이다:


// draw Planet
shader.use();
glm::mat4 model;
model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
shader.setMat4("model", model);
planet.Draw(shader);

// draw meteorites
for(unsigned int i = 0; i < amount; ++i)
{
   shader.setMat4("model), modelMatrices[i];
   rock.Draw(shader);
}

처음에 우리는 scene에 맞게하기 위해 우리가 평행이동 시키고 조금 스케일링한 행성 모델을 그린다. 그리고나서 우리는 우리가 계산한 transformations의 amount만큼과 동일한 수 많은 rock models들을 그린다. 우리가 그러나 각 rock을 그리기전에, 우리는 쉐이더 내에서 대응되는 model transformation matrix를 설정한다.

그 결과는 우주같은 scene이다. 거기에서 우리는 행성 주위에 자연스럽게 보이는 행성을 볼 수 있다:

이 scene은 1000개의 rock model들이 있는 프레임마다 총 1001개의 렌더링 호출을 포함한다. 너는 여기에서 scene에 대한 소스를 볼 수 있다.

우리가 이 숫자를 증가하기 시작하자마자, 우리는 빠르게 scene이 부드럽게 작동되는 것을 멈추고, 우리가 초마다 렌더링할 수 있는 프레임의 수가 급격하게 줄어드는 것을 눈치챌 것이다. 우리가 amount 2000으로 설정하자마자, 그 sccene이미 너무 느리고, 주위를 움직이기에 어렵다.

같은 scene을 렌더링하려고 해보지만, 이번에 instanced rendering을 사용해서 해보자. 우리는 처음에 vertex shader를 조정할 필요가 있따:


#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in mat4 instanceModel;
layout (location = 7) in mat3 view_instanceNormal;
layout (location = 11) in mat3 world_instanecNormal;

layout (std140) uniform Matrices
{
 mat4 view;   // col0 0 ~ 15
                  // col1 16 ~ 31
      // col2 32 ~ 47
      // col3 48 ~ 63
 mat4 projection;    // col0 64 ~ 79
      // col1 80 ~ 95
      // col2 96 ~ 111
      // col3 112 ~ 128
};

out vec3 view_FragPos;
out vec3 world_FragPos;

out vec3 view_Normal;
out vec3 world_Normal;

out vec2 TexCoords;

void main()
{
 gl_Position = projection * view * instanceModel * vec4(aPos, 1.0);
 
 view_FragPos = vec3(view * model * vec4(aPos, 1.0));
 world_FragPos = vec3(model * vec4(aPos, 1.0));

 view_Normal = view_instanceNormal * aNormal;
 world_Normal = world_instanecNormal * aNormal;
 
 TexCoords = aTexCoords;
}
(이건 내 코드)

우리는 더 이상 model uniform variable을 사용하지 않을 것이지만, 대신에 mat4를 vertex attribute로서 선언한다. 그래서 우리는 transformation 행렬의 인스턴스화된 배열을 저장할 수 있다. 그러나, 우리가 vec4보다 더 큰 한 datatype을 vertex attribute로서 선언할 때, 상황은 조금 다르게 작동한다. vertex attribute로서 허용되는 최대 데이터 양은 vec4이다. mat4는 기본적으로 4개의 vec4이기 때문에, 우리는 이 특정한 행렬에 대해 4개의 vertex attributes를 보유해야만 한다. 우리는 그것에 3의 위치를 할당 했기 때문에, 그 행렬의 열들은 3,4,5,6의 vertex attribute locations을 갖을 것이다.

우리는 그러고나서 그러한 4개의 vertex attributes의 attribute pointers에 대해 각각 설정해야만 한다. 그리고 그것들을 instanced arrays로서 설정해야만 한다:


void Mesh::setupInstance(unsigned int amount)
{
 instance_amount = amount;
 
 glBindVertexArray(VAO);

 // Allocate Memory
 glGenBuffers(1, &instanceModelVBO);
 glBindBuffer(GL_ARRAY_BUFFER, instanceModelVBO);
 glBufferData(GL_ARRAY_BUFFER, instance_amount * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);

 glGenBuffers(1, &instanceViewNormalVBO);
 glBindBuffer(GL_ARRAY_BUFFER, instanceViewNormalVBO);
 glBufferData(GL_ARRAY_BUFFER, instance_amount * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);

 glGenBuffers(1, &instanceWorldNormalVBO);
 glBindBuffer(GL_ARRAY_BUFFER, instanceWorldNormalVBO);
 glBufferData(GL_ARRAY_BUFFER, instance_amount * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
 // Allocate Memory

 GLsizei vec4Size = sizeof(glm::vec4);

 // Model Matrix Instance Vertex attribute Setting
 glBindBuffer(GL_ARRAY_BUFFER, instanceModelVBO);
 for (int i = 3; i < 3 + 4; ++i)
 {
  glEnableVertexAttribArray(i);
  glVertexAttribPointer(i, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(vec4Size * (i - 3)));
  glVertexAttribDivisor(i, 1);
 }

 // ViewNormal Matrix Instance Vertex attribute Setting
 glBindBuffer(GL_ARRAY_BUFFER, instanceViewNormalVBO);
 for (int i = 7; i < 7 + 4; ++i)
 {
  glEnableVertexAttribArray(i);
  glVertexAttribPointer(i, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(vec4Size * (i - 7)));
  glVertexAttribDivisor(i, 1);
 }

 // WorldNormal Matrix Instance Vertex attribute Setting
 glBindBuffer(GL_ARRAY_BUFFER, instanceWorldNormalVBO);
 for (int i = 11; i < 10 + 4; ++i)
 {
  glEnableVertexAttribArray(i);
  glVertexAttribPointer(i, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(vec4Size * (i - 10)));
  glVertexAttribDivisor(i, 1);
 }

 glBindVertexArray(0);
}
(ADS lighting system + Uniform buffer(layout std 140) + reflection of skycube를 포함한 쉐이더의 Instance buffer setting)

우리가 Mesh의 VAO 변수를 private 변수 대신 public 변수로 선언하여 조금 속임수를 쓴거에 주의해라. 그래서 우리는 그것의 vertex array object에 접근할 수 있다. 이것은 가장 깔끔한 솔루션은 아니지만, 이 튜토리얼에 적합한 간단한 수정이다. 작은 hack을 제외하고, 이 코드는 깔끔할 것이다. 우리는 기본적으로 OpenGL이 어떻게 행렬의 vertex attribute의 각각에 대해 buffer를 해석해야할지를 선언하고, 그러한 vertex attribute의 각각이 instanced array라고 선언한다.

다음으로, 우리는 다시 mesh(es)의 VAO를 취해서, glDrawElementsInstanced를 이용하여 draw를 한다:


// Draw Instanced meshes
glBindVertexArray(VAO);
glDrawElementsInstanced(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_INT, 0, instance_amount);

여기에서 우리는 이전의 예시와 같은 양의 소행성을 그리지만, 이 번에 instanced rendering을 사용한다. 그 결과는 비슷하지만, 너는 우리가 이 amount 변수를 증가시키기 시작할 때 instanced rendering의 효과를 보기 시작할 것이다. instanced rendering 없이 우리는 이제 이 값을 100000으로 설정할 수 있고, rock model들은 576개의 정점을 가지니, 어떠한 성능의 저하도 없이 매 프레임마다 약 57만 개의 정점들과 동일하다.


   (닭들에게 지진일으키기)

그 이미지는 radius가 150인 100000개의 소행성들로 렌더링 되어졌고, offset은 25.0f 이다.
너는 instanced rendering demo를 여기의 코드에서 볼 수 있다.

  Green Box
  다른 머신들에서, 100000개의 소행성 개수는 너무 높을지도 모른다. 그래서 너가 수용할만한 프레임율에 도달할 때 까지 그 값을 바꾸어보아라.

너도 볼 수 있듯이, 환겨의 올바른 타입과함께, instanced rendering은 너의 그래픽스 카드의 렌더링 능력에 엄청난 차이를 만들 수 있다. 이 이유 때문에, instanced rendering은 흔히 grass, flora particles 그리고 이것과 같은 scene에 사용된다. 기본적으로 많은 반복되는 모형을 가진 어떤 scene은 instanced rendering으로부터 혜택을 얻는다.

=================================================
각종 기능을 결합하여 instancing을 구현하느라 힘들었다.
좋은 경험이였다.

댓글 없음:

댓글 쓰기