래스터화된 각 픽셀의 최종 색상을 결정한다. 조명 계산, 텍스처 샘플링, 그림자 처리 등이 있다.

 

픽셀 하나당 한 번 실행한다.

 

버텍스 셰이더와의 관계

 

버텍스 셰이더

- 버텍스마다 실행한다.

- 실행 횟수가 적다. (메시 버텍스 수)

 

픽셀 셰이더

- 픽셀마다 실행한다.

- 실행 횟수가 많다. (화면 픽셀 수 x 오버드로우)

 

1920x1080 해상도

- 버텍스 셰이더 : 메시 버텍스 수천 ~ 수만 번

- 픽셀 셰이더 : 최대 207만 번

-> 픽셀 셰이더 최적화가 더 임팩트가 크다

 

역할

- 래스터화로 생성된 각 픽셀의 최종 색상(RGBA)을 결정하는 프로그램이다

 

입력

- 버텍스 셰이더에서 보간된 데이터 (UV, 법선, 월드 포지션...)

 

출력

- 픽셀 하나의 최종 색상 (float4)

 

픽셀 셰이더 실행 흐름

 

1. 버텍스 셰이더 출력

- 버텍스 A : UV(0.0, 0.0), Normal(0, 1, 0)

- 버텍스 B : UV(1.0, 0.0), Normal(0, 1, 0)

- 버텍스 C : UV(0.5, 1.0), Normal(0, 1, 0)

 

2. 래스터화 (보간)

 

3. 픽셀 P (삼각형 내부)

- UV = 보간된 값 (0.37, 0.43)

- Normal = 보간된 값

 

4. 픽셀 셰이더 실행 (이 픽셀 하나에 대해)

- 텍스처 샘플링 (UV로 색상 읽기)

- 법선 맵 샘플링 (법선 조작)

- 조명 계산 (PBR)

- 최종 색상 변환

 

5. 출력 병합 (Z버퍼, 블렌딩)

 

6. 프레임 버퍼에 기록

 

기본 픽셀 셰이더 구조

// HLSL 픽셀 셰이더 예제

// 텍스처 리소스
Texture2D AlbedoMap : register(t0);
Texture2D NormalMap : register(t1);
Texture2D RoughnessMap : register(t2);
Texture2D MetallicMap : register(t3);
Texture2D AOMap : register(t4);
SamplerState Sampler : register(s0);

// 조명 데이터
cbuffer LightBuffer : register(b1)
{
     float3 LightDirection;
     float3 LightColor;
     float3 LightIntensity;
     float3 CameraPosition;
     float3 AmbientColor;
};

// 버텍스 셰이더에서 받는 입력
struct PixelInput
{
     float4 ClipPosition : SV_POSITION;
     float3 WorldPosition : TEXCOORD0;
     float3 WorldNormal : TEXCOORD1;
     float2 TexCoord : TEXCOORD2;
     float3 Tangent : TEXCOORD3;
     float3 Bitangent : TEXCOORD4;
};

// 픽셀 하나의 최종 색상 반환
float4 PS(PixelInput input) : SV_TARGET {
     
     // 1. 텍스처 샘플링
     float4 albedo = AlbedoMap.Sample(Sampler, input.TexCoord);
     float roughness = RoughnessMap.Sample(Sampler, input.TexCoord).r;
     float metallic = MetallicMap.Sample(Sampler, input.TexCoord).r;
     float ao = AOMap.Sample(Sampler, input.TexCoord).r;
     
     // 2. 노멀 맵 적용
     float3 normalSample = NormalMap.Sample(Sampler, input.TexCoord).rgb;
     float3 tangentNormal = normalSample * 2.0 - 1.0;
     
     float3x3 TBN = float3x3(normalize(input.Tangent), normalize(input.Bitangent), normalize(input.WorldNormal));
     float3 N = normalize(mul(tangentNormal, TBN));
     
     // 3. 조명 벡터 계산
     float3 L = normalize(-LightDirection);
     float3 V = normalize(CameraPosition - input.WorldPosition);
     float3 H = normalize(L + V);
     
     // 4. PBR 조명 계산
     // Diffuse (Lambert)
     float NdotL = max(dot(N, L), 0.0);
     
     // F0 : 기본 반사율
     // 비금속: 0.04, 금속: albedo 색상
     float3 F0 = lerp(float3(0.04, 0.04, 0.04), albedo.rgb, metallic);
     
     // Fresnel (Schlick 근사)
     float VdotH = max(dot(V, H), 0.0);
     float3 F = F0 + (1.0 - F0) * pow(1.0 - VdotH, 5.0);
     
     // Distribution (GGX/Trowbridge-Reitz)
     float alpha = roughness * roughness;
     float alpha2 = alpha * alpha;
     float NdotH = max(dot(N, H), 0.0);
     float denom = NdotH * NdotH * (alpha2 - 1.0) + 1.0;
     float D = alpha2 / (3.14159 * denom * denom);
     
     // Geometry (Smith)
     float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
     float NdotV = max(dot(N, V), 0.0);
     float G = (NdotL / (NdotL * (1-k) + k)) * (NdotV / (NdotV * (1-k) + k));
     
     // Specular BRDF
     float3 specular = (D * F * G) / (4.0 * NdotV * NdotL + 0.001);
     
     // Diffuse (금속은 Diffuse 없다)
     float3 kD = (1.0 - F) * (1.0 - metallic);
     float3 diffuse = kD * albedo.rgb / 3.14159;
     
     // 5. 최종 색상 합산
     float3 color = (diffuse + specular) * LightColor * LightIntensity * NdotL;
     
     // AO 적용
     color += AmbientColor * albedo.rgb * ao;
     
     // 감마 보정 (Linear -> sRGB)
     color = pow(color, float3(1.0/2.2, 1.0/2.2, 1.0/2.2));
     
     return float4(color, albedo.a);
}

