🏗 Pixel Rendering Pipeline: 한 프레임 동안의 음얎나는 음

BamgasiJM·2026년 8월 8음

WebGL

목록 볎Ʞ
8/14
post-thumbnail

개요

WebGL에서 화멎에 한 프레임을 귞늬는 동안, GPU는 수백만 개의 픜셀을 병렬로 처늬합니닀. 읎 묞서는 한 픜셀의 ꎀ점에서 1/60쎈(한 프레임) 동안 음얎나는 몚든 음을 상섞히 섀명합니닀.


1. 전첎 렌더링 파읎프띌읞 구조

1.1 시간축 흐멄

시간 t = 0 ms (프레임 시작)
  ↓
[CPU] JavaScript 싀행 / 데읎터 쀀비
  ↓
[GPU] Vertex Shader (정점 처늬)
  ↓
[GPU] Rasterization (래슀터화)
  ↓
[GPU] Fragment Shader (픜셀 처늬) ← 우늬가 집쀑할 부분
  ↓
[GPU] Output Merger (최종 색상 결정)
  ↓
[Display] 프레임버퍌 → 몚니터 출력
  ↓
시간 t = 16.67 ms (60 FPS Ʞ쀀, 닀음 프레임 시작)

1.2 파읎프띌읞 닚계

닚계처늬 위치처늬 대상역할
ApplicationCPU데읎터프로귞랚 로직
Vertex ShaderGPU정점(vertices)3D → 2D 좌표 변환
RasterizationGPUꞰ하학Ʞ하학 → 픜셀 변환
Fragment ShaderGPU픜셀(fragments)픜셀의 색 결정
Output MergerGPU프레임버퍌최종 색상 Ʞ록

2. 한 픜셀의 띌읎프사읎큎

시나늬였: 1024×1024 정사각형 캔버슀에서 원 귞늬Ʞ

쎈Ʞ 상태:

캔버슀 핎상도: 1024 × 1024 (1,048,576개 픜셀)
렌더링 방식: Fragment Shader SDF
프레임윚: 60 FPS (프레임당 16.67ms)

2.1 Stage 0: CPU 닚계 (시간: t-1ms ~ t0ms)

1닚계 - JavaScript 데읎터 쀀비

// HTML5 Canvas와 WebGL 컚텍슀튞 확읞
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl2');

// Uniform 데읎터 쀀비 (몚든 Fragment Shader에 동음하게 전달)
const resolutionLoc = gl.getUniformLocation(program, 'u_resolution');
gl.uniform2f(resolutionLoc, 1024.0, 1024.0);

읎 닚계에서의 CPU 작업:

  • Canvas 컚텍슀튞 확읞
  • Shader program 검슝
  • Uniform 값 쀀비
  • Vertex buffer 확읞
  • Texture 바읞딩

메몚늬 위치: CPU RAM → GPU VRAM

2닚계 - GPU로 명령 전달

// GPU 렌더링 명령 발행
gl.clear(gl.COLOR_BUFFER_BIT);  // 프레임버퍌 쎈Ʞ화
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);  // 렌더링 시작

읎 혞출에서:

  • GPU에 명령 큐(Command Queue)에 작업 추가
  • CPU는 슉시 반환 (비동Ʞ)
  • GPU가 백귞띌욎드에서 처늬 시작

2.2 Stage 1: GPU Vertex Shader 닚계 (병렬 처늬)

개념

Vertex Shader는 각 정점에서 싀행됩니닀. 전첎 화멎을 덮는 직사각형(2개 삌각형)을 귞늬므로 4개의 정점에서만 싀행됩니닀.

#version 300 es
in vec4 position;  // 정점 위치

void main() {
    gl_Position = position;  // 큎늜 공간 좌표로 변환
}

싀행:

Vertex 0: (-1, -1) → gl_Position = (-1, -1, 0, 1)
Vertex 1: ( 1, -1) → gl_Position = ( 1, -1, 0, 1)
Vertex 2: (-1,  1) → gl_Position = (-1,  1, 0, 1)
Vertex 3: ( 1,  1) → gl_Position = ( 1,  1, 0, 1)

처늬 시간: 맀우 빠늄 (수 나녞쎈)

2.3 Stage 2: Rasterization 닚계

개념

정점 4개로 정의된 두 개의 삌각형을 픜셀 닚위로 변환합니닀.

삌각형 1: Vertex 0, 1, 2
삌각형 2: Vertex 1, 2, 3

결곌: 1024 × 1024 = 1,048,576개의 Fragment 생성

각 Fragment에 할당되는 정볎:

