1. 셰이더의 기본 구조와 속성 정의
셰이더 파일은 유니티 인스펙터와 소통하는 부분과 실제 그래픽 연산을 수행하는 부분으로 나뉩니다.
셰이더 블록 전체 구조
- Shader "경로/이름": 셰이더의 전체 이름을 정의하며, 슬래시를 사용해 메뉴 구조로 관리할 수 있습니다
- 셰이더 파일의 가장 첫 줄에 위치하며, 유니티 에디터 내에서 해당 셰이더를 찾기 위한 경로를 지정합니다.
- 코드 내의 이름과 실제 파일의 이름이 일치하지 않아도 작동하지만, 관리를 위해 통일하는 것이 권장됩니다.
- Properties: 재질(Material) 패널에 나타날 변수들을 선언합니다
- 이 블록에 선언된 변수들은 아티스트가 유니티 인스펙터 창에서 직접 조절할 수 있는 인터페이스가 됩니다.
- 선언된 변수들은 이후 Pass 블록 내부에서 동일한 이름으로 다시 선언되어야 셰이더 연산에 활용될 수 있습니다
- 주요 속성 타입 (Properties)
- Float: 단일 실수 값 (_Name ("Display", Float) = 1.0)
- Range: 슬라이더 형태의 범위 값 (Range(0.0, 1.0))
- Vector: 4차원 벡터 값 (Vector)
- Color: 색상 선택기 (회색 등 기본값 설정 가능)
- 2D (Texture): 텍스처 슬롯이며, 기본값으로 "white", "black", "red" 등을 지정하여 텍스처가 없을 때의 동작을 제어합니다
- 주요 속성 타입 (Properties)
| 타입 | 설명 | 코드 예시 |
| Float | 일반 실수 값 | _MyFloat ("Float", Float) = 1.0 |
| Range | 범위가 제한된 슬라이더 | _MyRange ("Range", Range(0, 1)) = 0.5 |
| Color | 색상 선택기 | _MyColor ("Color", Color) = (1,1,1,1) |
| Vector | 4차원 벡터 | _MyVector ("Vector", Vector) = (0,0,0,0) |
| 2D | 텍스처 이미지 슬롯 | _MainTex ("Texture", 2D) = "white" {} |
- SubShader: 실제 렌더링 로직이 포함되며, 그래픽 카드 사양에 따라 여러 개를 작성할 수 있습니다. 즉 옵션별로 다르게 동작하도록 여러 개를 만들 수 있습니다. 이 때는 LOD 300 이나 LOD 100 처럼 옵션별로 동작하는 설정을 정의할 수 있습니다.
- 여러 개의 SubShader가 있을 경우, 유니티는 위에서부터 아래로 확인하며 현재 하드웨어가 지원하는 첫 번째 SubShader를 실행합니다
- Pass: 하나의 완전한 렌더링 실행 단위를 의미하며, 이 안에서 CG 코드가 작성됩니다
- GPU가 모델의 데이터를 받아 화면에 그리는 한 번의 과정을 의미합니다.
- 여러 개의 Pass 를 가진 멀티패스 렌더링을 구현할 경우, 위쪽 Pass부터 순차적으로 실행되어 결과가 중첩됩니다.
// 1. Shader 경로 및 이름 정의
// 슬래시(/)를 사용하여 유니티 재질(Material) 메뉴에서 계층 구조로 관리할 수 있습니다.
Shader "Tutorial/ShaderBlockStructure"
{
// 2. Properties (속성 블록)
// 유니티 인스펙터 창(Material 패널)에 나타날 변수들을 선언하는 곳입니다.
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 텍스처 슬롯
_Color ("Color", Color) = (1, 1, 1, 1) // 색상 선택기
_Value ("Float Value", Float) = 1.0 // 수치 입력
}
// 3. SubShader (서브셰이더 블록)
// 실제 렌더링 로직이 포함됩니다. 사양이나 옵션별로 여러 개를 작성할 수 있습니다.
SubShader
{
// LOD(Level of Detail) 설정을 통해 기기 성능에 따라 실행될 서브셰이더를 구분할 수 있습니다.
LOD 300
// 4. Pass (패스 블록)
// 하나의 완전한 렌더링 실행 단위를 의미합니다.
// 특정 모델을 여러 번 그려야 할 때 여러 개의 Pass를 작성할 수 있습니다.
Pass
{
CGPROGRAM
// HLSL/Cg 코드가 작성되는 구역입니다.
// 정점 셰이더와 프래그먼트 셰이더의 이름을 정의합니다.
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" // 유니티 내장 함수 라이브러리 포함
// 셰이더 로직 구현 생략...
ENDCG
}
}
// 모든 SubShader가 실행 불가능할 때 사용할 예비(Fallback) 셰이더
Fallback "Diffuse"
}
2. GPU 파이프라인과 데이터 구조체 (Semantics)
셰이더 코드는 CPU에서 데이터를 받아 GPU의 각 단계를 거치며 처리됩니다.
데이터 전달 구조체
- appdata (Input): CPU에서 모델 데이터를 가져옵니다.
- POSITION: 모델의 정점 좌표.
- TEXCOORD0: 첫 번째 UV 채널 (요즘에는 최대 8개까지 가능하긴 함. 보간기로는 16)
- NORMAL: 법선 벡터.
- TANGENT: 탄젠트 벡터.
- COLOR: 정점 색상
- SV_VertexID : 버텍스 고유 인덱스
- SV_InstanceID: GPU 인스턴싱을 사용할때 현재 그려지고 있는 물체가 몇 번째 복사본(인스턴스)인지 알려주는 번호
- BLENDWEIGHTS: 각 정점이 주변 뼈(Bone)들에 의해 받는 영향력(가중치) 값. 보통 내부 메크로에서 처리해서잘 보기는 힘듬
- BLENDINDICES: 정점에 영향을 주는 뼈들의 인덱스 번호. 역시 보통 내부 메크로에서 처리해서잘 보기는 힘듬
struct appdata {
float4 vertex : POSITION; // 정점 좌표
float3 normal : NORMAL; // 법선
float2 uv : TEXCOORD0; // UV
// 추가로 가져올 수 있는 데이터들
uint vid : SV_VertexID; // 버텍스 ID (버텍스 넘버)
uint iid : SV_InstanceID; // 인스턴스 ID
float4 weights : BLENDWEIGHTS; // 본 가중치
float4 indices : BLENDINDICES; // 본 인덱스
};
v2f (Vertex to Fragment): 정점 셰이더가 계산하여 프래그먼트 셰이더로 넘겨주는 데이터입니다.
- SV_POSITION: 클립 공간 좌표 (필수).
- TEXCOORDn: UV나 기타 보간 데이터 전송용 범용 저장소 (최대 16개 슬롯).
// 2. 정점 셰이더에서 프래그먼트 셰이더로 넘겨주는 데이터 구조체 (v2f)
// SV_POSITION은 화면 출력을 위해 필수이며,
// TEXCOORDn 슬롯들은 데이터 전달용 보간기로 사용됩니다.
struct v2f
{
float4 pos : SV_POSITION; // 클립 공간 좌표 (필수)
float2 uv : TEXCOORD0; // 첫 번째 통로: UV 데이터
float3 wNormal : TEXCOORD1;// 두 번째 통로: 월드 공간 법선 데이터
float3 custom : TEXCOORD2; // 세 번째 통로: 커스텀 연산 데이터 (예: 월드 포지션 등)
// 이와 같은 TEXCOORD 슬롯을 최대 15번까지(총 16개) 확장할 수 있습니다.
};
3. 정점 셰이더 (Vertex Shader)와 공간 변환
- 공간 변환 과정: 모델 공간 → 세계 공간 → 카메라 공간 → 클립 공간 순으로 변환됩니다
- MVP 변환: 세 가지 행렬(Model, View, Projection)을 곱해 한 번에 변환할 수 있으며, 유니티에서는 UnityObjectToClipPos() 함수를 사용해 이를 수행합니다
- 변수 초기화: 출력 구조체(v2f)를 선언하고 모든 데이터를 채운 뒤 리턴해야 합니다
// 3. 정점 셰이더 (Vertex Shader)
v2f vert (appdata v)
{
// 변수 초기화: 출력 구조체 v2f를 선언합니다.
v2f o;
// 공간 변환 과정 (MVP 변환)
// 1. 모델 공간 -> 세계 공간 (Model Matrix)
// 2. 세계 공간 -> 카메라 공간 (View Matrix)
// 3. 카메라 공간 -> 클립 공간 (Projection Matrix)
// 유니티에서는 아래 함수 하나로 위 3단계 MVP 변환을 한 번에 수행합니다.
o.pos = UnityObjectToClipPos(v.vertex);
// 나머지 데이터 채우기
o.uv = v.uv;
// 모든 데이터를 채운 뒤 구조체를 리턴합니다.
return o;
}
4. 프래그먼트 셰이더 (Fragment Shader)와 연산
각 픽셀의 최종 색상을 결정하며 텍스처링과 정밀도 제어가 중요합니다.
-
데이터 정밀도: 성능 최적화를 위해 세 가지 타입을 구분해 사용합니다.
- float: 32비트 (좌표 계산용)
- half: 16비트 (대부분의 벡터 및 UV용)
- fixed: 8비트 (0~1 사이의 색상값용, 현재는 half로 대체됨)
- 그렇지만 모바일에서는 기기 칩셋 특성에 따른 오류때문에 float 만 사용하는것을 추천
- 텍스처 샘플링 (Texturing):
- _MainTex_ST: 텍스처 이름 뒤에 _ST를 붙여 선언하면 유니티 인스펙터의 Tiling(xy)과 Offset(zw) 데이터를 자동으로 받아옵니다.
- tex2D(_MainTex, i.uv): UV 좌표를 사용하여 텍스처에서 색상을 읽어옵니다.
// 4. 프래그먼트 셰이더 (Fragment Shader)
// SV_Target 시맨틱을 통해 최종 색상을 렌더 타겟으로 출력
float4 frag (v2f i) : SV_Target
{
// 모바일 칩셋 오류 방지를 위해 float 사용
float4 texColor;
// 텍스처 샘플링 (Texturing): UV 좌표를 사용하여 색상 추출
texColor = tex2D(_MainTex, i.uv);
// 최종 색상 결정: 샘플링된 색상
float4 finalColor = texColor;
return finalColor;
}
5. 고급 제어 및 특수 효과 기법
컬링과 투영 (Culling & Mapping)
- Culling: Cull Back(뒷면 제거), Cull Front(앞면 제거), Cull Off(양면 렌더링) 명령으로 가시성을 제어합니다

