티스토리 뷰

이 장에서는 Rendering Pipeline(RP)의 개요와 RP에서 Shader의 활용을 알아본다.
개요 단계의 내용이고 매우매우 자세한 내용은 없다.
RP의 하나 하나 단계는 그 자체만으로도 매우 깊은 단계이기 때문에 너무 자세한 이야기는 생략하도록 한다.

1. RP의 개념

1.1. RP의 정의

렌더링 파이프라인은 3D 모델을 스크린에 render 하기 위한 stage들의 모음이다.
이 중 4개의 단계는 ‘Shader’를 통해 프로그래밍 할 수 있다.
이 Shader는 보통 GLSL/HLSL 등의 언어를 통해 작성된다.
그렇다면 이 RP가 어떤 stage들로 구성되어 있는지 알아보자.

1.2. RP의 Stage

RP는 다음과 같이 9개의 stage로 구성되어있다.

정리해보면 다음 순서로 각각의 역할을 수행한다. 앞에 [Programmable]을 붙인 부분은 사용자가 제어 가능한 부분이다.

  1. Vertex Specification: render할 이미지의 vertex 정보를 작성하고 저장.
  2. [Programmable]Vertex Shader: vertex에 대한 정보를 수정할 수 있는 부분.
  3. [Programmable]Tessellation Shader: vertex data들이 작은 primitive들로 세분화되는 단계.
  4. [Programmable]Geometry Shader: primitive를 processing 하는 단계. 0개 이상의 primitive들을 생성할 수 있다.
  5. Vertex Post-Processing: clip space 좌표를 기반으로 vertex의 좌표계를 변환.
  6. Primitive Assembly: 각 vertex들을 연결하여 primitive를 만드는 단계.
  7. Rasterization: primitive를 2차원의 각 픽셀에 대응시키는 단계.
  8. [Programmable]Fragment Shader: 프래그먼트의 정보를 바탕으로 색상을 조작하는 단계.
  9. Per-Sample Operations: 유저가 지정한 여러 Test들을 수행하는 단계.

각 단계에 대해서는 아래에서 조금씩 더 자세하게 알아보자.

1.2.1. Vertex Specification

Vertex를 정의하고 저장하는 과정이다.
기술적으로는 Vertex stream을 만들어 vertex shader에 넘기는 과정이다.
이 과정에서 VAO/VBO라는 OpenGL Object를 이용하여 정보를 넘기게 된다.

깊은 글은 아니지만 VAO와 VBO는 해당 개념을 잘 이해해야 한다.
그러므로 간단하게나마 짚고 넘어가겠다.

VBO

이 specification 과정은 vertex를 정의하고 저장하는 과정이라 했다.
그렇다면 이 과정에서 필요할 정보들을 생각해보면 다음과 같다.

  • vertex data 그 자체: 각 vertex 별 position, uv(텍스쳐매핑), tangent 등의 정보.
  • vertex data를 어떻게 해석할 것인지에 대한 정보.

우선 VBO는 Vertex Buffer Object로서 vertex data 그 자체에 대한 정보를 저장한다.
예를 들어 다음과 같이 우리가 정의해볼 수도 있다.(xyzxyzxyz 순서)

float vertices1[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

혹은 uv를 포함하여 다음과 같이 정의할 수도 있다.(xyzuvxyzuvxyzuv 순서)

float vertices2[] = {
    -0.5f, -0.5f, 0.0f, 0.2f, 0.5f,
     0.5f, -0.5f, 0.0f, 0.1f, 0.4f,
     0.0f,  0.5f, 0.0f 0.3f, 0.7f,
};

이런 VBO는 그 해석 방법이 없으므로 이것으로는 아무 것도 그릴 수 없다.
몇 번째가 x고 몇 번째가 uv인가? uv 정보가 있긴 한가?
즉 배열만 보고서는 어떤 속성이 어디 있는지 알 수 없다. 단순한 raw data의 나열일 뿐이다.

따라서 Vertex의 속성 정보를 제공하는 ‘Vertex Attribute’ 의 선언이 필요하다.

Vertex Attribute

vertex attribute는 vertex의 속성으로서 xyz, uv, normal과 같은 vertex의 정보들을 나타낸다.
이 정보를 따로 선언해주면 opengl은 vbo를 해석할 수 있는 방법을 알게 된다.
예를 들어 아까의 vertex를 생각해보자.

float vertices2[] = {
    -0.5f, -0.5f, 0.0f, 0.2f, 0.5f,
     0.5f, -0.5f, 0.0f, 0.1f, 0.4f,
     0.0f,  0.5f, 0.0f 0.3f, 0.7f,
};

이 정보는 생각해보면 아래와 같이 해석해야 할 것이다.

이 때 attribute는 다음과 같다.

  • xyz attribute
  • uv attribute

또한, 각 attribute 별로 필요한 정보는 다음과 같다.

  • attribute id(location)
  • attribute의 element 개수
  • attribute의 type
  • attribute의 stride: 얼만큼의 size 뒤에 동일한 attribute가 나오는가의 보폭

위와 같은 경우 다음과 같이 코드를 작성하여 Attribute를 선언할 수 있다.

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GL_FLOAT), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);

