사전 GLSL
표준

GLSL

gabury1고친 사람 github-actions[bot]

GLSL 은 그래픽 카드 안에서 돌릴 짧은 프로그램을 적는 언어입니다. 도형의 꼭짓점을 화면 어디에 놓을지, 화면의 점 하나를 무슨 색으로 칠할지를 이 언어로 적습니다. 적어 둔 소스는 글자 그대로 넘어갑니다. 그래픽 카드를 모는 드라이버가 프로그램이 도는 도중에 그것을 기계어로 옮깁니다.

쉽고 빠른 이해

GLSL 은 그래픽 카드 안에서 돌 짧은 프로그램을 적는 언어입니다. 삼각형 한 장을 빨갛게 칠하는 일이라면 「내가 맡은 점의 색은 빨강이다」를 몇 줄로 적어 넘깁니다.

이게 없으면 그래픽 카드가 미리 정해 둔 계산만 쓸 수 있습니다. 그림자든 물결이든 만드는 쪽이 원하는 계산을 끼워 넣을 길이 없습니다. 카드 회사마다 기계어가 달라서, 사람이 한 번 적으면 여러 카드가 알아듣는 공통 언어가 필요했습니다.

어떻게 도나:

  1. 사람이 그 짧은 프로그램을 글자로 적어 둡니다
  2. 그림을 그리는 쪽이 그 글자를 그래픽 카드 쪽에 넘겨 도는 도중에 컴파일합니다
  3. 그림을 그릴 때마다 꼭짓점 하나마다·화면 점 하나마다 그 프로그램이 한 번씩 돕니다

대가는 오류를 늦게 안다는 것입니다. 컴파일이 프로그램을 돌리는 도중에 일어나서 문법이 틀렸는지도 그때 알게 됩니다. 언어 자체도 좁습니다. 함수가 자기를 다시 부르는 일도, 도중에 메모리를 늘리는 일도 없습니다.

상세

이 절은 GLSL 로 적은 글자가 그래픽 카드에서 도는 프로그램이 되기까지를 따라갑니다. 그다음 삼각형 하나를 빨갛게 칠하는 짧은 코드 두 벌을 놓고, 그 코드가 무엇을 받아 무엇을 내놓는지로 이 언어의 생김새를 봅니다.

글자로 넘기고 도는 도중에 컴파일한다

GLSL(OpenGL Shading Language, OpenGL 셰이딩 언어)은 그림을 그리는 흐름 가운데 비어 있는 단계에 꽂아 넣을 프로그램을 적는 언어입니다. 그렇게 꽂는 프로그램을 셰이더라고 부릅니다. 흐름 전체는 그래픽스 파이프라인입니다.

흐름의 나머지 단계는 카드가 미리 정해 둔 계산으로 돕니다. 꽂아 넣는 단계가 없으면 카드가 준비해 둔 그림만 나옵니다. 그림자를 어떻게 드리울지, 물결을 어떻게 일렁이게 할지를 만드는 쪽이 정하려면 그 단계에 적어 넣을 언어가 있어야 합니다.

셰이더는 그래픽 카드 안에서 돕니다. 그래픽 카드는 같은 계산을 수천 벌 한꺼번에 돌리라고 만든 장치입니다. 그 이름이 GPU(Graphics Processing Unit, 그래픽 처리 장치)입니다.

그래픽 카드는 회사마다 알아듣는 기계어가 다릅니다. 그래서 GLSL 소스를 미리 기계어로 빌드해 두면 그 카드에서만 돕니다. 대신 소스를 글자로 들고 다니다가 그림을 그릴 기계 위에서 그때 옮깁니다.

옮기는 컴파일러는 카드 회사가 만든 디바이스 드라이버 안에 들어 있습니다. 프로그램은 GLSL 소스를 문자열로 드라이버에 건네고 컴파일해 달라고 부릅니다.

컴파일한 셰이더 하나만으로는 그림을 못 그립니다. 꼭짓점을 맡는 셰이더와 화면 점을 맡는 셰이더를 한 벌로 묶어야 그리기에 쓸 수 있습니다. 그 한 벌이 셰이더 프로그램입니다.

flowchart TD
    A["GLSL 소스 · 프로그램이 들고 있는 문자열"] --> B["드라이버가 컴파일 · 도는 도중"]
    B --> C["셰이더 프로그램 · 단계별 셰이더를 한 벌로 묶는다"]
    C --> D["그림 한 번 그릴 때"]
    D --> E["꼭짓점 하나마다 · 화면 점 하나마다 실행"]

