HLSL : DirectX용 셰이더 언어.
GLSL : OpenGL용. 문법이 비슷하며 GPU에서 실행되는 C 계열 언어.
셰이더 언어가 따로 있는 이유
CPU 언어 ( C++, C#, Rust...)
- 순차적 실행
- 코어 수십 개
- 범용 연산
- 메모리 자유롭게 접근
GPU 셰이더 언어 (HLSL, GLSL, MSL, WGSL...)
- 병렬 실행 (수천 개 코어)
- 픽셀 하나 = 독립적 실행 단위
- 제한적 메모리 접근
- 수학 연산에 특화
-> CPU와 GPU는 완전히 다른 실행 환경.
-> 셰이더 언어는 GPU 전용 프로그래밍 언어
-> C++로 GPU를 직접 프로그래밍할 수 없음
셰이더 언어 전체 계보
| API | 셰이더 언어 | 특징 |
| DirectX | HLSL | Microsoft 표준. UE5 기본 |
| OpenGL | GLSL | Khronos 표준. 크로스플랫폼 |
| OpenGL ES | GLSL ES | GLSL 경량화. 모바일/임베디드 |
| WebGL | GLSL ES | 브라우저에서 GLSL ES |
| Vulkan | GLSL/HLSL | 둘 다 가능 -> SPIR-V로 컴파 |
| Metal | MSL | Apple 전용. C++14 기반 |
| WebGPU | WGSL | 브라우저 전용. Rust 영향 |
공통점 = 개념은 동일. 문법만 다름
버텍스 셰이더 + 픽셀 셰이더 + MVP 행렬 = 어느 언어나 같다.
GLSL vs HLSL 문법 1vs1 대응
버텍스 셰이더
| GLSL (OpenGL) | HLSL(DirectX) | |
| 버전 선언 | #version 330 core | 버전 선언이 없다 ( 타깃 프로파일로 지정 ) |
| 입력 | layout(location=0) in vec3 aPosition; layout(location=1) in vec2 aTexCoord; |
struct VertexInput{ float3 position : POSITION; float2 texCoord : TEXCOORD0; } |
| 출력 | out vec2 vTexCoord; | struct PixelInput { float4 position : SV_POSITION; float2 texCoord : TEXCOORD0; } |
| uniform (CPU에서 전달) | uniform mat4 uMVP | cbuffer Transform : register(b0) { matrix mvp; }; |
| 진입점 | void main() { gl_position = uMVP * vec4( aPosition, 1.0); vTexCoord = aTexCoord; } |
PixelInput VS(VertexInput input) { PixelInput output; output.position = mul(float4(input.position,1),mvp); output.texCoord = input.texCoord; return output; } |
픽셀(fragment) 셰이더
| GLSL | HLSL | |
| 입력 | in vec2 vTexCoord; | PixelInput에서 받음 |
| 텍스처 | uniform sampler2D uTexture; | Texture2D diffuse : register(t0); SamplerState sampler : register(s0); |
| 출력 | out vec4 FragColor; void main() { FragColor = texture( vTexture, vTexCoord); } |
float4 PS(PixelInput input) : SV_TARGET { return diffuse.Sample( sampler, input.texCoord); } |
UE5 머티리얼과 HLSL의 연결
| UE5 머티리얼 에디터 노드 | 내부 HLSL 코드 |
| TextureSample | float4 color = Texture.Sample(Sampler, TexCoord); |
| Multiply | result = A * B; |
| Lerp(Linear Interpolate) | result = lerp(A, B, Alpha); |
| Fresnel | float fresnel = pow(1.0 - dot(N, V), Exp); |
| DotProduct | result = dot(A, B); |
| Normalize | result = normalize(V); |
| WorldPosition | float3 worldPos = GetWorldPosition(Parameters); |
커스텀 노드 -> HLSL 직접 작성 가능
PBR 조명 계산 (HLSL)
- UE5가 내부에서 하는 일의 단순화 버전
- Physically Based Rendering 조명 계산
cbuffer LightBuffer : register(b1) {
float3 lightDirection;
float3 lightColor;
float3 cameraPosition;
};
float4 PS(PixelInput input) : SV_TARGET {
// 텍스처에서 값 읽기
float4 albedo = AlbedoMap.Sample(Sampler, input.texCoord);
float metallic = MetallicMap.Sample(Sampler, input.texCoord).r;
float roughness = RoughnessMap.Sample(Sampler, input.texCoord).r;
float3 normal = NormalMap.Sample(Sampler, input.texCoord).rgb;
// 법선 벡터 변환 (0 ~ 1 -> -1 ~ 1)
normal = normalize(normal * 2.0 - 1.0)
// 기본 조명 벡터 계산
float3 N = normalize(normal); // 법선
float3 L = normalize(-lightDirection); // 광원 방향
float3 V = normalize(cameraPosition - input.worldPos); // 시선 방향
float3 H = normalize(L + V); // 하프 벡터
// Diffuse (Lambert)
float NdotL = max(dot(N, L), 0.0);
float3 diffuse = albedo.rgb * lightColor * NdotL;
// Specular (Blinn-Phong 단순화)
float NdotH = max(dot(N, H), 0.0);
float shininess = (1.0 - roughness) * 256.0;
float3 specular = lightColor * pow(NdotH, shininess) * metallic;
// 최종 색상
return float4(diffuse + specular, albedo.a);
};
UE5 PBR은 이보다 훨씬 복잡하지만 기본 원리는 동일하다
GLSL ES - 임베디드/WebGL 특징
// GLSL ES 2.0 (OpenGL ES 2.0, WebGL 1.0)
#version 100
// 정밀도 명시 필수 - 데스크탑 GLSL에는 없는 개념
precision highp float; // 32비트 (데스크탑과 동일)
precision mediump float; // 16비트 (모바일 최적화)
precision lowp float; // 8비트 (빠르지만 부정확)
// attribute / varying - ES 3.0부터는 in/out으로 변경
attribute vec3 aPosition; // 버텍스 입력
varying vec2 vTexCoord; // 픽셀 셰이더로 전달
// texture2D - ES 3.0부터 texture()로 통일
gl_FragColor = texture2D(uTexture, vTexCoord);
// GLSL ES 3.0 (OpenGL ES 3.0, WebGL 2.0)
#version 300 es
// in/out 사용. texture() 통일.
// 데스크탑 GLSL 3.3과 거의 동일해짐
in vec3 aPosition;
out vec4 FragColor;
FragColor = texture(uTexture, vTexCoord);
셰이더 최적화 기법
// 비효율적 - 픽셀 셰이더에서 무거운 연산
float4 PS(PixelInput input) : SV_TARGET{
float3 N = normalize(input.normal); // 모든 픽셀마다 normalize
float3 L = normalize(lightDir); // 모든 픽셀마다 normalize
// 수백만 픽셀 x 이 연산 = 비효율적(비싸다)
}
// 효율적 - 버텍스 셰이더에서 미리 계산
PixelInput VS(VertexInput input) {
output.normal = normalize(mul(input.normal, (float3x3)normalMatrix));
output.lightDir = normalize(lightDir);
}
원칙 : 버텍스 수 << 픽셀 수. 가능하면 픽셀 셰이더에서 버텍스 셰이더로 연산을 옮길 것.
// 분기문 남용 - GPU에서 비쌈
float4 PS(PixelInput input) : SV_TARGET {
if(input.texCoord.x > 0.5) {
return float4(1,0,0,1);
}
else{
return float4(0,0,1,1);
}
}
// 수학으로 대체 - 분기 없음
float4 PS(PixelInput input) : SV_TARGET {
float t = step(0.5, input.texCoord.x); // 분기 없는 조건
return lerp(float4(0,0,1,1), float4(1,0,0,1), t);
}
연결되는 단어들
SPIR-V : Vulkan/WebGPU의 셰이더 중간 표현. GLSL/HLSL/WGSL -> SPIR-V - > GPU 바이너리 변환 경로
셰이더 퍼뮤테이션(Shader Permutation) : 같은 셰이더를 다양한 조건 조합으로 미리 컴파일한 것. UE5 첫 실행이 오래 걸리는 이유이며 조합이 많을수록 컴파일 시간이 증가한다.
Swizzling : 벡터 컴포넌트를 재배치하는 연산.color.bgr, pos.xzy 같은 것. HLSL/GLSL 공통 기능.
인트린식 함수 (Intrinsic Function) : GPU에서 하드웨어 수준으로 최적화된 내장 함수. lerp, setp, saturate, dot, cross, normalize, reflect 등. 직접 구현보다 항상 빠르다
'Word' 카테고리의 다른 글
| 노멀 맵 (Normal Map) (0) | 2026.07.21 |
|---|---|
| 컴퓨트 셰이더 ( Compute Shader ) (0) | 2026.07.20 |
| WebGL / WebGPU (0) | 2026.07.14 |
| OpenGL ES ( Embedded Systems ) (0) | 2026.07.13 |
| Metal (0) | 2026.07.10 |
