KoreanFoodie's Study

[OpenGL ES] 6강 : 정점 쉐이더(Vertex Shader)의 동작, Uniform 과 Attribute, DrawCall 본문

Game Dev/OpenGL ES

[OpenGL ES] 6강 : 정점 쉐이더(Vertex Shader)의 동작, Uniform 과 Attribute, DrawCall

GoldGiver 2023. 4. 15. 01:46

이 강의는 유투브에 무료로 공개되어 있는 한정현 교수님의 컴퓨터 그래픽스 강좌를 정리한 글입니다. 자세한 내용은 강의를 직접 들으시거나 을 구입하셔서 확인해 보세요. 강의 자료는 깃헙 링크에 올라와 있습니다.

요약 :

1. Vertex Shader 는 World transform, View transform, Projection transform 을 통해 오브젝트 공간을 월드, 씬, 클립 스페이스로 변환한다. 또한 OpenGL 에서 Shader 를 만들 때는 GLSL 이라는 언어를 사용한다.

2. Vertex Shader 에 입력으로 들어가는 것은 Attributes 와 Uniforms 가 있다. Attribute 는 각 Vertex 마다 입력되는 데이터이(e.g. position, normal, texture coordinate)고, Uniforms 는 정점들에 공통적으로 적용되는 녀석(e.g. World Transform Matrix)
 
3. index array 없이, vertex 들의 정점 정보만을 가지고도 fragment 에게 일을 시켜 폴리곤 메시를 그려낼 수 있다. 이를 Drawcall 이라고 하는데, 사실 이를 사용할 일은 거의 없다.

정점 쉐이터(Vertex Shader) 알아보기

우리는 world transform 을 통해 각각의 물체들의 오브젝트 공간을 월드 공간으로 변환했다. 그 후, view transform 을 수행하여 카메라 공간으로 좌표계를 변환하고 projection transform 으로 카메라 공간을 클립 공간으로 바꾸었다.

 

우리는 Vertex Array 에 Position 과 Normal 이 들어 있다는 것을 알고 있다. 그런데 사실 그것만큼 중요한 texture coordinate 이 들어 있는데... 이는 8강에서 자세히 다룰 것이다.

GPU 는 위 값들을 병렬적으로 처리하는데, 각각의 정점은 독립적으로 처리가 된다.

 

위는 OpenGL ES 의 버전 정보이다. 굳이 자세히 알 필요는 없다. 다만 한 가지 기억해야 할 것은, 우리가 직접 vertex shader 와 fragment shader 를 만들어 제공해 주어야 한다는 것이다!

다만 shader 는 GPU 를 제어하는 녀석이므로, 자체적인 Shader Language 를 이용한다(예를 들어 GLSL)! 

GLSL 은 C 와 매우 비슷하다. 다만 행렬 계산에 특화된 언어라고 생각하면 편하다.

 

Vertex Shader 는 대표적으로 두 가지 인풋을 받는다.

  • Attributes : 정점별로 Vertex Array 에 저장된 데이터(예를 들어 위치값)
  • Uniforms : 오브젝트별로 복수의 연산에서 공통적으로 적용되는 데이터(예를 들어 월드 행렬)

대표적인 Attribute 에는 position, normal, texture coordinate 이 있다. 이 녀석들은 vertex 별로 다른 값을 가지고 있을 것이다.

Output 을 한 번 보자. Vertex Shader 는 최종적으로 정점들을 Clip Space 로 변환해야 하는데, 그래서 gl_Position 만 특별하게 변수로 선언되어 있다. 😄

 

위의 코드를 보면서 Vertex Shader 코드를 어떻게 쓰는지 보자.

gl_Position 을 구하는 줄을 보면, position 에 1.0 을 더해 4 x 4 행렬(동차좌표계)로 만든 다음, 변환 행렬들을 곱해주는 것을 볼 수 있다.

layout 과 in 을 사용해 attribute 값들을 가져오고 있다.

 

GL 명령어는 gl 로, 데이터 타입은 GL 로 시작한다.

 

shader object 는 glCreateShader 를 이용해 생성된다. 자세한 것은 위의 코드를 보면서 이해하자 😅

 

shader object 는 program object 에 attach 되어야 한다. program object 는 glCreateProgram 으로 만든다.

 

 

Attributes - Vertex Shader

폴리곤 메시 데이터가 .obj 파일 등에 저장되었다고 하자. 이 데이터는 로드되어 objData 에 위와 같이 vertex array 형식으로 저장될 것이다.

glm 이라는 OpenGL Mathematics 라이브러리를 사용하면, 위의 데이터를 간단히 다룰 수 있다.

 

obj 파일을 로드하면, CPU 메모리에 vertex array 와 index array 가 올라갈 것이다. 그런데 실제 연산은 GPU 가 담당한다. 즉, GPU 에게 이것을 인계해야 하는데... GPU는 메모리에서 BUFFER OBJECT 를 만듦으로써 이를 가능케 한다.

각각을 GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER 라고 부른다.

 

위를 보면서 실습을 해 보자.

glGenBuffers 를 이용해 buffer object 를 만든다. glBindBuffers 를 이용해 vertex array 를 바인딩하고, glBufferData 로 내용을 채운다.

 

index array 도 마찬가지로 위의 방식처럼 버퍼 내용을 채워 주어야 할 것이다.

 

Attribute 를 읽을 때는 stride 라는 단위가 있다. stride 는 사실 위에서 정의한 pos, nor, tex 를 포함한 구조체의 크기와 같은데... 이게 어떻게 유용한 것일까?

우리는 이전에 GPU 가 버퍼 정보를 채울 때 vertex array 의 정보와 index array 의 정보를 각각 따로 저장하는 것을 관찰했다. 즉, GPU 는 Attribute 들을 종류별로 나누어 저장하는 것이다!

위의 코드를 보면, position 이 attribute 0 이고, normal 이 attribute 1 임을 알 수 있다. normal 에서 다음 normal 로 가려면 stride 만큼을 더한 위치를 읽으면 될 것이다. 😉

 

Uniforms - Vertex Shader

Vertex Shader 는 세 가지 Uniform 을 갖고 있다 : worldMat, viewMat, projMat.

그런데 만약 동적인 환경이라면 어떨까? 예를 들어, 주전자가 흔들리고 카메라가 움직인다면 worldMat 과 viewMat 은 이에 따라 변화해야 할 것이다.

 

glGetUniformLocation 을 호출하면 link 과정에서 uniform 의 위치를 프로그램 오브젝트에서 찾을 수 있다.

glUniformMatrix4fv 를 이용하면 실시간으로 변화하는 worldMatrix 를 구할 수 있다.

 

그런데 사실 우리는 index array 없이, vertex array 만으로도 폴리곤 메시들을 표현해낼 수는 있다. 위의 구가 48 개의 삼각형으로 이루어져 있고, vertex array 만으로 표현되었다고 하자.

우리는 fragment shader 에서 glDrawElements 만을 써서 구를 그릴 수 있다. 이를 drawcall 이라고 한다.

 

하지만 위 방식을 거의 사용할 일은 없다 🤣

 

OpenGL 과 Vulkan 의 차이... 에 대해 한 번 읽어보자. Vulkan 은 OpenGL 의 계승자라고 한다.

 

Metal 은 iOS 에서 사용하는 그래픽 API 이다. 2018년 부터는 OpenGL 로 방향을 틀었다고 한다.

 
Comments