<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나 혼자만 개발</title>
    <link>https://blogger2146.tistory.com/</link>
    <description>ShilLoon 노는 곳</description>
    <language>ko</language>
    <pubDate>Fri, 7 Aug 2026 16:42:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ShilLoon</managingEditor>
    <item>
      <title>픽셀/프래그먼트 셰이더 (Pixel / Fragment Shader)</title>
      <link>https://blogger2146.tistory.com/161</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;래스터화된 각 픽셀의 최종 색상을 결정한다. 조명 계산, 텍스처 샘플링, 그림자 처리 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 하나당 한 번 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더와의 관계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스마다 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실행 횟수가 적다. (메시 버텍스 수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 셰이더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀마다 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실행 횟수가 많다. (화면 픽셀 수 x 오버드로우)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1920x1080 해상도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 셰이더 : 메시 버텍스 수천 ~ 수만 번&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀 셰이더 : 최대 207만 번&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 픽셀 셰이더 최적화가 더 임팩트가 크다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 래스터화로 생성된 각 픽셀의 최종 색상(RGBA)을 결정하는 프로그램이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 셰이더에서 보간된 데이터 (UV, 법선, 월드 포지션...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀 하나의 최종 색상 (float4)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 셰이더 실행 흐름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 버텍스 셰이더 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 A : UV(0.0, 0.0), Normal(0, 1, 0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 B : UV(1.0, 0.0), Normal(0, 1, 0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 C : UV(0.5, 1.0), Normal(0, 1, 0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 래스터화 (보간)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 픽셀 P (삼각형 내부)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV = 보간된 값 (0.37, 0.43)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Normal = 보간된 값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 픽셀 셰이더 실행 (이 픽셀 하나에 대해)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 샘플링 (UV로 색상 읽기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 법선 맵 샘플링 (법선 조작)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조명 계산 (PBR)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최종 색상 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 출력 병합 (Z버퍼, 블렌딩)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 프레임 버퍼에 기록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 픽셀 셰이더 구조&lt;/p&gt;
&lt;pre id=&quot;code_1785916985219&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 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 -&amp;gt; sRGB)
     color = pow(color, float3(1.0/2.2, 1.0/2.2, 1.0/2.2));
     
     return float4(color, albedo.a);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL ES 픽셀 셰이더 - 임베디드/WebGL&lt;/p&gt;