0번 id로 크기가 3인 xyz attribute를, 1번 id로 크기가 2인 uv attribute를 만들고 각각을 활성화했다.
이렇게 되면 이제 opengl이 위의 VBO를 해석할 방법을 알게 될 것이고, 이후의 RP 단계에서 이를 사용할 수 있을 것이다.

API에 대한 설명은 이번 문서의 내용을 다소 벗어나므로 아래 문서를 참고해주면 고마울 것 같습니다..

https://registry.khronos.org/OpenGL-Refpages/gl4/html/glVertexAttribPointer.xhtml

VAO

우선 위와 같이 VBO와 attribute만 있어도 렌더링은 할 수 있다.
하지만 조금 raw한 면이 있다.
매 번 VBO와 attribute를 각각 설정해줘야 하고 상태로서 관리하는 것이 불가능하다.

따라서 이것을 상태로서 이용할 수 있도록 VAO가 존재한다.
VAO는 Vertex Array Object로서 Vertex Attribute와 여러 VBO들을 담을 수 있는 Object다.
다음과 같이 Attribute에 대한 정보와 여러 VBO들을 Binding하는 정보들을 들고 있다.

직접적인 사용법은 다음 글에서 다루기로 하며 우선은 VAO에 대한 설명은 이 정도에서 마친다.

1.2.2. Vertex Shader(Programmable)

Vertex들을 조작하는 부분.
주로 world 변환, view 변환, projection 변환 처리 등을 진행한다.
유니티 shader에서 사용했을 때는 스케일을 키워 외곽선을 만드는 패스를 그릴 때 사용한 적도 있었다.

1.2.3. Tesselation[Programmable]

data를 small primitives로 나누는 과정이다.
opengl 4.0 이상에서 등장한 최신 shader type.
상당히 higher level의 detail을 다룬다.

정확한 정의 자체는 ‘patch로 제공된 primitive를 더 작은 primitive로 쪼개는 것’을 의미한다.
(여기서 patch는 고차 primitive를 의미한다.)

여러 detail이 있지만 우선은 이 역할 정도로만 알아두고 넘어가자.

1.2.4. Geometry Shader[Programmable]

primitive를 processing할 수 있는 shader.
새로운 primitive 형태로 변환하는 등의 작업을 수행할 수 있다.

1.2.5. Vertex Post-Processing

Transform Feedback

  • 이전 stage들의 결과가 buffer에 저장된다.

Clipping

  • 보이지 않는 Primitive들을 삭제해버림.(볼 필요 없는 애들은 필요 없으니까)
  • vertex의 좌표계가 clipspace에서 window space로 변환됨.

1.2.6. Primitive-Assembly

vertex stream을 받아서 primitive들의 sequence로 조직하는 과정.

1.2.7. Rasterization

  • Primitive들을 Fragment들로 전환한다.
  • pixel에 맞는 형태로 변경.
  • 보간을 사용.

1.2.8. Fragment Shader

  • 각 fragment들을 다루는 shader 영역
  • 필수는 아니지만 보통 사용한다.
    • depth나 stencil 데이터 등이 필요할 때는 예외
  • 가장 중요한 결과는 픽셀의 색깔이다.
  • OpenGL에서 가장 간단한 형태는 Vertex Shader와 Fragment Shader만을 가진 형태.

1.2.9. Per-Sample Operation

각종 test들의 모음으로서 scissor test / stencil test / depth test 등이 이 단계에서 수행된다.

Refs

매우 많이 참고한 매우 좋은 글: https://kyoungwhankim.github.io/ko/blog/opengl_triangle1/

https://www.khronos.org/opengl/wiki/vertex_Specification

https://github.com/rlatkddn212/opengl-graphics-pipeline?tab=readme-ov-file

https://velog.io/@aqusta/OpenGL-파이프라인

https://learn.microsoft.com/en-us/windows/uwp/graphics-concepts/geometry-shader-stage--gs-

댓글