사전 메시 셰이더
개념

메시 셰이더

gabury1고친 사람 github-actions[bot]

메시 셰이더는 그래픽 카드가 그릴 꼭짓점과 삼각형을 프로그램이 직접 만들어 내보내게 하는 단계입니다. 모형을 작은 조각으로 나눈 뒤 조각 하나씩 맡아 그 조각의 꼭짓점과 삼각형을 한꺼번에 내놓습니다. 예전에는 꼭짓점을 하나씩 읽어 삼각형으로 묶는 일을 정해진 회로가 했습니다. 메시 셰이더는 삼각형을 픽셀로 바꾸기 전 단계들, 곧 꼭짓점을 읽고 옮기는 단계들을 대신합니다.

쉽고 빠른 이해

메시 셰이더는 모형의 작은 조각 하나를 받아 그 조각의 꼭짓점과 삼각형을 직접 만들어 내보내는 프로그램입니다. 삼각형 수십만 개로 된 건물 모형을 조각 수천 개로 나누면, 조각마다 이 프로그램이 한 번씩 돕니다.

이게 없으면 그래픽 카드는 꼭짓점을 하나씩 읽어 하나씩 처리합니다. 카메라 뒤에 숨은 벽이라도 꼭짓점을 다 읽고 계산한 뒤에야 버릴 수 있습니다. 메시 셰이더를 쓰는 흐름은 조각 단위로 일합니다. 그래서 안 보일 조각은 꼭짓점 하나 읽지 않고 버립니다. 안 보이는 부분이 많은 큰 장면일수록 이득이 큽니다.

어떻게 도나:

  1. 앞에 붙는 단계가 조각들을 훑어 이번에 그릴 조각만 고릅니다
  2. 고른 조각마다 메시 셰이더가 한 번씩 돌며 꼭짓점과 삼각형을 내보냅니다
  3. 그다음은 예전과 같습니다. 삼각형이 픽셀로 쪼개져 색이 칠해집니다

대가는 준비가 늘어난다는 것입니다. 모형을 조각으로 미리 나눠 두어야 합니다. 조각 하나에 담을 수 있는 꼭짓점과 삼각형 수에도 상한이 있습니다.

예전에 회로가 해 주던 일, 곧 같은 꼭짓점을 두 번 계산하지 않게 챙기는 일도 프로그램 쪽이 떠맡습니다. 이 단계를 모르는 옛 그래픽 카드에서는 돌지 않습니다.

상세

이삿짐을 나르는 방법은 둘입니다. 하나는 물건 하나마다 일꾼 하나가 붙어 저마다 옮기는 방법입니다. 다른 하나는 방 단위로 나르는 방법입니다. 한 사람이 먼저 방들을 둘러보고 안 가져갈 방을 빼 둡니다. 가져갈 방마다 팀 하나가 붙어 그 방의 짐을 한 번에 싸서 내보냅니다.

아래 본문에서 방은 모형의 작은 조각(메시렛)입니다. 방을 고르는 사람은 조각을 고르는 앞 단계(태스크 셰이더)이고, 짐을 싸는 팀은 조각 하나를 함께 맡는 계산 묶음(워크그룹)입니다. 안 가져갈 방을 빼 두는 일은 안 보일 조각을 미리 버리는 일(컬링)입니다.

이 절은 먼저 그림 그리는 흐름에서 메시 셰이더가 어느 단계를 대신하는지 짚습니다. 그다음 모형을 조각으로 나누는 방법, 조각을 고르는 앞 단계, 조각 하나를 내보내는 짧은 코드로 넘어갑니다. 마지막은 언제 쓰고 무엇을 치르는지입니다.

그림 그리는 흐름과 셰이더

화면에 보이는 입체 모형은 작은 삼각형 수만 개를 이어 붙인 것입니다. 삼각형의 꼭짓점 하나를 정점이라고 부릅니다.

그래픽 카드(GPU, Graphics Processing Unit, 그래픽 처리 장치)가 한 번에 그리는 가장 작은 도형을 프리미티브라고 합니다. 점 하나, 선분 하나, 삼각형 하나가 각각 프리미티브 하나입니다. 이 글에서 다루는 프리미티브는 삼각형입니다.

그래픽 카드는 정해진 순서대로 그림을 그립니다. 이 순서 전체를 그래픽스 파이프라인이라고 부릅니다.

순서 안의 몇 단계는 개발자가 직접 프로그램을 적어 넣는 단계입니다. 그 단계에서 도는 짧은 프로그램을 통틀어 셰이더라고 합니다.