Pass {
Cull Back // 뒷면 제거 (기본값) [cite: 9]
Cull Front // 앞면 제거 [cite: 9]
Cull Off // 양면 렌더링 (그림자나 평면 물체에 사용)
CGPROGRAM
// ... 셰이더 코드
}
- 트라이플레너 매핑 (Triplanar Mapping): UV 대신 모델 공간의 XYZ 좌표를 사용하여 텍스처를 투영할 수 있습니다
아래 코드는 그중 일부인 XY 좌표만으로 UV를 만든 예시이고, 노말 방향과 함께 섞어서 최종적으로 Z 방향까지 대입시켜야 합니다. 그럼 코드가 길어지니까 귀찮아서 여기까지만...
// 프래그먼트 셰이더 내부
// 모델 공간의 정점 좌표(v.vertex)를 샘플링 데이터로 사용
float2 customUV = i.objPos.xy; // XY 평면 투영
float4 col = tex2D(_MainTex, customUV);
알파 테스트와 애니메이션
- Alpha Test (Clip): clip(pixel.r - _Cutoff) 함수를 사용해 특정 밝기 이하의 픽셀을 완전히 버립니다

// 프래그먼트 셰이더 내부
float alpha = tex2D(_MainTex, i.uv).r; // 텍스처의 R 채널을 알파로 사용
clip(alpha - _Cutout); // 결과값이 0보다 작으면 픽셀을 렌더링하지 않음
- UV 애니메이션: _Time.y 변수를 UV에 더해 텍스처가 흐르거나 움직이는 효과(충격파, 물결 등)를 만들 수 있습니다.