이 방식의 대가는 오류를 늦게 안다는 것입니다. 문법이 틀렸는지, 이 카드가 못 받는 문장을 썼는지를 프로그램이 도는 도중에야 알게 됩니다. 그래서 셰이더를 넘기는 쪽은 컴파일 결과와 오류 메시지를 받아 보는 코드를 늘 함께 씁니다.

한 번 도는 동안 값 하나를 맡는다

셰이더는 한 번 불려서 그림 전체를 그리지 않습니다. 값 하나마다 따로 한 번씩 돕니다.

정점(꼭짓점)은 도형을 이루는 점 하나입니다. 이 글에서는 꼭짓점이라고 부릅니다. 꼭짓점 하나를 맡아 화면 어디에 놓을지 정하는 셰이더가 정점 셰이더입니다.

화면을 이루는 점 하나는 픽셀입니다. 도형이 덮은 픽셀을 골라내는 단계가 래스터화입니다. 그렇게 골라낸 점 하나를 프래그먼트라고 부릅니다. 그 점 하나를 맡아 색을 정하는 셰이더라서 이름이 프래그먼트 셰이더입니다.

삼각형 한 장이 화면에서 만 개의 점을 덮으면 색을 정하는 셰이더가 만 번 돕니다. 한 번 도는 동안 GLSL 코드가 보는 것은 자기가 맡은 값 하나뿐입니다. 옆 점이 무슨 색이 됐는지는 못 봅니다.

값이 드나드는 통로는 셋입니다. 소스에 변수를 적을 때 어느 통로인지를 앞에 붙여 밝힙니다.

적는 꼴 무엇이 드나드나
in 이번에 맡은 값 하나가 들어온다
out 다음 단계로 넘길 값을 내놓는다
uniform 그리기 한 번 동안 안 바뀌는 값. 모든 실행이 같은 값을 본다

셋째 통로로 받는 값은 실무에서 유니폼이라고 부릅니다. 카메라가 어디를 보고 있는지처럼 그림 한 장 안에서 안 바뀌는 값이 이 통로로 들어옵니다.

앞 단계의 out 이 뒤 단계의 in 으로 이어집니다. 다만 값이 손대지 않은 채로 오지는 않습니다.

화면 점은 꼭짓점 사이에 놓입니다. 그래서 꼭짓점 셋의 값을 거리에 맞춰 섞은 값이 점마다 하나씩 들어옵니다. 이렇게 섞어 만드는 것이 보간입니다.

flowchart TD
    subgraph S1["꼭짓점 하나마다 한 번"]
        A["꼭짓점마다 딸려 오는 값 · 위치와 색"] --> B["정점 셰이더"]
        B --> C["넘길 값 · 화면 위치와 색"]
    end
    C --> D["래스터화 · 도형이 덮은 화면 점을 고른다"]
    D --> E["보간 · 꼭짓점 셋의 값을 섞어 점마다 하나씩"]
    subgraph S2["화면 점 하나마다 한 번"]
        E --> F["프래그먼트 셰이더"]
        F --> G["그 점의 색"]
    end
    U["유니폼 · 그리기 한 번 동안 안 바뀐다"] --> B
    U --> F

값을 담는 꼴

이 언어가 기본으로 들고 있는 값의 꼴은 그래픽 계산에 맞춰 고른 것입니다. 좌표도 색도 숫자 서너 개를 묶은 덩이라, 그 덩이 자체가 값 하나입니다.

꼴 담는 것
float 소수 하나
vec2 · vec3 · vec4 소수 둘 · 셋 · 넷을 묶은 덩이. 좌표와 색이 이 꼴이다
mat4 소수 열여섯을 네 줄 네 칸으로 묶은 덩이
sampler2D 그림 한 장을 읽어 오는 손잡이

덩이끼리 더하고 곱하는 셈이 한 줄로 끝납니다. 꼭짓점 위치에 mat4 를 곱해 화면 좌표를 얻는 계산이 곱셈 기호 하나입니다. 돌리고 늘리는 셈이 그 곱셈 안에 들어 있습니다.

덩이 안의 칸은 이름으로 꺼냅니다. 좌표로 쓸 때는 x·y·z·w, 색으로 쓸 때는 r·g·b·a 입니다. 이름이 두 벌일 뿐 칸은 같은 네 칸입니다.

여러 칸을 한꺼번에 골라 새 덩이를 만들 수도 있습니다. 색 넷에서 앞의 셋만 뽑는 일이 c.rgb 한 마디입니다. 이렇게 칸을 골라 쓰는 문법이 스위즐링입니다.

