사전 프래그먼트 셰이더
개념

프래그먼트 셰이더

gabury1고친 사람 github-actions[bot]

프래그먼트 셰이더는 화면의 점 하나를 무슨 색으로 칠할지 정하는 프로그램입니다. 삼각형이 덮은 점마다 따로 한 번씩 돌면서 색 하나를 내놓습니다. 그 색을 어떻게 계산할지는 그림을 그리는 쪽이 직접 적어 넣습니다.

쉽고 빠른 이해

프래그먼트 셰이더는 화면 점 하나의 색을 계산해 내놓는 짧은 프로그램입니다. 빨간 삼각형을 그린다면 이 프로그램은 삼각형이 덮은 점마다 불려 나와 매번 빨강을 하나씩 내놓습니다.

이게 없으면 색을 내는 방식이 그래픽 카드에 미리 박혀 있는 몇 가지 중 하나로 묶입니다. 나무껍질이나 물결처럼 미리 준비돼 있지 않은 표현은 낼 방법이 없습니다. 색 계산 칸을 비워 두면 만드는 쪽이 원하는 계산을 적어 넣을 수 있습니다.

어떻게 도나:

  1. 삼각형이 화면의 어느 점을 덮는지를 먼저 정합니다
  2. 덮인 점마다 이 프로그램이 따로 한 번씩 돕니다
  3. 각각이 색 하나를 내놓고, 그 색이 화면 메모리에 쓰입니다

대가는 도는 횟수입니다. 화면이 클수록, 물체가 서로 겹칠수록 이 프로그램이 불려 나오는 횟수가 늘어납니다. 프로그램에 한 줄을 더하면 그 한 줄이 수백만 번 더 돕니다. 그래서 그래픽 프로그램이 느려지는 원인이 이 칸에 몰리는 경우가 흔합니다.

상세

이 절은 프래그먼트 셰이더가 그림 그리는 흐름의 어디에 끼어 있는지부터 봅니다. 그다음 이 프로그램에 무엇이 들어오고 무엇이 나가는지, 실제 코드가 어떻게 생겼는지, 마지막으로 비용이 어디에 붙는지를 차례로 따라갑니다.

프래그먼트와 픽셀의 차이

GPU(Graphics Processing Unit, 그래픽 처리 장치)는 3차원 도형을 화면에 바로 옮기지 않습니다. 중간에 도형을 화면의 격자 칸으로 쪼개는 단계가 있습니다. 이 단계를 래스터화라고 부릅니다.

래스터화가 내놓는 것은 격자 칸 하나마다의 후보입니다. 칸의 가로세로 좌표와, 그 점이 화면에서 얼마나 먼지를 나타내는 깊이 값이 붙어 있습니다. 이 후보 하나를 프래그먼트라고 부릅니다.

프래그먼트는 아직 픽셀이 아닙니다. 픽셀은 화면에 실제로 찍히는 점이고, 프래그먼트는 그 칸을 차지하겠다고 나선 후보입니다. 삼각형 셋이 같은 칸을 덮으면 그 칸에는 프래그먼트가 셋 생기지만 픽셀은 하나뿐입니다.

이름이 갈려 있는 까닭이 그것입니다. 프래그먼트 셰이더는 화면에 남을 점이 아니라 남을지 아직 모르는 후보를 받습니다.

프래그먼트 셰이더가 꽂히는 칸

그림 그리는 흐름은 정해진 순서로 돕니다. 그 순서 안의 몇 칸이 비어 있어서 만드는 쪽이 프로그램을 적어 꽂을 수 있습니다. 그렇게 꽂는 프로그램을 통틀어 셰이더라고 부릅니다.

칸을 비워 둔 까닭은 앞서 그 칸이 고정돼 있었기 때문입니다. 표면이 빛을 어떻게 받는지 계산하는 방식이 하드웨어에 미리 몇 가지로 박혀 있었고, 그리는 쪽은 그중 하나를 고른 뒤 값만 맞춰 넣을 수 있었습니다. 준비된 방식에 없는 표현을 내려면 계산식 자체를 적어 넣을 칸이 필요했습니다.

flowchart TD
    V["정점 셰이더 · 꼭짓점 하나마다"] --> R["래스터화 · 덮인 칸을 고른다"]
    R --> F["프래그먼트 셰이더 · 후보 하나마다"]
    F --> T["깊이 테스트 · 블렌딩"]
    T --> B["프레임버퍼 · 화면 메모리"]

위 그림에서 프로그램을 꽂는 칸은 맨 위와 가운데 둘입니다. 맨 위 칸에 꽂는 것이 정점 셰이더입니다. 정점은 도형의 꼭짓점 하나이고, 정점 셰이더는 꼭짓점마다 한 번 돌아 그 점을 화면 어디에 둘지 정합니다. 가운데 칸이 프래그먼트 셰이더입니다.