메시 셰이더도 그런 프로그램입니다. 무엇이 다른지는 어느 단계에서 도는지를 보면 드러납니다.

빠지는 앞쪽 단계

예전 흐름의 앞쪽에서는 두 단계가 꼭 돕니다. 먼저 입력 어셈블러가 메모리에 쌓인 정점 값을 하나씩 읽어 삼각형으로 묶습니다. 이 단계는 프로그램이 아니라 정해진 회로입니다.

그다음 정점 셰이더가 정점 하나마다 한 번씩 돕니다. 그 정점이 화면 어디에 놓일지를 정합니다.

정점 셰이더 뒤에는 필요할 때만 끼어드는 단계가 둘 있습니다. 프리미티브를 잘게 쪼개 삼각형 수를 늘리는 테셀레이션과 프리미티브 개수를 바꾸는 지오메트리 셰이더입니다. 쓰지 않으면 이 둘은 건너뜁니다.

메시 셰이더를 쓰는 흐름에서는 이 앞쪽이 전부 빠집니다. 입력 어셈블러도, 정점 셰이더도, 테셀레이션과 지오메트리 셰이더도 없습니다. 그 대신 메시 셰이더 하나가 정점과 삼각형을 직접 만들어 내보냅니다. 그 앞에는 조각을 고르는 태스크 셰이더를 붙일 수 있습니다.

삼각형이 만들어진 뒤로는 두 흐름이 같습니다. 픽셀은 화면을 이루는 점 하나입니다. 래스터화는 삼각형이 덮은 픽셀을 골라내는 단계입니다.

골라낸 픽셀마다 프래그먼트 셰이더가 한 번씩 돌며 색을 정합니다. 이 뒷단은 어느 흐름에서 온 삼각형이든 똑같이 돕니다.

flowchart TD
    subgraph OLD["예전 흐름"]
        A["입력 어셈블러 · 정점을 읽어 묶는 회로"] --> B["정점 셰이더 · 정점 하나마다"]
        B --> C["테셀레이션 · 지오메트리 셰이더 · 필요할 때만"]
    end
    subgraph NEW["메시 셰이더 흐름"]
        D["태스크 셰이더 · 조각을 고른다 · 필요할 때만"] --> E["메시 셰이더 · 조각 하나마다"]
    end
    C --> F["래스터화 · 프래그먼트 셰이더 · 두 흐름이 같다"]
    B -.->|"두 단계를 안 쓸 때"| F
    E --> F

메시 셰이더 흐름에는 정점을 읽는 회로가 없습니다. 정점을 어디서 어떤 형식으로 읽을지를 프로그램이 정합니다. 이 차이가 아래 소절들의 출발점입니다.

예전 흐름이 막히던 대목

예전 흐름의 앞쪽 단계에는 막히던 대목이 셋 있었습니다. 메시 셰이더가 그 단계들을 대신하게 된 까닭이 이 셋입니다.

첫째, 정점을 읽는 방식이 정해져 있었습니다. 인덱스 버퍼는 삼각형마다 정점 번호 셋을 적어 둔 목록입니다. 입력 어셈블러는 정해진 형식으로 적힌 정점 목록과 인덱스 버퍼만 읽습니다. 정점 값을 압축해 두거나 다른 형식으로 두면 이 회로가 못 읽습니다.

둘째, 한 번에 보는 범위가 좁았습니다. 정점 셰이더는 정점 하나를 받아 정점 하나를 내놓습니다. 옆 정점을 못 보고 개수도 못 바꿉니다.

지오메트리 셰이더는 개수를 바꿀 수 있습니다. 대신 프리미티브마다 내놓는 개수가 제각각입니다. 그런데 결과는 들어온 순서대로 이어져야 해서, 먼저 끝난 쪽도 앞 순서가 끝나기를 기다려야 합니다. 그래서 여러 개를 나란히 돌려도 빨라지기 어렵습니다.

셋째, 안 보일 삼각형을 늦게 버렸습니다. 카메라에 안 보이는 프리미티브를 그리기 전에 버리는 일을 컬링이라고 합니다. 예전 흐름에서 삼각형 하나하나의 컬링은 정점을 다 읽고 위치를 계산한 뒤에야 일어납니다. 모형 절반이 카메라 뒤에 있어도 그 절반의 정점을 전부 계산합니다.

모형 하나를 한꺼번에 버리는 일은 그리기 전에 CPU(Central Processing Unit, 중앙 처리 장치)가 할 수 있었습니다. 하지만 모형 안의 일부만 골라 버리기는 어려웠습니다. 메시 셰이더는 그 중간 크기, 곧 모형의 조각 단위로 버릴 길을 엽니다.