&lt;pre id=&quot;code_1785917037406&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 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
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 셰이더 테크닉들&lt;/p&gt;
&lt;pre id=&quot;code_1785917163283&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 테크닉 1: 알파 클리핑 (Masked 모드)
float4 PS_Masked(PixelInput input) : SV_TARGET {
    float4 albedo = AlbedoMap.Sample(
        Sampler, input.TexCoord);

    // 임계값 이하면 픽셀 버림
    // &amp;rarr; Z버퍼 쓰기 가능 &amp;rarr; Early-Z 작동
    clip(albedo.a - 0.5);
    // if(albedo.a &amp;lt; 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);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 셰이더 최적화 기법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용이 높은 연산들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 텍스처 샘플링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리 대역폭 소모&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 캐시 미스 시 지연이 크다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 수 최소화, 채널 패킹&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. sin/cos/pow/sqrt&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GPU에서 비교적 비싸다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사전 계산해서 uniform으로 전달한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 근사식으로 대체한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 분기문 (if/else)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GPU 분기 = 양쪽 다 실행 후 선택한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- step(), lerp(), saturate()로 대체한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적화 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 버텍스 셰이더로 연산 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀마다 고정된 값 -&amp;gt; 버텍스에서 계산하고 보간한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Unlit 셰이더 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조명 계산이 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파티클, 이펙트에서 오버드로우 비용 감소한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 텍스처 채널 패킹&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- R = Roughness, G = Metallic, B = AO, A = Emissive 의 텍스처 4개를 1개로 합친다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샘플링 횟수 75% 감소된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 하프 정밀도 (half/mediump)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- float -&amp;gt; half (16비트)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모바일/임베디드에서 연산 속도 향상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 색상, UV, 간단한 조명에 충분하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감마 보정 - 자주 헷갈리는 부분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터가 비선형적으로 밝기를 표시한다. 0.5값이 50% 밝기가 아니라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sRGB 공간 vs 선형 공간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 저장 : sRGB(비선형, 사람 눈에 맞게)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조명 계산 : 선형 공간에서 해야 정확하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 흐름 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 읽기 -&amp;gt; sRGB to Linear 변환 -&amp;gt; 선형 공간에서 조명 계산 -&amp;gt; Linear to sRGB 변환 -&amp;gt; 프레임버퍼 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵은 sRGB OFF -&amp;gt; 벡터 데이터이므로 감마 보정이 불필요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BRDF(Bidirectional Reflectance Distribution Function) : 빛이 표면에서 어떻게 반사되는지 정의하는 함수이다. PBR의 수학적 기반이며 Lambert(Diffuse) + GGX(Specular)가 게임 업계 표준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 감마 보정 (Gamma Correction) : 선형 색상 공간과 sRGB 색상 공간 변환. 픽셀 셰이더에서 조명은 선형 공간에서 계산해야 정확&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MRT (Multiple Render Targets) : 픽셀 셰이더가 여러 렌더 타깃에 동시에 출력하는 기능이다. 디퍼드 렌더링의 G-Buffer 채우기에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SV_TARGET&amp;nbsp; : 픽셀 셰이더의 출력 시맨틱. SV_TARGET0, SV_TARGET1...으로 여러 렌더 타킷에 동시 출력이 가능하다&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/161</guid>
      <comments>https://blogger2146.tistory.com/161#entry161comment</comments>
      <pubDate>Wed, 5 Aug 2026 17:16:06 +0900</pubDate>
    </item>
    <item>
      <title>버텍스 셰이더 (Vertex Shader)</title>
      <link>https://blogger2146.tistory.com/160</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;3D 공간의 점을 화면 좌표로 변환하는 프로그램이다. MVP 행렬 변환을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더는 버텍스 하나당 한 번 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더의 위치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU (드로우콜 발생)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 어셈블러 (버텍스 데이터 읽기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더 &amp;lt;- 여기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;래스터화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 셰이더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 : 버텍스(점) 하나를 받아서 변환된 버텍스를 출력하는 프로그램이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 : 3D 공간의 점 하나 (위치, UV, 법선...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 : 클립 공간의 점 하나 + 픽셀 셰이더로 보낼 데이터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU가 버텍스 수만큼 이 셰이더를 병렬 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시에 버텍스 1000개 -&amp;gt; 1000번 병렬 실행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP 행렬 변환 ( 버텍스 셰이더의 핵심 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3D 오브젝트가 화면에 그려지기까지 좌쵸 공간을 단계별로 변환해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model 행렬 (M)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 로컬 공간 -&amp;gt; 월드 공간 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트의 위치, 회전, 스케일 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)원점에 있는 큐브를 위치 (5, 0, 3)으로 이동한 뒤 Y축으로 45도 회전시킨 다음 크기를 2배로 늘린다 -&amp;gt; Model 행렬 하나로 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View 행렬 (V)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 월드 공간 -&amp;gt; 뷰 공간 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카메라를 원점으로 이동시키는 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카메라가 움직이는게 아니라 카메라가 원점에 고정되고 세계 전체가 반대로 이동하는 것이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Projection 행렬 (P)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 뷰 공간 -&amp;gt; 클립 공간 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원근감 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원근 투영 (Perspective) : 멀리 있으면 작게 보인다. 시야각(FOV)을 적용하며 frustum을 잘라낸다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 직교 투영 (Orthographic) : 거리와 상관없이 같은 크기이며 UI, 2D 게임, 도면에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 : gl_Position = P * V * M * vertex &amp;lt;- 반드시 이 순서로 곱한다. ( 행렬 곱 순서 중요하다 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP 행렬의 수학&lt;/p&gt;
&lt;pre id=&quot;code_1785483161688&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cbuffer TransformBuffer : register(b0) {
     float4x4 ModelMatrix; // 오브젝트 변환
     float4x4 ViewMatrix; // 카메라 변환
     float4x4 ProjectionMatrix; // 원근 변환
     float4x4 MVPMatrix; // 세 행렬 미리 곱한 것 (최적화)
     float4x4 NormalMatrix; // 법선 변환용 (Model의 역전치)
};

struct VertexInput{
     float3 Position : POSITION;  // 로컬 공간 위치
     float3 Normal : NORMAL; // 법선 벡터
     float2 TexCoord : TEXCOORD0; // UV 좌표
     float4 Tangent : TANGENT; // 탄젠트 (노멀 맵용)
};

struct VertexOutput {
     float4 ClipPosition : SV_POSITION; // 클립 공간 위치 (필수)
     float3 WorldPosition : TEXCOORD0; // 월드 공간 위치
     float3 WorldNormal : TEXCOORD1; //월드 공간 법선
     float2 TexCoord : TEXCOORD2; // UV (그대로 전달)
     float3 Tangent : TEXCOORD3; // 탄젠트
     float3 Bitangent : TEXCOORD4; // 바이탄젠트
};

VertexOutput VS(VertexInput input) {
     VertexOutput output;
     
     // 1. 클립 공간 변환 (MVP 한 번에)
     output.ClipPosition = mul(
          float4(input.Position, 1.0),
          MVPMatrix
     );
     
     // 2/ 월드 공간 위치 (조명 계산에 필요)
     output.WorldPosition = mul(
           float4(input.Position, 1.0),
           ModelMatrix
     ).xyz;
     
     // 3. 법선 변환 (Model의 역전치 행렬 사용)
     // 단순히 Model 행렬을 쓰면 비균등 스케일 시 법선이 틀어진다.
     output.WorldNormal = normalize(
           mul(input.Normal,
                (float3x3)NormalMatrix)
     );
     
     // 4. 탄젠트 변환 (노멀 맵 TBN용)
     output.Tangent = normalize(
           mul(input.Tangent.xyz,
                (float3x3)ModelMatrix)
     );
     
     // 5. 바이탄젠트 계산
     output.Bitangent = normalize(
           cross(output.WorldNormal, output.Tangent) * input.Tangent.w // 손방향(+- 1)
     );
     
     // 6. UV는 변환 없이 그대로 전달
     output.TexCoord = input.TexCoord;
     
     return output;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;법선 변환에 역전치 행렬이 필요한 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 비균등 스케일의 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트를 X축으로 2배 늘릴 경우 버텍스 위치는 Model 행렬로 올바르게 변환다. 법선을 같은 행렬로 변환하면 법선이 표면과 더 이상 수직이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 원래 : 표면 수평, 법선 수직 (0,1,0) X 2배 스케일 후 :&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 표면이 더 완만해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 법선을 그냥 X 2배 하면 (0, 1, 0) -&amp;gt; 기울어진다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실제로는 표면에 수직인 방향으로 회전해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 : 역전치 행렬 (Invers Transpose)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Normal = normalize( (ModelMatrix^-1)^T * inputNormal )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;균등 스케일(x,y,z 같은 배율)이면 문제가 없지만 비균등 스케일에서 반드시 역전치 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더로 할 수 있는 것들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기본 : 좌표 변환 (모든 메시에 필수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MVP 행렬 곱 -&amp;gt; 화면 좌표로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 스키닝 (캐릭터 애니메이션)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 버텍스가 여러 본(Bone)의 영향을 받는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 본 행렬 x 가중치로 최종 위치 계산한다&lt;/p&gt;
&lt;pre id=&quot;code_1785483500373&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;float3 skinnedPos = float3(0,0,0);
for(int i = 0; i &amp;lt; 4; i++) {
    skinnedPos += mul(
        boneMatrices[boneIndices[i]],
        float4(position, 1.0)
    ).xyz * boneWeights[i];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 인스턴싱 (Instancing)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 같은 메시를 여러 위치에 배치한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 인스턴스의 Transform을 버퍼로 전달한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SV_InstanceID로 어떤 인스턴스인지 구별한다.&lt;/p&gt;
&lt;pre id=&quot;code_1785483589680&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;instanceTransform = transformBuffer[instanceID];
output.position = mul(vertex, instanceTransform);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 드로우콜 1개로 수천 개 오브젝트 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; UE5 ISM/HISM의 핵심 동작 원리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 디졸브 / 클리핑 효과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스를 조건에 따라 화면 밖으로 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 완전히 숨기는 것보다 유연한 제어&lt;/p&gt;
&lt;pre id=&quot;code_1785483716828&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(dissolveAmount &amp;gt; threshold) {
    output.position = float4(2,2,2,1); // 클립 공간 밖
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더 최적화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 병목 발생 조건&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 버텍스 수가 매우 많을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메시 폴리곤 수 x 드로우콜 수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수백만 버텍스 x 무거운 셰이더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 스키닝이 복잡할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 본 수가 많은 캐릭터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다수의 캐릭터를 동시에 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. WP0 계산이 복잡할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삼각함수 남발&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 샘플링 (버텍스 셰이더에서도 가능하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적화 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. LOD로 원거리 버텍스 수 감소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 수가 줄면 버텍스 셰이더 실행 횟수 감수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 연산을 CPU로 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프레임마다 바뀌지 않는 계산 -&amp;gt; CPU에서 미리 계산 후 uniform으로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 스키닝을 컴퓨트 셰이더로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GPU Skinning -&amp;gt; Compute Shader&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 렌더링 파이프라인과 분리해 병렬 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. WP0 비용 제한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 복잡한 수식 -&amp;gt; 단순화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 기반 WP0 -&amp;gt; 밉맵 최저 레벨 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL vs HLSL 버텍스 셰이더 비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL (OpenGL / OpenGL ES)&lt;/p&gt;
&lt;pre id=&quot;code_1785484179726&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#version 330 core

layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aNormal;
layouy(location = 2) in vec3 aTexCoord;

out vec3 vWorldPosition;
out vec3 vWorldNormal;
out vec2 vTexCoord;

uniform mat4 uModel;
uniform mat4 uView;
uniform mat4 uProjection;
uniform mat3 uNormalMatrix;

void main() {
    vec4 worldPos = uModel * vec4(aPosition, 1.0);
    vWorldPosition = worldPos.xyz;
    vWorldNormal = normalize(uNormalMatrix * aNormal);
    vTexCoord = aTexCoord;
    
    gl_Position = uProjection * uView * worldPos;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLSL (DirectX / UE5)&lt;/p&gt;
&lt;pre id=&quot;code_1785484419470&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cbuffer Transforms : register(b0) {
     float4x4 Model;
     float4x4 View;
     float4x4 Projection;
     float3x3 NormalMatrix;
};

struct VIn {
     float3 pos : POSITION;
     float3 normal : NORMAL;
     float2 uv : TEXCOORD0;
};

struct VOut {
     float4 clipPos : SV_POSITION;
     float3 worldPos : TEXCOORD0;
     float3 worldNormal : TEXCOORD1;
     float2 uv : TEXCOORD2;
};

VOut VS(VIn v) {
     VOut o;
     float4 worldPos = mul(float4(v.pos,1), Model);
     o.worldPos = worldPos.xyz;
     o.worldNormal = normalize(mul(v.normal, NormalMatrix));
     o.uv = v.uv;
     o.clipPos = mul(worldPos, mul(View, Projection));
     
     return o;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스키닝 (Skinning) : 본 애니메이션을 버텍스에 적용하는 과정. 버텍스 셰이더에서 실행하며 본 행렬 x 가중치 합산.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인스턴싱 (Instancing) : 같은 메시를 드로우콜 하나로 여러 위치에 그리는 최적화 기법이다. 버텍스 셰이더의 SV_InstanceID로 각 인스턴스 구분.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- WPO (World Position Offset) : UE5 머티리얼에서 버텍스 위치를 셰이더로 조작하는 기능이다. 폴리곤 추가 없이 변형 효과가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클리핑 (Clipping) : 클립 공간에서 -1 ~ 1 범위 밖 버텍스를 잘라내는 것. 버텍스 셰이더 이후 하드웨어가 자동 처리한다.&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/160</guid>
      <comments>https://blogger2146.tistory.com/160#entry160comment</comments>
      <pubDate>Fri, 31 Jul 2026 16:56:20 +0900</pubDate>
    </item>
    <item>
      <title>렌더링 파이프라인 (Rendering Pipeline)</title>
      <link>https://blogger2146.tistory.com/159</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;3D 데이터를 2D 화면으로 변환하는 전체 과정이다. 버텍스 처리 -&amp;gt; 래스터화 -&amp;gt; 픽셀 처리 순으로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D 데이터가 화면이 되는 전체 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3D 세계 데이터 (메시, 텍스처, 조명) -&amp;gt; 렌더링 파이프라인 (GPU) -&amp;gt; 2D 화면 픽셀 (프레임버퍼) -&amp;gt; 모니터 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인(Pipeline)이라는 이름의 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 공장 생산라인처럼 각 단계가 순서대로 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 앞 단계 출력 = 뒷 단계 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 단계가 병렬로 동시 실행이 가능하다. (단계 A가 버텍스 2를 처리하는 동안 단계 B는 버텍스 1을 처리한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 파이프라인 전체 단계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 단계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 씬 구성, 컬링, 드로우콜 발행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어떤 오브젝트를 그릴지 결정한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 데이터를 GPU에 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드로우콜(Draw Call)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU 파이프라인 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 입력 어셈블러 (Input Assembler)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- VBO에서 버텍스 데이터 읽는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스로 삼각형 조립한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 버텍스 셰이더 (Vertex Shader) &amp;lt;- 프로그래머블 버텍스마다 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MVP 행렬로 3D -&amp;gt; 클립 공간 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 법선, UV&amp;nbsp; 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 테셀레이션 (Tessellation)&amp;nbsp; (선택적)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 헐 셰이더 + 테셀레이터 + 도메인 셰이더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 폴리곤을 세분화해 디테일 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 지오메트리 셰이더 (Geometry Shader) (선택적)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삼각형 단위 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 새 삼각형 생성 가능 ( 파티클, 그림자 볼륨 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 래스터화 (Rasterization) (고정 기능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클립 공간 -&amp;gt; 화면 픽셀 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삼각형 내부 픽셀 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 속성 보간 (UV, 법선, 색상)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Early-Z 테스트 ( 고정 기능 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀 셰이더 실행 전 깊이 검사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가려진 픽셀 기각 -&amp;gt; 오버드로우 방지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 픽셀/프래그먼트 셰이더 &amp;lt;- 프로그래머블&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀마다 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 샘플링, 조명 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최종 색상 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 출력 병합 (Output Merger)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 깊이 테스트 (Z버퍼)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스텐실 테스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 알파 블렌딩 (반투명 처리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프레임버퍼에 최종 색상 쓰기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임버퍼 -&amp;gt; 화면 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계 상세 - 프로그래머블 vs&amp;nbsp; 고정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머블 단계 (개발자가 셰이더로 제어)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 셰이더 : 버텍스 변환, 스키닝&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀 셰이더 : 조명, 텍스처, 색상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 테셀레이션 : 폴리곤 세분화 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지오메트리 셰이더 : 삼각형 조작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컴퓨트 셰이더 : 파이프라인 외 GPU 연산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 기능 단계 (하드웨어가 처리, 설정만 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 입력 어셈블러 : 버텍스 데이터 읽기 방식 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 래스터화 : 삼각형 채우기 방식 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Early-Z : 깊이 테스트 활성화 여부&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 출력 병합 : 블렌딩 방식 설정 (Add, Alpha 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌표 변환의 흐름 - 버텍스 세이더의 핵심&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 공간 (Model Space / Object Space)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트 자신의 로컬 좌표계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메시 원점 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model 행렬 (월드 변환) 위치, 회전, 스케일 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월드 공간 (World Space)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 씬 전체의 공통 좌표계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 오브젝트가 같은 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View 행렬 (카메라 변환) 카메라를 원점으로 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 공간 (View Space / Camera Space)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카메라가 원점, 카메라가 바라보는 방향이 -Z&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Projection 행렬 (원근 변환) 원근감 적용, 시야각 처리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클립 공간 (Clip Space)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 화면에 보이는 범위 : -1 ~ +1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이 범위 밖은 클리핑 (잘라낸다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원근 나누기 (하드웨어)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NDC (Normalized Device Coordinates)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- -1 ~ +1 정규화 좌표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰포트 변환 (하드웨어)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면공간 (Screen Space)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실제 픽셀 좌표 (0 ~ 1920, 0 ~ 1080)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;래스터화 단계 - 고정 기능의 핵심&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;래스터화가 하는 일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삼각형 3개 버텍스 -&amp;gt; 삼각형 내부 픽셀 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 A (UV = 0.0, 0.0), 버텍스 B (UV = 1.0, 0.0), 버텍스 C (UV = 0.5, 1.0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삼각형 내부 픽셀 P의 UV = A.UV * 무게중심_A + B.UV * 무게중심_B + C.UV * 무게중심_C&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 버텍스 사이의 모든 값이 자동으로 보간된다. UV, 법선, 색상, 월드 포지션 모두 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면 방향 컬링 (Face Culling)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삼각형 버텍스 순서(와인딩 오더)로 앞/뒷면 판별&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 뒷면은 래스터화 자체를 건너뛴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 드로우콜 수는 그대로, 픽셀 셰이더 실행 감소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Two Sided 머티리얼은 이 컬링을 비활성화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 병합 단계 - 최종 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 병합(Output Merger)이 하는 일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀 셰이더가 출력한 색상을 프레임 버퍼에 쓸지 말지 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정 기준1. 깊이 테스트 (Depth Test)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 픽셀의 Z값 vs Z버퍼의 기존 Z값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 더 가까우면 덮어쓰고 더 멀면 버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정 기준2 : 스텐실 테스트 (Stencil Test)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스텐실 버퍼의 값으로 픽셀 마스킹&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 영역에만 그리거나 그리지 않는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 외곽선 렌더링, 포탈 효과, 거울에 활용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정 기준3 : 알파 블렌딩 (Alpha Blending)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반투명 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- src * srcAlpha + dst * (1-srcAlpha)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Z버퍼 쓰기 비활성화가 필요하다 -&amp;gt; 오버드로우 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포워드 vs 디퍼드 렌더링 - 깊이 비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포워드 렌더링 (Forward Rendering)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트마다 모든 조명을 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for each object:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;for each light:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;color += calculateLighting(object, light)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트 100개, 광원 10개 -&amp;gt; 1000번 조명 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점 : 단순하며 반투명 처리가 쉽다. MSAA 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점 : 광원 많으면 급격히 느려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용도 : 모바일, 광원 적은 게임에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디퍼드 렌더링 (Deferred Rendering)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 먼저 표면 정보를 G-Buffer에 저장한 다음 나중에 한 번에 조명 계산한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Pass 1 : G-Buffer 채우기 ( 조명 계산 없다 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Pass 2 : for each light:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;화면 픽셀마다 G-Buffer 읽어서 조명 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트 100개, 광원 10개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; G-Buffer : 100번&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 조명 : 화면 픽셀 수&amp;nbsp; x 10번&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점 : 광원 수에 강하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점 : 메모리를 많이 쓴다 (G-Buffer), 반투명 처리가 어려우며 MSAA 어렵다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용도 : PC/콘솔, 오픈월드, 광원 많은 게임에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 배운 파이프라인 위치&lt;/p&gt;
&lt;pre id=&quot;code_1785312274314&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;드로우콜          &amp;rarr; CPU-GPU 경계
버텍스 셰이더     &amp;rarr; 2번 단계
MVP 행렬          &amp;rarr; 버텍스 셰이더 내부
테셀레이션        &amp;rarr; 3번 단계
래스터화          &amp;rarr; 5번 단계
UV 매핑           &amp;rarr; 래스터화 보간 + 픽셀 셰이더
Early-Z           &amp;rarr; 6번 단계
픽셀 셰이더       &amp;rarr; 7번 단계
PBR 조명          &amp;rarr; 픽셀 셰이더 내부
텍스처 샘플링     &amp;rarr; 픽셀 셰이더 내부
노멀 맵           &amp;rarr; 픽셀 셰이더 내부
HLSL / GLSL       &amp;rarr; 버텍스/픽셀 셰이더 언어
Z버퍼             &amp;rarr; 8번 출력 병합
알파 블렌딩       &amp;rarr; 8번 출력 병합
오버드로우        &amp;rarr; 6-8번에서 발생
LOD               &amp;rarr; 버텍스 수 자체를 조절
드로우콜 최적화   &amp;rarr; CPU 측 최적화
컴퓨트 셰이더     &amp;rarr; 파이프라인 외부 GPU 연산&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프레임버퍼 (Framebuffer) : 렌더링 결과를 저장하는 최종 버퍼. 색상 버퍼 + 깊이 버퍼 + 스텐실 버퍼로 구성하며 화면에 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스왑체인 (Swap Chain) : 화면에 표시하는 버퍼와 다음 프레임을 그리는 버퍼를 그대로 사용한다. 더블/트리플 버퍼링의 기반이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 렌더 타킷 (Render Target) : 화면 대신 텍스처에 렌더링하는 것. G-Buffer, 그림자 맵, 반사 텍스처에 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- VSync : 렌더링 완료를 모니터 주사율과 동기화하며 테어링(화면 찢김) 방지한다. 프레임레이트를 모니터 주사율로 제한한다.&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/159</guid>
      <comments>https://blogger2146.tistory.com/159#entry159comment</comments>
      <pubDate>Wed, 29 Jul 2026 17:06:59 +0900</pubDate>
    </item>
    <item>
      <title>텍스처 샘플링 (Texture Sampling)</title>
      <link>https://blogger2146.tistory.com/158</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처에서 UV 좌표의 색상값을 읽어온다. 필터링 방식에 따라 품질과 성능이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UV로 텍스처에서 색상을 읽는 것이 핵심 기념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 셰이더에서 텍스처 읽기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) UV 좌표 (0.5, 0.3)이 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 (1024x1024)에서 U = 0.5 -&amp;gt; 512번째 열, V = 0.3 -&amp;gt; 307번째 행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 위치의 색상을 읽어서 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제1. UV 좌표가 정수가 아닐 수 있다. 소수는 정확히 픽셀 경계에 떨어지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 사이 값을 어떻게 읽을 것인가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 필터링 방식에 따라 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제2. 화면의 픽셀 하나가 텍스처의 여러 픽셀에 걸칠 수 있다. (오브젝트가 작게 보일 때)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 밉맵으로 해결이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터링 방식 4종류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Nearest (Point) Filtering&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 가까운 텍셀 하나만 읽는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀화된(Pixelated) 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비용이 가장 낮다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 텍스처에서 (4x4)에서 UV(0.37, 0.25) 읽기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;U -&amp;gt; 0.37 * 4 = 1.48 -&amp;gt; 반올림 -&amp;gt; 1번 열&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V -&amp;gt; 0.25 * 4 = 1.00 -&amp;gt; 반올림 -&amp;gt; 1번 행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 1,1 텍셀 하나만 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용도로는 픽셀 아트 게임, 디버깅이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Bilinear Filtering&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 가까운 4개 텍셀을 선형 보간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 부드럽지만 먼 거리에서 흐릿하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비용이 낮다. (GPU 하드웨어 가속)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TL TR&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;■ ■&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;↕ 가중치 보간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;■ ■&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BL BR&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- result = lerp(lerp(TL, TR, fx), lerp(BL, BR, fx), fy)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- fx, fy = 소수점 부분 (보간 가중치)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용도로는 모바일 기본값, 대부분 상황에서 적절하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Trilinear Filtering&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Bilinear + 밉맵 레벨 간 보간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 밉맵 전환 시 경계선이 안 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Bilinear보다 비용이 약간 높다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밉맵 레벨 2 (Bilinear 결과)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;↕&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;보간 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;밉맵 레벨 3 (Bilinear 결과)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용도로는 PC 게임 기본값, 밉맵 팝핑 방지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Anisotropic Filtering (AF)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비스듬한 각도에서 텍스처 품질 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 방향으로 여러 샘플로 읽는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 비용이 높다. (AF 16x = 최대 16샘플)&amp;nbsp; 그러나 성능 대비 품질이 가장 좋다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바닥 텍스처름 비스듬히 볼 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trilinear : 멀어질수록 흐릿하고 찌그러진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AF 16x : 선명하고 정확한 형태 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용도로는 PC 고품질 설정의 기본값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밉맵(Mipmap) - 텍스처의 LOD&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밉맵이 필요한 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 1024x1024인 경우 오브젝트가 멀어져서 화면에 10x10 픽셀로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밉맵이 없는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 10x10 픽셀을 그리려고 1024x1024에서 샘플링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처의 극히 일부만 쓰고 나머지는 버린다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 앨리어싱(깜빡이는 현상), 캐시 미스 (메모리 비효율) 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밉맵이 있을 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1024x1024 (Level 0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;512x512 (Level 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;256x256 (Level 2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;128x128 (Level 3)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2x2 (Level 9)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1x1 (Level 10)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트가 10x10으로 보이면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 16x16 레벨 밉맵 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 적절한 해상도에서 샘플링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 앨리어싱이 없고 캐시가 효율적이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 비용 : 원본의 33% 추가. 수학적으로 1 + 1/4 + 1/16 + ... = 4/3 &amp;asymp; 1.33'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밉맵 레벨 선택 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; GPU가 어느 밉맵을 쓸지 결정하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LOD 편향(Bias) 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인접 픽셀과의 UV 변화량(ddx, ddy) 측정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV가 많이 변하면 -&amp;gt; 작은 밉맵 (낮은 해상도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV가 조금 변하면 -&amp;gt; 큰 밉맵 (높은 해상도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- float lod = textureQueryLod(sampler, uv).x;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLSL에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- float2 dx = ddx(uv * textureSize);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- float2 dy = ddy(uv * textureSize);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- float lod = 0.5 * log2(max(dot(dx, dx), dot(dy, dy)));&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 밉맵 레벨 지정도 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HLSL : texture.SampleLevel(sampler, uv, mipLevel)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GLSL : textureLod(sampler, uv, mipLevel)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨트 셰이더에서는 자동 계산이 불가하다 -&amp;gt; SampleLevel로 직접 지정 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플러(Sampler) - 샘플링 방식을 담은 오브젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 vs 샘플러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 : 픽셀 데이터를 저장하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샘플러 : 텍스처를 어떻게 읽을지 설정하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 텍스처를 다른 방식으로 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플러 A : Bilinear + Repeat&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플러 B : Nearest + Clamp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 텍스처 복사 없이 다른 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플러 설정 항목&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Filter : Nearest / Bilinear / Trilinear / Anisotropic&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Wrap U : Repeat / Clamp / Mirror / Border&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Wrap V : (U와 독립적으로 설정 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Mip Bias : 밉맵 레벨 편향 (+ 더 흐릿, - 더 선명)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Max Aniso : AF 최대 샘플 수 (2x, 4x, 8x, 16x)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Min/Max LOD : 밉맵 레벨 제한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLSL에서&lt;/p&gt;
&lt;pre id=&quot;code_1785231576012&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SamplerState linearSampler {
    Filter   = MIN_MAG_MIP_LINEAR;  // Trilinear
    AddressU = WRAP;
    AddressV = WRAP;
    MaxAnisotropy = 16;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 Wrap 모드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV가 0~1 범위를 벗어날 때 동작한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.Repeat (Wrap)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV = 1.7 -&amp;gt; 0.7과 동일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처가 반복되기에 바닥, 벽, 반복 패턴에 사용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Clamp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV = 1.7 -&amp;gt; 1.0으로 고정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장자리 픽셀이 늘어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UI, 스프라이트에 사용하며 UV 경계 밖에 다른 텍스처가 없어야 할 때 사용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Mirror&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV = 1.7 -&amp;gt; 0.3과 동일 (뒤집어서 반복한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반복되지만 이음새가 자연스러워 대칭 텍스처에 효과적이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Border&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 범위 밖 = 지정된 색상 (보통 검정색)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 섀도우 맵에서 사용하며 범위 밖은 그림자 없음으로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 샘플링과 GPU 캐시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU 텍스처 캐시 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인접한 픽셀은 UV 좌표를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 인접한 텍셀을 읽을 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; GPU 텍스처 캐시에 이미 로드된 경우가 많다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 캐시 히트가 발생하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 빨라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 미스가 생기는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 좌표가 불규칙적으로 점프한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 예측 불가능한 메모리 접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 캐시 미스 증가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 메모리 지연 발생 (Latency)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밉맵의 캐시 효율&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원본(1024x1024) 대신 축소 밉맵(16x16) 사용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 읽는 데이터 크기 감소한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 캐시에 더 많이 들어간다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 캐시 히트율이 증가한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 압축 (BC/DXT)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 압축된 상태로 GPU 캐시에 저장한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 같은 캐시에 더 많은 텍셀 저장한다. -&amp;gt; 캐시 효율 향상된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 압축 포맷 - 플랫폼별&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC (DirectX)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BC1 (DXT1) : RGB, 불투명, 4bpp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BC3 (DXT3) : RGBA, 반투명, 8bpp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BC4 : 단일 채널 (R), 4bpp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BC5 : 두 채널 (RG), 8bpp&amp;nbsp; -&amp;gt; 노멀 맵&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BC6H : HDR 텍스처, 8bpp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BC7 : 고품질 RGBA, 8bpp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 Android&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ETC1 : RGB, OpenGL ES 2.0 이상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ETC2 : RGB/RGBA, OpenGL ES 3.0 이상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ASTC : 가변 압축률, 최신 표준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 iOS (Apple)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PVRTC : PowerVR GPU 전용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ASTC : Apple A8 이상 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 압축된 상태로 GPU에 저장/사용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CPU 복원은 없으며 메모리 대역폭이 절약된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 4x4 픽셀 블록 단위로 압축한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 캐시 : GPU가 최근 읽은 텍셀을 저장하는 온칩 캐시. 인접 픽셀이 인접 텍셀을 읽는 특성으로 높은 히트율. 무작위 UV 접근은 캐시 미스 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍셀 (Texel) : Texture Pixel의 합성어로 텍스처의 개별 픽셀이다. 화면 픽셀과 구분하기 위해 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 앨리어싱 (Aliasing) : 밉맵 없이 원거리 텍스처를 샘플링할 때 생기는 깜빡임. 밉맵과 필터링으로 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 스트리밍 (Texture Streaming) : 필요한 밉맵 레벨만 동적으로 GPU 메모리에 로드된다. 메모리 사용량 최적화의 핵심이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/158</guid>
      <comments>https://blogger2146.tistory.com/158#entry158comment</comments>
      <pubDate>Tue, 28 Jul 2026 18:54:54 +0900</pubDate>
    </item>
    <item>
      <title>오버드로우 (Overdraw)</title>
      <link>https://blogger2146.tistory.com/157</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;같은 픽셀을 여러 번 그리는 것이다. 반투명 오브젝트, 파티클이 많을 때 발생하며 GPU Fill Rate 병목 원인이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 개념은 같은 픽셀을 여러 번 그리는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 해상도 1920x1080 = 약 200만 픽셀이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상적인 경우 각 픽셀이 딱 한 번만 그려진다. 즉, GPU가 200만 번 픽셀 셰이더가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 오버드로우가 있는 경우 같은 픽셀 위치에 여러 오브젝트가 겹친다. GPU가 400만, 600만 번 픽셀 셰이더가 실행하게 되며 나중에 그린 것이 앞에 것을 덮어쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 앞에서 한 연산이 전부 버려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버드로우 비율 (Overdraw Ratio)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1x = 이상적이며 각 픽셀 한 번이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2x = 픽셀상 평균 2번 그린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 4x = 픽셀당 평균 4번으로 심각한 성능 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버드로우가 발생하는 상황들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp; 불투명 오브젝트 겹침 현상 (Z버퍼로 해결 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 멀리 있는 벽 먼저 그린다 ( 픽셀 셰이더 실행 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그 앞에 캐릭터들 그린다 ( 픽셀 셰이더 실행 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 벽의 픽셀 연산이 낭비된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Solution : Early-Z / Z-Prepass&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Z버퍼 통과 여부를 픽셀 셰이더 실행 전에 검사한다. 앞 오브젝트에 가려지면 픽셀 셰이더를 건너뛴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 반투명 오브젝트 (Z버퍼로 해결 불가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파티클, 유리, 연기, 물 같은 것들.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 뒤에 것이 비쳐 보여야 해서 Z버퍼 기각이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 모든 겹친 레이어를 다 그려야하며 파티클이 많이 겹치면 치명적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp; UI 레이어 겹침 현상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HUD 위에 메뉴 위에 팝업 위에.... -&amp;gt; 각 레이어가 전체 화면을 다 그린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 화면 전체가 N배 오버드로우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 파티클 이펙트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 폭발 이펙트: 수백 개 파티클이 한 곳에 집중된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 파티클이 반투명하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 같은 픽셀에 수십 번 픽셀 셰이더 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 모바일에서 가장 흔한 병목 원인 중 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Z버퍼와 오버드로우의 관계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불투명 오브젝트 렌더링 순서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 순서 : 앞에서 뒤로 (Front-to-Back)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카메라에 가장 가까운 오브젝트 -&amp;gt; 그 다음 가까운 오브젝트 -&amp;gt; 그 다음.......&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Early-Z 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀 셰이더 실행 전에 Z값 비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이미 앞에 뭔가 있을 경우 -&amp;gt; 픽셀 셰이더 건너뛰기 -&amp;gt; 오버드로우 방지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Z-Prepass&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1패스 : 모든 오브젝트를 깊이만 그린다. ( 픽셀 셰이더가 없다 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2패스 : Z버퍼 채워진 상태에서 실제 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 가려진 픽셀은 픽셀 셰이더 실행을 안한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 복잡한 씬에서 효과적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반투명 오브젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 뒤에서 앞으로(Back-to-Front) 그려야 올바른 블렌딩이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Z버퍼 쓰기 비활성화 해야한다. (읽기만)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Early-z 작동 안한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 오버드로우를 피할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일에서 오버드로우가 특히 치명적인 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데스크탑 GPU (Immediate Mode Rendering)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- VRAM이 크고 메모리 대역폭이 넓다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오버드로우가 있어도 빠른 메모리로 커버 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 GPU (Tile-Based Deferred Rendering)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 화면을 타일로 나눠 온칩 SRAM에서 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 온칩 메모리는 작고 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버드로우 발생 시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 같은 타일을 여러 번 픽셀 셰이더 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 온칩 메모리에서 반복 읽기/쓰기 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 전력 소모 급증, 발열, 배터리 방전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베디드 디스플레이에서도 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한된 GPU -&amp;gt; 오버드로우 = 즉각적 성능 저하 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL ES 시절부터 오버드로우 최소화가 핵심 과제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버드로우 최적화 전략&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 렌더링 순서 최적화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 불투명 오브젝트 : 앞에서 뒤로 정렬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 앞 오브젝트가 Z버퍼를 채우면 뒤 오브젝트의 가려진 픽셀은 Early-Z로 기각&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 반투명 최소화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반투명 = 오버드로우의 주범으로 가능하면 불투명으로 대체한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파티클 : 불투명 스프라이트 사용 가능한지 검토&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 연기 : 경량화된 플립북 텍스처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 물 : 깊이 기반 단순화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 파티클 최적화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파티클 수 줄이기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 시각적 효과는 유지하면서 개수 감소한다. 큰 파티클 몇 개 vs 작은 파티클 많은 개 중 전자가 오버드로우가 적다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LOD 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 멀리서는 파티클 수 자동 감소한다. Niagara LOD 설정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Cutout 텍스처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 반투명 대신 알파 클리핑 (Masked 모드)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 픽셀이 완전 불투명 or 완전 투명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Early-Z가 작동하며 오버드로우가 감소된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 오클루전 컬링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보이지 않는 오브젝트는 아예 그리지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Frustum Culling : 카메라 밖 오브젝트는 제외한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Occlusion Culling : 다른 오브젝트에 가린 것 제외한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 오버드로우 자체를 줄이는 근본적 해결 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 셰이더 복잡도 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오버드로우가 불가피한 경우 (반투명, UI 레이어) 픽셀 셰이더 자체를 가볍게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파티클 머티리얼 : Unlit 사용하며 조명 계산이 없다 -&amp;gt; 픽셀 셰이더 비용 감소한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UI : 단순한 셰이더는 텍스처 1개 + 색상 곱 수준으로 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알파 블렌딩 vs 알파 클리핑 ( 오버드로우 관점 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알파 블렌딩 (Alpha Blending /&amp;nbsp; Translucent)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀이 반투명할 경우 Z버퍼 쓰기 비활성화 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 겹친 모든 레이어를 그려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 오버드로우 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- result = src * srcAlpha + dst * (1 - srcAlpha)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 나뭇잎, 파티클, 유리에 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오버드로우 피할 수 없기에 셰이더를 가볍게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알파 클리핑 (Alpha Clipping / Masked)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀이 임계값 이상이면 완전 불투명하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 임계값 이하면 완전 투명으로 하며 그리지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Z버퍼 쓰기 활성화하며 Early-Z가 작동된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- if(alpha &amp;lt; threshold) discard;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 나뭇잎(외곽선), 철망, 헤어카드에 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오버드로우 방지 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 외곽선이 계단처럼 보이는 (앨리어싱) 단점이 있으며 Temporal AA로 보완 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Fill Rate : GPU가 초당 채울 수 있는 픽셀 수. 오버드로우가 많으면 Fill Rate 한계에 도달한다. 모바일 GPU에서 가장 먼저 포화되는 리소스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; Early-Z / Z-Prepass : 픽셀 셰이더 실행 전에 깊이 테스트로 가려진 픽셀을 기각하는 최적화이다. 오버드로우의 구조적 해결책.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Shader Complexity : UE5의 오버드로우 시각화 뷰이다. 초록-&amp;gt;노랑-&amp;gt;빨강-&amp;gt;흰색 순으로 셰이더 비용이 높아진다. 파티클 이펙트 최적화의 시작점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Separate Translucency : UE5에서 반투명 오브젝트를 낮은 해상도에서 따로 렌더링하는 옵션이다. 오버드로우 비용을 절반으로 줄이는 효과가 있다.&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/157</guid>
      <comments>https://blogger2146.tistory.com/157#entry157comment</comments>
      <pubDate>Mon, 27 Jul 2026 17:29:58 +0900</pubDate>
    </item>
    <item>
      <title>UV 매핑 (UV Mapping)</title>
      <link>https://blogger2146.tistory.com/156</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;3D 메시의 각 버텍스에 2D 텍스처 좌표(U, V)를 할당하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D 메시에 텍스처를 어떻게 입히는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지구본과 세계 지도로 비유가 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지구본 (3D 구) 껍데기를 잘라서 펼치면 평평한 세계 지도 (2D)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D 메시 -&amp;gt; UV 언래핑 -&amp;gt; 2D UV 맵&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 버텍스에 UV 좌표 (U, V) 할당한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;U : 가로 방향 (0.0 ~ 1.0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V : 세로 방향 (0.0 ~ 1.0)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처의 어느 픽셀이 이 버텍스에 대응하는지 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 U, V인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;X, Y, Z는 이미 3D 공간 좌표에 쓰이고 있기에 겹치지 않게 하기 위해 U, V로 명명하였다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UV 좌표계 - API별 차이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenGL/ Vulkan / Metal&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- (0, 0) = 왼쪽 아래, (1, 1) = 오른쪽 위이며 V축이 위로 향한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DirectX / UE5 / Unity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- (0, 0) = 왼쪽 위, (1, 1) = 오른쪽 아래 V축이 아래로 향한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이가 텍스처가 뒤집히는 원인이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blender(OpenGL) -&amp;gt; UE5(DX) 임포트 시 V 채널 반전 필요한 경우가 생기는 이유이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UV 언래핑의 종류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;박스 매핑 (Box/Cubic Mapping)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 6방향에서 투영한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 단순한 박스형 오브젝트에 적합하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이음새(Seam)이 많이 생긴다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원통형 매핑 (Cylindrical Mapping)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; 원통으로 감싸서 투영한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기둥, 나무, 캐릭터 팔다리에 적합하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구형 매핑 (Spherical Mapping)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 구로 감싸서 투영한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 천체, 눈알, 구형 오브젝트에 적합하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수동 언래핑 (Manual UV Unwrapping)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아티스트가 직접 골라서 펼치기에 가장 정교한 결과를 얻을 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 캐릭터, 주요 프롭에 사용된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Blender, Maya, 3ds Max에서 작업된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이트맵 UV (Lightmap UV)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스태틱 라이팅 베이킹 전용 UV&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 채널 0: 텍스처용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 채널 1: 라이트맵용 (겹치면 안된다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5에서는 반드시 분리해야한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UV 언래핑의 핵심 규칙&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 겹치지 않아야 한다 (Lightmap UV)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 아일랜드가 겹치면 (텍스처 UV에서는 의도적으로 가능하다) 라이트맵 베이킹 시 겹친 부분 조명이 섞인다 -&amp;gt; 조명 아티팩트 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5에서 Generate Lightmap UVs 옵션이 이를 자동으로 처리해준다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이음새(Seam)를 눈에 안 보이는 곳에 적용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3D를 2D로 펼칠 때 반드시 어딘가를 잘라야 한다. (잘린 경계를 Seam이라 한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처의 불연속점이 될 수 있다 -&amp;gt; 겨드랑이, 뒤쪽, 헤어라인 등 안보이는 곳에 배치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 텍셀 밀도 균일하게 (Texel Density)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 아일랜드 크기 = 텍스처 해상도 할당량이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 얼굴 UV가 작으면 얼굴 텍스처가 흐릿해진다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 아일랜드 크기를 실제 메시 면적에 비례하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 스트레칭 최소화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV를 너무 늘리면 텍스처가 늘어난 것처럼 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Checker 텍스처로 검증 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정사각형 체커가 찌그러지면 스트레칭 발생했다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 데이터에서 GPU까지의 흐름&lt;/p&gt;
&lt;pre id=&quot;code_1784702856728&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// UV가 GPU에서 전달되는 과정

// 메시 버텍스 데이터 (CPU)
struct Vertex {
    float3 position; // 3D 위치
    float3 normal; // 법선
    float2 uv0; // UV 채널 0 (텍스처용)
    float2 uv1; // UV 채널 1 (라이트맵용)
};

// VBO로 GPU 메모리에 업로드. 각 버텍스의 UV 좌표가 GPU로 전달된다

// 버텍스 셰이더 - UV를 픽셀 셰이더로 전달
PixelInput VS(VertexInput input){
     PixelInput output;
     output.position = mul(float4(input.position,1), mvp);
     output.uv0 = input.uv0; // 보간되어 픽셀 셰이더로
     output.uv1 = input.uv1;
     return output;
}

// 픽셀 셰이더 - UV로 텍스처 샘플링
float4 PS(PixelInput input) : SV_TARGET {
     // UV 좌표로 텍스처에서 색상 읽기
     float4 color = AlbedoTex.Sample(Samp, input.uv0);
     float3 normal = NormalTex.Sample(Samp, input.uv0).rgb;
     float4 light = LightMap.Sample(Samp, input.uv1);
     
     return color * light;
}

// 버텍스 사이의 UV는 래스터화 단계에서 보간된다.
// 버텍스 3개의 UV -&amp;gt; 삼각형 내부 픽셀의 UV 자동 계산된다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UV 타일링과 오프셋&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UV 범위가 0~1을 벗어날 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본 동작: Repeat (타일링)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV = 1.5 -&amp;gt; 0.5와 동일 , UV = 2.3 -&amp;gt; 0.3과 동일 ( 반복 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 작은 텍스처를 반복해서 넓은 바닥 표현이 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바닥 텍스처 UV 스케일 조정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UVScale = 10.0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TexCoord * 10 -&amp;gt; 텍스처가 10x10으로 타일링된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 작은 텍스처로 넓은 바닥을 커버할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클램프 (Clamp) 모드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV = 1.5 -&amp;gt; 1.0으로 고정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반복 없이 가장자리 색상을 늘린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5 머터리얼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TextureCoordinate 노드: UV 스케일/오프셋 조정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Texture Sample -&amp;gt; UV 핀에 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Tiling 방식은 Texture Asset의 X/Y Tiling 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UV 채널 (UE5에서 자주 생기는 문제)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널 0 (UV0) : 기본 텍스처 UV&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Albedo, Normal, Roughness 등이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타일링이 가능하고 겹쳐도 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널 1 (UV1) : 라이트맵 UV&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Static Lighting 베이킹용으로 반드시 겹치면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 0 ~ 1 범위 안에 전부 들어와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널 2+ :&amp;nbsp; 특수 용도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 레이어링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데칼 UV&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 색상 대체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5에서 Lightmap UV 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Static Mesh Editor -&amp;gt; Build Settings -&amp;gt; Generate Lightmap UVs : ON -&amp;gt; Min Lightmap Resolution 설정 -&amp;gt; 자동으로 UV1 생성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 상황&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &quot;Overlapping UVs&quot; 경고 -&amp;gt; UV1 채널에 겹친 아일랜드가 있다. -&amp;gt; Static Lighting 품질 저하 -&amp;gt; 자동 생성 또는 수동 언래핑을 통해 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blender -&amp;gt; UE5 UV 임포트 워크플로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blender에서 UV 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UV Editor에서 언래핑&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV Map 0: &quot;UVMap&quot; (텍스처용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV Map 1: &quot;LightmapUV&quot; (베이킹용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FBX 익스포트 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Apply Modifiers : ON&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV Maps 내보내기 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5 임포트 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Generate Lightmap UVs : 이미 Blender에서 만든 경우 OFF, 없으면 ON (자동 생성)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 생기는 문제들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처가 뒤집힌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; OpenGL(Blender) vs DX(UE5) V축 차이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Blender에서 V 반전 후 익스포트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 스케일이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Blender 단위와 UE5 단위 차이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Scale 적용(Ctrl + A) 후 익스포트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 라이트맵 이음새에 빛 새는 현상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; UV1 아일랜드 간격 부족&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Light Map Resolution 높이기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍셀 밀도 (Texel Density) : 메시 표면 단위 면적당 텍스처 픽셀 수. 균일해야 해상도가 일관된게 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UV 아일랜드 (UV Island) : UV 공간에서 연결된 UV 조각. 이음새로 분리된 각 조각이 하나의 아일랜드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이음새 (Seam) : 3D 메시를 2D로 펼칠 때 잘리는 경계선. 텍스처 불연속이 생길 수 있어서 안보이는 곳에 배치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UDIM : UV 공간을 여러 타일로 확장하는 방식으로 고 해상도 캐릭터 텍스처에서 사용한다. UE5에서 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/156</guid>
      <comments>https://blogger2146.tistory.com/156#entry156comment</comments>
      <pubDate>Wed, 22 Jul 2026 16:03:56 +0900</pubDate>
    </item>
    <item>
      <title>노멀 맵 (Normal Map)</title>
      <link>https://blogger2146.tistory.com/155</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;폴리곤 수를 늘리지 않고 법선 벡터를 조작해 표면 디테일을 표현하는 기법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 개념 : 빛 반응을 속이는 원리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴리곤이 적은 메시 (Low Poly)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 지오메트리가 평평함 -&amp;gt; 빛이 균열하게 반사 -&amp;gt; 밋밋해 보이는 효과 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;노멀 맵 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지오메트리는 그대로 (폴리곤 추가 없음) -&amp;gt; 픽셀마다 법선 벡터(Normal Vector)를 다르게 조작 -&amp;gt; 빛이 마치 울퉁불퉁한 표면에서 반사된 것처럼 계산 -&amp;gt; 디테일이 있어보이는 효과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉. 실제로 튀어나온 것은 없이 빛 계산만 속인다. 폴리고 비용 없이 디테일 표현이 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;법선 벡터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 표면에 수직인 방향 벡터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 평평한 표면 : 모든 법선이 동일하여 빛 반응이 균일하고 평평해 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 울툴불퉁한 표면 : 법선이 각각 다르기에 빛 반응이 다양하고 굴곡이 있어 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조명 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;color = albedo * dot(Normal, LightDir)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dot(Normal, LightDir)의 값이 달리지면 빛 방향이 달라진다. 노멀 맵은 이 Normal을 픽셀마다 바꾸는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵이 파란색인 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노멀 맵 = 법선 벡터를 RGB 텍스처로 저장한 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 법선 벡터 범위 : -1 ~ +1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 색상 범위 : 0 ~ 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변환 공식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색상 = (법선 + 1) / 2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;법선 = 색상 * 2&amp;nbsp; - 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평평한 표면의 법선 = (0, 0, 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- R = (0 + 1) / 2 = 0.5 -&amp;gt; 128 -&amp;gt; 회색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- G = (0 + 1) / 2 = 0.5 -&amp;gt; 128 -&amp;gt; 회색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- B = (1 + 1) / 2 = 1.0 -&amp;gt; 255 -&amp;gt; 파란색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 완전히 평평한 곳은 RGB(128, 128, 255) = 파란색. 그렇기에 노멀 맵이 파란빛이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Z축이 파란색인 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노멀 맵은 탄젠트 공간 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 탄젠트 공간에서 표면 정면 = Z축 = (0, 0, 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Z가 가장 큰 값(1.0 -&amp;gt; 파란색 255)이 기본값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌표 공간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵이 저장하는 법선의 좌표 공간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월드 공간 노멀 맵 (World Space)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 글로벌 XYZ 기준으로 거장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트가 회전하면 잘못된 방향 가리킴 -&amp;gt; 움직이는 오브젝트에 부적합하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탄젠트 공간 노멀 맵 (Tangent Space) &amp;lt;- 일반적으로 이것으 가리킨다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메시 표면 기준 좌표계로 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- T(탄젠트) + B(바이탄젠트) + N(노멀)로 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트가 어떻게 회전해도 정상 동작 -&amp;gt; 캐릭터, 애니메이션 메시에 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀 셰이더에서 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처에서 읽은 법선 ( 탄젠트 공간 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TBN 행렬로 월드/뷰 공간으로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조명 계산에 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TBN 행렬 (셰이더 구현)&lt;/p&gt;
&lt;pre id=&quot;code_1784614686337&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 노멀 맵 픽셀 셰이더(HLSL)

Texture2D NoramlMap : register(t1);
SamplerState Sampler : register(s0);

cbuffer LightBuffer : register(b1)
{
     float3 lightDir;
     float3 lightColor;
}

float4 PS(PixelInput input) : SV_TARGET
{
     // 노멀 맵에서 법선 일기
     float3 normalSample = NormalMap.Sample(Sampler, input.texCoord).rgb;
     
     // 0 ~ 1 범위를 -1 ~ 1 범위로 변환
     float3 tangentNormal = normalSample  * 2.0 - 1.0;
     
     // TBN 행렬 구성. T, B, N은 버텍스 셰이더에서 전달받은 값
     float3 T = normalize(input.tangent);
     float3 B = normalize(input.bitangent);
     float3 N = normalize(input.normal);
     float3x3 TBN = float3x3(T, B, N);
     
     // 탄젠트 공간 -&amp;gt; 월드 공간으로 변환
     float3 worldNormal = normalize(mul(tangentNormal, TBN));	
     
     // 변환된 법선으로 조명 계산
     float NdotL = max(dot(worldNormal, normalize(-lightDir)), 0.0);
     float4 albedo = AlbedoMap.Sample(Sampler, input.texCoord);
     
     return float4(albedo.rgb * lightColor * NdotL, albedo.a);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵 종류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵 (Normal Map)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 법선 방향만 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 빛 반응 조작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 기본적이고 가장 많이 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파일 : _N, _Normal 접미사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범프 맵 (Bump Map)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 높이값(흑백)으로 법선을 유추&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노멀 맵보다 구형 기술&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GPU가 높이 -&amp;gt; 법선 변환 필요가 필요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파일 : _B, _Bump 접미사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이트 맵 (Height Map)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 높이 정보를 흑백으로 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노멀 맵 생성에 쓰거나 테셀레이션으로 실제 지오메트리 변위에 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스플레이스먼트 맵 (Displacement Map)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하이트 맵 기반으로 버텍스를 실제로 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 테셀레이션이 필요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실제 실루엣이 변하기에 노멀 맵보다 비용이 크다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5에서 World Displacement 노드가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵 vs 디스플레이스먼트 맵&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 폴리곤 수 변화가 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실루엣에도 변화가 없다. 여전히 가장자리는 평평하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 샘플링만 쓰기에 비용이 낮다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장자리에서 깨진다(silhouette problem)는 한계점이 존재하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스플레이스먼트 맵&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 폴리곤 수가 테셀레이션으로 인해 증가한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실루엣이 실제로 변형된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지오메트리 생성으로 인해 비용이 높다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실루엣까지 디테일하게 표현되는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평평한 바닥, 벽 -&amp;gt; 노멀 맵으로 충분하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바위, 지형 실루엣 -&amp;gt; 디스플레이스먼트 맵&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터 피부 -&amp;gt; 노멀 맵 + 약간의 테셀레이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5에서 노멀 맵 작업 흐름 (제작 파이프라인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이폴리 메시(High Poly) : Blender / ZBrush에서 수백만 폴리곤으로 디테일 조각한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr; 베이킹 (Baking)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로우폴리 메시 + 노멀맵 : 게임에서 실제로 쓰는 메시 + 하이폴리의 디테일을 구운 노멀 맵&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5 임포트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 텍스처 설정 : Normal Map 타입 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sRGB 체크 해제 ( 노멀 맵은 색상 데이터가 아니다 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compression : BC5 / DXT5nm (채널 최적화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머티리얼 연결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TextureSample -&amp;gt; Normal 핀&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Flat Normal 노드 : (0, 0, 1) 기본값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Normal Intensity 조절 : Lerp로 강도 제어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵 최적화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BC5&amp;nbsp; 압축 (두 채널 압축)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노멀 맵은 XY만 저장해도 된다. Z = sqrt(1 - X^2 - Y^2)으로 재계산 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; BC5로 RG만 저장한다 -&amp;gt; 메모리 절약 + 품질 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더에서 Z 재계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;float3 normal;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;normal.xy = normalSample.rg * 2.0 - 1.0;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;normal = normalize(normal);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sRGB 설정 주의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노멀 맵은 색상이 아닌 벡터 데이터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sRGB 감마 보정을 적용하면 잘못된 법선이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5에서 반드지 sRGB 체크를 해제해야한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밉맵과 노멀 맵&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 일반 텍스처 : 밉맵 = 평균값으로 다운샘플&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 노멀 맵 : 평균 -&amp;gt; 법선이 정규화되지 않는다. -&amp;gt; 각 밉맵 레벨에서 법선 재정규화가 필요하다. 근데 UE5에서는 자동으로 처리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패럴렉스 매핑 (Parallax Mapping) : 노멀 맵의 실루엣 한계를 보완으로 하이트 맵으로 UV 좌표를 시선 방향에 따라 이동시켜 깊이감을 포현하며 자기 그림자까지 구현 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앰비언트 오클루전 맵 (AO Map) : 노멀 맵과 함께 쓰이는 텍스처. 구석진 곳에 그림자를 추가해 입체감을 강화한다. 노멀 맵이 빛 방향 반응을 담당하면 AO는 전역 그림자를 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;베이킹 (Baking) : 하이폴리 메시의 디테일을 로우폴리 메시의 노멀 맵으로 굽는 과정. Marmoset Toolbag, Blender, Substance Painter에서 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탄젠트 (Tangent) : 노멀과 수직이면서 UV의 U 방향을 따르는 벡터. 노멀 맵의 TBN 행렬 구성에 필수이다. 버텍스 데이터에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포함되어 GPU로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/155</guid>
      <comments>https://blogger2146.tistory.com/155#entry155comment</comments>
      <pubDate>Tue, 21 Jul 2026 18:28:30 +0900</pubDate>
    </item>
    <item>
      <title>컴퓨트 셰이더 ( Compute Shader )</title>
      <link>https://blogger2146.tistory.com/154</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 파이프라인과 별개로 GPU에서 범용 연산을 수행한다. 파티클, 물리 시뮬레이션, AI 연산에 활용된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 파이프라인 밖의 GPU&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 셰이더 (렌더링 파이프라인 안)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 버텍스 셰이더 -&amp;gt; 래스터화 -&amp;gt; 픽셀 셰이더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3D를 2D 화면으로 변환하는 것이 목적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 입력 = 버텍스 데이터, 출력 = 픽셀 색상. 입출력이 정해져있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨트 셰이더 (렌더링 파이프라인 밖)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 렌더링과 무관하게 GPU에서 실행한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 입출력이 자유롭다. -&amp;gt; GPU의 병렬 연산 능력만 빌려 쓰는 것이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU = 수천 개의 코어가 동시에 같은 연산을 하는 기계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨트 셰이더 = 이 병렬성을 렌더링 외에도 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크그룹과 스레드 : 컴퓨트 셰이더의 실행 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨트 셰이더 실행 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dispatch (CPU에서 GPU에 실행 명령) : 몇 개의 워크그룹을 실행할지 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WorkGroup : GPU의 실행 블록 단위로 같은 워크그룹 내 스레드는 공유&amp;nbsp; 메모리 사용 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread ( 스레드 = Invocation ) : 가장 작은 실행 단위로 실제로 셰이더 코드를 실행하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 1024 x 1024 텍스처 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dispatch(64, 64, 1) -&amp;gt; 64 x 64 = 4096 워크그룹&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[numthreads(16, 16, 1)]&amp;nbsp; -&amp;gt; 워크그룹당 16x16 = 256 스레드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 스레드 수: 4096 x 256 = 1048576개 -&amp;gt; 픽셀 하나당 스레드 하나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 ID&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SV_GroupThreadID : 워크그룹 내 스레드 번호 (0~15, 0~15)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SV_GroupID : 워크그룹 번호 (0~63, 0~63)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SV_DispatchThreadID : 전체에서 고유한 스레드 번호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HLSL 컴퓨트 셰이더 기본 구조&lt;/p&gt;
&lt;pre id=&quot;code_1784540330683&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 파티클 시뮬레이션 컴퓨트 셰이더 (HLSL)

// 파티클 데이터 구조
struct Particle {
     float3 position;
     float3 velocity;
     float lifetime;
     float age;
};

// GPU 버퍼 - 읽기 / 쓰기 가능
RWStructuredBuffer&amp;lt;Particle&amp;gt; particles : register(u0);

// CPU에서 전달하는 상수
cbuffer SimConstants : register(b0) {
     float deltaTime;
     float3 gravity;
     int particleCount;
};

// 워크그룹 크기 선언
[numthreads(64, 1, 1)]
void CSMain(
     uint3 dispatchID : SV_DispatchThreadID // 전체에서 고유 ID
){
     uint id = dispatchID.x;
     
     // 범위 체크 - 스레드가 파티클 수 초과하면 종류
     if(id &amp;gt;= (uint)particleCount) return;
     
     Particle p = particles[id];
     
     // 각 스레드가 자신의 파티클만 독립적으로 처리
     p.velocity += gravity * deltaTime;
     p.position += p.velocity * deltaTime;
     p.age += deltaTime;
     
     // 수명 다하면 리셋
     if(p.age &amp;gt;= p.lifetime) {
           p.position = float3(0, 0, 0);
           p.veloctiy = float3((float(id % 7) - 3.0) * 0.5, 5.0, (float(id % 5) - 2.0) * 0.5);
           p.age = 0.0;
     }
     
     // 결과 저장
     particles[id] = p;
}

// CPU에서 디스패치
// int groups = (particleCount + 63) / 64;
// commandList-&amp;gt;Dispatch(groups, 1, 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL 컴퓨트 셰이더 ( OpenGL/Vulkan )&lt;/p&gt;
&lt;pre id=&quot;code_1784540756811&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#version 430 // OpenGL ES 3.1 / OpenGL 4.3 이상

// 워크그룹 크기
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;

// 이미지 읽기/쓰기 (이미지 프로세싱)
layout(rgba8, binding = 0)
     uniform readonly image2D inputImage;
layout(rgba8, binding = 1)
     uniform writeonly image2D outputImage;
    
void main() {
     // 현재 스레드가 처리할 픽셀 좌표
     ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
     
     // 이미지 크기 체크
     ivec2 size = imageSize(inputImage);
     if(coord.x &amp;gt;= size.x || coord.y &amp;gt;= size.y) return;
     
     // 블러 처리 (3x3 평균)
     vec4 sum = vec4(0.0);
     for(int dy = -1; dy &amp;lt;= 1; dy++)
     {
          for(int dx = -1; dx &amp;lt;= 1; dx++)
          {
                ivec2 sampleCoord = coord + ivec2(dx, dy);
                sampleCoord = clamp(sampleCoord, ivec2(0), size - 1);
                sum += imageLoad(inputImage, sampleCoord);
          }
     }
     
     // 평균값 출력
     imageStore(outputImage, coord, sum / 9.0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨트 셰이더의 실제 활용 사례&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티클 시스템&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5 niagara : 컴퓨트 셰이더로 수백만 파티클 병렬 시뮬레이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CPU로 하면 초당 수천 개 한계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GPU로 하면 초당 수백만 개 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리 시뮬레이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 천 시뮬레이션 (Cloth) : 각 버텍스를 스레드로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 유체 시뮬레이션 (Fluid) : 격자 셀을 스레드로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 군중 AI (Crowd) : 각 에이전트를 스레드로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬링 (GPU Driven Rendering)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수만 개 오브젝트 중 화면에 보이는 것을 CPU가 아닌 GPU에서 직접 판별&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5 Nanite의 핵심 기술&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트 프로세싱&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 블룸, 블러, SSR, SSAO&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀 셰이더 대신 컴퓨트로 처리하면 더 효율적인 경우가 많음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키닝 (Vertex Skinning)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 캐릭터 애니메이션의 본 변환 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수천 개 버텍스를 병렬 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝 / AI&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TensorFlow, PyTorch의 GPU 연산 기반&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- WebGPU 컴퓨트 셰이더로 브라우저 AI 추론&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 메모리 (워크그룹 내 최적화)&lt;/p&gt;
&lt;pre id=&quot;code_1784541414051&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 공유 메모리는 같은 워크그룹 스레드들이 공유하는 빠른 메모리
// GPU 온칩 메모리 -&amp;gt; 글로벌 메모리보다 훨씬 빠르다

[numthreads(16, 16, 1)]
void BlurCS(uint8 groupID : SV_GroupID,
            uint8 threadID : SV_GroupThreadID,
            uint8 dispatchID : SV_DispatchThreadID)
{
     // 공유 메모리 선언 (16 + 2) x (16 + 2) = 경계 포함
     groupshared float4 sharedPixels[18][18];
     
     // 각 스레드가 자신 담당 픽셀을 공유 메모리에 로드
     // (글로벌 메모리 -&amp;gt; 공유 메모리: 한 번만)
     ivec2 coord = dispatchID.xy;
     sharedPixels[threadID.y+1][threadID.x+1] = inputTexture[coord];
     
     // 모든 스레드 로드 완료 대기 (동기화)
     GroupMemoryBarrierWithGroupSync();
     
     // 공유 메모리에서 빠르게 읽기
     float4 sum = float4(0,0,0,0);
     for(int dy = -1; dy &amp;lt;= 1; dy++)
     for(int dx = -1; dx &amp;lt;= 1; dx++)
          sum += sharedPixels[threadID.y+1+dy][threadID.x+1+dx];
          
     outputTexture[coord] = sum / 9.0;
     // 글로벌 메모리 접근 : 로드 1번 + 저장 1번
     // 공유 메모리 없으면 : 로드 9번 필요
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5에서 컴퓨트 셰이더 직접 작성하기&lt;/p&gt;
&lt;pre id=&quot;code_1784541442659&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// UE5 커스텀 컴퓨트 셰이더 C++ 선언
class FMyComputeShader : public FGlobalShader {
    DECLARE_GLOBAL_SHADER(FMyComputeShader);
    SHADER_USE_PARAMETER_STRUCT(FMyComputeShader,
                                FGlobalShader);

    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER_RDG_BUFFER_UAV(
            RWStructuredBuffer&amp;lt;FParticle&amp;gt;,
            ParticleBuffer)
        SHADER_PARAMETER(float, DeltaTime)
        SHADER_PARAMETER(int32, ParticleCount)
    END_SHADER_PARAMETER_STRUCT()

    static bool ShouldCompilePermutation(
        const FGlobalShaderPermutationParameters&amp;amp; Params) {
        return IsFeatureLevelSupported(
            Params.Platform, ERHIFeatureLevel::SM5);
    }
};

// HLSL 파일과 연결
IMPLEMENT_GLOBAL_SHADER(FMyComputeShader,
    &quot;/Project/Shaders/MyComputeShader.usf&quot;,
    &quot;CSMain&quot;,
    SF_Compute);

// 실행 (RDG &amp;mdash; Render Dependency Graph)
void RunComputeShader(FRDGBuilder&amp;amp; GraphBuilder,
                      int32 ParticleCount) {
    FMyComputeShader::FParameters* PassParams =
        GraphBuilder.AllocParameters
            FMyComputeShader::FParameters&amp;gt;();

    PassParams-&amp;gt;DeltaTime    = GetWorld()-&amp;gt;DeltaTimeSeconds;
    PassParams-&amp;gt;ParticleCount = ParticleCount;

    TShaderMapRef&amp;lt;FMyComputeShader&amp;gt; ComputeShader(
        GetGlobalShaderMap(GMaxRHIFeatureLevel));

    // 디스패치
    int32 GroupCount = FMath::DivideAndRoundUp(
        ParticleCount, 64);

    FComputeShaderUtils::AddPass(
        GraphBuilder,
        RDG_EVENT_NAME(&quot;MyComputePass&quot;),
        ComputeShader,
        PassParams,
        FIntVector(GroupCount, 1, 1));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPGPU : General Purpose GPU. GPU를 그래픽스 외 범용 연산에 사용하는 개념. CUDA, OpenCL, 컴퓨트 셰이더가 모두 이 범주&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wrap/Wavefront : GPU의 최소 실행 단위. NVIDIA: Wrap(32스레드), AMD: Wavefront(64스레드). 워크그룹 크기를 이 배수로 맞춰야 효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UAV (Unordered Access View) : DX12에서 컴퓨트 셰이더가 읽기/쓰기 가능한 버퍼. GLSL의 SSBO에 대응. RWStructuredBuffer가 UAV.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDG (Render Dependency Graph) : UE5의 렌더링 의존성 관리 시스템. 컴퓨트 셰이더 패스를 추가할 때 사용. 자동으로 리소스 배리어와 패스 순서를 관리한다.&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/154</guid>
      <comments>https://blogger2146.tistory.com/154#entry154comment</comments>
      <pubDate>Mon, 20 Jul 2026 19:01:04 +0900</pubDate>
    </item>
    <item>
      <title>HLSL/GLSL</title>
      <link>https://blogger2146.tistory.com/153</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HLSL : DirectX용 셰이더 언어.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL : OpenGL용. 문법이 비슷하며 GPU에서 실행되는 C 계열 언어.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더 언어가 따로 있는 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 언어 ( C++, C#, Rust...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 순차적 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코어 수십 개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 범용 연산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리 자유롭게 접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU 셰이더 언어 (HLSL, GLSL, MSL, WGSL...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 병렬 실행 (수천 개 코어)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 픽셀 하나 = 독립적 실행 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 제한적 메모리 접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수학 연산에 특화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; CPU와 GPU는 완전히 다른 실행 환경.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 셰이더 언어는 GPU 전용 프로그래밍 언어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; C++로 GPU를 직접 프로그래밍할 수 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더 언어 전체 계보&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;API&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;셰이더 언어&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;DirectX&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;HLSL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Microsoft 표준. UE5 기본&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;OpenGL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GLSL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Khronos 표준. 크로스플랫폼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;OpenGL ES&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GLSL ES&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GLSL 경량화. 모바일/임베디드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;WebGL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GLSL ES&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;브라우저에서 GLSL ES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Vulkan&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GLSL/HLSL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;둘 다 가능 -&amp;gt; SPIR-V로 컴파&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Metal&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;MSL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Apple 전용. C++14 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;WebGPU&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;WGSL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;브라우저 전용. Rust 영향&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통점 = 개념은 동일. 문법만 다름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더 + 픽셀 셰이더 + MVP 행렬 = 어느 언어나 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL vs HLSL 문법 1vs1 대응&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버텍스 셰이더&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GLSL (OpenGL)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;HLSL(DirectX)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전 선언&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;#version 330 core&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전 선언이 없다 ( 타깃 프로파일로 지정 )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;입력&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;layout(location=0)&lt;br /&gt;in vec3 aPosition;&lt;br /&gt;layout(location=1)&lt;br /&gt;in vec2 aTexCoord;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;struct VertexInput{&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; float3 position : POSITION;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; float2 texCoord : TEXCOORD0;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;출력&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;out vec2 vTexCoord;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;struct PixelInput {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; float4 position : SV_POSITION;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; float2 texCoord : TEXCOORD0;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;uniform (CPU에서 전달)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;uniform mat4 uMVP&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;cbuffer Transform : register(b0) {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; matrix mvp;&lt;br /&gt;};&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;진입점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;void main() {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; gl_position =&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;uMVP * vec4( aPosition, 1.0);&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; vTexCoord = aTexCoord;&lt;br /&gt;}&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;PixelInput VS(VertexInput input) {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; PixelInput output;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; output.position =&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;mul(float4(input.position,1),mvp);&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; output.texCoord = input.texCoord;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; return output;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;픽셀(fragment) 셰이더&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GLSL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;HLSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;입력&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;in vec2 vTexCoord;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;PixelInput에서 받음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;텍스처&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;uniform sampler2D uTexture;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Texture2D diffuse : register(t0);&lt;br /&gt;SamplerState sampler : register(s0);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;출력&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;out vec4 FragColor;&lt;br /&gt;&lt;br /&gt;void main() {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; FragColor = texture(&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;vTexture, vTexCoord);&lt;br /&gt;}&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;float4 PS(PixelInput input)&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; : SV_TARGET {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; return diffuse.Sample(&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; sampler, input.texCoord);&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5 머티리얼과 HLSL의 연결&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;UE5 머티리얼 에디터 노드&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;내부 HLSL 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;TextureSample&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;float4 color = Texture.Sample(Sampler, TexCoord);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Multiply&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;result = A * B;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Lerp(Linear Interpolate)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;result = lerp(A, B, Alpha);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Fresnel&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;float fresnel = pow(1.0 - dot(N, V), Exp);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;DotProduct&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;result = dot(A, B);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Normalize&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;result = normalize(V);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WorldPosition&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;float3 worldPos = GetWorldPosition(Parameters);&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 노드 -&amp;gt; HLSL 직접 작성 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PBR 조명 계산 (HLSL)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5가 내부에서 하는 일의 단순화 버전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Physically Based Rendering 조명 계산&lt;/p&gt;
&lt;pre id=&quot;code_1784202737515&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cbuffer LightBuffer : register(b1) {
     float3 lightDirection;
     float3 lightColor;
     float3 cameraPosition;
};

float4 PS(PixelInput input) : SV_TARGET {
     // 텍스처에서 값 읽기
     float4 albedo = AlbedoMap.Sample(Sampler, input.texCoord);
     float metallic = MetallicMap.Sample(Sampler, input.texCoord).r;
     float roughness = RoughnessMap.Sample(Sampler, input.texCoord).r;
     float3 normal = NormalMap.Sample(Sampler, input.texCoord).rgb;
     
     // 법선 벡터 변환 (0 ~ 1 -&amp;gt; -1 ~ 1)
     normal = normalize(normal * 2.0 - 1.0)
     
     // 기본 조명 벡터 계산
     float3 N = normalize(normal); // 법선
     float3 L = normalize(-lightDirection); // 광원 방향
     float3 V = normalize(cameraPosition - input.worldPos); // 시선 방향
     float3 H = normalize(L + V); // 하프 벡터
     
     // Diffuse (Lambert)
     float NdotL = max(dot(N, L), 0.0);
     float3 diffuse = albedo.rgb * lightColor * NdotL;
     
     // Specular (Blinn-Phong 단순화)
     float NdotH = max(dot(N, H), 0.0);
     float shininess = (1.0 - roughness) * 256.0;
     float3 specular = lightColor * pow(NdotH, shininess) * metallic;
     
     // 최종 색상
     return float4(diffuse + specular, albedo.a);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5 PBR은 이보다 훨씬 복잡하지만 기본 원리는 동일하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GLSL ES - 임베디드/WebGL 특징&lt;/p&gt;
&lt;pre id=&quot;code_1784203107184&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GLSL ES 2.0 (OpenGL ES 2.0, WebGL 1.0)
#version 100

// 정밀도 명시 필수 - 데스크탑 GLSL에는 없는 개념
precision highp float; // 32비트 (데스크탑과 동일)
precision mediump float; // 16비트 (모바일 최적화)
precision lowp float; // 8비트 (빠르지만 부정확)

// attribute / varying - ES 3.0부터는 in/out으로 변경
attribute vec3 aPosition; // 버텍스 입력
varying vec2 vTexCoord; // 픽셀 셰이더로 전달

// texture2D - ES 3.0부터 texture()로 통일
gl_FragColor = texture2D(uTexture, vTexCoord);

// GLSL ES 3.0 (OpenGL ES 3.0, WebGL 2.0)
#version 300 es

// in/out 사용. texture() 통일. 
// 데스크탑 GLSL 3.3과 거의 동일해짐
in vec3 aPosition;
out vec4 FragColor;
FragColor = texture(uTexture, vTexCoord);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더 최적화 기법&lt;/p&gt;
&lt;pre id=&quot;code_1784203489283&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 비효율적 - 픽셀 셰이더에서 무거운 연산
float4 PS(PixelInput input) : SV_TARGET{
     float3 N = normalize(input.normal); // 모든 픽셀마다 normalize
     float3 L = normalize(lightDir); // 모든 픽셀마다 normalize
     // 수백만 픽셀 x 이 연산 = 비효율적(비싸다)
}

// 효율적 - 버텍스 셰이더에서 미리 계산
PixelInput VS(VertexInput input) {
     output.normal = normalize(mul(input.normal, (float3x3)normalMatrix));
     output.lightDir = normalize(lightDir); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원칙 : 버텍스 수 &amp;lt;&amp;lt; 픽셀 수. 가능하면 픽셀 셰이더에서 버텍스 셰이더로 연산을 옮길 것.&lt;/p&gt;
&lt;pre id=&quot;code_1784203658641&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 분기문 남용 - GPU에서 비쌈
float4 PS(PixelInput input) : SV_TARGET {
     if(input.texCoord.x &amp;gt; 0.5) {
           return float4(1,0,0,1);
     }
     else{
           return float4(0,0,1,1);
     }
}

// 수학으로 대체 - 분기 없음
float4 PS(PixelInput input) : SV_TARGET {
     float t = step(0.5, input.texCoord.x); // 분기 없는 조건
     return lerp(float4(0,0,1,1), float4(1,0,0,1), t);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPIR-V : Vulkan/WebGPU의 셰이더 중간 표현. GLSL/HLSL/WGSL -&amp;gt; SPIR-V - &amp;gt; GPU 바이너리 변환 경로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셰이더 퍼뮤테이션(Shader Permutation) : 같은 셰이더를 다양한 조건 조합으로 미리 컴파일한 것. UE5 첫 실행이 오래 걸리는 이유이며 조합이 많을수록 컴파일 시간이 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swizzling : 벡터 컴포넌트를 재배치하는 연산.color.bgr, pos.xzy 같은 것. HLSL/GLSL 공통 기능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인트린식 함수 (Intrinsic Function) : GPU에서 하드웨어 수준으로 최적화된 내장 함수. lerp, setp, saturate, dot, cross, normalize, reflect 등. 직접 구현보다 항상 빠르다&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/153</guid>
      <comments>https://blogger2146.tistory.com/153#entry153comment</comments>
      <pubDate>Thu, 16 Jul 2026 21:11:25 +0900</pubDate>
    </item>
    <item>
      <title>WebGL / WebGPU</title>
      <link>https://blogger2146.tistory.com/152</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;WebGL : 브라우저에서 OpenGL ES 기반 3D 렌더링.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGPU : 후계자로 컴퓨트 셰이더와 멀티스레딩 지원.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 브라우저가 GPU가 필요한가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 브라우저의 진화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 웹&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTML + CSS -&amp;gt; GPU가 2D 화면 그림 -&amp;gt; 애니메이션 = 느린 CPU 연산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Canvas 2D API (2004)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CPU 기반 2D 드로잉&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게임/시각화에는 너무 느리다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGL 1.0 (2011)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브라우저에서 처음으로 GPU 직접 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- OpenGL ES 2.0 기반&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3D 게임, 시각화 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGL 2.0 (2017)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- OpenGL ES 3.0 기반&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컴퓨트 셰이더 제외한 대부분 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGPU (2023~)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Vulkan/DX12/Metal 수준 저수준 제어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컴퓨트 셰이더 완전 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브라우저에서 본격 GPU 연산 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGL vs WebGPU&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WebGL&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WebGPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OpenGL ES 2.0/3.0 기반&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Vulkan/DX12/Metal 수준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2011년 등장&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2023년 정식 출시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;전역 상태 머신&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;명시적 커맨드 버퍼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;싱글 스레드&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;멀티스레드 가능 ( Web Worker )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;GLSL ES&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WGSL (WebGPU Shading Language)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;드라이버가 많이 처리&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;개발자가 직접 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;현재 모든 브라우저 지원&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;최신 브라우저에서 지원 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;안정적이지만 구식&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;복잡하지만 미래 표준&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 그래픽스 API가 동작하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 내부 계층&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript / WASM&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGL API / WebGPU API&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 내부 변환 레이어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chorme : ANGLE (WebGL) / Dawn (WebGPU)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firefox : WebGL 자체 구현 / wgpu&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Safari : WebKit WebGL / WebGPU&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이티브 그래픽스 API&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows : OpenGL -&amp;gt; DX11 / DX12 (ANGLE 변환)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS : OpenGL -&amp;gt; Metal (ANGLE/MoltenGL)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android : OpenGL ES&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux : OpenGL / Vulkan&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU 드라이버 -&amp;gt;&amp;nbsp; GPU 하드웨어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 WebGL을 쓰면 실제로는 DX12나 Metal이 실행되는 경우가 많다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGL 기본 코드&lt;/p&gt;
&lt;pre id=&quot;code_1784014388799&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// HTML Canvas 연결
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl'); // WebGL 1.0
// const gl = canvas.getContext('webgl2'); // WebGL 2.0

// 셰이더 (GLSL ES - OpenGL ES와 동일)
const vertexShaderSource = `
    attribute vec4 aPosition;
    attribute vec2 aTexCoord;
    varying vec2 vTexCoord;
    uniform mat4 uMVPMatrix;
    
    void main() {
         gl_Position = uMVPMatrix * aPosition;
         vTexCoord = aTexCoord;
    }
`;

const fragmentShaderSource = `
    precision mediump float; // 정밀도 명시
    varying vec2 vTexCoord;
    uniform sampler2D uTexture;
    
    void main() {
        gl_FragColor = texture2D(uTexture, vTexCoord);
    }
`;

// 셰이더 컴파일
function createShader(gl, type, source) 
{
     const shader = gl.createShader(type);
     gl.shaderSource(shader, source);
     gl.complieShader(shader);
     return shader;
}

// VBO 생성
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

// 드로우콜
gl.drawArrays(gl.TRIANGLES, 0, 3);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGL이 OpenGL ES 2.0 기반인게 코드에서 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGPU - 차세대 표준&lt;/p&gt;
&lt;pre id=&quot;code_1784015283413&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// WebGPU 초기화
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

// WGSL 셰이더 (WebGPU Shading Language)
const shaderCode = `
     // 버텍스 셰이더
     struct VertexOutput {
          @builtin(position) position: vec4f,
          @location(0) texCoord: vec2f,
     }
     
     @group(0) @binding(0) var&amp;lt;uniform&amp;gt; mvpMatrix: mat4x4f;
     
     @vertex
     fn vertexMain(
          @location(0) position: vec3f,
          @location(1) texCoord: vec2f
     ) -&amp;gt; VertexOutput {
          var out: VertexOutput;
          out.position = mvpMatrix * vec4f(position, 1.0);
          out.texCoord = texCoord;
          return out;
     }
     
     // 픽셀 셰이더
     @group(0) @binding(1) var myTexture : texture_2d&amp;lt;f32&amp;gt;;
     @group(0) @binding(2) var mySampler : sampler;
     
     @fragment
     fn fragmentMain(in: VertexOutput) -&amp;gt; @location(0) vec4f {
          return textureSample(myTexture, mySampler, in.texCoord);
     }
`;

// 파이프라인 생성
const pipeline = device.createRenderPipeline({
    vertex: {
          module: device.createShaderModule({ code: shaderCode }),
          entryPoint: 'vertexMain',
    },
    fragment: {
          module: device.createShaderModule({ code: shaderCode }),
          entryPoint: 'fragmentMain',
          targets: [{ format: 'bgra8unorm' }],
    },
});

// 커맨드 버퍼
const commandEncoder = device.createCommandEncoder();
const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
renderPass.setPipeline(pipeline);
renderPass.draw(3);
renderPass.end();
device.queue.submit([commandEncoder.finish()]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGPU 코드 구조가 Vulkan이랑 거의 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WGSL : WebGPU 셰이더 언어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Rust&amp;nbsp; 문법에 영향으 ㄹ받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타입 안전성이 강함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- @builtin, @location 어노테이션 시스템&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브라우저 샌드박스 보안 고려한 설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SPIR-V로 컴파일 후 네이티브 API로 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Three.js/Babylon.js - WebGL 위의 고수준 프레임워크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebGL (저수준 API)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 드로우콜 직접 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 셰이더 직접 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 복잡하지만 완전한 제어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Three.js / Babylon.js ( 고수준 프레임워크 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- WebGL을 추상화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5 / Unity처럼 씬, 메시, 머티리얼 개념 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게임 엔진의 브라우저 버전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5 대비&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DX12/Vulkan 위의 고수준 엔진&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Babylon.js의 특징&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Microsoft 개발&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- WebGPU 지원 가장 빠름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UE5와 유사한 PBR 머티리얼 시스템&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨트 셰이더 : WebGPU의 핵심 혁신&lt;/p&gt;
&lt;pre id=&quot;code_1784016525320&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// WebGPU 컴퓨트 셰이더
// 브라우저에서 GPU 병렬 연산 가능

const computeShader = `
    @group(0) @binding(0)
    var&amp;lt;storage, read_write&amp;gt; data: array&amp;lt;f32&amp;gt;;
    
    @compute @workgroup_size(64)
    fn main(@builtin(global_invocation_id) id: vec3u)
    {
         let index = id.x;
         data[index] = data[index] * 2.0; // 병렬 연산
    }
`;

// 수백만 개 데이터를 GPU에서 병렬 처리
// AI 추론, 물리 시뮬레이션, 이미지 처리 등등

// TensorFlow.js -&amp;gt; WebGPU 백엔드로 브라우저 AI 추론
// 파티클 시뮬레이션 -&amp;gt; 수백만 파티클을 JS 대신 GPU에서&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결되는 단어들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ANGLE : Almost Native Graphics Layer Engine. Chorme/Firefox/Edge가 WebGL을 DX11/DX12/Metal로 변환하는 레이어. Google이 개발했고 WebGL이 모든 플랫폼에서 동작하는 이유이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dawn : Chrome의 WebGPU 구현체. WebGPU API를 Vulkan/DX12/Metal로 변환. Vulkan의 MoltenVK와 유사한 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wgpu : Rust로 작성된 크로스플랫폼 WebGPU 구현체. Firefox WebGPU, 게임 엔진 Bevy의 렌더링 백엔드. WebGPU를 네이티브에서도 사용 가능하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPIR-V : Vulkan/WebGPU의 셰이더 중간 표현. GLSL/HLSL/WGSL을 SPIR-V로 컴파일 후 네이티브 API에 제출. 크로스플랫폼 셰이더의 공통 언어.&lt;/p&gt;</description>
      <category>Word</category>
      <author>ShilLoon</author>
      <guid isPermaLink="true">https://blogger2146.tistory.com/152</guid>
      <comments>https://blogger2146.tistory.com/152#entry152comment</comments>
      <pubDate>Tue, 14 Jul 2026 17:13:51 +0900</pubDate>
    </item>
  </channel>
</rss>