두 칸으로 갈라 놓은 까닭은 도는 횟수가 다르기 때문입니다. 삼각형 하나의 꼭짓점은 셋뿐이지만 그 삼각형이 덮는 칸은 수만 개가 되기도 합니다. 점을 옮기는 계산을 앞 칸에서 세 번만 하고 색을 내는 계산만 뒤 칸에서 수만 번 하면, 같은 그림을 훨씬 적은 계산으로 얻습니다.

그래서 어느 계산을 어느 칸에 둘지가 그리는 쪽의 판단거리가 됩니다. 꼭짓점에서 한 번 계산해 넘겨도 되는 값은 앞 칸으로 올리고, 칸마다 달라야 하는 값만 뒤 칸에 남깁니다.

후보 하나마다 한 번씩 도는 프로그램

프래그먼트 셰이더는 프래그먼트 하나를 받아 색 하나를 내놓습니다. 받는 것도 하나, 내놓는 것도 하나입니다. 이 프로그램이 몇 번 도는지는 삼각형이 화면을 얼마나 넓게 덮었는지가 정합니다.

한 번 도는 동안 이 프로그램은 자기 몫의 후보만 봅니다. 옆 칸이 무슨 색을 냈는지 읽을 수 없고, 누가 먼저 도는지도 정할 수 없습니다. 서로 쳐다보지 않기 때문에 GPU 가 수천 개를 한꺼번에 돌릴 수 있습니다.

이 제약은 쓰는 쪽으로 그대로 넘어옵니다. 옆 점의 색이 있어야 하는 계산, 예를 들어 화면을 흐릿하게 만드는 처리는 한 번에 끝낼 수 없습니다. 먼저 한 장을 그려 메모리에 담아 두고, 그 결과를 읽으면서 다시 한 번 그립니다.

들어오는 값 세 갈래

프래그먼트 셰이더가 읽을 수 있는 값은 크게 셋으로 갈립니다. 그 값이 어디서 왔는지가 가르는 기준입니다.

갈래 어디서 오나 예
꼭짓점에서 섞여 온 값 정점 셰이더가 꼭짓점마다 내놓은 값을 래스터화가 칸마다 섞어 준다 그 점의 색 · 표면이 향한 방향
그리기 한 번 동안 고정된 값 바깥 프로그램이 미리 넣어 두고 모든 후보가 똑같이 읽는다 광원 위치 · 카메라 위치
미리 올려 둔 그림 좌표를 하나 대서 그림의 한 점 색을 뽑아 읽는다 벽돌 무늬 · 나무 결

첫째 갈래를 만드는 계산이 보간입니다. 값은 세 꼭짓점에만 있고 그 사이의 칸에는 없습니다. 래스터화는 칸이 세 꼭짓점에서 각각 얼마나 떨어졌는지를 보고 그 비율대로 값을 섞습니다. 꼭짓점을 빨강·초록·파랑으로 두면 삼각형 안쪽이 부드럽게 물드는 것이 이 때문입니다.

둘째 갈래는 유니폼이라고 부릅니다. 그리기 한 번이 도는 동안 모든 후보에게 같은 값이라 붙은 이름입니다. 광원 하나의 위치처럼 점마다 달라질 이유가 없는 값이 여기 들어갑니다.

셋째 갈래가 텍스처입니다. 텍스처는 물체 표면에 붙이는 그림입니다. 표면의 무늬를 계산식으로 만들어 내는 대신 사진 한 장을 올려 두고 읽는 쪽이 싸기 때문에 널리 쓰입니다.

코드 한 벌

셰이더를 적는 언어는 그래픽 API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스)마다 다릅니다. 아래는 그중 GLSL(OpenGL Shading Language, OpenGL 셰이딩 언어)로 적은 가장 단순한 축의 프래그먼트 셰이더입니다. 위로 갈수록 밝아지는 빨간 면을 냅니다.

glsl
varying vec2 vUv;      // (0.5, 0.25)
uniform vec3 uColor;   // (1.0, 0.0, 0.0)

void main() {
  float s = vUv.y;     // 0.25
  vec3 c = uColor * s; // (0.25, 0, 0)
  gl_FragColor = vec4(c, 1.0);
}

오른쪽 주석은 후보 하나가 돌 때 그 줄이 쥔 값입니다. vUv 는 섞여 들어온 값이라 칸마다 다릅니다. uColor 는 고정된 값이라 어느 후보가 읽어도 같은 빨강입니다.

본체는 세 줄입니다. 세로 좌표를 밝기로 삼아 색에 곱하고, 결과에 불투명하다는 뜻의 값을 하나 더 붙여 내놓습니다. 그래서 세로 좌표가 작은 아래쪽 칸은 어두운 빨강이 되고 위쪽 칸은 밝은 빨강이 됩니다.

색을 내놓은 뒤의 검사