컴퓨트 셰이더에서 빌려 온 방식

메시 셰이더가 도는 방식은 컴퓨트 셰이더에서 왔습니다. 컴퓨트 셰이더는 그래픽 카드에 그림과 상관없는 계산을 시키는 프로그램입니다. 같은 프로그램을 스레드 수천 개로 띄워 일을 나눠 돌립니다.

이 스레드들은 몇 개씩 묶여 돕니다. 이 묶음을 워크그룹이라고 부릅니다. 워크그룹 하나가 일 한 덩어리를 맡고, 안의 스레드들이 그 덩어리를 나눠 처리합니다.

한 워크그룹 안의 스레드들은 공유 메모리라는 작은 메모리를 같이 씁니다. 저마다 계산한 값을 여기에 두고 서로 가져다 씁니다. 모두가 한 지점에 올 때까지 기다렸다가 함께 넘어갈 수도 있습니다.

메시 셰이더는 이 묶음을 그리기에 가져왔습니다. 워크그룹 하나가 모형 조각 하나를 맡습니다. 안의 스레드들이 정점을 나눠 계산합니다. 그 결과를 모아 조각 하나의 정점 목록과 삼각형 목록을 내보냅니다.

앞 소절의 두 셰이더와 나란히 놓으면 차이가 보입니다.

정점 셰이더 지오메트리 셰이더 메시 셰이더
한 번에 맡는 것 정점 하나 프리미티브 하나 모형 조각 하나
입력을 읽는 쪽 입력 어셈블러 앞 단계가 넘겨준다 프로그램 자신
내보내는 것 정점 하나 프리미티브 여럿 또는 0개 정점 목록과 삼각형 목록
한 번에 도는 단위 스레드 하나 스레드 하나 워크그룹 하나

표에서 입력을 스스로 읽는 것은 메시 셰이더뿐입니다. 그래서 정점 값을 압축해 두거나 자기만의 형식으로 둘 수 있습니다.

모형을 조각으로 나누기

워크그룹 하나가 한 번에 내보낼 수 있는 정점과 삼각형 수에는 상한이 있습니다. 상한은 그래픽 API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스)와 그래픽 카드가 정합니다. 그래서 큰 모형을 한 번에 못 내보냅니다.

삼각형을 이어 붙인 모형 전체는 메시라고 부릅니다. 삼각형이 그물처럼 이어진 모양이라 붙은 이름입니다.

메시를 작은 조각으로 나눈 하나하나를 메시렛이라고 부릅니다. 서로 붙어 있는 삼각형 수십 개와 그 정점을 한데 묶은 것입니다.

메시렛 하나에는 목록이 둘 들어 있습니다. 하나는 이 조각이 쓰는 정점 목록입니다. 다른 하나는 삼각형 목록입니다. 삼각형마다 정점 번호 셋을 적습니다.

flowchart TD
    subgraph T["삼각형 목록"]
        T0["삼각형 0 · 번호 0·1·2"]
        T1["삼각형 1 · 번호 1·3·2"]
    end
    subgraph V["정점 목록 · 조각 안 번호"]
        V0["0번 칸"]
        V1["1번 칸"]
        V2["2번 칸"]
        V3["3번 칸"]
    end
    T0 --> V0
    T0 --> V1
    T0 --> V2
    T1 --> V1
    T1 --> V3
    T1 --> V2

삼각형 목록의 번호는 모형 전체의 번호가 아니라 조각 안의 번호입니다. 그림에서 삼각형 0 은 정점 목록의 0·1·2번 칸을 가리키고, 이웃한 삼각형 1 은 그중 1·2번 칸을 같이 씁니다. 조각 하나의 정점이 수십 개뿐이라 번호가 작습니다. 작은 번호는 적은 바이트로 적을 수 있어 메모리를 아낍니다.

조각을 나누는 일은 대개 그리기 전에 미리 해 둡니다. 모형 파일을 만들거나 불러올 때 한 번 나눠 둡니다. 그릴 때는 나눠 둔 조각을 읽기만 합니다.

나눌 때는 가까이 붙은 삼각형끼리 묶습니다. 그래야 한 조각이 화면의 한 구역에 모입니다. 한 구역에 모인 조각이어야 조각 전체가 안 보이는 일이 잦아집니다.

조각을 고르는 태스크 셰이더

메시 셰이더 앞에 붙이는 단계가 태스크 셰이더입니다. 마이크로소프트의 그래픽 API 인 Direct3D에서는 같은 단계를 증폭 셰이더라고 부릅니다.

