콘텐츠로 이동

Unity Shader Graph 커스텀 함수 노드

Shader Graph의 Custom Function Node는 HLSL 코드를 직접 작성해 기본 제공 노드로는 표현하기 어려운 수학 함수, 특수 효과, 성능 최적화 셰이더를 구현할 때 사용합니다.


Shader Graph 창 → 빈 공간 우클릭 → Create Node → Custom Function
또는 노드 검색 창에서 "Custom Function" 입력

Node 설정:

  • Name: 함수 이름 (HLSL 함수명과 동일)
  • Source: String (인라인 코드) 또는 File (외부 .hlsl 파일)
  • Inputs/Outputs: 포트 정의

/* Custom Function Node 설정:
Name: Remap
Inputs: In(Float), InMin(Float), InMax(Float), OutMin(Float), OutMax(Float)
Outputs: Out(Float)
Body: */
void Remap_float(float In, float InMin, float InMax,
float OutMin, float OutMax, out float Out)
{
Out = OutMin + (In - InMin) * (OutMax - OutMin) / (InMax - InMin);
}

Assets/Shaders/CustomFunctions.hlsl
#ifndef CUSTOM_FUNCTIONS_INCLUDED
#define CUSTOM_FUNCTIONS_INCLUDED
// 물결 효과
void WaveOffset_float(float2 UV, float Speed, float Frequency,
float Amplitude, float Time, out float2 Out)
{
float wave = sin(UV.x * Frequency + Time * Speed) * Amplitude;
Out = float2(UV.x, UV.y + wave);
}
// 디졸브 효과
void Dissolve_float(float2 UV, Texture2D NoiseTexture,
SamplerState NoiseSampler, float Threshold,
float EdgeWidth, out float Alpha, out float Edge)
{
float noise = SAMPLE_TEXTURE2D(NoiseTexture, NoiseSampler, UV).r;
Alpha = step(Threshold, noise);
Edge = step(Threshold - EdgeWidth, noise) - Alpha;
}
#endif

Custom Function Node에서 File 모드로 이 파일을 참조하고 함수명을 지정합니다.


// Texture2D와 SamplerState를 받아야 함
void SampleWithOffset_float(
float2 UV, float2 Offset,
Texture2D MainTex, SamplerState Sampler,
out float4 Color)
{
Color = SAMPLE_TEXTURE2D(MainTex, Sampler, UV + Offset);
}

Shader Graph에서 포트 타입:

  • 텍스처 입력: Texture2D 타입
  • 샘플러 입력: SamplerState 타입 (별도 포트)

void ApplyNormalMap_float(
float3 NormalMap, // 노멀 맵 샘플 (0~1)
float3 Normal, // 버텍스 노멀 (월드)
float3 Tangent, // 버텍스 탄젠트 (월드)
float3 Bitangent, // 버텍스 바이탄젠트 (월드)
out float3 WorldNormal)
{
// 0~1 → -1~1 범위로 변환
float3 n = NormalMap * 2.0 - 1.0;
// TBN 매트릭스로 탄젠트→월드 변환
float3x3 tbn = float3x3(Tangent, Bitangent, Normal);
WorldNormal = normalize(mul(n, tbn));
}

void Checkerboard_float(float2 UV, float Scale, out float Out)
{
float2 scaled = UV * Scale;
float2 id = floor(scaled);
Out = fmod(id.x + id.y, 2.0);
}

// 모바일에서는 half 정밀도로 성능 향상
void RimLight_half(half3 ViewDir, half3 Normal,
half RimPower, out half RimFactor)
{
half NdotV = saturate(dot(Normal, ViewDir));
RimFactor = pow(1.0h - NdotV, RimPower);
}

// UV 좌표를 색상으로 시각화
void DebugUV_float(float2 UV, out float4 Color)
{
Color = float4(UV.x, UV.y, 0.0, 1.0);
}
// 노멀을 색상으로 시각화
void DebugNormal_float(float3 Normal, out float4 Color)
{
Color = float4(Normal * 0.5 + 0.5, 1.0);
}

Shader Graph → Create → Sub Graph → MyFunctions.shadersubgraph

SubGraph를 만들면 여러 셰이더에서 동일한 노드 그룹을 재사용할 수 있습니다. Custom Function Node를 SubGraph로 감싸면 입력/출력 포트를 일관된 인터페이스로 노출할 수 있습니다.

SubGraph 설계 원칙:

  • 입력 포트에 기본값을 설정해 단독 테스트 가능하게 구성
  • SubGraph 내부 노드는 Preview 켜서 중간 결과 시각 확인
  • 복잡한 노멀 계산, PBR 항 계산을 SubGraph로 캡슐화

10. Shader Graph Keyword — 변형 셰이더

섹션 제목: “10. Shader Graph Keyword — 변형 셰이더”

Keyword 노드는 #pragma multi_compile / #pragma shader_feature를 시각적으로 처리합니다.

Shader Graph → Blackboard → (+) → Keyword
- Boolean Keyword: on/off 분기
- Enum Keyword: 여러 분기 (최대 8개)
- Scope: Local (shader_feature) / Global (multi_compile)
// 런타임에서 Global Keyword 설정
public class WeatherController : MonoBehaviour
{
void SetRainEnabled(bool enabled)
{
if (enabled)
Shader.EnableKeyword("RAIN_ON");
else
Shader.DisableKeyword("RAIN_ON");
}
// 특정 머티리얼에만 적용
void SetMaterialWet(Material mat, bool wet)
{
if (wet)
mat.EnableKeyword("_WET");
else
mat.DisableKeyword("_WET");
}
}

Local Keyword(shader_feature)는 해당 머티리얼을 사용하는 오브젝트에만 variant가 빌드되므로 빌드 크기에 유리합니다. Global(multi_compile)은 항상 모든 variant가 빌드됩니다.


Custom Function Node의 핵심은 함수 이름이 {Name}_float (또는 _half) 형식이어야 한다는 것입니다. 외부 .hlsl 파일을 사용하면 여러 Shader Graph에서 함수를 재사용할 수 있고, 버전 관리도 용이합니다. 텍스처 샘플링은 반드시 SAMPLE_TEXTURE2D 매크로를 사용해 SRP 호환성을 유지하세요.