Unity HLSL Shader 기초
Unity 셰이더 시스템 개요
섹션 제목: “Unity 셰이더 시스템 개요”Unity는 세 가지 방식으로 셰이더를 작성합니다.
- ShaderLab: Unity 고유 선언 언어. Properties, SubShader, Pass 구조를 정의
- HLSL: GPU에서 실행되는 실제 셰이더 코드
- Shader Graph: 노드 기반 비주얼 에디터 (코드 불필요)
이 글은 URP(Universal Render Pipeline)에서 HLSL을 직접 작성하는 방법을 다룹니다. 셰이더 그래프로 표현하기 어려운 복잡한 효과나 커스텀 렌더링 패스에서 직접 HLSL을 다뤄야 합니다.
URP 커스텀 셰이더 기본 구조
섹션 제목: “URP 커스텀 셰이더 기본 구조”Shader "Custom/MyFirstShader"{ Properties { _BaseColor ("Base Color", Color) = (1, 1, 1, 1) _BaseMap ("Base Texture", 2D) = "white" {} _Glossiness("Smoothness", Range(0, 1)) = 0.5 }
SubShader { // URP 태그 (필수) Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "Queue" = "Geometry" }
Pass { Name "ForwardLit" Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM #pragma vertex vert #pragma fragment frag
// URP 핵심 헤더 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
// Properties를 HLSL 변수에 연결 CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _BaseMap_ST; // 텍스처 타일링/오프셋 float _Glossiness; CBUFFER_END
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
// 버텍스 입력 struct Attributes { float4 positionOS : POSITION; // 오브젝트 공간 위치 float3 normalOS : NORMAL; float2 uv : TEXCOORD0; };
// 프래그먼트 입력 (버텍스 출력) struct Varyings { float4 positionHCS : SV_POSITION; // 클립 공간 위치 float3 normalWS : TEXCOORD0; // 월드 공간 법선 float2 uv : TEXCOORD1; };
// 버텍스 셰이더 Varyings vert(Attributes IN) { Varyings OUT;
// 오브젝트 → 클립 공간 변환 (URP 매크로) OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
// 법선 변환 OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS);
// UV 타일링/오프셋 적용 OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
return OUT; }
// 프래그먼트 셰이더 half4 frag(Varyings IN) : SV_Target { // 텍스처 샘플링 half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
// 기본 컬러 × 텍스처 half4 color = texColor * _BaseColor;
return color; }
ENDHLSL } }}좌표 공간과 변환
섹션 제목: “좌표 공간과 변환”셰이더에서 좌표 공간은 가장 혼동하기 쉬운 개념입니다.
Object Space (오브젝트 공간) ↓ M (Model Matrix)World Space (월드 공간) ↓ V (View Matrix)View Space (카메라 공간) ↓ P (Projection Matrix)Clip Space (클립 공간) ↓ 원근 나눗셈NDC (Normalized Device Coordinates) ↓ 뷰포트 변환Screen Space (화면 공간)URP 변환 함수
섹션 제목: “URP 변환 함수”// 오브젝트 → 월드float3 worldPos = TransformObjectToWorld(IN.positionOS.xyz);
// 오브젝트 → 클립 (가장 자주 사용)float4 clipPos = TransformObjectToHClip(IN.positionOS.xyz);
// 월드 → 클립float4 clipPos = TransformWorldToHClip(worldPos);
// 법선 변환 (역전치 행렬 사용)float3 normalWS = TransformObjectToWorldNormal(IN.normalOS);디퓨즈 라이팅 구현
섹션 제목: “디퓨즈 라이팅 구현”// Lighting.hlsl 포함 필요half4 frag(Varyings IN) : SV_Target{ // 법선 정규화 float3 normalWS = normalize(IN.normalWS);
// 메인 라이트 가져오기 Light mainLight = GetMainLight();
// Lambert 디퓨즈: N·L half NdotL = saturate(dot(normalWS, mainLight.direction));
// 라이트 색상 × 강도 × N·L half3 diffuse = mainLight.color * mainLight.distanceAttenuation * NdotL;
// 텍스처 샘플링 half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
half3 finalColor = texColor.rgb * _BaseColor.rgb * (diffuse + 0.1); // 0.1: 앰비언트 return half4(finalColor, texColor.a * _BaseColor.a);}법선 맵 적용
섹션 제목: “법선 맵 적용”// 추가 Properties_NormalMap ("Normal Map", 2D) = "bump" {}_NormalStrength ("Normal Strength", Range(0, 2)) = 1.0
// 추가 변수TEXTURE2D(_NormalMap);SAMPLER(sampler_NormalMap);
// Attributes에 탄젠트 추가struct Attributes{ float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; // 탄젠트 float2 uv : TEXCOORD0;};
// Varyings에 TBN 행렬 정보 추가struct Varyings{ float4 positionHCS : SV_POSITION; float3 normalWS : TEXCOORD0; float3 tangentWS : TEXCOORD1; float3 bitangentWS : TEXCOORD2; float2 uv : TEXCOORD3;};
// 버텍스 셰이더Varyings vert(Attributes IN){ Varyings OUT; OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); OUT.tangentWS = TransformObjectToWorldDir(IN.tangentOS.xyz); OUT.bitangentWS = cross(OUT.normalWS, OUT.tangentWS) * IN.tangentOS.w; OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); return OUT;}
// 프래그먼트 셰이더half4 frag(Varyings IN) : SV_Target{ // 탄젠트 공간 법선 샘플링 half4 normalSample = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, IN.uv); float3 normalTS = UnpackNormal(normalSample); normalTS.xy *= _NormalStrength;
// 탄젠트 → 월드 공간 변환 float3x3 TBN = float3x3( normalize(IN.tangentWS), normalize(IN.bitangentWS), normalize(IN.normalWS) ); float3 normalWS = normalize(mul(normalTS, TBN));
// 이후 라이팅 계산에 normalWS 사용 // ... return half4(normalWS * 0.5 + 0.5, 1.0); // 시각화}투명 오브젝트 셰이더
섹션 제목: “투명 오브젝트 셰이더”SubShader{ Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" }
Pass { Blend SrcAlpha OneMinusSrcAlpha // 알파 블렌딩 ZWrite Off // 깊이 쓰기 비활성화
// ...HLSL 코드... }}| 구성 요소 | 역할 |
|---|---|
CBUFFER_START(UnityPerMaterial) | Properties → HLSL 변수 연결 |
TEXTURE2D / SAMPLER | 텍스처 선언 |
TransformObjectToHClip | 오브젝트 → 클립 공간 변환 |
TRANSFORM_TEX | UV 타일링/오프셋 적용 |
SAMPLE_TEXTURE2D | 텍스처 샘플링 |
GetMainLight() | URP 메인 라이트 정보 |
HLSL 셰이더를 직접 작성하면 셰이더 그래프로 표현하기 어려운 커스텀 라이팅 모델, 특수 효과, 성능 최적화된 렌더링 패스를 구현할 수 있습니다.