Learning
레슨 6 / 8·20분

포스트 프로세싱

포스트 프로세싱 셰이더

포스트 프로세싱은 3D 씬이 렌더링된 후 최종 이미지에 셰이더 효과를 적용하는 기법입니다. 렌더링 결과를 텍스처(FBO/Framebuffer Object)에 저장한 뒤, 이 텍스처를 화면 전체 사각형(Quad)에 매핑하면서 셰이더로 블러, 비네팅, 색상 보정, 글리치 등의 효과를 적용합니다.

glsl
precision mediump float;

uniform sampler2D u_scene;    // 렌더링된 씬 텍스처
uniform vec2 u_resolution;
uniform float u_time;

// ── 박스 블러 (Box Blur) ──
vec4 boxBlur(sampler2D tex, vec2 uv, vec2 texelSize, float radius) {
  vec4 color = vec4(0.0);
  float count = 0.0;

  for (float x = -4.0; x <= 4.0; x += 1.0) {
    for (float y = -4.0; y <= 4.0; y += 1.0) {
      if (length(vec2(x, y)) <= radius) {
        color += texture2D(tex, uv + vec2(x, y) * texelSize);
        count += 1.0;
      }
    }
  }

  return color / count;
}

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;
  vec2 texelSize = 1.0 / u_resolution; // 1 픽셀 크기

  // 블러 적용
  vec4 blurred = boxBlur(u_scene, uv, texelSize, 4.0);
  vec4 original = texture2D(u_scene, uv);

  // 원본과 블러 블렌딩
  gl_FragColor = mix(original, blurred, 0.5);
}
glsl
precision mediump float;

uniform sampler2D u_scene;
uniform vec2 u_resolution;
uniform float u_time;

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;

  // ── 비네팅 (Vignette) ──
  // 화면 가장자리를 어둡게 하는 효과
  float dist = distance(uv, vec2(0.5));
  float vignette = smoothstep(0.5, 0.2, dist);

  // ── 색수차 (Chromatic Aberration) ──
  // RGB 채널을 약간 분리하여 렌즈 효과
  float offset = 0.005;
  float r = texture2D(u_scene, uv + vec2(offset, 0.0)).r;
  float g = texture2D(u_scene, uv).g;
  float b = texture2D(u_scene, uv - vec2(offset, 0.0)).b;
  vec3 color = vec3(r, g, b);

  // ── 색상 보정 ──
  // 밝기 (brightness)
  color *= 1.1;
  // 대비 (contrast)
  color = (color - 0.5) * 1.2 + 0.5;
  // 채도 (saturation)
  float gray = dot(color, vec3(0.299, 0.587, 0.114));
  color = mix(vec3(gray), color, 1.3);

  // 비네팅 적용
  color *= vignette;

  gl_FragColor = vec4(color, 1.0);
}
glsl
precision mediump float;

uniform sampler2D u_scene;
uniform vec2 u_resolution;
uniform float u_time;

// ── 의사 랜덤 ──
float random(vec2 st) {
  return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
}

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;

  // ── 글리치 효과 ──
  // 랜덤한 시간에 수평 줄이 어긋남
  float glitchStrength = step(0.95, random(vec2(floor(u_time * 10.0))));
  float lineNoise = step(0.5, random(vec2(floor(uv.y * 50.0), u_time)));
  float offset = glitchStrength * lineNoise * 0.1;
  uv.x += offset;

  vec4 color = texture2D(u_scene, uv);

  // 글리치 시 RGB 분리 강화
  if (glitchStrength > 0.5) {
    float shift = 0.02;
    color.r = texture2D(u_scene, uv + vec2(shift, 0.0)).r;
    color.b = texture2D(u_scene, uv - vec2(shift, 0.0)).b;
  }

  // ── 스캔라인 (CRT 효과) ──
  float scanline = sin(gl_FragCoord.y * 1.5) * 0.04;
  color.rgb -= scanline;

  // ── 필름 그레인 ──
  float grain = (random(uv + u_time) - 0.5) * 0.08;
  color.rgb += grain;

  gl_FragColor = color;
}
  • Box Blur — 주변 픽셀의 평균으로 흐림 효과
  • Vignette — distance(uv, center)로 가장자리 어둡게
  • Chromatic Aberration — RGB 채널별 UV 오프셋으로 색수차
  • Color Grading — 밝기, 대비, 채도 조절
  • Glitch — 랜덤 수평 오프셋과 RGB 분리로 디지털 왜곡
  • Scanline — sin(y)로 CRT 모니터 스캔라인 효과
  • Film Grain — random()으로 필름 노이즈 추가
💡

포스트 프로세싱 효과를 체이닝(연쇄)하려면 FBO를 여러 개 사용하여 핑퐁 렌더링을 합니다. 예를 들어 FBO-A에 블러를 적용한 결과를 FBO-B에 저장하고, FBO-B에 비네팅을 적용한 결과를 화면에 출력합니다.