2018년 1월 15일 월요일

Vertex Shader and Pixel Shader (GLSL)

Vertex Shader and Pixel Shader (GLSL)

Shader에 대하여 인터넷을 검색해 보면 많은 자료가 나온다. 이러한 자료들은 대부분 비슷한 내용들을 담고 있다.

Shader의 의미나 그 코딩 방법에 대하여 설명이 잘 나와 있다.

이제부터 Shader 의 동작 방식 또는 특징을 이야기 해보자.

우리가 만드는 하나의 3D 그래픽 어플리케이션에는 그래픽 엔진을 사용하게 될 것이다.
그것이 OpenGL 이 됬든 아니면 Direct3D 가 됬든.

위의 그림을 한번 설명한 적이 있다.

위의 그림에서 사각영역 안의 내용 각각이 렌더링 파이프라인의 하나하나의 단계가 된다. 이중에 그레이 사각형 부분은 우리가 관여할 필요가 없다.
또한 기초적인 부분에 있어서 Geometry Shader는 작성하지 않아도 된다.

그러므로 우리는 Vertex Shader와 Fragment Shader의 작동 원리나 아니면 이들의 목적에 대하여 살펴보는 것이 가장 우선적으로 해야 할 일이 된다.

이들을 이해하지 않고는 OpenGL API 나 Direct3D API를 설명하더라도 이해는 갈지 모르나 그 응용력에서 떨어질 것이다.

위의 그림에서 Vertex Data는 프로그램 코드에서 작성된 Vertex 정보들을 OpenGL 인 경우 Vertex Buffer Object 즉 VBO 에 의하여 입력된 버퍼 데이터 이다.
Direct3D에서도 이러한 Buffer 를 만들어 데이터를 입력하는 것을 살펴 본적이 있다.
만약 이러한 작업에 대한 설명을 못보신 분들은 다음 사이트의 내용을 우선 살펴보기 바란다.

Buffers and Shader - Buffers

위에 링크된 글중 다음 그림을 찾을 수 있을 것이다.

이 그림에서 Shader 부분은 설명되지 않고 있다.

사실 Shader들은 Rendering Pipeline 의 각 단계를 구성하는 요소들로 작동된다. 그러므로 Rendering Pipeline 을 위 그림에 추가해 주어야만 어느정도 완전한 그림이 될 것이다.

그러면 이러한 렌더링 파이프라인은 어느 위치에 있게 될까?

위 그림에서 Buffer data 가 GPU를 통한 후 Back buffer로 이동하는 것을 볼 수 있다.
이 과정이 렌더링 파이프라인인 것이다.

즉 위의 그림을 해당 부분을 좀더 자세하게 그린다면 다음 그림과 같을 것이다.


우리가 관여하지 않거나 지금 설명할 필요가 없는 것들은 빼고 단순화 하였다.

렌더링 파이프라인에서 Vertex Shader는 가정 먼저 만나게 되는 Shader 가 된다. 또한 Fragment Shader는 가장 마지막에 위치하는 Shader 가 된다.

입력된 Buffer 데이터는 그리고자 하는 그림에 대한 정보를 갖고 있다는 것이다.

Buffer data에는 한개의 그림 요소만 있을수도 있고 여러개의 그림요소가 있을수도 있다.
또한 각 그림 요소가 같은 종류(삼각형이면 모두 삼각형, 원이면 모두 원) 아니면 각각 다른 그림 형태가 복합적으로 구성되어 있을 수도 있다.

이러한 규칙은 순전히 프로그래머인 자신이 정하는 것이다.

Vertex Buffer Data 는 Vertex 정보를 가지고 있는 메모리 블럭이다. 여기서 버텍스에 대하여 다시 알아보자.

버텍스 (Vertex 정점) 의 정의

우리가 앞서 Vertex(정점) 의 정의를 말한 적이 있다. 역기서 다시한번 살펴보고 가자.

수학적으로 Vertex(정점) 이라 하면 다각형을 구성하는 꼭지점의 좌표, 즉 위치 정보를 말한다.

그러나 Computer Geometry Graphics 에서의 버텍스 (정점) 에 대한 정의는 이러한 “위치 좌표 정보에 부가적인 정보를 더 추가한것” 이라 할 수 있다.

가령 위치정보 + 색상정보 를 하나의 버텍스 라 정할 수도 있다.

또한 위치정보+색상정보+텍스쳐정보 를 하나의 버텍스 정보로 정의할 수도 있다.

이렇게 우리는 구현하고자 하는 목적에 맞도록 정하면 된다.


Vertex Shader Code


이제 쉐이더 코드의 형태를 살펴보자.. 쉐이더 코드는 main 함수를 갖는 하나의 C 언어 프로그램 코드라 생각하면 된다.

C 언어 에서와 같이 쉐이더 프로그램도 하나의 main 함수를 갖고 있다는 것이다.
즉 main 함수가 진입점이 되며 하나의 정점에 대하여 main함수가 한번 호출되게 된다.
정점의 수가 만약에 10개 있다고 하면 버텍스 쉐이더 코드의 main 함수는 10번 호출되게 된다. 그러나 이것이 하나의 코어에 의해서 호출되는 것이 아니다.

GPU는 병렬처리 컴퓨팅 시스템이다. 즉 다수의 코어가 동시에 작동하는 구조이다. 그러므로 이러한 10번의 main 함수를 동시에 서로 다른 코어가 호출하여 계산을 빠르게 수행하는 것이다.

그러므로 하나의 main 함수는 자신에게 들어온 버텍스 정보 를 제외한 다른 버텍스의 정보는 모른다. 전혀 관여 할 수도 없다. 그 이유는 병렬처리 구조이기 때문이다.