// 정점 또는 프래그먼트 셰이더 내부
float2 animatedUV = i.uv;
animatedUV.y += _Time.y * _Speed; // 시간에 따라 Y축 방향으로 흐르는 효과
float4 col = tex2D(_MainTex, animatedUV);
알파 블렌딩 (Alpha Blending)
- 설정: Blend SrcAlpha OneMinusSrcAlpha 공식을 주로 사용합니다. 물론 다른 조합도 있습니다.
- 순서 제어: 깊이 판정 오류를 막기 위해 ZWrite Off로 깊이 쓰기를 끄고, Render Queue를 Transparent (3000)로 설정해야 하는 것이 일반적입니다

Tags { "Queue" = "Transparent" } // 렌더 큐를 3000번대로 설정
ZWrite Off // 깊이 버퍼 기록 중단
Blend SrcAlpha OneMinusSrcAlpha // 기본 알파 블렌딩 공식
// Blend One One // 가산 혼합 (Additve)
림 라이트 (Rim Light) 구현
- 노멀 벡터 변환: 모델의 법선을 월드공간으로 변환합니다 (UnityObjectToWorldNormal 또는 행렬 연산)
- 시선 벡터 계산: _WorldSpaceCameraPos에서 세계 공간의 정점 위치를 빼서 구합니다
- 내적 연산 (N dot V): 법선과 시선 벡터를 내적하여 카메라를 향하는 면을 판별합니다
- 결과 반전: 1.0 - (N · V) 공식을 통해 가장자리만 밝게 빛나는 림 라이트를 완성합니다