Fragment (512, 256):
  - 화멎 좌표: (512, 256)
  - gl_FragCoord: (512.5, 256.5)  (픜셀 쀑심)
  - Interpolated values: (VaryingData 볎간)
  - Depth: 0.0 (깊읎 테슀튞용)

처늬 시간: 병렬 처늬 (몚든 픜셀 동시)


3. Fragment Shader 싀행 닚계 (핵심)

3.1 한 픜셀의 Fragment Shader 싀행

타겟 픜셀: (512, 256)

Step 1: Fragment Shader 혞출

#version 300 es
precision highp float;

uniform vec2 u_resolution;
out vec4 outColor;

float circleSDF(vec2 p, float radius) {
    return length(p) - radius;
}

void main() {
    // ===== 픜셀 (512, 256)의 Shader 싀행 시작 =====
    
    // Step 1: 현재 픜셀 좌표 읜Ʞ
    // gl_FragCoord.xy = (512.5, 256.5)  ← GPU가 자동 제공
    
    // Step 2: 정규화 좌표 계산
    vec2 uv = gl_FragCoord.xy / u_resolution * 2.0 - 1.0;
    // uv = (512.5, 256.5) / (1024.0, 1024.0) * 2.0 - 1.0
    // uv = (1.00098, 0.50098) * 2.0 - 1.0
    // uv = (2.00196, 1.00196) - 1.0
    // uv ≈ (1.00196, 0.00196)
    
    // Step 3: 종횡비 볎정 (정사각형읎므로 영향없음)
    uv.x *= u_resolution.x / u_resolution.y;
    // uv.x *= 1024.0 / 1024.0 = 1.0 (변화없음)
    
    // Step 4: 거늬 계산
    float sdf = circleSDF(uv, 0.3);
    // sdf = length((1.00196, 0.00196)) - 0.3
    // sdf = √(1.00196² + 0.00196²) - 0.3
    // sdf = √1.00394 - 0.3
    // sdf ≈ 1.00196 - 0.3
    // sdf ≈ 0.70196  (왞부)
    
    // Step 5: 겜계 처늬
    float edge = fwidth(sdf) * 1.5;
    // fwidth: 읞접 픜셀 간 믞분값
    // 화멎 핎상도에 따띌 동적 계산
    
    // Step 6: smoothstep윌로 볎간
    float fill = smoothstep(edge, -edge, sdf);
    // edge ≈ 0.001 읎띌 가정
    // smoothstep(0.001, -0.001, 0.70196)
    // sdf > edge읎므로 → fill = 0.0
    
    // Step 7: 색상 결정
    vec3 mintColor = vec3(0.0, 0.85, 0.64);
    vec3 color = mix(vec3(0.0), mintColor, fill);
    // color = mix((0.0, 0.0, 0.0), (0.0, 0.85, 0.64), 0.0)
    // color = (0.0, 0.0, 0.0)  ← 검정색
    
    // Step 8: 출력
    outColor = vec4(color, 1.0);
    // outColor = (0.0, 0.0, 0.0, 1.0)  ← 완전 불투명한 검정색
}

처늬 시간: 픜셀당 앜 200~500 나녞쎈 (수천 게읎튞 연산)

Step 2: 병렬 처늬의 규몚

동시에 싀행되는 Fragment Shader:

GPU 윔얎 귞룹 1: 픜셀 (0, 0), (1, 0), (2, 0), (3, 0) 동시 싀행
GPU 윔얎 귞룹 2: 픜셀 (4, 0), (5, 0), (6, 0), (7, 0) 동시 싀행
GPU 윔얎 귞룹 3: 픜셀 (8, 0), (9, 0), (10, 0), (11, 0) 동시 싀행
...
GPU 윔얎 귞룹 N: 픜셀 (1020, 1023), (1021, 1023), (1022, 1023), (1023, 1023) 동시 싀행

쎝 1,048,576개 픜셀을 앜 262,144개의 "워프(warp)"로 나누얎 처늬
(NVIDIA GPU Ʞ쀀 한 워프 = 32개 슀레드)

결곌적윌로 몚든 픜셀의 색상읎 앜 16ms 낎에 계산됚

3.2 닀양한 픜셀의 처늬 예시

픜셀 A: 원의 낎부 (256, 512)

uv = (256.5, 512.5) / 1024.0 * 2.0 - 1.0
   ≈ (-0.499, 0.001)

sdf = length((-0.499, 0.001)) - 0.3
    = √(0.249 + 0.000001) - 0.3
    ≈ 0.499 - 0.3
    ≈ 0.199  (겜계 귌처)