block-beta
columns 4
  x1["x"] y1["y"] z1["z"] w1["w"]
  r1["r"] g1["g"] b1["b"] a1["a"]
  s1["c.rgb 가 뽑는 앞 세 칸"]:3 n1["남는 칸"]:1

그림에서 색을 읽는 일도 언어에 붙어 있습니다. sampler2D 는 텍스처 한 장과 그 그림을 어떻게 읽을지 정한 규칙을 함께 가리킵니다. 좌표를 넘기면 그 점의 색이 나옵니다. 이 손잡이가 샘플러입니다.

코드 한 벌

아래는 삼각형 하나를 빨갛게 칠하는 가장 단순한 축의 셰이더 두 벌입니다. 앞의 것이 꼭짓점을 맡고 뒤의 것이 화면 점을 맡습니다. 오른쪽 주석은 그 줄이 내놓는 값입니다.

glsl
in vec3 pos;           // (0.5, 0.25, 0.0)
uniform float scale;   // 2.0
out vec3 tint;

void main() {
  vec3 p = pos * scale;   // (1.0, 0.5, 0.0)
  gl_Position = vec4(p, 1.0);
  tint = vec3(1.0, 0.0, 0.0);  // 빨강
}

pos 는 이번에 맡은 꼭짓점의 위치로 들어옵니다. scale 은 그리기 한 번 동안 안 바뀌는 값이라 유니폼으로 받습니다. 둘을 곱한 값이 이 꼭짓점의 새 위치입니다.

gl_Position 은 이름이 미리 정해진 출력입니다. 정점 셰이더는 여기에 화면 좌표를 넣어야 합니다. 뒤 단계가 그 값을 읽어 갑니다. 이렇게 언어가 이름과 쓰임을 정해 둔 변수가 내장 변수입니다.

vec4(p, 1.0) 는 값 셋에 하나를 덧붙여 넷으로 만든 것입니다. 곱셈만으로는 좌표를 옮길 수 없습니다. 네 번째 칸에 1.0 을 넣어 두면 그 칸에 곱해진 수가 좌표에 그대로 더해집니다. 그래서 옮기는 셈도 같은 곱셈 한 번에 들어갑니다.

glsl
in vec3 tint;          // (1.0, 0.0, 0.0)
out vec4 color;

void main() {
  color = vec4(tint, 1.0);  // 불투명 빨강
}

앞 벌의 tint 가 뒤 벌로 이어집니다. 이름과 꼴이 같아야 이어집니다. 꼭짓점 셋이 서로 다른 색이면 그 사이의 점들은 섞인 색으로 들어옵니다. 마지막 칸 1.0 은 이 색이 비쳐 보이지 않는다는 뜻입니다.

두 벌 다 main 이 한 번 도는 동안 하는 일 전부입니다. 돌려주는 값이 없습니다. 결과는 out 변수에 넣는 것으로 끝납니다.

실제 소스에는 이 앞에 #version 으로 시작하는 줄이 하나 더 붙습니다. 이 언어는 여러 판으로 고쳐 나왔고 판마다 쓸 수 있는 문장이 다릅니다. 컴파일러는 그 줄을 보고 어느 판의 문법으로 읽을지 정합니다.

없는 문법과 비싼 문법

수백만 번 동시에 도는 코드라 언어가 좁습니다. 범용 언어에서 익숙한 것 몇 가지가 아예 없습니다. 아예 없는 것을 먼저 보고, 있지만 비용이 큰 문법 하나를 마지막에 봅니다.

함수가 자기를 다시 부르는 재귀 호출이 없습니다. 자기를 부른 함수는 돌아올 곳을 쌓아 두어야 합니다. 몇 겹까지 쌓일지 모르면 카드가 실행마다 잡아 둘 공간을 못 정합니다.

도는 도중에 메모리를 새로 얻는 일도 없습니다. 포인터도 없습니다. 쓸 수 있는 것은 크기가 미리 정해진 변수와 배열뿐입니다.

파일을 열거나 바깥과 통신하는 문법도 없습니다. 값을 받는 통로는 앞 절에서 본 셋뿐입니다. 내놓는 통로도 정해진 출력뿐입니다.

조건에 따라 갈리는 문장은 있지만 비용이 큽니다. 카드는 이웃한 실행 수십 벌을 한 명령으로 함께 돌립니다. 그 안에서 갈림이 생기면 양쪽을 다 돕니다. 그리고 자기와 안 맞는 쪽 결과를 버립니다.

