Primitive Processing
Tessellation
테셀레이션은 patch로 제공된 큰 프리미티브를 렌더링 전에 많은 작은 프리미티브로 쪼개는 작업이다. 일반적으로는 적은 폴리곤 메시에 지오메트리 디테일을 추가하는 것이다. opengl에서 테셀레이션은 파이프라인에서 세 개의 스테이지 테셀레이션 컨트롤 쉐이더(TCS), 고정 함수 테셀레이션 엔진, 테셀레이션 이벨류에이션 쉐이더(TES)를 사용해서 생성된다.
테셀레이션 컨트롤 쉐이더는 한 번에 32개에 달하는 버텍스의 그룹에 대해 수행된다. 이를 patch라고 한다.
테셀레이션 컨트롤 쉐이더는 다음 세 가지를 생성해야한다.
- 패치당 내부 및 외부 테셀레이션 인자
- 각 출력 제어점에 대한 위치 및 기타 속성
- 패치당 사용자 정의 베어링
테셀레이션 인자는 고정 함수 테셀레이션 엔진으로 보내져서, 어떻게 패치를 더 작은 프리미티브로 쪼갤지를 결정한다. 테셀레이션 컨르롤 쉐이더의 출력 또한 패치이다. 이는 엔진에 의해 테셀레이션된 다음 테셀레이션 이벨류에이션 쉐이더로 전달된다.
고정 함수 테셀레이터가 실행되면 테셀레이셔 인자와 테셀레이션 모드에 의해 패치 내에 새로운 버텍스들의 세트를 생성한다. 테셀레이션 모드는 테셀레이션 이벨류에이션 쉐이더의 레이아웃 선언을 사용해서 지정한다. Opengl이 제공하는 테셀레이션 이벨류에이션 쉐이더의 유일한 입력은 일련의 좌표로서 패치 내 버텍스가 어디에 위치하는지에 대한 정보이다. 테셀레이더가 삼각형을 생성시, 그 좌표들은 중심 좌표계를 따른다.
Tessellation Primitive Modes
opengl이 패치를 래스터라이제이션으로 전달하기 전, 어떻게 여러 프리미티브로 쪼개는 지 결정하는데 사용된다.
이 모드는 테셀레이션 이벨류에이션 쉐이더의 입력 레이아웃 지시어를 사용해서 설정하고, quads(사각형), triangles(삼각형), isolines(등차선) 중 하나이다.
이 모드는 테셀레이터에 의해 생성된 프리미티브의 형태를 제어하는 것 뿐 아니라 테셀레이션 이벨류에이션 쉐이더의 gl_TessCoord 입력 변수를 해석하는 방법또한 결정한다.
Tessellation Using Quads
모드를 quads로 선택하면, 테셀레이션 엔진은 사변형(혹은 사각형)을 생성하고, 일련의 삼각형으로 쪼개준다.
위 그림을 보면 gl_TessLevelInner[]와 gl_TessLevelOuter[]가 있다. 둘 다 테셀레이션 컨트롤 쉐이더로 작성하며, gl_TessLevelInner[]는 내부 영역에 적용할 테셀레이션의 레벨을 결정한다. gl_TessLevelInnerd의 두 요소는 각각 수평 방향의 레벨과 수직 방향의 레벨이다.
마찬가지로 두 요소가 있는 gl_TessLevelOuter[]는 외부 가장자리에 적용된 테셀레이션 레벨을 결정한다.
#include <sb6.h>
class tessmodes_app : public sb6::application
{
public:
tessmodes_app()
: program_index(0)
{
}
void init()
{
static const char title[] = "OpenGL SuperBible - Tessellation Modes";
sb6::application::init();
memcpy(info.title, title, sizeof(title));
}
virtual void startup()
{
static const char * vs_source[] =
{
"#version 420 core \n"
" \n"
"void main(void) \n"
"{ \n"
" const vec4 vertices[] = vec4[](vec4( 0.4, -0.4, 0.5, 1.0), \n"
" vec4(-0.4, -0.4, 0.5, 1.0), \n"
" vec4( 0.4, 0.4, 0.5, 1.0), \n"
" vec4(-0.4, 0.4, 0.5, 1.0)); \n"
" \n"
" gl_Position = vertices[gl_VertexID]; \n"
"} \n"
};
static const char * tcs_source_triangles[] =
{
"#version 420 core \n"
" \n"
"layout (vertices = 3) out; \n"
" \n"
"void main(void) \n"
"{ \n"
" if (gl_InvocationID == 0) \n"
" { \n"
" gl_TessLevelInner[0] = 5.0; \n"
" gl_TessLevelOuter[0] = 8.0; \n"
" gl_TessLevelOuter[1] = 8.0; \n"
" gl_TessLevelOuter[2] = 8.0; \n"
" } \n"
" gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; \n"
"} \n"
};
static const char * tes_source_triangles[] =
{
"#version 420 core \n"
" \n"
"layout (triangles) in; \n"
" \n"
"void main(void) \n"
"{ \n"
" gl_Position = (gl_TessCoord.x * gl_in[0].gl_Position) + \n"
" (gl_TessCoord.y * gl_in[1].gl_Position) + \n"
" (gl_TessCoord.z * gl_in[2].gl_Position); \n"
"} \n"
};
static const char * tes_source_triangles_as_points[] =
{
"#version 420 core \n"
" \n"
"layout (triangles, point_mode) in; \n"
" \n"
"void main(void) \n"
"{ \n"
" gl_Position = (gl_TessCoord.x * gl_in[0].gl_Position) + \n"
" (gl_TessCoord.y * gl_in[1].gl_Position) + \n"
" (gl_TessCoord.z * gl_in[2].gl_Position); \n"
"} \n"
};
static const char * tcs_source_quads[] =
{
"#version 420 core \n"
" \n"
"layout (vertices = 4) out; \n"
" \n"
"void main(void) \n"
"{ \n"
" if (gl_InvocationID == 0) \n"
" { \n"
" gl_TessLevelInner[0] = 9.0; \n"
" gl_TessLevelInner[1] = 7.0; \n"
" gl_TessLevelOuter[0] = 3.0; \n"
" gl_TessLevelOuter[1] = 5.0; \n"
" gl_TessLevelOuter[2] = 3.0; \n"
" gl_TessLevelOuter[3] = 5.0; \n"
" } \n"
" gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; \n"
"} \n"
};
static const char * tes_source_quads[] =
{
"#version 420 core \n"
" \n"
"layout (quads) in; \n"
" \n"
"void main(void) \n"
"{ \n"
" vec4 p1 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x); \n"
" vec4 p2 = mix(gl_in[2].gl_Position, gl_in[3].gl_Position, gl_TessCoord.x); \n"
" gl_Position = mix(p1, p2, gl_TessCoord.y); \n"
"} \n"
};
static const char * tcs_source_isolines[] =
{
"#version 420 core \n"
" \n"
"layout (vertices = 4) out; \n"
" \n"
"void main(void) \n"
"{ \n"
" if (gl_InvocationID == 0) \n"
" { \n"
" gl_TessLevelOuter[0] = 5.0; \n"
" gl_TessLevelOuter[1] = 5.0; \n"
" } \n"
" gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; \n"
"} \n"
};
/*
static const char * tes_source_isolines[] =
{
"#version 420 core \n"
" \n"
"layout (isolines, equal_spacing, cw) in; \n"
" \n"
"void main(void) \n"
"{ \n"
" vec4 p1 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x); \n"
" vec4 p2 = mix(gl_in[2].gl_Position, gl_in[3].gl_Position, gl_TessCoord.x); \n"
" gl_Position = mix(p1, p2, gl_TessCoord.y); \n"
"} \n"
};
*/
static const char * tes_source_isolines[] =
{
"#version 420 core \n"
" \n"
"layout (isolines) in; \n"
" \n"
"void main(void) \n"
"{ \n"
" float r = (gl_TessCoord.y + gl_TessCoord.x / gl_TessLevelOuter[0]); \n"
" float t = gl_TessCoord.x * 2.0 * 3.14159; \n"
" gl_Position = vec4(sin(t) * r, cos(t) * r, 0.5, 1.0); \n"
"} \n"
};
static const char * fs_source[] =
{
"#version 420 core \n"
" \n"
"out vec4 color; \n"
" \n"
"void main(void) \n"
"{ \n"
" color = vec4(1.0); \n"
"} \n"
};
int i;
static const char * const * vs_sources[] =
{
vs_source, vs_source, vs_source, vs_source
};
static const char * const * tcs_sources[] =
{
tcs_source_quads, tcs_source_triangles, tcs_source_triangles, tcs_source_isolines
};
static const char * const * tes_sources[] =
{
tes_source_quads, tes_source_triangles, tes_source_triangles_as_points, tes_source_isolines
};
static const char * const * fs_sources[] =
{
fs_source, fs_source, fs_source, fs_source
};
for (i = 0; i < 4; i++)
{
program[i] = glCreateProgram();
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vs, 1, vs_sources[i], NULL);
glCompileShader(vs);
GLuint tcs = glCreateShader(GL_TESS_CONTROL_SHADER);
glShaderSource(tcs, 1, tcs_sources[i], NULL);
glCompileShader(tcs);
GLuint tes = glCreateShader(GL_TESS_EVALUATION_SHADER);
glShaderSource(tes, 1, tes_sources[i], NULL);
glCompileShader(tes);
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fs, 1, fs_sources[i], NULL);
glCompileShader(fs);
glAttachShader(program[i], vs);
glAttachShader(program[i], tcs);
glAttachShader(program[i], tes);
glAttachShader(program[i], fs);
glLinkProgram(program[i]);
glDeleteShader(vs);
glDeleteShader(tcs);
glDeleteShader(tes);
glDeleteShader(fs);
}
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glPatchParameteri(GL_PATCH_VERTICES, 4);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
virtual void render(double currentTime)
{
static const GLfloat black[] = { 0.0f, 0.0f, 0.0f, 1.0f };
glClearBufferfv(GL_COLOR, 0, black);
glUseProgram(program[program_index]);
glDrawArrays(GL_PATCHES, 0, 4);
}
virtual void shutdown()
{
int i;
glDeleteVertexArrays(1, &vao);
for (i = 0; i < 4; i++)
{
glDeleteProgram(program[i]);
}
}
void onKey(int key, int action)
{
if (!action)
return;
switch (key)
{
case 'M': program_index = (program_index + 1) % 4;
break;
}
}
private:
GLuint program[4];
int program_index;
GLuint vao;
};
DECLARE_MAIN(tessmodes_app)
github 소스코드에서 해당 예제는 tessmodes project이다.
위 그림은 사각형의 테셀레이션 된 형태이다. 사각형의 경우 테셀레이션 될 때, 테셀레이션 엔진이 사각형 내에 정규화 된 2차원 영역에 대해 버텍스들을 생성한다.
테셀레이션 이벨류에이션 쉐이더로 전달된 gl_TessCoord입력 변수 값은 사각형 내 정규화된 버텍스 좌표를 담는 2차원 벡터가 된다. 테셀레이션 이벨류에이션 쉐이더에서는 테셀레이션 컨트롤 쉐이더로부터 전달받은 입력으로부터 출력 생성 시 이 좌표들을 사용할 수 있다.
#version 450 core
layout (vertices = 4) out;
void main(void)
{
if (gl_InvocationID == 0)
{
gl_TessLevelInner[0] = 9.0;
gl_TessLevelInner[1] = 7.0;
gl_TessLevelOuter[0] = 3.0;
gl_TessLevelOuter[1] = 5.0;
gl_TessLevelOuter[2] = 3.0;
gl_TessLevelOuter[3] = 5.0;
}
gl_out[gl_InvocationID].gl_Position =
gl_in[gl_InvocationID].gl_Position;
}
위 코드는 테셀레이션 컨트롤 쉐이더 코드이며, 테셀레이션 인자들을 설정하였다. gl_TessLevelInner와 gl_TessLevelOuter값들을 설정하였다.
#version 450 core
layout (quads) in;
void main(void)
{
// Interpolate along bottom edge using x component of the
// tessellation coordinate
vec4 p1 = mix(gl_in[0].gl_Position,
gl_in[1].gl_Position,
gl_TessCoord.x);
// Interpolate along top edge using x component of the
// tessellation coordinate
vec4 p2 = mix(gl_in[2].gl_Position,
gl_in[3].gl_Position,
gl_TessCoord.x);
// Now interpolate those two results using the y component
// of tessellation coordinate
gl_Position = mix(p1, p2, gl_TessCoord.y);
}
위 코드는 테셀레이션 결과를 출력하는 테셀레이션 이벨류에이션 쉐이더 코드이다.
쉐이더 앞쪽에 quads 레이아웃 지시어로 설정하였고, TesCoordinate함수로 버텍스 위치 보간을 수행한다.
'개발 · 컴퓨터공학' 카테고리의 다른 글
Learning Unreal 4 언리얼 공부일지 - 패키징을 해보자 (0) | 2022.04.05 |
---|---|
OpenGL 공부일지 - OpenGL Super Bible 프리미티브 프로세싱 - 2 (0) | 2022.04.04 |
Learning Unreal 4 언리얼 공부일지 - 언리얼 엔진 최적화에 대해서 (0) | 2022.04.02 |
OpenGL 공부일지 - OpenGL Super Bible 버텍스 프로세싱 및 드로잉 커맨드 - 5 (0) | 2022.04.01 |
Learning Unreal 4 언리얼 공부일지 - 라이트 모빌리티 종류와 라이트 최적화의 관계를 알아보자 (0) | 2022.03.31 |