게임공장
[Learn OpenGL 번역] 5-8. 고급 OpenGL - 고급 GLSL 본문
고급 GLSL
고급 OpenGL/고급 GLSL
이번 강좌는 여러분의 scene에 대한 시각적 효과를 증대시킬 아주 멋진 기능을 소개하지는 않을 것입니다. 이 강좌는 GLSL의 흥미로운 부분을 소개할 것이고 앞으로 도움이 될만한 멋진 트릭들도 소개할 것입니다. 기본적으로 OpenGL 응용 프로그램을 만들 때 알면 좋은 기능과 여러분의 삶을 쉽게 만들어 줄 기능들입니다.
우리는 흥미로운
GLSL의 내장 변수
Shader는 혀재 shader 밖의 다른 소스의 데이터가 필요한 경우 데이터를 전달해야 합니다. 우리는 이를 vertex attributes, uniforms, sampler 에서 볼 수 있습니다. 하지만 GLSL에는 추가적인 여러가지 내장 변수들을 가지고 있습니다. gl_
접두사가 붙어있고 이는 데이터를 모으거나 작성하는 추가적인 의미를 가지고 있습니다. 우리는 이미 강좌에서 두 개의 내장 변수를 보았습니다. vertex shader의 출력 벡터인 gl_Position과 fragment shader의 gl_FragCoord가 바로 그것입니다.
우리는 흥미로운 내장 입력 출력 변수들을 다룰 것이고 이들이 우리에게 어떠한 이점을 주는지에 대해서도 알아볼 것입니다. 우리는 GLSL에 존재하는 모든 내장 변수들을 다루지는 않을 것이므로 모든 내장 변수들을 보고 싶다면 OpenGL의 wiki에서 확인해보세요.
Vertex shader 변수
우리는 이미 vertex shader의 clip-space 출력 위치 벡터인 gl_Position을 보았습니다. Vertex shader에서 gl_Position을 설정하는 것은 화면에 무엇이든 렌더링하기 위해 필수적인 요구사항입니다. 우리가 이를 수행하기 전에는 아무것도 볼 수 없습니다.
gl_PointSize
기초 도형을 렌더링할 때 우리는 GL_POINTS를 선택할 수 있습니다. 이 것은 하나의 vertex가 점으로 렌더링되는 것입니다. 우리는 이 점의 크기를 OpenGL의
gl_PointSize라고 불리는 GLSL의 출력 내장 변수는
Vertex shader에서 점의 크기를 수정하는 것은 기본값으로는 비활성화되어 있습니다. 하지만 활성화시키고자 한다면 OpenGL의 GL_PROGRAM_POINT_SIZE를 활성화시켜야 합니다.
glEnable (GL_PROGRAM_POINT_SIZE);
이 것의 간단한 예제는 점의 크기를 viewer와 vertex 사이의 거리인 clip-space 위치의 z 값과 동일하게 설정하는 것입니다. 이 점 크기는 viewer와 멀리 떨어진 vertex일수록 더 커질 것입니다.
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
gl_PointSize = gl_Position.z;
}
결과는 크게 그려진 점일수록 우리와 멀리 떨어져있다는 것입니다.
Vertex 마다 점의 크기를 변하게 하여 흥미로운 기술인 particle을 만들어 낼 수도 있습니다.
gl_VertexID
gl_Position과 gl_PointSize는 출력 변수입니다. 우리는 이들을 작성함으로써 결과에 영향을 줄 수 있습니다. 이 vertex shader는 또한 흥미로운 입력 변수를 제공합니다. gl_VertexID가 바로 그것입니다.
이 정수 변수인 gl_VertexID는 우리가 지금 그리고 있는 vertex의 ID를 가지고 있습니다. (
지금은 별 도움이 안될지라도 알고 있으면 도움이 될 것입니다.
Fragment shader 변수
Fragment shader 내부에서 우리는 일부 흥미로운 변수들을 볼 수 있습니다. GLSL은 gl_FragCoord와 gl_FrontFacing이라고 불리는 흥미로운 입력 변수들을 제공합니다.
gl_FragCoord
우리는 depth testing을 다룰 때에 gl_FragCoord 변수를 본 적이 있었습니다. gl_FragCoord 벡터가 특정 fragment의 깊이 값과 동일하기 때문이었죠. 하지만 우리는 또한 이 벡터의 x, y 요소를 사용하여 흥미로운 효과를 낼 수 있습니다.
이 gl_FragCoord의 x, y 요소는 fragment의 window-space 좌표입니다. 이 좌표는 좌측 하단부터 시작하죠. 우리는
이 fragment shader를 사용하여 우리는 fragment의 윈도우 좌표를 기반으로 다른 컬러 값을 계산할 수 있습니다. gl_FragCoord 변수가 자주 쓰이는 용도는 다른 fragment 연산과 시각적 효과를 비교하기 위해 쓰입니다. 마치 테크 데모처럼 말이죠. 예를 들어 우리는 하나의 출력은 화면 왼쪽에 렌더링하고 다른 출력은 화면 오른쪽에 렌더링하여 분할된 화면을 만들 수 있습니다. 아래와 같이 fragment의 윈도우 좌표에 따라 다른 출력을 줄수가 있습니다.
void main()
{
if(gl_FragCoord.x < 400)
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
else
FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
윈도우 창의 너비가 800이기 때문에 픽셀의 x 좌표가 400보다 작다는 것은 화면의 왼쪽에 있다는 것을 의미하고 따라서 우리는 이 오브젝트에 다른 컬러를 줄 수 있습니다.
우리는 이제 두개의 완전히 다른 fragment shader 결과를 화면 양쪽에 보여줄 수 있게 되었습니다. 이는 예를 들어서 다른 조명 기술들을 테스팅할 때 아주 유용합니다.
gl_FrontFacing
FragmentShader의 또다른 흥미로운 입력 변수는 gl_FrontFacing 변수입니다. Face culling 강좌에서 OpenGL은 면이 전면인지 후면인지 winding 순서에 따라 판별할 수 있다고 하였습니다. 만약 face culling을 사용하지 않는다면 gl_FrontFacing 변수가 우리에게 현재 fragment가 전면인지 후면인지 알려줍니다. 그러면 우리는 예를 들어 전면에만 다른 컬러를 줄 수가 있습니다.
gl_FrontFacing 변수는 이 fragment가 전면이면 true
, 후면이면 false
를 가지는
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D frontTexture;
uniform sampler2D backTexture;
void main()
{
if(gl_FrontFacing)
FragColor = texture(frontTexture, TexCoords);
else
FragColor = texture(backTexture, TexCoords);
}
컨테이너의 내부를 보면 다른 텍스처가 입혀져 있는 것을 볼 수 있습니다.
만약 face culling을 활성화시키면 컨테이너의 내부가 보이지 않기 때문에 gl_FrontFacing이 아무 의미가 없음을 알아두세요.
gl_FragDepth
gl_FragCoord는 현재 fragment의 window-space 좌표와 depth 값을 얻을 수 있는 입력 변수이지만
Shader 내부에서 실제로 depth 값을 설정하기 위해 우리는 간단히 이 출력 변수에 0.0
과 1.0
사이의
gl_FragDepth = 0.0; // 이 fragment는 이제 0.0의 depth 값을 가집니다
Shader에서 gl_FragDepth에 값을 작성하지 않으면 이 변수는 자동으로 gl_FragCoord.z
값을 취합니다.
하지만 우리가 직접 depth 값을 설정하는 데에는 단점이 존재합니다. OpenGL이 모든
gl_FragDepth에 작성함으로써 여러분은 이러한 성능적인 패널티를 고려해야합니다. 하지만 OpenGL 4.2버전 부터 우리는 fragment shader의 시작지점에
layout (depth_<condition>) out float gl_FragDepth;
이 condition
은 다음과 같은 값들을 취할 수 있습니다.
Condition | 설명 |
---|---|
any |
기본값. Early depth testing이 비활성화되고 대부분의 성능을 잃게됩니다. |
greater |
오직 gl_FragCoord.z 값보다 큰 depth 값만을 작성할 수 있습니다. |
less |
오직 gl_FragCoord.z 값보다 작은 depth 값만을 작성할 수 있습니다. |
unchanged |
gl_FragDepth 를 작성하면 정확히 gl_FragCoord.z 의 값을 작성합니다. |
Depth condition으로 greater
나 less
를 지정함으로써 OpenGL은 여러분이 오직 fragment의 depth 값보다 크거나 작은 값만을 작성할 수 있도록 합니다. 이 방법으로 OpenGL은 early depth test를 fragment의 depth 값보다 작거나 큰 값에 대해서 수행할 수 있습니다.
Fragment shader에서 depth 값을 증가시키지만 일부 early depth testing을 수행하는 예제는 아래와 같습니다.
#version 420 core // note the GLSL version!
out vec4 FragColor;
layout (depth_greater) out float gl_FragDepth;
void main()
{
FragColor = vec4(1.0);
gl_FragDepth = gl_FragCoord.z + 0.1;
}
이 기능은 오직 OpenGL 4.2 버전이상부터 사용가능하다는 것을 알아두세요.
Interface blocks
지금 까지 vertex shader 에서 fragment shader로 데이터를 보내고 싶을때마다 우리는 여러 입력/출력 변수들을 선언했었습니다. 이들을 동시에 선언하는 것은 한 shader에서 다른 shader로 데이터를 보낼 때 가장 쉬운 방법이지만 여러분이 보낼 데이터가 많아질 수록 응용 프로그램은 커지게 됩니다.
이 변수들을 묶을 수 있도록 GLSL은
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out VS_OUT
{
vec2 TexCoords;
} vs_out;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vs_out.TexCoords = aTexCoords;
}
이번에는 vs_out라고 불리는 interface block을 선언했습니다. 이 것은 우리가 다음 shader로 넘겨주고 싶은 모든 출력 변수들을 서로 묶어줍니다. 이 것은 아주 간단한 예제이지만 여러분은 이 것이 shader의 입력/출력을 체계화해줄 수 있다는 것을 알아두세요. 이는 또한 다음 강좌에서 볼 shader의 입력/출력을 배열로 묶어줄 때도 유용합니다.
그런 다음 또한 다음 shader인 fragment shader에서도 입력 interface block을 선언해야 합니다. 이
#version 330 core
out vec4 FragColor;
in VS_OUT
{
vec2 TexCoords;
} fs_in;
uniform sampler2D texture;
void main()
{
FragColor = texture(texture, fs_in.TexCoords);
}
두 interface block의 이름이 동일기만하면 해당 입력과 출력은 서로 연결됩니다. 이는 여러분의 코드를 체계화해주고 geometry shader와 같은 특정 shader 단계를 거칠 때 유용합니다.
Uniform buffer objects
우리는 OpenGL을 잠시동안 사용해왔고 일부 멋진 트릭들을 배웠지만 몹시 성가심니다. 예를 들어 하나 이상의 shader를 사용할 때 우리는 계속해서 각 shader 에서 정확히 동일한 값을 가지는 uniform 들을 설정해야 합니다.
OpenGL은
Uniform buffer object는 다른 buffer들과 같은 buffer이기 때문에 우리는
#version 330 core
layout (location = 0) in vec3 aPos;
layout (std140) uniform Matrices
{
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
대부분의 우리 예제들에서 우리가 사용할 각 shader에 반복문이 돌때마다 projection, view 행렬을 설정합니다. 이 것은 uniform buffer object의 유용함을 보여줄 훌륭한 예제입니다. 지금 오직 이 행렬들을 하나로 저장하기만 했지만 말이죠.
여기에서 우리는 Matrices라고 불리고 두 개의 4x4 행렬을 저장하는 uniform block을 선언하였습니다. Uniform block에 들어있는 변수들은 접두사로 block 이름을 쓰지 않아도 직접 사용할 수 있습니다. 그런 다음 OpenGL 코드의 어딘가에서 우리는 이 행렬 변수들을 buffer에 저장합니다. 그러면 이 uniform block을 선언한 각 shader에서는 이 행렬들을 사용할 수 있습니다.
여러분은 아마 지금 layout
(std140)
가 무슨 의미인지 궁금할 것입니다. 이 것은 현재 정의된 uniform block은 지정된 메모리 layout을 사용한다는 뜻입니다. 이 코드는
Uniform block layout
Uniform block의 내용은 기본적으로 메모리를 예약해 놓은 것 이상도 아닌 buffer 객체에 저장됩니다. 이 메모리 조각이 어떠한 유형의 데이터를 가지고 있는지에 대한 정보를 가지고 있지 않기 때문에 우리는 OpenGL에게 메모리의 어떠한 부분이 어떠한 shader의 어떠한 uniform 변수에 해당하는 지 알려주어야 합니다.
Shader에서 다음과 같은 uniform block이 선언되어있다고 생각해보세요.
layout (std140) uniform ExampleBlock
{
float value;
vec3 vector;
mat4 matrix;
float values[3];
bool boolean;
int integer;
};
우리가 알고 싶은 것은 크기(바이트로)와 각 변수들의 offset(block의 시작위치로부터)이므로 우리는 이들을 buffer에 각자의 순서로 위치시킬 수 있습니다. 각 요소의 크기는 명확히 OpenGL에 명시되어있고 직접적으로 C++ 데이터 타입에 해당합니다. 벡터와 행렬은 float의 (커다란) 배열입니다. OpenGL이 명확히 명시하지 않는 것은 변수들 사이의
기본적으로 GLSL은
Shared layout이 일부 공간을 절약하는 최적화를 제공해주는 반면 우리는 각 uniform 변수들에 대한 offset들을 알아야하고 이는 많은 작업을 요구합니다. 하지만 일반적으로는 shared layout을 사용하지 않고
각 변수는 uniform block 내에서 변수가 가질수 있는 공간(여백을 포함)인
정확한 layout 규칙은 OpenGL uniform buffer 스펙 여기에서 확인할 수 있습니다. 하지만 우리는 가장 많이 쓰이는 규칙들만 아래에 나타내었습니다. N
으로 표시했습니다.
타입 | Layout 규칙 |
---|---|
스칼라(예. |
각 스칼라는 N의 base alignment를 가지고 있습니다. |
벡터 | 2N 아니면 4N. 이는 |
스칼라나 벡터의 배열 | 각 요소들은 |
행렬 | 벡터의 큰 배열로서 저장됩니다. 각 벡터들은 |
Struct | 각 요소들이 위의 규칙에 따라 계산된 크기와 동일합니다. 하지만 |
OpenGL의 설명서의 대부분의 것처럼 이는 예제를 통해서 이해하기 쉽습니다. 우리는 ExampleBlock라고 불리는 uniform block을 사용하고 std140 layout을 사용하여 각 멤버들의 aligned offset을 계산합니다.
layout (std140) uniform ExampleBlock
{
// base alignment // aligned offset
float value; // 4 // 0
vec3 vector; // 16 // 16 (16의 배수여야하므로 4->16)
mat4 matrix; // 16 // 32 (0 열)
// 16 // 48 (1 열)
// 16 // 64 (2 열)
// 16 // 80 (3 열)
float values[3]; // 16 // 96 (values[0])
// 16 // 112 (values[1])
// 16 // 128 (values[2])
bool boolean; // 4 // 144
int integer; // 4 // 148
};
연습삼아, 여러분 스스로 offset 값을 계산해보세요. 그리고 이 표와 비교해보세요. 이 std140 layout 규칙에 따라 계산된 offset 값들과 함께 우리는
Uniform block을 정의하기 전에 layout
(std14)
코드를 추가함으로써 우리는 OpenGL에게 이 uniform block은 std140 layout을 사용한다라는 것을 알려줍니다. Buffer를 채우기 전에 offset에 대해서 알아보는 다른 방법이 두가지 존재합니다. 우리는 이미 shared
layout을 보았고 다른 나머지 layout은 packed
입니다. 이 packed layout을 사용할 때는 layout이 program 같에 동일하게 유지되지 않습니다. 이는 컴파일러가 uniform 변수들을 uniform block에 상관없이 최적화할 수 있도록 하기 때문이죠.
Uniform buffers 사용
Shader에서의 uniform block 선언과 메모리 layout 지정에 대해서 다루었지만 실제로 어떻게 사용하는지는 아직 다루지 않았습니다.
먼저
unsigned int uboExampleBlock;
glGenBuffers (1, &uboExampleBlock);
glBindBuffer (GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData (GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 152 바이트 메모리 할당
glBindBuffer (GL_UNIFORM_BUFFER, 0);
이제 buffer에 데이터를 집어넣거나 수정하고 싶을때마다 uboExampleBlock을 바인딩하고
OpenGL에는 우리가 uniform buffer를 연결시킬 수 있는 곳에 정의된
보시다시피 우리는 여러 uniform buffer들을 여러 binding point에 바인딩할 수 있습니다. shader A와 shader B 둘 다 동일한 binding point 0
에 연결되어있기 때문에 그들의 uniform block은 uboMatrices의 동일한 uniform data를 공유합니다. 요구되어야할 사항은 두 개의 shader 모두 같은 Matrices uniform block을 정의해야 된다는 것이지요.
특정 binding point에 uniform block을 연결하기 위해 우리는 2
에 설정할 수 있습니다.
unsigned int lights_index = glGetUniformBlockIndex (shaderA.ID, "Lights");
glUniform BlockBinding (shaderA.ID, lights_index, 2);
우리는 이를 각 shader에 반복해야한다는 것을 알아두세요.
layout(std140, binding = 2) uniform Lights { ... };
그런 다음 또한 우리는 uniform buffer object를 동일한 binding point에 바인딩해야하고 이는
glBindBuffer Base (GL_UNIFORM_BUFFER, 2, uboExampleBlock);
// 또는
glBindBuffer Range (GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);
2
에 연결시키고 이 이후부터 binding point의 양쪽은 연결되어집니다. 또한 여러분은
이제 모든 것이 세팅되었으므로 우리는 uniform buffer에 데이터를 추가할 수 있습니다. 우리는 모든 데이터를 하나의 바이트 배열로 추가하거나
glBindBuffer (GL_UNIFORM_BUFFER, uboExampleBlock);
int b = true; // GLSL에서 bool은 4바이트로 표현되므로 integer 타입으로 저장합니다
glBufferSubData (GL_UNIFORM_BUFFER, 144, 4, &b);
glBindBuffer (GL_UNIFORM_BUFFER, 0);
그리고 같은 과정을 uniform block 내부의 모든 다른 uniform 변수들에 적용할 수 있습니다. 하지만 range 파라미터는 다를 것입니다.
간단한 예제
이제 실제 유용한 예제를 설명드리겠습니다. 이전의 강좌들을 살펴보면 우리는 계속해서 3가지의 행렬을 사용해왔습니다. projection, view, model 행렬 말이죠. 이 행렬들 중에서 오직 model 행렬만 자주 변경됩니다. 만약 우리가 이러한 동일한 행렬의 모음을 사용하는 여러 shader를 가지고 있다면 uniform buffer object를 사용하는 것이 좋습니다.
우리는 projection, view 행렬을 Matrices라고 불리는 uniform block에 저장할 것입니다. Model 행렬을 제외할 것입니다. Model 행렬은 shader 사이에서 자주 변경되므로 uniform buffer object를 사용하는 이점이 없기 때문입니다.
#version 330 core
layout (location = 0) in vec3 aPos;
layout (std140) uniform Matrices
{
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
여기에 특별한 것은 없습니다. std140 layout을 사용하는 uniform block을 사용한다는 점만 제외하고 말이죠. 우리가 해야할 일은 4개의 큐브를 그리는 것인데 각 큐브는 다른 shader program을 사용합니다. 각 4 shader program은 동일한 vertex shader를 사용하지만 shader마다 다른 오직 하나의 색만 출력하는 다른 fragment shader를 사용합니다.
먼주 우리는 vertex shader의 uniform block을 binding point 0
으로 설정합니다. 이를 각 shader에 대해 수행해야한다는 것을 알아두세요.
unsigned int uniformBlockIndexRed = glGetUniformBlockIndex (shaderRed.ID, "Matrices");
unsigned int uniformBlockIndexGreen = glGetUniformBlockIndex (shaderGreen.ID, "Matrices");
unsigned int uniformBlockIndexBlue = glGetUniformBlockIndex (shaderBlue.ID, "Matrices");
unsigned int uniformBlockIndexYellow = glGetUniformBlockIndex (shaderYellow.ID, "Matrices");
glUniform BlockBinding (shaderRed.ID, uniformBlockIndexRed, 0);
glUniform BlockBinding (shaderGreen.ID, uniformBlockIndexGreen, 0);
glUniform BlockBinding (shaderBlue.ID, uniformBlockIndexBlue, 0);
glUniform BlockBinding (shaderYellow.ID, uniformBlockIndexYellow, 0);
그 다음 실제 uniform buffer object를 생성하고 binding point 0
에 바인딩합니다.
unsigned int uboMatrices
glGenBuffers (1, &uboMatrices);
glBindBuffer (GL_UNIFORM_BUFFER, uboMatrices);
glBufferData (GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer (GL_UNIFORM_BUFFER, 0);
glBindBuffer Range (GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4));
먼저 buffer에 충분한 메모리를 할당합니다. 이 메모리는 0
에 바인딩합니다.
이제 남은 해야할 일들은 실제로 buffer를 채우는 것입니다. 우리가 field of view 값을 상수로 유지시키고 싶다면 (카메라 줌이 없다) 우리는 오직 이 것을 한번만 정의하기만 하면 됩니다. 이는 이 데이터를 buffer에 한번만 삽입하면 되다는 의미입니다. 우리는 buffer object에 충분한 메모리를 할당하였기 때문에
glm::mat4 projection = glm::perspective (glm::radians (45.0f), (float)width/(float)height, 0.1f, 100.0f);
glBindBuffer (GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData (GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection));
glBindBuffer (GL_UNIFORM_BUFFER, 0);
여기에서 우리는 uniform buffer의 처음 반절 공간에 projection 행렬을 저장합니다. 각 렌더링 루프에서 오브젝트를 그리기 전에 우리는 buffer의 두 번째 공간에 view 행렬을 넣습니다.
glm::mat4 view = camera.GetViewMatrix();
glBindBuffer (GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData (GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
glBindBuffer (GL_UNIFORM_BUFFER, 0);
그리고 이제 됬습니다. Matrices uniform block을 가지고 있는 각 vertex shader는 이제 uboMatrices에 저장되어 있는 데이터를 가지고 있을 것입니다. 그리고 이제 4개의 다른 shader들을 사용하여 4개의 큐브를 그려보면 이들의 projection, view 행렬이 동일하게 유지되는 것을 알 수 있을 것입니다.
glBindVertexArray (cubeVAO);
shaderRed.use();
glm::mat4 model;
model = glm::translate (model, glm::vec3(-0.75f, 0.75f, 0.0f)); // 좌측 상단으로 이동
shaderRed.setMat4("model", model);
glDrawArrays (GL_TRIANGLES, 0, 36);
// ... 녹색 큐브 그리기
// ... 파란색 큐브 그리기
// ... 노란색 큐브 그리기
우리가 설정해야할 uniform은 오직 model uniform입니다. Uniform buffer object를 사용하여 shader마다의 약간의 uniform 호출을 줄일 수 있었습니다. 결과는 다음과 같습니다.
각 큐브는 model 행렬에 의해 화면의 한쪽으로 이동되어졌고 서로 다른 fragment shader 때문에 각자 색이 달라졌습니다. 이는 비교적 간단한 예제이지만 거대한 렌더링 응용 프로그램은 수백개의 shader program을 가질수 있습니다. 이 경우에는 uniform buffer object가 빛을 발하기 시작할 것입니다.
여기에서 전체 소스 코드를 확인할 수 있습니다.
Uniform buffer object는 여러가지 장점을 가지고 있습니다. 첫 째, 많은 uniform들을 한번에 설정하는 것은 하나하나 설정하는 것보다 빠릅니다. 둘 째, 여러 shader에 걸쳐있는 동일한 uniform을 수정하고 싶을 때 uniform buffer에 있는 uniform을 한번 수정하기가 쉽습니다. 직접적으로 드러나지 않은 마지막 장점은 uniform buffer object를 사용하여 shader에서 아주 많은 uniform들을 사용할 수 있다는 것입니다. OpenGL은 관리할 수 있는 uniform의 갯수에 제한이 있습니다. 이는 GL_MAX_VERTEX_UNIFORM_COMPONENTS를 사용하여 확인할 수 있습니다. Uniform buffer object를 사용할 때 이 제한은 아주 높아집니다. 그래서 uniform 갯수의 한계치에 닿았을 때(예를 들어 스켈레톤 애니메이션)마다 여러분은 항상 uniform buffer object를 사용할 수 있습니다.
'OpenGL' 카테고리의 다른 글
[Learn OpenGL 번역] 5-9. 고급 OpenGL - Geometry Shader (2) | 2018.08.23 |
---|---|
[Learn OpenGL 번역] 5-7. 고급 OpenGL - 고급 Data (0) | 2018.08.16 |
[Learn OpenGL 번역] 5-6. 고급 OpenGL - Cubemaps (0) | 2018.08.15 |
[Learn OpenGL 번역] 5-5. 고급 OpenGL - Framebuffers (0) | 2018.08.11 |
[Learn OpenGL 번역] 5-4. 고급 OpenGL - Face culling (0) | 2018.08.08 |