태스크 셰이더도 워크그룹으로 돕니다. 하는 일은 둘입니다. 메시렛 여럿을 훑어 이번에 그릴 조각만 고릅니다. 그리고 메시 셰이더 워크그룹을 몇 개 띄울지 정합니다.

고른 조각의 번호 같은 값은 메시 셰이더에 넘겨줍니다. 메시 셰이더는 그 번호로 자기가 맡을 조각을 찾아 읽습니다.

sequenceDiagram
    participant 태스크 as 태스크 셰이더
    participant 메시 as 메시 셰이더
    participant 래스터 as 래스터화
    태스크->>태스크: 메시렛 32개를 훑는다
    Note over 태스크: 안 보이는 20개는 버린다
    태스크->>메시: 워크그룹 12개를 띄우고 조각 번호를 넘긴다
    메시->>메시: 조각마다 정점을 계산한다
    메시->>래스터: 정점 목록과 삼각형 목록

그림에서 버린 20개는 메시 셰이더가 한 번도 돌지 않습니다. 그 조각의 정점은 하나도 읽히지 않습니다.

조각을 버리는 기준은 여럿입니다. 카메라가 보는 범위는 꼭대기가 잘린 피라미드 모양이라 절두체라고 부릅니다. 이 범위 밖에 있는 조각을 버리는 것이 절두체 컬링입니다.

삼각형에는 앞면과 뒷면이 있습니다. 카메라에서 등을 돌린 면만 모인 조각을 버리는 것이 후면 컬링입니다.

태스크 셰이더는 워크그룹을 자기 수보다 많이 띄울 수도 있습니다. 조각 하나를 보고 메시 셰이더 여럿을 띄우면 삼각형이 불어납니다. 증폭이라는 다른 이름은 이 쓰임을 가리킵니다.

코드 한 벌

아래는 삼각형 하나를 직접 만들어 내보내는 가장 작은 메시 셰이더입니다. 셰이더 언어 GLSL(OpenGL Shading Language, OpenGL 셰이딩 언어)로 적었습니다. 이름의 OpenGL은 오래 쓰여 온 그래픽 API 하나입니다. 이 코드는 Vulkan이라는 그래픽 API 에서 쓰는 형식입니다.

glsl
#version 450
#extension GL_EXT_mesh_shader : require

layout(local_size_x = 1) in;
layout(triangles) out;
layout(max_vertices = 3, max_primitives = 1) out;

void main() {
  SetMeshOutputsEXT(3,1); // 정점3·삼각형1
  vec4 a = vec4(-0.5, 0.5, 0, 1); // 0번
  vec4 b = vec4( 0.5, 0.5, 0, 1); // 1번
  vec4 c = vec4( 0.0,-0.5, 0, 1); // 2번
  gl_MeshVerticesEXT[0].gl_Position = a;
  gl_MeshVerticesEXT[1].gl_Position = b;
  gl_MeshVerticesEXT[2].gl_Position = c;
  gl_PrimitiveTriangleIndicesEXT[0] =
      uvec3(0, 1, 2); // 0·1·2 → 삼각형
}

첫 두 줄은 이 언어에서 메시 셰이더 기능을 켜는 선언입니다. 메시 셰이더는 나중에 더해진 기능이라 켜고 써야 합니다.

local_size_x = 1 은 워크그룹 하나에 스레드를 몇 개 묶을지 정합니다. 이 예는 하나뿐입니다. 실제 조각을 다룰 때는 수십 개를 묶어 정점을 나눠 계산합니다.

다음 두 줄은 내보낼 것의 약속입니다. 프리미티브는 삼각형입니다. 한 번에 정점 셋과 삼각형 하나까지 내보냅니다. 이 최대치는 돌기 전에 정해져 있어야 합니다.

SetMeshOutputsEXT 는 이번에 실제로 내보낼 개수를 알립니다. 최대치보다 적어도 됩니다. 정점과 삼각형을 적기 전에 먼저 불러야 합니다.

가운데 여섯 줄이 정점 셋을 만듭니다. vec4 는 값 넷을 담는 자료형입니다. 앞의 둘이 화면 위의 가로·세로 위치입니다. 만든 값은 정점 목록 gl_MeshVerticesEXT 의 0·1·2번 칸에 넣되, 칸마다 gl_Position 이라는 위치 필드에 넣습니다.

마지막 두 줄은 삼각형 목록 gl_PrimitiveTriangleIndicesEXT 의 0번 칸을 채웁니다. 이름의 Primitive 가 앞에서 푼 프리미티브이고, 여기서는 삼각형 하나입니다. 정점 0·1·2번을 이어 삼각형 하나를 만들라고 적습니다. 앞 소절에서 본 조각 안 번호가 이 번호입니다.

