Buffers and Shader (2) - Shader
Shader 란 무엇인가? 라는 질문에 대한 답을 하기에는 매우 많은 지면이 필요할 것이다. 또한 초보적인 그래픽 프로그래머에게 만족스럽게 설명한다는 것은 어려운 일이 될 것이다.
우선 “The Book of Shaders” 라는 책을 권하고 싶다.
The book of shaders 에 나온 내용중 다음을 소개한다.
왜 쉐이더를 사람들이 어렵다고 할까?
2차 세계대전에서 쌀공급으로 유명한 미국의 벤삼촌이 이르길,
"큰 힘에는 큰 책임이 따른다"
고 했다.
병렬연산 또한 이 법칙을 따른다; GPU의 강력한 컴퓨테이션 능력은 이것을 쓰기 위해 따라야 할 제약과 제한이 있다.
파이프상에서 병렬처리를 하기 위해서는, 각 쓰레드마다, 서로에 대해 철저히 개별적이여야 한다.
쓰레드는 다른 쓰레드에 대해 "실명" 되어 있다고 미국에서는 표현하는데, 서로의 데이터에 대해 엑세스가 없다는 말이다.
그래서 각각의 프로세스들은 서로 데이터를 주고 받고 처리하는것이 불가능하다. 쓰레드끼리 소통하게 하는것은 데이터를 더럽힐수 있다.
또한, GPU는 모든 병렬 마이크로 프로세서들 (파이프들) 을 항시 바쁘게 하고 있다;
이 파이프들은 해당작업이 끝나는 대로 다른 작업을 받아 수행하도록 설계되어 있다.
또한, 각 쓰레드는 이미 수행한 작업에 대한 어떠한 정보도 가질수 없다.
보통 이런 작업들은 운영체제의 UI요소를 그리고 있는것이거나, 게임의 배경화면을 그리거나, 브라우져의 이메일 텍스트를 그리는 것들이다.
각 쓰레드는 서로에게 실명되있다고 표현할뿐아니라, 기억이없음 이라고도 표현하는데, 이것은 이미 수행한 작업에 대한 어떠한 정보도 가지고 있지 않다는 말이다.
바로 이런 점들이 일반적인 프로그래밍 요소와 크게 다른부분이라고 할수 있어서 프로그래밍을 막 접한 이들에게는 어려운 컨셉일수도 있는것이다.
왜 쉐이더는 빠를까?
이 질문에 대한 가장 간단한 답은 “Parallel processing” 병렬 처리 이다.
CPU가 하나의 큰 공장같은 파이프라고 가정하자.
파이프로 들어오는것들은 어떤 작업들이고, 그 작업들이 파이프를 지나면 나오면서 결과가 된다고 하자.
어떤 작업은 크고 복잡하기도 할것이고 어떤건 매우 단순하기도 할것이다.
그리고 컴퓨터는 설계상 절차적으로 작업을 실행하도록 되어 있다; 작업하나가 끝나야 그다음 작업이 시작된다.
근대의 컴퓨터들은 4개의 프로세서로 이루어진 그룹이 이런 파이프처럼 작동하는데, 역시 순차적으로 작업을 하나 끝내고 다음걸 시작하는 방식이다.
각 파이프는 thread라고도 알려져 있다.
CPU
비디오 게임이나 그래픽 응용 어플리케이션들은 일반적으로 보다 많은 프로세싱 파워를 요구한다.
이유는 그래픽 요소들 자체가 수많은 픽셀들의 연산으로 이루어졌기 때문이다.
스크린위에 모든 픽셀들이 각각 계산되고, 3D 게임의 경우, 각 3D 객체들과 모든 퍼스펙티브가 계산되어야 할 것이다.
다시 파이프와 작업들의 개념으로 돌아와서, 스크린위에 각 픽셀들은 각각 하나의 작은 작업들을 의미한다.
그리고 이 각각의 작은 작업들은 CPU에게 그다지 버겁지 않은 작업일 것이다.
하지만 문제는 이렇게 작은 작업들이 스크린위에 픽셀개수만큼 반복되어야 한다는 점이다.
800x600 의 해상도의 게임이라고 하면 480,000개의 픽셀들이 매 프레임 계산되어야 하는데 이말은 14,400,000 번의 계산이 초당 이루어 져야 한다는 말이다.
이것은 꽤나 비효율적일수 있다. 레티나 디스플레이의 경우, 2880x1800 해상도의 픽셀들이 초당 60번 계산되어야 하는데, 계산해보면 초당 311,040,000번이다.
그래픽 엔지니어들은 이 문제를 어떻게 풀까?
바로 이 문제를 풀기 위해 등장 하는 솔루션이 있다. 바로 parellel processing이다.
강력한 마이크로 프로세서를 몇개 또는 큰 파이프를 쓰는대신, 매우 작지만 많은 수의 마이크로 프로세서들을 한번에 동시에 돌리는 것이다.
그것이 바로 Graphic Processor Unit. GPU이다.
GPU
작은 마이크로프로세서들을 여러개의 파이프들로 이루어진 테이블로 생각해보자.
그리고 각 픽셀의 데이터를 핑퐁볼이라고 생각해보자.
14,400,000개의 핑퐁볼이 있다고 한다면, 800x600개의 작은 파이프들로 이루어진 테이블에 480,000개의 핑퐁볼을 30번 떨어뜨리면 아마 최대한 효과적으로 모든 핑퐁볼을 이 테이블 파이프를 통해 통과 시킬수 있을 것이다.
고해상도 화면의 렌더링도 이런식으로 진행되는 것이다 - 병렬하드웨어 지원이 높을수록 더 큰 픽셀데이터들을 다룰수 있게 되는것이다.
GPU의 또다른 슈퍼 파워중 하나는, 하드웨어 지원으로 가속된 수학 함수 연산이다. 즉, 소프트웨어상에서가 아닌, 마이크로칩상에서 바로 연산이 진행된다는 이야기다.
이말은 더 많은 삼각함수 연산, 매트릭스 연산이 가능하다는 말이다.
Shading Language
Shader를 구현하기 위해서는 Shading 언어를 알아야 한다. 즉 Shading 언어도 C 언어와 마찬가지로 프로그래밍 언어이다. C 언어와 매우 유사하다.
Shading 언어는 Graphic Engine에 따라 다르다.
OpenGL 은 GLSL 이라는 언어를 사용하고 Direct3D 는 HLSL 이라는 언어를 사용한다. 비슷하지만 서로 다른 라이브러리에서 사용되는 것이기 때문에 100% 호환은 아니다.
Shading 언어는 예약어나 규칙은 기타 다른 컴퓨터 컴파일 언어보다는 복잡하지 않다. 그 이유는 Shading 언어가 처리해야 하는것은 매우 한정적이기 때문이다.
GLSL : OpenGL Shading Language
C 언어를 기초로 한 상위레벨 쉐이딩 언어이다. GLslang로도 알려져 있다. HLSL 과 유사한 이 언어는 어셈블리 언어나 하드웨어에 의존한 언으를 사용하지 않고 개발자가 그래픽스 파이프라인을 직접 제어할 수 있도록 OpenGL ARB(Architecture Review Board) 가 책정하였다.
HLSL : High Level Shading Language
HLSL 은 마이크로소프트의 Direct3D API에 사용되는 쉐이딩 언어이다. 고급 쉐이딩 언어라고도 불리며 줄여서 HLSL이라고 불린다. 이 쉐이딩 언어는 OpenGL API에서 표준으로 사용되는 쉐이딩 언어인 GLSL과 유사한 언어이다. 또한 Nvidia 의 Cg 쉐이딩 언어와도 유사하다. Cg와 서로 협력하여 개발 하였기 때문이기도 하다.
OpenGL Shding Language 공식 사이트 주소는 아래와 같다.
HLSL 프로그래밍 가이드 사이트는 다음과 같다.
Shader 의 종류
Shader 는 Rendering Pipeline의 각 stage를 구성하는 요소로 작동하며 대표적으로 다음의 3가지를 들 수 있다.
- Vertex Shader 정점 쉐이더
- Pixel Shader 픽셀 쉐이더
- Geometry Shader 지오메트리 쉐이더
Vertex Shader (버텍스 쉐이더, 정점 쉐이더)
- 물체의 정점 정보에 수학적인 연산을 함으로써 물체에 특별한 효과를 주는데 사용되는 쉐이더.
- 정점은 3차원의 위치를 나타내는 x, y, z좌표나 색상, 텍스쳐, 조명 정보 등의 정보를 가짐.
- 버텍스 쉐이더는 정점의 정보값들을 변화시켜서 물체를 특별한 위치로 옮기거나 텍스쳐를 바꾸거나 색상을 바꾸는 등의 작업을 수행.
- 기존의 정점을 삭제하거나 새로운 정점을 추가하는 등의 작업은 불가능.
Pixel Shader (픽셀 쉐이더, PS)
- 렌더링 될 각각의 픽셀들의 색을 계산하는 방식의 쉐이더.
- 최종적으로 픽셀이 어떻게 보일지를 결정 함.
- 언제나 같은 색을 출력하는 간단한 일로부터 텍스쳐로부터 색을 읽어 오거나 빛을 적용하는 것, 범프 맵핑, 그림자, 반사광, 투명처리 등 복잡한 현상 등을 수행하는 것이 가능.
- 각각의 픽셀들이 렌더링될 때 수행되므로 다른 픽셀들과 아무런 연관이 없음.
- 오직 픽셀만을 연산하므로 주변의 픽셀 혹은 그리는 도형에 대한 정보를 알 수 없음.
- 매우 복잡한 효과를 생성하는 것은 불가능.
- 픽셀의 색 이외에 깊이(Z버퍼)나 다른색(다른 렌더 목표물)의 출력이 가능.
Geometry Shader (지오메트리 쉐이더)
- 버텍스 쉐이더가 수행된 후, 수행 됨.
- 버텍스 쉐이더를 거쳐온 도형 정보를 입력받아 출력 함.
정점 3개가 지오메트리 쉐이더에 입력되면 쉐이더는 정점을 모두 없앨 수도 있고 더 많은 도형을 만들어 내보내는 것도 가능.
- 지오메트리 쉐이더를 지나간 도형 정보는 레스터라이즈를 거친 뒤 픽셀 쉐이더를 통과하게 됨.
- 버텍스 쉐이더에서 할 수 없는 점이나, 선, 삼각형 등의 도형 생성 가능.
- 지오메트리 쉐이더는 테셀레이션이나 그림자 효과, 큐브 맵을 한번의 처리로 렌더링 하는데 주로 사용 됨.
렌더링 파이프라인에서의 쉐이더
렌더링 파이프라인에는 Vertex Shader와 Fragment Shader는 반드시 구성되어 있어야 하며 Geometry Shader는 필요하지 않으면 없어도 된다.
아래 그림은 각각의 Shader가 렌더링 파이프라인에서 어느 부분에 위치 하는지를 보여준다.
우선 Vertex Data 는 Vertex Shader 에 전달되고 입력된 정점들을 정해진 규칙에 의하여 조립된다. 여기서 정해진 규칙이란, 정점들이 어떤 형태를 이루는 정점 데이터 들인지를 알려주는 규칙이다. 이러한 규칙은 정점들이 Line List인지 Line Strip 인지, 또는 Triangle List인지 Triangle Strip인지 에 대한 정보이다.
이렇게 조립된 정보는 이제 Geometry Shader로 전달된다. Geometry Shader는 반드시 있어야 하는 Shader는 아니다. Geometry Shader에서는 전달된 Geometry 정보를 쪼개거나 더하거나 하는 작업을 할 수 있다.
이렇게 만들어진 백터 그래픽 정보는 레스터라이징을 수행 하여 픽셀 정보로 바꾸게 된다.
만들어진 픽셀 정보는 Fragment Shader로 들어가게 되며 Fragment Shader에서는 각각의 픽셀에 대한 색상을 지정하게 된 후 Test and Blending 을 거쳐 최종의 이미지를 생산해 낸다.