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

+ Recent posts