flowchart TD
    subgraph W["한 명령이 함께 도는 실행 묶음"]
        E1["실행 1"]
        E2["실행 2"]
        E3["나머지 수십 벌"]
    end
    W --> C["갈림"]
    C --> T["참 쪽을 돈다"]
    C --> F["거짓 쪽을 돈다"]
    T --> X["자기와 안 맞는 쪽 결과를 버린다"]
    F --> X

GLSL 을 받아들이는 그래픽 API

그래픽 API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스)는 프로그램이 그래픽 카드에게 그림을 그려 달라고 부탁할 때 쓰는 약속입니다. GLSL 은 OpenGL 과 그 계열의 약속에 딸린 언어입니다. 다른 계열은 셰이더 언어를 따로 둡니다.

브라우저에서 그림을 그리는 WebGL 은 GLSL 을 작은 기계에 맞춰 다듬은 GLSL ES(OpenGL ES Shading Language, OpenGL ES 셰이딩 언어)를 씁니다. 자바스크립트가 소스를 문자열로 넘기고 브라우저가 그것을 컴파일합니다.

Vulkan 은 소스를 바로 받지 않습니다. GLSL 로 적은 것을 빌드할 때 미리 바이트코드로 컴파일해 넘깁니다. 바이트코드는 사람이 읽는 소스와 카드가 읽는 기계어의 중간 형태입니다.

같은 소스에서 갈라지지만 컴파일하는 쪽과 때가 셋 다 다릅니다.

flowchart TD
    S["GLSL 소스 · 글자"]
    S --> O["OpenGL · 소스 문자열을 그대로 넘긴다"]
    S --> W["WebGL · GLSL ES 문자열을 넘긴다"]
    S --> V["Vulkan · 빌드할 때 바이트코드로 미리 컴파일"]
    O --> OC["드라이버가 도는 도중 컴파일"]
    W --> WC["브라우저가 도는 도중 컴파일"]
    V --> VD["드라이버는 바이트코드만 받는다"]

같은 몫을 맡는 다른 언어로는 HLSL(High Level Shader Language, 고수준 셰이더 언어)이 있습니다. 문법과 내장 변수 이름이 GLSL 과 다르므로 셰이더를 옮길 때는 다시 적거나 옮겨 주는 도구를 씁니다.

관련 항목

GLSL 로 적는 셰이더 단계

셰이더 · 정점 셰이더 · 프래그먼트 셰이더 · 지오메트리 셰이더 · 테셀레이션 제어 셰이더 · 테셀레이션 평가 셰이더 · 컴퓨트 셰이더 · 셰이더 단계

GLSL 을 셰이더 언어로 삼는 그래픽 API

OpenGL · OpenGL ES · WebGL · Vulkan · 그래픽스 파이프라인 · 렌더링

같은 몫을 맡는 다른 셰이더 언어

GLSL ES · HLSL · WGSL · MSL · Cg · Open Shading Language · SPIR-V

GLSL 소스가 기계어가 되기까지 거치는 단계

셰이더 컴파일 · 셰이더 컴파일러 · 셰이더 프로그램 · 셰이더 캐시 · 바이트코드 · 컴파일러 · 디바이스 드라이버

GLSL 코드가 다루는 값의 꼴

벡터 · 행렬 · 동차 좌표 · 부동소수점 · 스위즐링 · 내장 변수 · 정밀도 한정자 · 구조체

셰이더가 값을 주고받는 통로

정점 속성 · 유니폼 · 유니폼 버퍼 · 정점 버퍼 · 텍스처 · 샘플러 · 텍스처 좌표 · 프레임버퍼 · 보간

GLSL 프로그램을 돌리는 하드웨어와 그 성질

GPU · 셰이더 코어 · SIMD · 워프 · 병렬 처리 · 분기 발산

GLSL 로 만드는 표현 기법

퐁 셰이딩 · 노멀 매핑 · 그림자 매핑 · 포스트 프로세싱 · 레이 마칭 · 감마 보정

GLSL 을 쓸 때 자주 나는 오류·장애

셰이더 컴파일 오류 · 셰이더 링크 오류 · 셰이더 컴파일 지연 · 드라이버 버그 · 오버드로

GLSL 이 다루는 도형과 화면 단위

정점 · 프리미티브 · 프래그먼트 · 픽셀 · 래스터화 · 메시

GLSL 이 속하는 상위 분류

셰이딩 언어 · 도메인 특화 언어 · 프로그래밍 언어 · 그래픽스 · 실시간 렌더링

다른 이름: OpenGL Shading Language · 오픈지엘 셰이딩 언어 · 지엘에스엘