이 코드에는 정점을 받아 오는 입력이 하나도 없습니다. 정점 셰이더라면 입력 어셈블러가 정점을 읽어 넘겨줬을 것입니다. 쓸모 있는 메시 셰이더는 메시렛 번호로 버퍼를 찾아 정점 값을 스스로 읽습니다.

쓰는 경우

메시 셰이더가 이득을 내는 장면은 셋입니다. 공통점은 조각 단위로 손댈 일이 많다는 것입니다.

첫째는 삼각형이 아주 많은 장면입니다. 카메라에 안 보이는 부분이 많을수록 조각 단위로 일찍 버려 아끼는 계산이 커집니다.

둘째는 정점 값을 자기 형식으로 두고 싶을 때입니다. 입력을 프로그램이 읽으므로 값을 압축해 두었다가 읽으면서 풀 수 있습니다.

셋째는 삼각형을 그리는 순간에 계산해 만들 때입니다. 수식으로 정한 지형이나 풀잎이 그렇습니다. 메모리에 정점을 쌓아 두지 않고 워크그룹이 바로 만들어 내보냅니다.

반대로 삼각형이 적고 거의 다 보이는 장면에서는 버릴 것이 적습니다. 버려서 아끼는 몫이 작으니 예전 흐름과 차이도 작습니다.

치르는 비용

첫째는 준비가 늘어나는 것입니다. 모형을 메시렛으로 나누는 단계를 따로 두어야 합니다.

둘째는 회로가 하던 일을 떠맡는 것입니다. 예전 흐름에서는 같은 정점을 두 번 계산하지 않도록 회로가 결과를 잠깐 기억해 두었습니다. 이 기억 장치를 정점 캐시라고 부릅니다. 메시 셰이더 흐름에서는 조각 안에서 정점을 한 번씩만 계산하도록 조각을 짜는 일은 개발자가 맡습니다.

셋째는 상한에 맞춰야 하는 것입니다. 조각 크기를 워크그룹이 내보낼 수 있는 수 안으로 잘라야 합니다. 상한과 알맞은 크기가 그래픽 카드마다 달라서 한 가지 크기로 모든 카드를 맞추기 어렵습니다.

넷째는 돌릴 수 있는 곳이 좁다는 것입니다. 메시 셰이더는 비교적 새 그래픽 카드와 그래픽 API 에만 있습니다. 옛 카드까지 지원하려면 예전 흐름으로 그리는 길을 한 벌 더 유지해야 합니다.

관련 항목

메시 셰이더가 대신하는 앞쪽 단계

입력 어셈블러 · 정점 셰이더 · 테셀레이션 · 테셀레이션 제어 셰이더 · 테셀레이션 평가 셰이더 · 지오메트리 셰이더 · 프리미티브 조립 · 정점 페치

메시 셰이더 앞뒤에 놓이는 처리 단계

태스크 셰이더 · 증폭 셰이더 · 래스터화 · 프래그먼트 셰이더 · 픽셀 셰이더 · 출력 병합기 · 그래픽스 파이프라인

메시 셰이더가 받고 내보내는 도형 단위

메시렛 · 메시 · 정점 · 프리미티브 · 삼각형 · 인덱스 버퍼 · 정점 버퍼 · 버퍼

메시 셰이더가 빌려 온 계산 방식

컴퓨트 셰이더 · 워크그룹 · 스레드 그룹 · 공유 메모리 · 디스패치 · SIMD · 워프

메시렛 단위로 안 보일 도형을 버리는 기법

컬링 · 절두체 컬링 · 후면 컬링 · 오클루전 컬링 · 법선 원뿔 컬링 · GPU 주도 렌더링 · 레벨 오브 디테일

메시 셰이더로 그리는 도형 생성 기법

절차적 생성 · 지형 렌더링 · 파티클 시스템 · 머리카락 렌더링

메시 셰이더를 규정하는 그래픽 API와 셰이더 언어

API · Vulkan · Direct3D · Direct3D 12 · OpenGL · Metal · GLSL · HLSL · SPIR-V

메시 셰이더를 돌리는 하드웨어와 그 성질

GPU · CPU · 셰이더 코어 · 병렬 처리 · 비디오 메모리 · 정점 캐시

메시 셰이더가 속하는 상위 분류

그래픽스 · 렌더링 · 실시간 렌더링 · 셰이더 · 지오메트리 처리 · 3D 그래픽스

다른 이름: mesh shader · 메쉬 셰이더 · 메시 쉐이더