fill = smoothstep(0.001, -0.001, 0.199)
     ≈ 0.8  (부분적윌로 채워짐)

color = mix((0.0, 0.0, 0.0), (0.0, 0.85, 0.64), 0.8)
      ≈ (0.0, 0.68, 0.512)  ← 진한 믌튞색

픜셀 B: 원의 쀑심 (512, 512)

uv ≈ (0.0, 0.0)

sdf = length((0.0, 0.0)) - 0.3
    = 0.0 - 0.3
    = -0.3  (낎부)

fill = smoothstep(0.001, -0.001, -0.3)
     = 1.0  (완전히 채워짐)

color = mix((0.0, 0.0, 0.0), (0.0, 0.85, 0.64), 1.0)
      = (0.0, 0.85, 0.64)  ← 밝은 믌튞색

픜셀 C: 원의 왞부 (768, 768)

uv ≈ (0.501, 0.501)

sdf = length((0.501, 0.501)) - 0.3
    = √(0.502) - 0.3
    ≈ 0.709 - 0.3
    = 0.409  (왞부)

fill = smoothstep(0.001, -0.001, 0.409)
     = 0.0  (채워지지 않음)

color = (0.0, 0.0, 0.0)  ← 검정색

4. Stage 3: Output Merger 닚계

4.1 프레임버퍌 Ʞ록

각 Fragment Shader가 outColor륌 반환하멎, GPU의 Output Merger 닚계가 읎 값을 프레임버퍌(Framebuffer)에 Ʞ록합니닀.

픜셀 (512, 256):
  outColor = (0.0, 0.0, 0.0, 1.0)
  → 프레임버퍌[512][256] = (0.0, 0.0, 0.0, 1.0)

픜셀 (256, 512):
  outColor ≈ (0.0, 0.68, 0.512, 1.0)
  → 프레임버퍌[256][512] = (0.0, 0.68, 0.512, 1.0)

픜셀 (512, 512):
  outColor = (0.0, 0.85, 0.64, 1.0)
  → 프레임버퍌[512][512] = (0.0, 0.85, 0.64, 1.0)

메몚늬 위치: GPU VRAM (프레임버퍌 메몚늬)

4.2 추가 처늬 (선택사항)

Blending (알파 랔렌딩)

if (gl_FragCoord가 읎믞 닀륞 묌첎로 채워짐) {
    최종색 = Ʞ졎색 * (1 - alpha) + 새색 * alpha
}

Depth Test (깊읎 테슀튞)

if (현재 픜셀의 깊읎 < 프레임버퍌의 깊읎) {
    프레임버퍌에 Ʞ록
} else {
    버늌 (뒀에 있윌므로 볎읎지 않음)
}

현재 예제는 깊읎 테슀튞 없윌므로 몚든 픜셀읎 Ʞ록됩니닀.


5. Stage 4: 디슀플레읎 닚계 (t ≈ 15ms)

5.1 프레임버퍌 → 디슀플레읎 메몚늬 전송

GPU 프레임버퍌 (VRAM)
  ↓ (PCIe 버슀)
디슀플레읎 제얎Ʞ
  ↓
몚니터 메몚늬
  ↓
화멎 렌더링

시간: 앜 1ms (고속 전송)

5.2 몚니터 갱신

몚니터 갱신윚: 60 Hz (16.67ms마닀)

시간축:
t=0ms:    프레임 1 렌더링 시작
t=15ms:   프레임 1 프레임버퍌 완성
t=16.67ms: 몚니터가 프레임 1 표시 시작
          프레임 2 렌더링 시작 (동시)
t=32ms:   프레임 2 프레임버퍌 완성
t=33.33ms: 몚니터가 프레임 2 표시 시작

사용자가 볎는 것:

  • 원읎 검정 배겜에 믌튞색윌로 칠핎진 읎믞지
  • 각 픜셀의 색상읎 Fragment Shader 계산 결곌

6. 병렬 처늬의 구첎적 흐멄

6.1 시간축윌로 볞 GPU 처늬

t=0ms
  Vertex Shader (4개 정점) 완료 ← 맀우 빠늄

t=0.1ms
  Rasterization 완료 ← 픜셀 생성

t=0.2ms
  Fragment Shader 시작
  ├─ GPU 윔얎 귞룹 1~256: 동시 싀행
  ├─ 각 귞룹: 앜 4,096개 픜셀 처늬
  └─ 쎝 1,048,576개 픜셀 병렬 처늬

t=5ms
  몚든 Fragment Shader 완료
  (1,048,576개 픜셀 × 200나녞쎈 / 256개 병렬 처늬)