GLSL ES 픽셀 셰이더 - 임베디드/WebGL

// GLSL ES 2.0 (OpenGL ES / WebGL)
#version 100
precision mediump float;  // 정밀도 명시 필수

varying vec2 vTexCoord;
varying vec3 vWorldNormal;
varying vec3 vWorldPosition;

uniform sampler2D uAlbedo;
uniform sampler2D uNormalMap;
uniform vec3      uLightDir;
uniform vec3      uLightColor;
uniform vec3      uCameraPos;

void main() {
    // 텍스처 샘플링 (ES 2.0 문법)
    vec4 albedo = texture2D(uAlbedo, vTexCoord);

    // 법선 맵
    vec3 normalSample = texture2D(
        uNormalMap, vTexCoord).rgb;
    vec3 normal = normalize(
        normalSample * 2.0 - 1.0);

    // 단순 Lambert 조명
    float NdotL = max(
        dot(normal, normalize(-uLightDir)),
        0.0);

    // 최종 색상
    // ES에서는 gl_FragColor 사용
    gl_FragColor = vec4(
        albedo.rgb * uLightColor * NdotL,
        albedo.a
    );
}

 

픽셀 셰이더 테크닉들

// 테크닉 1: 알파 클리핑 (Masked 모드)
float4 PS_Masked(PixelInput input) : SV_TARGET {
    float4 albedo = AlbedoMap.Sample(
        Sampler, input.TexCoord);

    // 임계값 이하면 픽셀 버림
    // → Z버퍼 쓰기 가능 → Early-Z 작동
    clip(albedo.a - 0.5);
    // if(albedo.a < 0.5) discard; 와 동일

    return albedo;
}

// 테크닉 2: 절차적 텍스처 (Procedural)
float4 PS_Procedural(PixelInput input)
    : SV_TARGET {
    float2 uv = input.TexCoord;

    // 체커보드 패턴 (텍스처 없이)
    float2 grid = floor(uv * 8.0);
    float checker = fmod(
        grid.x + grid.y, 2.0);

    float3 color = lerp(
        float3(0.1, 0.1, 0.1),
        float3(0.9, 0.9, 0.9),
        checker
    );

    return float4(color, 1.0);
}

// 테크닉 3: 외곽선 렌더링
float4 PS_Outline(PixelInput input)
    : SV_TARGET {
    // 법선이 카메라 방향과 수직에 가까우면
    // 외곽선으로 처리
    float3 N = normalize(input.WorldNormal);
    float3 V = normalize(CameraPosition
                         - input.WorldPosition);
    float rim = 1.0 - abs(dot(N, V));
    rim = pow(rim, 3.0);  // 외곽선 두께 조절

    float3 color = lerp(
        float3(1, 1, 1),   // 기본 색상
        float3(0, 0, 0),   // 외곽선 색상
        rim
    );
    return float4(color, 1.0);
}

