GPU 프로그래밍의 착각과 진실
1. 셰이더는 정말 함수일까?
void main()
{
gl_Position = ...;
}
처음 GLSL 셰이더 코드를 보면 진짜 함수처럼 생겼다.
C 언어와 똑같은 void main()에, 변수 선언, 수식, return 없이도 끝나는 심플한 구조까지.
그래서 흔히 이렇게 착각하곤 한다.
"아하, 셰이더도 함수니까 그냥 내가 부르면 되겠네?"
그런데 막상 실행해 보면 이상하다.
main()을 딱 한 번 쓴 것 같은데, 화면 전체에 셰이더가 작동한다.
어라? 내가 호출한 적 없는데?
그럼 셰이더는 대체 뭐지?
그냥 함수처럼 보이는데.... 진짜 함수는 아닌 걸까?
2. 셰이더란 무엇인가?
셰이더는 GPU에서 실행되는 작은 프로그램이다.
이 프로그램은 정점(Vertex), 픽셀(Fragment), 도형(geometry) 같은 그래픽 데이터를 처리하는 전용 처리 유닛으로 작동한다.
대표적인 셰이더 종류는 다음과 같다
- Vertex Shader: 정점 하나하나의 위치와 속성 처리
- Fragment Shader: 픽셀 하나하나의 색상 결정
- (선택적) Gemetry / Tessellation / Compute 등
이 셰이더들은 모두 특정 그래픽 파이프라인의 단계에 '자동'으로 삽입되어 실행된다.
즉, 우리가 직접 호출하는 것이 아니라, GPU가 데이터를 처리할 때마다 스스로 실행된다.
3. 셰이더 구조는 함수처럼 생겼다
// vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
// fragment shader
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0, 0.5, 0.2, 1.0); // 주황색
}
이 코드를 보면 정말 함수처럼 생겼다.
심지어 void main() 도 똑같고, 변수도 선언하고, 곱셈 연산도 하고, 값을 대입하고...
하지만 이 셰이더는 우리가 실행하지 않는다.
대신 OpenGL이 glDraw*()를 호출하는 순간 수천 번 자동 실행한다.
예를 들어 1000개의 정점이 있다면, vertex shader의 main()은 1000번 실행된다.
4. CPU함수 vs GPU 셰이더의 결정적 차이점
| 항목 | CPU함수 | GPU 셰이더 |
| 실행 주체 | 내가 호출함 | GPU가 자동 실행 |
| 실행 횟수 | 호출한 만큼 | 정점/픽셀 개수 만큼 반복 실행 |
| 입출력 구조 | 인자, 반환값 | in/out, uniform, gl_Position등 특수 변수 |
| 실행 위치 | CPU의 한 쓰레드 | GPU의 수천 개 코어에서 동시에 실행 |
| 목적 | 논리 처리, 계산 | 그래픽 처리 전용 |
5. 데이터가 흐르면 실행되는 파이프라인
셰이더는 함수처럼 생겼지만, 실행 흐름이 함수가 아니다.
우리는 vertexShader.main() 같은 걸 호출하지 않는다.
대신 우리가 하는 일은 단 하나다.
glDrawArrays(...);
그러면 GPU는 내부적으로 다음과 같은 일을 한다
- 버퍼에 담긴 정점 데이터를 꺼냄
- 정점 개수만큼 vertex shader의 main()을 병렬 실행
- 삼각형 조립 -> rasterization -> 각 픽셀마다 fragment shader의 main() 병렬 실행
즉, 셰이더는 데이터 흐름에 붙은 작은 프로그램이다.
마치 공장 컨베이어 벨트 위의 자동화 로봇팔처럼,
데이터가 지나갈 때 알아서 일하는 프로그램이다.
6. 함수처럼 짜긴 짤 수 있다
vec3 brighten(vec3 color, float power) {
return clamp(color + vec3(power), 0.0, 1.0);
}
void main()
{
FragColor = vec4(brighten(vec3(1.0, 0.3, 0.1), 0.2), 1.0);
}
GLSL은 일반적인 함수도 지원한다.
그래서 셰이더 안에서도 보조 함수를 정의해서 재사용할 수 있다.
하지만 이 코드도 픽셀 수만큼 main()이 실행되며,
그 안에서 각각 brighten()이 호출되는 것일 뿐이다.
결국 구조는 함수 같지만, 전체 흐름은 병렬 파이프라인에 속해 있다.
7. "프로그래머가 직접 호출하지 않는 함수"
프로그래밍의 본질은 "함수를 정의하고 호출"하는 데 있다고 생각할 수 있다.
하지만 셰이더는 다르다.
- 우리는 직접 호출하지 않는다.
- 호출 순서를 정하지도 않는다.
- return 값도 없다.
그럼에도 불구하고, 정의된 코드는 자동으로 동작하며 세상을 바꾼다.
이건 마치 JavaScript의 이벤트 리스너 같기도 하고,
함수형 프로그래밍의 map/filter처럼 데이터에 따라 자동 호출되는 콜백 구조와도 닮았다.
셰이더는 "나를 불러줘"가 아니라
"내가 그 자리에 있으면 알아서 불러주겠지"에 가까운 존재다.
8. 요약 - 셰이더는 GPU의 함수 같지만, '그런 함수'는 아니다
- 셰이더는 함수처럼 보이지만,
- 파이프라인에서 자동으로 실행되며,
- GPU의 수많은 코어에서 병렬적으로 작동하는 그래픽 전용 프로그램이다.
이런 점에서 셰이더는 일반적인 프로그래밍 함수보다
GPU 병렬 처리에 특화된 작은 데이터 기반 스크립트라고 보는 게 더 정확하다.
'Dev > Grapics' 카테고리의 다른 글
| Scop 구현기 1편: 윈도우 하나 띄우는 게 뭐라고 (Object Viewer) (0) | 2025.04.09 |
|---|---|
| Rasterization vs Ray Tracing (0) | 2025.04.09 |
| OpenGL의 좌표계, 진짜로 이해하기 (1) | 2025.04.08 |
| 정점(Vertex)이란 무엇인가? - 그래픽 세계의 가장 작은 시작점 (0) | 2025.04.08 |
| 렌더링 파이프라인: 그래픽이 만들어지는 과정 (0) | 2025.04.07 |