t=5.5ms
  Output Merger 닚계 (색상 Ʞ록)

t=6ms
  프레임 완성 (프레임버퍌 사용 가능)

t=15ms
  GPU 유휎 상태 (CPU가 닀음 프레임 쀀비)

t=16.67ms
  몚니터 갱신, 닀음 프레임 시작

6.2 "큮럭 죌Ʞ" 닚위 동작

GPU 큮럭: 앜 1.5~2.0 GHz (현대 GPU)
큮럭 죌Ʞ: 앜 0.5~0.67 나녞쎈

한 픜셀의 Fragment Shader 싀행:
  - 명령얎 개수: 앜 200~300개
  - 큮럭 죌Ʞ: 앜 400~600 큮럭
  - 싀제 시간: 앜 200~400 나녞쎈

1,048,576개 픜셀 처늬:
  - 순찚 처늬 시간: 앜 209ms (불가능)
  - 256개 동시 처늬: 앜 4ms (싀제)
  - 1024개 동시 처늬: 앜 1ms (최신 GPU)

7. 메몚늬 읎동 겜로

7.1 한 프레임 동안의 데읎터 흐멄

┌─────────────────────────────────────────┐
│          CPU (Main Memory)           │
│  Shader source code                  │
│  Canvas context                      │
│  Uniform values (u_resolution)       │
│  Vertex positions                    │
└───────────────┬─────────────────────────┘
               │ (glDrawArrays 혞출)
               ↓
┌─────────────────────────────────────────┐
│        GPU Command Queue             │
│      (PCIe Bus륌 통핎 전송)            │
└──────────────┬──────────────────────────┘
               │
               ↓
┌─────────────────────────────────────────┐
│         GPU VRAM (Video Memory)      │
│  ├─ Vertex Buffer                    │
│  ├─ Compiled Shaders                 │
│  ├─ Uniforms (상수 메몚늬)             │
│  └─ Framebuffer (출력 버퍌)            │
│     ├─ 프레임 시작: 검정색로 쎈Ʞ화        │
│     ├─ 프레임 쀑: 각 픜셀 색상 Ʞ록       │
│     └─ 프레임 종료: 완성된 읎믞지         │
└──────────────┬──────────────────────────┘
               │
               ↓
┌─────────────────────────────────────────┐
│      Display Memory (VRAM의 음부)     │
│  (프레임버퍌 복사볞)                    │
└──────────────┬──────────────────────────┘
               │
               ↓
┌─────────────────────────────────────────┐
│         Monitor (Display)            │
│  (60 Hz로 갱신)                       │
└─────────────────────────────────────────┘

7.2 메몚늬 대역폭

프레임 크Ʞ: 1024 × 1024 × 4 바읎튞 (RGBA) = 4MB

프레임버퍌 ì“°êž°:
  - 1,048,576개 픜셀 × 4 바읎튞 = 4MB
  - 메몚늬 대역폭: 앜 200~300 GB/s (현대 GPU)
  - 전송 시간: 앜 13~20 나녞쎈 (맀우 빠늄)

전첎 메몚늬 ì ‘ê·Œ 시간:
  - Vertex buffer 읜Ʞ: 0.1ms
  - Uniform 읜Ʞ: 맀우 빠늄 (캐시)
  - Framebuffer ì“°êž°: 앜 3ms
  - 쎝합: 앜 3.5ms

8. 동Ʞ화 포읞튞

8.1 CPU-GPU 동Ʞ화

// 프레임 1
gl.drawArrays(...);  // GPU 명령 발행 (비동Ʞ)
// CPU는 슉시 반환 (GPU가 처늬 쀑)

// 만앜 여Ʞ서 동Ʞ화륌 원하멎:
gl.finish();  // GPU 작업읎 몚두 완료될 때까지 대Ʞ
              // → 성능 저하 (병렬 처늬 불가)

// 올바륞 방법:
gl.drawArrays(...);  // GPU 명령 발행
// CPU는 닀음 프레임 쀀비 시작 (병렬 처늬)

8.2 GPU 파읎프띌읞 낎부 동Ʞ화

Vertex Shader 완료
         ↓
    [동Ʞ화]
         ↓
Rasterization 시작
         ↓
Fragment Shader 시작
         ↓
    [동Ʞ화]
         ↓
Output Merger 시작

GPU는 읎 동Ʞ화륌 자동윌로 처늬합니닀. 개발자는 명시적윌로 제얎할 필요 없습니닀.


9. 병목 현상 분석

9.1 가능한 병목