색을 내놓았다고 해서 그 색이 화면에 남는 것은 아닙니다. 뒤에 검사가 몇 가지 더 있습니다.

깊이 테스트는 같은 칸을 두고 다투는 후보 중 앞에 있는 것을 고릅니다. 후보에 붙어 온 깊이 값을 그 칸에 이미 쓰여 있는 깊이 값과 견줍니다. 뒤에 있는 후보가 내놓은 색은 이 검사에서 버려집니다.

블렌딩은 새 색과 이미 쓰여 있던 색을 섞습니다. 반투명한 유리 너머로 뒤가 비쳐 보이는 것이 이 처리입니다. 섞는 비율은 색에 함께 딸려 온 알파 값이 정합니다.

살아남은 색이 마지막으로 쓰이는 메모리가 프레임버퍼입니다. 화면 한 장을 칸마다 색으로 적어 둔 메모리입니다.

깊이 테스트가 뒤에 온다는 것은 버려질 색도 일단 계산한다는 뜻입니다. 그래서 요즘 GPU 는 프래그먼트 셰이더를 돌리기 전에 깊이를 먼저 견줘 가려질 후보를 미리 버리기도 합니다. 다만 셰이더가 깊이 값을 직접 고쳐 쓰면 이 앞당기기를 쓸 수 없습니다. 무엇이 가려지는지를 셰이더가 돌기 전에는 알 수 없기 때문입니다.

비용이 불어나는 방식

이 프로그램이 도는 횟수는 물체가 화면에서 차지한 넓이를 따라갑니다. 멀리 있어 작게 보이는 물체는 삼각형 수가 같아도 후보를 몇 개만 냅니다. 화면을 가득 채운 물체는 화면 칸 수만큼 냅니다.

그래서 창 크기를 키우면 꼭짓점 쪽 계산은 그대로인데 프래그먼트 쪽만 늘어납니다. 같은 장면이 작은 창에서는 부드럽게 돌다가 화면을 키우면 버벅이는 일이 여기서 나옵니다.

같은 칸을 여러 물체가 거듭 덮으면 그만큼 되풀이됩니다. 결국 하나만 보이는데도 가려진 것까지 색을 계산한 셈입니다. 이렇게 버려질 색을 계산하는 낭비를 오버드로라고 부릅니다.

줄이는 방법은 대개 둘입니다. 앞에 있는 물체부터 그려 뒤엣것이 깊이 테스트에서 일찍 버려지게 하거나, 줄 수가 많은 계산을 정점 칸으로 올려 보냅니다.

관련 항목

프래그먼트 셰이더 앞뒤에 놓이는 처리 단계

그래픽스 파이프라인 · 정점 셰이더 · 프리미티브 · 래스터화 · 프래그먼트 · 프래그먼트별 테스트 · 출력 병합기

프래그먼트 셰이더로 들어오는 입력

보간 · 유니폼 · 배리잉 · 텍스처 · 텍스처 샘플링 · 밉맵 · 법선 · UV 좌표

프래그먼트 셰이더가 내놓은 색을 거르는 검사

깊이 테스트 · 스텐실 테스트 · 알파 테스트 · 블렌딩 · 알파 채널 · 얼리 Z

프래그먼트 셰이더가 값을 써 넣는 저장소

프레임버퍼 · 색 버퍼 · 깊이 버퍼 · 스텐실 버퍼 · 렌더 타깃 · 프레임버퍼 객체

같은 흐름의 다른 칸에 꽂히는 셰이더

셰이더 · 지오메트리 셰이더 · 테셀레이션 제어 셰이더 · 테셀레이션 평가 셰이더 · 컴퓨트 셰이더

프래그먼트 셰이더를 적는 언어와 그 처리

GLSL · HLSL · WGSL · SPIR-V · 셰이더 컴파일 · 셰이더 프로그램

프래그먼트 셰이더를 돌리는 하드웨어와 그 성질

GPU · 셰이더 코어 · SIMD · 워프 · 병렬 처리 · 비디오 메모리

프래그먼트 셰이더 비용을 키우는 요인

오버드로 · 필레이트 · 해상도 · 멀티샘플링 · 디퍼드 셰이딩 · 포워드 렌더링

프래그먼트 셰이더로 만들어 내는 표현

셰이딩 · 물리 기반 셰이딩 · 텍스처 매핑 · 노멀 매핑 · 그림자 매핑 · 포스트 프로세싱 · 안티에일리어싱

프래그먼트 셰이더를 제공하는 그래픽 API

OpenGL · WebGL · Vulkan · Direct3D · Metal · WebGPU

프래그먼트 셰이더가 속하는 상위 분류

그래픽스 · 렌더링 · 실시간 렌더링 · 래스터 그래픽스 · 픽셀

다른 이름: fragment shader · 픽셀 셰이더 · pixel shader