커스텀 HLSL 노드
Custom Expression으로 HLSL 코드를 직접 작성하여 머티리얼의 한계를 넘어서는 고급 기법.
Custom 노드 기본 사용법
HLSL 코드를 머티리얼에 삽입하기
Custom Expression 노드를 사용하면 HLSL 코드를 머티리얼 그래프에 직접 삽입할 수 있습니다. 노드 팔레트에서 "Custom"을 검색하거나 우클릭 메뉴에서 추가합니다.
Custom 노드 설정
| 속성 | 설명 |
|---|---|
| Code | HLSL 코드 본문. 단일 표현식 또는 return문이 있는 함수 본문 |
| Output Type | 반환값 타입: CMOT Float1, Float2, Float3, Float4 등 |
| Inputs | 외부 노드에서 받는 입력 배열. 이름과 타입 지정 |
| Additional Outputs | 다중 출력이 필요한 경우 추가 출력 핀 정의 |
| Description | 노드의 표시 이름 (가독성용) |
// 예제 1: 간단한 표현식 (return 불필요)
// Output Type: Float3
// 입력: Color (float3), Intensity (float)
Color * Intensity
// 예제 2: return문이 있는 코드
// Output Type: Float1
// 입력: UV (float2), Time (float)
float wave = sin(UV.x * 10.0 + Time * 3.0);
wave = wave * 0.5 + 0.5;
return wave;
Custom 노드를 사용하면 상수 폴딩(Constant Folding)이 비활성화됩니다. 컴파일러가 상수 연산을 미리 계산하는 최적화를 할 수 없으므로, 동일한 로직을 내장 노드로 구현할 수 있다면 내장 노드를 사용하는 것이 더 효율적입니다.
멀티 라인 함수 작성
복잡한 로직과 헬퍼 함수 정의
// Voronoi 노이즈 구현
// 입력: UV (float2), Scale (float), Time (float)
// Output Type: Float1
float2 p = UV * Scale;
float2 i = floor(p);
float2 f = frac(p);
float minDist = 1.0;
for(int y = -1; y <= 1; y++)
{
for(int x = -1; x <= 1; x++)
{
float2 neighbor = float2(x, y);
float2 cellPos = frac(
sin(dot(i + neighbor,
float2(127.1, 311.7))) * 43758.5453
);
cellPos = 0.5 + 0.5 * sin(Time + 6.2831 * cellPos);
float d = length(neighbor + cellPos - f);
minDist = min(minDist, d);
}
}
return minDist;
헬퍼 함수 정의 (Include 방식)
// 방법 1: 코드 내에서 함수 정의 후 호출
// (함수를 Main 코드 앞에 작성)
// Custom 노드 Code에 입력:
float hash(float2 p)
{
return frac(sin(dot(p, float2(127.1,311.7))) * 43758.5453);
}
return hash(UV * Scale);
// 방법 2: .ush 파일을 Include
// Additional Shader Directories에 경로 추가 후:
// #include "/Project/Shaders/MyFunctions.ush"
// Custom 노드의 Include File Paths에 추가
커스텀 HLSL 파일을 프로젝트에서 사용하려면: 1) 프로젝트의 Shaders/ 폴더에 .ush 파일 생성, 2) 모듈의 .Build.cs에 셰이더 디렉토리 추가, 3) Custom 노드의 Include File Paths에 가상 경로 입력. 자세한 셋업은 프로젝트 설정에 따라 다릅니다.
실전 HLSL 예제 모음
자주 사용하는 커스텀 셰이더 패턴
1. 라운드 마스크 (SDF Circle)
// 입력: UV (float2), Radius (float), Softness (float)
float d = length(UV - 0.5) * 2.0;
return 1.0 - smoothstep(Radius - Softness, Radius + Softness, d);
2. 그리드 패턴
// 입력: UV (float2), Scale (float), LineWidth (float)
float2 grid = abs(frac(UV * Scale - 0.5) - 0.5);
float line = min(grid.x, grid.y);
return 1.0 - smoothstep(0.0, LineWidth, line);
3. 극좌표 변환 (Polar Coordinates)
// 입력: UV (float2)
// UV를 극좌표(각도, 거리)로 변환
float2 centered = UV - 0.5;
float angle = atan2(centered.y, centered.x) / (2.0 * 3.14159) + 0.5;
float dist = length(centered) * 2.0;
return float2(angle, dist);
4. 디졸브/소멸 효과
// 입력: NoiseVal (float), Progress (float),
// EdgeWidth (float), EdgeColor (float3)
// Output Type: Float4 (RGB=Color, A=OpacityMask)
float dissolve = NoiseVal - Progress;
float edge = smoothstep(0.0, EdgeWidth, dissolve);
float edgeGlow = 1.0 - smoothstep(0.0, EdgeWidth * 0.5, dissolve);
float3 color = EdgeColor * edgeGlow * 5.0;
float mask = step(Progress, NoiseVal);
return float4(color, mask);
디버깅과 주의사항
Custom 노드의 함정과 해결 방법
주요 주의사항
상수 폴딩 비활성화
Custom 노드를 거치면 컴파일러의 상수 최적화가 깨집니다. 가능하면 내장 노드를 사용하세요.
플랫폼 호환성
HLSL 코드가 모든 타겟 플랫폼(DX12, Vulkan, Metal)에서 컴파일되는지 확인하세요.
분기(if문) 비용
GPU에서 동적 분기는 비용이 높습니다. 가능하면 step(), lerp(), smoothstep()으로 대체하세요.
텍스처 샘플링
Custom 노드 내에서 텍스처 샘플링은 입력으로 Texture Object와 Sampler를 받아야 합니다.
디버깅 팁
// 1. 값을 시각화하여 확인
// Custom 출력을 Emissive에 직접 연결
return float3(value, 0, 0); // 빨간색 강도로 확인
// 2. 범위 확인
return saturate(value); // 0~1 범위인지 확인
// 3. 에러 하이라이트
if(isnan(value)) return float3(1,0,1); // NaN은 마젠타
if(isinf(value)) return float3(0,1,1); // Inf는 시안
// 4. 생성된 HLSL 확인
// Window → Shader Code → HLSL Code
// 머티리얼이 어떤 HLSL로 변환되었는지 확인 가능
머티리얼 에디터에서 Window → HLSL Code를 통해 전체 생성된 셰이더 코드를 확인할 수 있습니다. Custom 노드의 코드가 최종 셰이더에 어떻게 삽입되는지, 변수 이름이 어떻게 변환되는지 파악할 때 유용합니다.
핵심 요약
- Custom Expression 노드로 HLSL 코드를 직접 삽입할 수 있으며, Inputs 배열로 외부 노드의 값을 받습니다.
- Custom 노드는 상수 폴딩을 비활성화하므로, 내장 노드로 가능한 연산은 내장 노드를 사용하세요.
- 복잡한 로직은 코드 내부에 헬퍼 함수를 정의하거나, .ush 파일을 Include하여 모듈화합니다.
- 보로노이 노이즈, SDF 마스크, 극좌표 변환, 디졸브 효과 등이 자주 사용되는 커스텀 HLSL 패턴입니다.
- GPU에서 동적 분기(if문)는 비용이 높으므로 step(), lerp(), smoothstep()으로 대체합니다.
- Window → HLSL Code에서 생성된 전체 셰이더 코드를 확인하여 디버깅할 수 있습니다.
도전 과제
배운 내용을 직접 실습해보세요
Custom 노드를 추가하고 'return sin(Time * Speed) * 0.5 + 0.5;' 같은 간단한 HLSL 코드를 작성하세요. Input으로 Speed 파라미터를 추가하고 Emissive에 연결하여 맥동 효과를 만드세요.
Custom 노드에서 Perlin Noise 또는 Simplex Noise 함수를 직접 HLSL로 구현하세요. 2D/3D 입력을 받아 노이즈 값을 반환하고, 여러 옥타브를 합산하는 fBm(Fractal Brownian Motion)도 구현하세요.
Custom 노드 여러 개를 체이닝하여 Voronoi 패턴 + Edge Detection + 색상 매핑의 3단계 절차적 셰이더를 완성하세요. Include File Path로 외부 .ush 파일을 참조하는 방법도 시도하세요.