쌀읎슀 1: Fragment Shader 계산 복잡도 높음
┌─────────────────────┐
│  Vertex: 0.1ms      │
│  Rasterize: 0.1ms   │
│  Fragment: 12ms     │ ← 병목
│  Merge: 1ms         │
│  쎝합: 13.2ms       │ (60 FPS 유지)
└─────────────────────┘

쌀읎슀 2: Framebuffer ì“°êž° 속도 제한
┌─────────────────────┐
│  Vertex: 0.1ms      │
│  Rasterize: 0.1ms   │
│  Fragment: 3ms      │
│  Merge: 8ms         │ ← 병목
│  쎝합: 11.2ms       │ (60 FPS 유지)
└─────────────────────┘

쌀읎슀 3: 정점 개수 많음
┌─────────────────────┐
│  Vertex: 5ms        │ ← 병목
│  Rasterize: 2ms     │
│  Fragment: 3ms      │
│  Merge: 1ms         │
│  쎝합: 11ms         │ (60 FPS 유지)
└─────────────────────┘

9.2 최적화 전략

Fragment Shader 최적화:
  - 불필요한 계산 제거
  - 정밀도 낮춀 (highp → mediump)
  - 룚프 얞례링
  
메몚늬 최적화:
  - 텍슀처 압축
  - 메몚늬 정렬 개선
  
병렬 처늬 활용:
  - 정점/픜셀 동시 처늬
  - 여러 프레임 파읎프띌읞

10. 싀전 잡정 방법

10.1 프레임 시간 잡정

let frameTime = 0;

function renderLoop() {
    const frameStart = performance.now();
    
    // Fragment Shader 싀행
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    
    // GPU 작업 대Ʞ (성능 잡정용)
    gl.finish();  // 프로덕션에서는 사용 ꞈ지
    
    const frameEnd = performance.now();
    frameTime = frameEnd - frameStart;
    
    console.log(`Frame time: ${frameTime.toFixed(2)}ms`);
    console.log(`FPS: ${(1000 / frameTime).toFixed(1)}`);
    
    requestAnimationFrame(renderLoop);
}

10.2 WebGL Timer Query (정확한 잡정)

const query = gl.createQuery();

gl.beginQuery(gl.TIME_ELAPSED, query);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.endQuery(gl.TIME_ELAPSED);

// 나쀑에 결곌 확읞
setTimeout(() => {
    const gpuTime = gl.getQueryParameter(query, gl.QUERY_RESULT);
    console.log(`GPU time: ${gpuTime / 1000000}ms`);
}, 1);

11. 정늬: 한 프레임의 완벜한 타임띌읞

t=0ms
  ├─ JavaScript: 데읎터 쀀비
  ├─ gl.clear(gl.COLOR_BUFFER_BIT) 혞출
  └─ gl.drawArrays(...) 혞출 (GPU 명령 발행)

t=0.05ms
  ├─ GPU: Vertex Shader 싀행 (4개 정점)
  └─ 결곌: 큎늜 공간 좌표 4개

t=0.1ms
  ├─ GPU: Rasterization
  └─ 결곌: 1,048,576개 Fragment 생성

t=0.15ms
  ├─ GPU: Fragment Shader 시작 (256개 병렬)
  └─ 픜셀 (512, 256):
     1. gl_FragCoord 읜Ʞ
     2. uv 정규화
     3. sdf 계산
     4. fill 계산
     5. color 결정
     6. outColor Ʞ록

t=5ms
  └─ GPU: 몚든 픜셀 계산 완료

t=5.5ms
  ├─ GPU: Output Merger
  └─ 프레임버퍌에 몚든 색상 Ʞ록 완료

t=6ms
  └─ 프레임 렌더링 완료 (CPU는 닀음 프레임 쀀비)

t=16.67ms
  ├─ 몚니터: 읎 프레임 표시 시작 (60 Hz)
  └─ GPU: 닀음 프레임 렌더링 시작

12. 용얎집

용얎정의
Fragment픜셀곌 유사하지만, 최종 색상 결정 전의 쀑간 데읎터 닚위
FramebufferGPU VRAM의 영역윌로, 몚든 픜셀의 색상 데읎터륌 저장
Rasterization3D Ʞ하학 데읎터륌 2D 픜셀로 변환하는 곌정
WarpGPU의 32개 슀레드 묶음 (병렬 처늬 닚위)
fwidth()읞접 픜셀 간의 믞분값 (화멎 핎상도 독늜적 계산용)
smoothstep()선형 볎간 핚수 (겜계 부드럜게 처늬)
Uniform몚든 픜셀에서 동음한 값 (읜Ʞ 전용)
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0개의 댓Ꞁ