// 1. 데이터 구조체 정의
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0; // 월드 노멀 전달용
float3 worldPos : TEXCOORD1; // 월드 포지션 전달용
};
// 2. 정점 셰이더 (변환 및 좌표 계산)
v2f vert (appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// 단계 1: 모델의 법선을 월드 공간으로 변환
o.worldNormal = UnityObjectToWorldNormal(v.normal);
// 단계 2: 세계 공간의 정점 위치 계산
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
// 3. 프래그먼트 셰이더 (내적 및 반전 연산)
fixed4 frag (v2f i) : SV_Target {
// 벡터 정규화 (보간 과정에서 변형된 강도 보정)
float3 n = normalize(i.worldNormal);
// 단계 2 확장: 시선 벡터 계산 (카메라 위치 - 정점 위치)
float3 v = normalize(_WorldSpaceCameraPos - i.worldPos);
// 단계 3: 내적 연산 (N dot V) 수행 및 0~1 제한
float nv = saturate(dot(n, v));
// 단계 4: 결과 반전 (1.0 - NV)으로 가장자리 추출
float rim = 1.0 - nv;
// 최종 출력 (림 라이트 강도 조절 가능)
return fixed4(rim, rim, rim, 1.0);
}반응형
'자료는 자료지 > 외부에서 퍼온자료' 카테고리의 다른 글
| 4. Unity 셰이더: MatCap과 박막 간섭 구현하기 (0) | 2026.01.04 |
|---|---|
| 03. 캐릭터 림 라이트 및 스캔(유광) 효과 구현과 코드 변환 [ASE 강좌] (0) | 2026.01.04 |
| 실시간 렌더링 파이프라인 기초 (6) | 2026.01.02 |
| ASTC_User_Guide 번역 6 (0) | 2025.12.15 |
| ASTC_User_Guide 번역 5 (0) | 2025.12.14 |
댓글