// 테크닉 4: Fresnel 효과
float4 PS_Fresnel(PixelInput input)
    : SV_TARGET {
    float3 N = normalize(input.WorldNormal);
    float3 V = normalize(CameraPosition
                         - input.WorldPosition);

    // 시선과 수직일수록 강한 반사
    float fresnel = pow(
        1.0 - abs(dot(N, V)),
        4.0
    );

    float3 baseColor   = float3(0.1, 0.3, 0.8);
    float3 fresnelColor = float3(1.0, 1.0, 1.0);
    float3 color = lerp(
        baseColor, fresnelColor, fresnel);

    return float4(color, 1.0);
}

// 테크닉 5: 디졸브 효과
float4 PS_Dissolve(PixelInput input)
    : SV_TARGET {
    float4 albedo = AlbedoMap.Sample(
        Sampler, input.TexCoord);
    float  noise  = NoiseMap.Sample(
        Sampler, input.TexCoord).r;

    // 노이즈 값이 임계값 이하면 픽셀 버림
    clip(noise - DissolveAmount);

    // 경계선 발광 효과
    float edge = smoothstep(
        DissolveAmount,
        DissolveAmount + 0.05,
        noise
    );
    float3 color = lerp(
        EdgeColor.rgb,
        albedo.rgb,
        edge
    );

    return float4(color, 1.0);
}

 

픽셀 셰이더 최적화 기법

 

비용이 높은 연산들

 

1. 텍스처 샘플링

- 메모리 대역폭 소모

- 캐시 미스 시 지연이 크다

- 텍스처 수 최소화, 채널 패킹

 

2. sin/cos/pow/sqrt

- GPU에서 비교적 비싸다

- 사전 계산해서 uniform으로 전달한다

- 근사식으로 대체한다

 

3. 분기문 (if/else)

- GPU 분기 = 양쪽 다 실행 후 선택한다

- step(), lerp(), saturate()로 대체한다

 

최적화 방법

 

1. 버텍스 셰이더로 연산 이동

- 픽셀마다 고정된 값 -> 버텍스에서 계산하고 보간한다

 

2. Unlit 셰이더 사용

- 조명 계산이 없다

- 파티클, 이펙트에서 오버드로우 비용 감소한다

 

3. 텍스처 채널 패킹

- R = Roughness, G = Metallic, B = AO, A = Emissive 의 텍스처 4개를 1개로 합친다

- 샘플링 횟수 75% 감소된다

 

4. 하프 정밀도 (half/mediump)

- float -> half (16비트)

- 모바일/임베디드에서 연산 속도 향상

- 색상, UV, 간단한 조명에 충분하다

 

감마 보정 - 자주 헷갈리는 부분

 

모니터가 비선형적으로 밝기를 표시한다. 0.5값이 50% 밝기가 아니라는 것이다.

 

sRGB 공간 vs 선형 공간

- 텍스처 저장 : sRGB(비선형, 사람 눈에 맞게)

- 조명 계산 : 선형 공간에서 해야 정확하다

 

올바른 흐름 :

텍스처 읽기 -> sRGB to Linear 변환 -> 선형 공간에서 조명 계산 -> Linear to sRGB 변환 -> 프레임버퍼 출력

 

노멀 맵은 sRGB OFF -> 벡터 데이터이므로 감마 보정이 불필요하다

 

연결되는 단어들

- BRDF(Bidirectional Reflectance Distribution Function) : 빛이 표면에서 어떻게 반사되는지 정의하는 함수이다. PBR의 수학적 기반이며 Lambert(Diffuse) + GGX(Specular)가 게임 업계 표준이다.

- 감마 보정 (Gamma Correction) : 선형 색상 공간과 sRGB 색상 공간 변환. 픽셀 셰이더에서 조명은 선형 공간에서 계산해야 정확

- MRT (Multiple Render Targets) : 픽셀 셰이더가 여러 렌더 타깃에 동시에 출력하는 기능이다. 디퍼드 렌더링의 G-Buffer 채우기에 사용된다.

- SV_TARGET  : 픽셀 셰이더의 출력 시맨틱. SV_TARGET0, SV_TARGET1...으로 여러 렌더 타킷에 동시 출력이 가능하다

'Word' 카테고리의 다른 글

버텍스 셰이더 (Vertex Shader)  (0) 2026.07.31
렌더링 파이프라인 (Rendering Pipeline)  (0) 2026.07.29
텍스처 샘플링 (Texture Sampling)  (0) 2026.07.28
오버드로우 (Overdraw)  (0) 2026.07.27
UV 매핑 (UV Mapping)  (0) 2026.07.22

+ Recent posts