Files
game_engine/docs/superpowers/specs/2026-03-25-phase7-4-post-processing.md
2026-03-25 13:55:09 +09:00

220 lines
5.9 KiB
Markdown

# Phase 7-4: HDR + Bloom + ACES Tonemap — Design Spec
## Overview
디퍼드 파이프라인을 HDR로 전환하고, Bloom과 ACES 톤매핑 포스트 프로세싱을 추가한다.
## Scope
- HDR 렌더 타겟 (Rgba16Float)
- Lighting Pass HDR 출력 (Reinhard/감마 제거)
- Bloom (bright extract + downsample chain + upsample blend)
- ACES 톤매핑 + bloom 합성 + 감마 보정
- deferred_demo에 HDR+Bloom+Tonemap 통합
## Out of Scope
- TAA (Temporal Anti-Aliasing)
- SSR (Screen-Space Reflections)
- Motion Blur, Depth of Field
- Adaptive exposure / auto-exposure
- Filmic 등 다른 톤매핑 커브
## Render Pass Flow (최종)
```
Pass 1: G-Buffer (변경 없음)
Pass 2: SSGI (변경 없음)
Pass 3: RT Shadow (변경 없음)
Pass 4: Lighting → HDR texture (Rgba16Float) — 톤매핑/감마 제거
Pass 5: Bloom (NEW) — bright extract → downsample chain → upsample chain
Pass 6: Tonemap (NEW) — HDR + Bloom → ACES tonemap + gamma → surface
```
## Module Structure
### 새 파일
- `crates/voltex_renderer/src/hdr.rs` — HdrTarget (Create)
- `crates/voltex_renderer/src/bloom.rs` — BloomResources, mip chain 생성 (Create)
- `crates/voltex_renderer/src/bloom_shader.wgsl` — downsample + upsample 셰이더 (Create)
- `crates/voltex_renderer/src/tonemap.rs` — TonemapUniform (Create)
- `crates/voltex_renderer/src/tonemap_shader.wgsl` — ACES + bloom merge + gamma (Create)
### 수정 파일
- `crates/voltex_renderer/src/deferred_pipeline.rs` — bloom/tonemap 파이프라인 추가 (Modify)
- `crates/voltex_renderer/src/deferred_lighting.wgsl` — Reinhard+감마 제거 (Modify)
- `crates/voltex_renderer/src/lib.rs` — 새 모듈 등록 (Modify)
- `examples/deferred_demo/src/main.rs` — HDR+Bloom+Tonemap 통합 (Modify)
## Types
### HdrTarget
```rust
pub const HDR_FORMAT: TextureFormat = TextureFormat::Rgba16Float;
pub struct HdrTarget {
pub view: TextureView,
pub width: u32,
pub height: u32,
}
```
- `new(device, width, height)` — Rgba16Float, RENDER_ATTACHMENT | TEXTURE_BINDING
- `resize(device, width, height)`
### BloomResources
```rust
pub struct BloomResources {
pub mip_views: Vec<TextureView>, // 5 levels
pub threshold: f32,
pub intensity: f32,
pub bloom_uniform: Buffer,
}
```
**Mip chain**: 5단계, 각 레벨은 이전의 1/2 크기.
- Level 0: width/2 x height/2
- Level 1: width/4 x height/4
- Level 2: width/8 x height/8
- Level 3: width/16 x height/16
- Level 4: width/32 x height/32
각 mip은 Rgba16Float, RENDER_ATTACHMENT | TEXTURE_BINDING.
### BloomUniform
```rust
#[repr(C)]
pub struct BloomUniform {
pub threshold: f32,
pub soft_threshold: f32, // threshold - knee
pub _padding: [f32; 2],
}
```
### TonemapUniform
```rust
#[repr(C)]
pub struct TonemapUniform {
pub bloom_intensity: f32,
pub exposure: f32,
pub _padding: [f32; 2],
}
```
## Bloom Algorithm
### Pass 5a: Bright Extract + First Downsample
입력: HDR 텍스처 (full res)
출력: Bloom mip 0 (half res)
```wgsl
let color = textureSample(hdr, sampler, uv);
let luminance = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
let contribution = max(luminance - threshold, 0.0);
let bloom_color = color.rgb * (contribution / (luminance + 0.0001));
// 2x2 box filter (downsample)
output = bloom_color;
```
### Pass 5b: Downsample Chain (4회)
mip N → mip N+1: 텍스처 샘플링으로 자연스럽게 2x 다운샘플. 13-tap box filter로 품질 향상.
```wgsl
// 13-tap downsample (Karis average 스타일)
let a = textureSample(src, s, uv + vec2(-2, -2) * texel);
let b = textureSample(src, s, uv + vec2( 0, -2) * texel);
// ... (center + 4 corners + 4 edges + 4 diagonal)
output = weighted_average;
```
### Pass 5c: Upsample Chain (4회)
mip N+1 → mip N: bilinear 업샘플 + additive blend with current level.
```wgsl
let upsampled = textureSample(lower_mip, s, uv);
let current = textureSample(current_mip, s, uv);
output = current + upsampled;
```
### 최종 bloom = mip 0 (half res)
## Tonemap Pass (Pass 6)
입력: HDR 텍스처 + Bloom 텍스처 (mip 0)
출력: surface texture (sRGB)
```wgsl
let hdr_color = textureSample(t_hdr, s, uv).rgb;
let bloom_color = textureSample(t_bloom, s, uv).rgb;
let combined = hdr_color + bloom_color * bloom_intensity;
let exposed = combined * exposure;
let tonemapped = aces_tonemap(exposed);
let gamma = pow(tonemapped, vec3(1.0 / 2.2));
output = vec4(gamma, 1.0);
```
### ACES Filmic Tonemap
```wgsl
fn aces_tonemap(x: vec3<f32>) -> vec3<f32> {
let a = 2.51;
let b = 0.03;
let c = 2.43;
let d = 0.59;
let e = 0.14;
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), vec3(0.0), vec3(1.0));
}
```
## Lighting Pass 수정
현재 `deferred_lighting.wgsl` 끝부분:
```wgsl
// Reinhard tone mapping
color = color / (color + vec3<f32>(1.0));
// Gamma correction
color = pow(color, vec3<f32>(1.0 / 2.2));
```
변경: 이 두 줄을 **제거**. raw HDR 값을 그대로 출력.
Lighting pipeline의 color target format을 surface format → HDR_FORMAT(Rgba16Float)으로 변경.
## Bind Group Details
### Bloom Downsample/Upsample (각 패스)
- Group 0: 입력 텍스처 + sampler + BloomUniform (downsample 시)
단일 파이프라인을 재사용, 입력/출력만 변경.
### Tonemap
- Group 0: HDR 텍스처 + bloom 텍스처 + sampler + TonemapUniform
## Simplified Bloom (구현 단순화)
복잡한 13-tap 대신, 첫 구현은:
1. Bright extract → mip 0 (half res, single fullscreen pass)
2. Downsample: 순차 4회 (bilinear sampler가 자동 2x2 평균)
3. Upsample: 순차 4회 (additive blend)
4. 총 9 fullscreen passes (1 extract + 4 down + 4 up)
단일 셰이더 + 다른 바인드 그룹으로 반복 실행.
## Test Plan
### bloom.rs
- mip_sizes: 입력 크기에서 5단계 크기 계산
- BloomResources 생성
### tonemap.rs
- ACES tonemap: 0→0, 1→~0.59, large→~1.0
### 통합 (수동)
- deferred_demo: bloom ON/OFF 비교 (밝은 라이트 주변 glow)
- exposure 조절 (어두움/밝음)