앞서 말했듯이 Shader 프로그램 코드의 진입점은 main 함수가 된다.

일단 GLSL 로  설명 하겠다.

Vertex Shader Code
#version 150 core
in vec3 position;
void main()
{
gl_Position = vec4(position.x,position.y, position.z, 1.0);
}

코드 설명
#version 150 core
GLSL 쉐이더 버전을 뜻한다. 150 이면  GLSL 버전은 1.5 이고 지원되는 OpenGL 버전은 3.2 이상을 뜻한다. 이 표는 여기를 참고하면 된다.

in vec3 position;

position 이라는 변수를 지정하였다. 그 형식은 vec3 즉 3개의 내부 컴포넌트를 갖는 vector 인 것이다. 3개의 내부 컴포넌트는 x,y,z 가 될 것이다.
in 은 이 변수가 입력으로 받는 변수라는 것이다. 즉 Vertex Buffer Data에서 오는 정보가 이 변수로 오는 것이다.


void main()
{
}

위  코드는 더 설명은 필요 없을 것 같다. main 함수 블럭이다.
.

gl_Position = vec4(position.x,position.y, position.z, 1.0);

위 코드는 gl_Position 이라는 변수에 입력된 position 을 vec4 형식으로 대입하는 코드가 된다.

여기서 의문은 gl_Position 은 어디서 온 변수일까? 분명 변수가 맞다. 그러나 이것을 선언해준 코드가 없다.

바로 Built-in Variable 즉 GLSL 에서 제공하는 이미 정의된 변수인 것이다.
GLSL 에는 몇 가지 정의된 변수들이 있다. 이들에 대한 설명이 다음 링크에 있으니 살펴보기 바란다.

gl_Position 변수는 버텍스 쉐이더의 출력 변수들중 하나이다.

gl_Position
the clip-space output position of the current vertex.

즉 지금 처리되는 버텍스의 clip 영역에서의 위치를 뜻한다. 이 말은 렌더링 파이프라인에서 버텍스 쉐이더에 입력된 버텍스에 대하여 다음 단계로 전달되는 위치 정보인 것이다.
위의 경우 입력되는 위치가 출력 위치로 바로 전달된 것이다. 만약 여기서 변경을 가하게 되면 결국 다른 위치로 해당 버텍스가 정해지는 것이다.


Fragment Shader Code


#version 150 core
out vec4 color;
void main()
{
   color = vec4(1.0,1.0,1.0,1.0);
}

위의 코드는 Fragment Shader 코드이다. 역시 main 함수가 있다.
위 코드의 경우 입력은 없다. 물론 입력을 받을 수도 있지만 위의 경우 입력을 사용하지 않고 있다.

Fragment Shader는 과연 몇번 호출될까? Vertex Shader 에서와 같이 Vertex 갯수만큼 호출될까?

답은 “아니요” 이다.

앞서 설명한 그림이지만 이 그림보다 설명하기 편한 그림은 없다. 버텍스 쉐이더를 거쳐서 Shape Assembly 를 통과하면 버텍스들은 다각형으로 조립된다. 다시 말해 입력된 버텍스 정보로 다각형 (여기서는 삼각형) 을 만들어 낸 것이다. Geometry Shader 를 통해서 이러한 다각형다 여러개로 나뉠 수 있지만 어쨌든 이러한 단계를 지나면 결국 Rasterization 을 수행하게 된다.

Rasterization 이라는 것은 벡터 데이터를 이용하여 Pixel 데이터를 만들어 내는 것을 말한다.
즉 아래 그림에서 삼각형에 대하여 무수히 많은 내부 점들이 만들어 지는 것이다.

쉐이더 프로그램 입문자에게 아래 그림을 보여주면 그냥 그런가 보다 하고 잘 이해가 안갈 수도 있다.

이러한 렌더링 순서와 쉐이딩 방식을 알게 되면 아래 그림이 매우 친숙하고 잘 설명된 그림이다는 것을 느낄 것이다. 그러면 성공한 것이다.

그림을 유심히 보면 Rasterization 까지는 그림에 색상이 없다. 그냥 단색이다..정확히 말하면  단색도 아니다.. 색이 없는 것이다.

Fragment Shader에 와서 색상이 입혀지게 된다.
즉 Fragment Shader에서는 각 픽셀에 대한 색상을 결정해주는 역할을 수행한다.

Fragment Shader는 하나의 output 을 갖게 된다. 그것이 바로 색상 vector이며 색상 백터는 vec4 형식의 벡터 값이 된며 이는 r,g,b,a 의 값이 된다.

color = vec4(1.0,1.0,1.0,1.0);

위의 코드는 출력되는 color 값으로 1,1,1,1 을 출력하기 때문에 흰색이 된다.

출력은 아래 그림과 같다.


만약 해당 라인을 다음과 같이 변경하면 빨간색의 삼각형이 그려지는 것이다.
color = vec4(1.0,0.0,0.0,1.0);


이상으로 GLSL 로 단순한  Vertex Shader 와 Fragment Shader의 작성과 동작 원리에 대하여 살펴 보았다.

다음에 HLSL 로 작성하는 것을 설명하도록 하겠다.

댓글 없음:

댓글 쓰기

Vertex , 정점 정보의 확장

Vertex , 정점 정보의 확장 그동안의 설명에 있어서 우리는 VERTEX 정보에 대하여 3 차원의 위치 정보만 사용하였다. struct VERTEX {float x,y,z;}; 이제 이를 확장하여 색상을...