docs: add Phase 7-1 through 7-3 specs and plans
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
199
docs/superpowers/specs/2026-03-25-phase7-1-deferred-rendering.md
Normal file
199
docs/superpowers/specs/2026-03-25-phase7-1-deferred-rendering.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# Phase 7-1: Deferred Rendering — Design Spec
|
||||
|
||||
## Overview
|
||||
|
||||
`voltex_renderer`에 디퍼드 렌더링 파이프라인을 추가한다. 기존 포워드 PBR은 유지하고, G-Buffer + Lighting Pass 구조의 디퍼드 파이프라인을 새 모듈로 구현한다.
|
||||
|
||||
## Scope
|
||||
|
||||
- G-Buffer (4 MRT: Position, Normal, Albedo, Material + Depth)
|
||||
- G-Buffer Pass 셰이더 (기하 데이터 기록)
|
||||
- Lighting Pass 셰이더 (풀스크린 쿼드, Cook-Torrance BRDF, 멀티 라이트, 섀도우, IBL)
|
||||
- 풀스크린 삼각형
|
||||
- deferred_demo 예제
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 포워드 파이프라인 제거/변경
|
||||
- 투명 오브젝트 (디퍼드에서 처리 어려움, 별도 포워드 패스 필요)
|
||||
- G-Buffer 압축/최적화 (octahedral normal, depth-position 복원 등)
|
||||
- Light volumes (sphere/cone 렌더링으로 라이트 컬링)
|
||||
- Stencil 기반 최적화
|
||||
|
||||
## Render Pass Architecture
|
||||
|
||||
### Pass 1: G-Buffer Pass
|
||||
|
||||
MRT(Multiple Render Targets)로 기하 데이터 기록.
|
||||
|
||||
| RT | Format | Content |
|
||||
|----|--------|---------|
|
||||
| RT0 | Rgba32Float | World Position (xyz) |
|
||||
| RT1 | Rgba16Float | World Normal (xyz, normalized) |
|
||||
| RT2 | Rgba8UnormSrgb | Albedo (rgb) |
|
||||
| RT3 | Rgba8Unorm | R=metallic, G=roughness, B=ao |
|
||||
| Depth | Depth32Float | Depth (기존 공유) |
|
||||
|
||||
**Bind Groups:**
|
||||
- Group 0 (dynamic): CameraUniform (view_proj, model)
|
||||
- Group 1: PBR Textures (albedo + normal map)
|
||||
- Group 2 (dynamic): MaterialUniform
|
||||
|
||||
**Shader:** 버텍스 → 월드 변환, 프래그먼트 → G-Buffer 기록. TBN 노멀맵 적용.
|
||||
|
||||
### Pass 2: Lighting Pass
|
||||
|
||||
풀스크린 삼각형 렌더, G-Buffer를 텍스처로 읽어 라이팅 계산.
|
||||
|
||||
**Bind Groups:**
|
||||
- Group 0: G-Buffer textures (4개) + sampler
|
||||
- Group 1: LightsUniform + CameraPosition
|
||||
- Group 2: Shadow map + shadow sampler + ShadowUniform + BRDF LUT + BRDF sampler
|
||||
|
||||
**Shader:** 기존 pbr_shader.wgsl의 Cook-Torrance BRDF 로직을 재사용.
|
||||
- G-Buffer에서 position, normal, albedo, metallic/roughness/ao 읽기
|
||||
- 멀티 라이트 루프 (directional, point, spot)
|
||||
- PCF 섀도우
|
||||
- IBL ambient (procedural sky + BRDF LUT)
|
||||
- Reinhard 톤매핑 + 감마 보정
|
||||
|
||||
## Module Structure
|
||||
|
||||
### 새 파일
|
||||
- `crates/voltex_renderer/src/gbuffer.rs` — GBuffer 타입 (텍스처 생성/리사이즈)
|
||||
- `crates/voltex_renderer/src/fullscreen_quad.rs` — 풀스크린 삼각형 정점
|
||||
- `crates/voltex_renderer/src/deferred_pipeline.rs` — 파이프라인 생성 (gbuffer pass + lighting pass)
|
||||
- `crates/voltex_renderer/src/deferred_gbuffer.wgsl` — G-Buffer pass 셰이더
|
||||
- `crates/voltex_renderer/src/deferred_lighting.wgsl` — Lighting pass 셰이더
|
||||
|
||||
### 수정 파일
|
||||
- `crates/voltex_renderer/src/lib.rs` — 새 모듈 등록
|
||||
|
||||
## Types
|
||||
|
||||
### GBuffer
|
||||
|
||||
```rust
|
||||
pub struct GBuffer {
|
||||
pub position_view: TextureView, // Rgba32Float
|
||||
pub normal_view: TextureView, // Rgba16Float
|
||||
pub albedo_view: TextureView, // Rgba8UnormSrgb
|
||||
pub material_view: TextureView, // Rgba8Unorm
|
||||
pub depth_view: TextureView, // Depth32Float
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
```
|
||||
|
||||
- `new(device, width, height) -> Self`
|
||||
- `resize(device, width, height)` — 윈도우 리사이즈 시 재생성
|
||||
|
||||
### DeferredPipeline
|
||||
|
||||
```rust
|
||||
pub struct DeferredPipeline {
|
||||
pub gbuffer_pipeline: RenderPipeline,
|
||||
pub lighting_pipeline: RenderPipeline,
|
||||
pub gbuffer_bind_group_layouts: [BindGroupLayout; 3], // camera, texture, material
|
||||
pub lighting_bind_group_layouts: [BindGroupLayout; 3], // gbuffer, lights, shadow+ibl
|
||||
}
|
||||
```
|
||||
|
||||
- `new(device, surface_format) -> Self`
|
||||
|
||||
### Fullscreen Triangle
|
||||
|
||||
```rust
|
||||
pub struct FullscreenTriangle {
|
||||
pub vertex_buffer: Buffer,
|
||||
}
|
||||
```
|
||||
|
||||
3 정점: (-1,-1), (3,-1), (-1,3) — 클리핑으로 화면 커버. UV는 셰이더에서 position으로 계산.
|
||||
|
||||
## Bind Group Details
|
||||
|
||||
### G-Buffer Pass
|
||||
|
||||
**Group 0 — Camera (dynamic offset):**
|
||||
- binding 0: CameraUniform (view_proj, model, camera_pos)
|
||||
|
||||
**Group 1 — Textures:**
|
||||
- binding 0: albedo texture
|
||||
- binding 1: albedo sampler
|
||||
- binding 2: normal map texture
|
||||
- binding 3: normal map sampler
|
||||
|
||||
**Group 2 — Material (dynamic offset):**
|
||||
- binding 0: MaterialUniform (base_color, metallic, roughness, ao)
|
||||
|
||||
### Lighting Pass
|
||||
|
||||
**Group 0 — G-Buffer:**
|
||||
- binding 0: position texture
|
||||
- binding 1: normal texture
|
||||
- binding 2: albedo texture
|
||||
- binding 3: material texture
|
||||
- binding 4: sampler (shared, nearest)
|
||||
|
||||
**Group 1 — Lights:**
|
||||
- binding 0: LightsUniform
|
||||
- binding 1: CameraPositionUniform (vec3 + padding)
|
||||
|
||||
**Group 2 — Shadow + IBL:**
|
||||
- binding 0: shadow depth texture
|
||||
- binding 1: shadow comparison sampler
|
||||
- binding 2: ShadowUniform
|
||||
- binding 3: BRDF LUT texture
|
||||
- binding 4: BRDF LUT sampler
|
||||
|
||||
## Shader Summary
|
||||
|
||||
### deferred_gbuffer.wgsl
|
||||
|
||||
Vertex: position → world (model * pos), normal → world (model * normal), TBN 계산, UV 전달.
|
||||
|
||||
Fragment outputs (4 targets):
|
||||
```wgsl
|
||||
struct GBufferOutput {
|
||||
@location(0) position: vec4<f32>,
|
||||
@location(1) normal: vec4<f32>,
|
||||
@location(2) albedo: vec4<f32>,
|
||||
@location(3) material: vec4<f32>,
|
||||
}
|
||||
```
|
||||
- position.xyz = world position
|
||||
- normal.xyz = TBN-mapped world normal
|
||||
- albedo.rgb = texture sample * base_color
|
||||
- material = vec4(metallic, roughness, ao, 1.0)
|
||||
|
||||
### deferred_lighting.wgsl
|
||||
|
||||
Vertex: 풀스크린 삼각형, UV 계산.
|
||||
|
||||
Fragment:
|
||||
1. G-Buffer 샘플링
|
||||
2. Cook-Torrance BRDF (기존 pbr_shader.wgsl 로직)
|
||||
3. 멀티 라이트 루프
|
||||
4. PCF 섀도우
|
||||
5. IBL ambient
|
||||
6. Reinhard 톤매핑 + 감마
|
||||
|
||||
## Test Plan
|
||||
|
||||
### gbuffer.rs
|
||||
- GBuffer 생성: 텍스처 크기 확인
|
||||
- 리사이즈: 새 크기로 재생성
|
||||
|
||||
### fullscreen_quad.rs
|
||||
- 정점 데이터: 3개 정점, 올바른 좌표
|
||||
|
||||
### 통합 (수동)
|
||||
- deferred_demo 예제: 다수 포인트 라이트 + 디퍼드 렌더링
|
||||
- G-Buffer 시각화 (디버그용: position/normal/albedo 각각 출력)
|
||||
|
||||
## Constraints
|
||||
|
||||
- max_bind_groups=4: G-Buffer pass 3개, Lighting pass 3개 사용 → 제약 내
|
||||
- MRT: wgpu는 최대 8개 color attachment 지원. 4개 사용.
|
||||
- Rgba32Float: Position에 32-bit float 사용 (정밀도 우선, 최적화는 추후)
|
||||
202
docs/superpowers/specs/2026-03-25-phase7-2-ssgi.md
Normal file
202
docs/superpowers/specs/2026-03-25-phase7-2-ssgi.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Phase 7-2: SSGI (Screen-Space Global Illumination) — Design Spec
|
||||
|
||||
## Overview
|
||||
|
||||
디퍼드 파이프라인에 SSGI 포스트 프로세싱 패스를 추가한다. SSAO 확장형으로, 반구 샘플링을 통해 Ambient Occlusion과 Color Bleeding(간접광)을 동시에 계산한다.
|
||||
|
||||
## Scope
|
||||
|
||||
- SSGI 리소스 (반구 커널, 4x4 노이즈 텍스처, 출력 텍스처)
|
||||
- SSGI 풀스크린 셰이더 (AO + indirect color 계산)
|
||||
- SSGI 파이프라인 + 바인드 그룹 레이아웃
|
||||
- Lighting Pass 수정 (SSGI 결과를 ambient에 적용)
|
||||
- deferred_demo에 SSGI 통합
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 블러 패스 (노이즈 제거용 bilateral blur — 추후 추가)
|
||||
- 반해상도 렌더링 (성능 최적화)
|
||||
- 시간적 누적 (temporal accumulation)
|
||||
- Light Probes
|
||||
|
||||
## Render Pass Flow (디퍼드 확장)
|
||||
|
||||
```
|
||||
Pass 1: G-Buffer (기존, 변경 없음)
|
||||
Pass 2: SSGI Pass (NEW) → Rgba16Float 출력
|
||||
Pass 3: Lighting Pass (수정) → SSGI 텍스처 읽어서 ambient에 적용
|
||||
```
|
||||
|
||||
## Module Structure
|
||||
|
||||
### 새 파일
|
||||
- `crates/voltex_renderer/src/ssgi.rs` — SsgiResources, SsgiUniform, 커널/노이즈 생성
|
||||
- `crates/voltex_renderer/src/ssgi_shader.wgsl` — SSGI 풀스크린 셰이더
|
||||
|
||||
### 수정 파일
|
||||
- `crates/voltex_renderer/src/deferred_pipeline.rs` — SSGI 파이프라인 + 바인드 그룹 레이아웃 추가
|
||||
- `crates/voltex_renderer/src/deferred_lighting.wgsl` — SSGI 결과 적용
|
||||
- `crates/voltex_renderer/src/lib.rs` — ssgi 모듈 등록
|
||||
- `examples/deferred_demo/src/main.rs` — SSGI 패스 추가
|
||||
|
||||
## Types
|
||||
|
||||
### SsgiUniform (128 bytes)
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Pod, Zeroable)]
|
||||
pub struct SsgiUniform {
|
||||
pub projection: [f32; 16], // view → clip
|
||||
pub view: [f32; 16], // world → view
|
||||
pub radius: f32, // 샘플링 반경 (기본 0.5)
|
||||
pub bias: f32, // depth 바이어스 (기본 0.025)
|
||||
pub intensity: f32, // AO 강도 (기본 1.0)
|
||||
pub indirect_strength: f32, // color bleeding 강도 (기본 0.5)
|
||||
}
|
||||
```
|
||||
|
||||
### SsgiResources
|
||||
|
||||
```rust
|
||||
pub struct SsgiResources {
|
||||
pub output_view: TextureView, // Rgba16Float — R=AO, G=indirect_r, B=indirect_g, A=indirect_b
|
||||
pub kernel_buffer: Buffer, // 64 * vec4 = 1024 bytes (반구 샘플)
|
||||
pub noise_view: TextureView, // 4x4 Rgba16Float (랜덤 회전 벡터)
|
||||
pub uniform_buffer: Buffer, // SsgiUniform
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
```
|
||||
|
||||
- `new(device, width, height)` — 리소스 생성, 커널/노이즈 초기화
|
||||
- `resize(device, width, height)` — 출력 텍스처 재생성
|
||||
|
||||
### 반구 커널 생성
|
||||
|
||||
64개 샘플, 반구(+z 방향) 내 랜덤 분포. 중심 가까이에 더 많은 샘플 (코사인 가중):
|
||||
```rust
|
||||
fn generate_kernel(count: usize) -> Vec<[f32; 4]> {
|
||||
// 의사 랜덤 (시드 고정)
|
||||
// 각 샘플: normalize(random_in_hemisphere) * lerp(0.1, 1.0, scale^2)
|
||||
// scale = i / count
|
||||
}
|
||||
```
|
||||
|
||||
### 4x4 노이즈 텍스처
|
||||
|
||||
16개 랜덤 회전 벡터 (xy 평면). TBN 구성 시 tangent 방향을 랜덤화하여 밴딩 방지.
|
||||
```rust
|
||||
fn generate_noise() -> Vec<[f32; 4]> {
|
||||
// 16개 vec4(random_x, random_y, 0.0, 0.0)
|
||||
}
|
||||
```
|
||||
|
||||
## SSGI Shader (ssgi_shader.wgsl)
|
||||
|
||||
### 바인드 그룹
|
||||
|
||||
**Group 0: G-Buffer**
|
||||
- binding 0: position texture (Float, non-filterable)
|
||||
- binding 1: normal texture (Float, filterable)
|
||||
- binding 2: albedo texture (Float, filterable)
|
||||
- binding 3: sampler (NonFiltering)
|
||||
|
||||
**Group 1: SSGI Data**
|
||||
- binding 0: SsgiUniform
|
||||
- binding 1: kernel buffer (storage or uniform, 64 * vec4)
|
||||
- binding 2: noise texture
|
||||
- binding 3: noise sampler
|
||||
|
||||
### 알고리즘
|
||||
|
||||
```
|
||||
@fragment
|
||||
fn fs_main(uv):
|
||||
world_pos = sample(t_position, uv)
|
||||
if length(world_pos) < 0.001: discard (background)
|
||||
|
||||
normal = sample(t_normal, uv)
|
||||
|
||||
// View space conversion
|
||||
view_pos = (ssgi.view * vec4(world_pos, 1.0)).xyz
|
||||
view_normal = normalize((ssgi.view * vec4(normal, 0.0)).xyz)
|
||||
|
||||
// Random rotation from noise (4x4 tiling)
|
||||
noise_uv = uv * vec2(width/4.0, height/4.0)
|
||||
random_vec = sample(t_noise, noise_uv).xyz
|
||||
|
||||
// Construct TBN in view space
|
||||
tangent = normalize(random_vec - view_normal * dot(random_vec, view_normal))
|
||||
bitangent = cross(view_normal, tangent)
|
||||
TBN = mat3x3(tangent, bitangent, view_normal)
|
||||
|
||||
occlusion = 0.0
|
||||
indirect = vec3(0.0)
|
||||
|
||||
for i in 0..64:
|
||||
// Sample position in view space
|
||||
sample_offset = TBN * kernel[i].xyz * ssgi.radius
|
||||
sample_pos = view_pos + sample_offset
|
||||
|
||||
// Project to screen
|
||||
clip = ssgi.projection * vec4(sample_pos, 1.0)
|
||||
screen_uv = clip.xy / clip.w * 0.5 + 0.5
|
||||
screen_uv.y = 1.0 - screen_uv.y
|
||||
|
||||
// Read actual depth at that screen position
|
||||
sample_world_pos = sample(t_position, screen_uv).xyz
|
||||
sample_view_pos = (ssgi.view * vec4(sample_world_pos, 1.0)).xyz
|
||||
|
||||
// Occlusion check
|
||||
range_check = smoothstep(0.0, 1.0, ssgi.radius / abs(view_pos.z - sample_view_pos.z))
|
||||
if sample_view_pos.z >= sample_pos.z + ssgi.bias:
|
||||
occlusion += range_check
|
||||
// Color bleeding: read albedo at occluder position
|
||||
sample_albedo = sample(t_albedo, screen_uv).rgb
|
||||
indirect += sample_albedo * range_check
|
||||
|
||||
ao = 1.0 - (occlusion / 64.0) * ssgi.intensity
|
||||
indirect = indirect / 64.0 * ssgi.indirect_strength
|
||||
|
||||
return vec4(ao, indirect)
|
||||
```
|
||||
|
||||
## Lighting Pass 수정
|
||||
|
||||
### 바인드 그룹 변경
|
||||
|
||||
기존 Group 2 (Shadow+IBL, 5 bindings)에 SSGI 출력 추가:
|
||||
- binding 5: SSGI output texture (Float, filterable)
|
||||
- binding 6: SSGI sampler
|
||||
|
||||
### 셰이더 변경
|
||||
|
||||
```wgsl
|
||||
// 기존
|
||||
let ambient = (diffuse_ibl + specular_ibl) * ao;
|
||||
|
||||
// 변경
|
||||
let ssgi_data = textureSample(t_ssgi, s_ssgi, in.uv);
|
||||
let ssgi_ao = ssgi_data.r;
|
||||
let indirect_light = ssgi_data.gba;
|
||||
let ambient = (diffuse_ibl + specular_ibl) * ao * ssgi_ao + indirect_light;
|
||||
```
|
||||
|
||||
## Bind Group Constraint (max 4)
|
||||
|
||||
**SSGI Pass:** 2 groups (0: G-Buffer, 1: SSGI data) — OK
|
||||
|
||||
**Lighting Pass:** 기존 3 groups. Group 2에 SSGI binding 추가 (5,6) — 같은 그룹 내 binding 추가이므로 group 수 변화 없음. OK.
|
||||
|
||||
## Test Plan
|
||||
|
||||
### ssgi.rs
|
||||
- generate_kernel: 64개 샘플, 모두 반구 내 (z >= 0), 정규화됨
|
||||
- generate_noise: 16개 벡터
|
||||
- SsgiResources 생성/리사이즈
|
||||
|
||||
### 통합 (수동)
|
||||
- deferred_demo에서 SSGI ON/OFF 비교
|
||||
- 구석/틈에서 AO 어두워짐 확인
|
||||
- 밝은 물체 근처에서 color bleeding 확인
|
||||
197
docs/superpowers/specs/2026-03-25-phase7-3-rt-shadows.md
Normal file
197
docs/superpowers/specs/2026-03-25-phase7-3-rt-shadows.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Phase 7-3: RT Shadows — Design Spec
|
||||
|
||||
## Overview
|
||||
|
||||
wgpu의 EXPERIMENTAL_RAY_QUERY를 활용하여 하드웨어 레이트레이싱 기반 그림자를 구현한다. 기존 PCF shadow map을 대체하는 정확한 그림자.
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
- GPU: RTX 20xx+ / RDNA2+ (ray query 지원)
|
||||
- wgpu Features: EXPERIMENTAL_RAY_QUERY
|
||||
- 검증 완료: RTX 4050 Laptop GPU, Vulkan backend
|
||||
|
||||
## Scope
|
||||
|
||||
- BLAS/TLAS acceleration structure 생성 관리
|
||||
- RT Shadow 컴퓨트 셰이더 (ray query로 directional light shadow)
|
||||
- RT Shadow 출력 텍스처 (R8Unorm)
|
||||
- Lighting Pass에 RT shadow 통합
|
||||
- deferred_demo에 RT shadow 적용
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- RT Reflections
|
||||
- RT AO
|
||||
- Point/Spot light RT shadows
|
||||
- Soft RT shadows (multi-ray)
|
||||
- BLAS 재빌드 (정적 지오메트리만)
|
||||
|
||||
## Render Pass Flow (디퍼드 확장)
|
||||
|
||||
```
|
||||
Pass 1: G-Buffer (변경 없음)
|
||||
Pass 2: SSGI (변경 없음)
|
||||
Pass 3: RT Shadow (NEW) — 컴퓨트 셰이더, ray query로 shadow 텍스처 출력
|
||||
Pass 4: Lighting (수정) — RT shadow 텍스처 사용
|
||||
```
|
||||
|
||||
## Module Structure
|
||||
|
||||
### 새 파일
|
||||
- `crates/voltex_renderer/src/rt_accel.rs` — RtAccel (BLAS/TLAS 관리)
|
||||
- `crates/voltex_renderer/src/rt_shadow.rs` — RtShadowResources + 컴퓨트 파이프라인
|
||||
- `crates/voltex_renderer/src/rt_shadow_shader.wgsl` — RT shadow 컴퓨트 셰이더
|
||||
|
||||
### 수정 파일
|
||||
- `crates/voltex_renderer/src/deferred_pipeline.rs` — lighting shadow bind group에 RT shadow 텍스처 추가
|
||||
- `crates/voltex_renderer/src/deferred_lighting.wgsl` — RT shadow 사용
|
||||
- `crates/voltex_renderer/src/lib.rs` — 새 모듈 등록
|
||||
- `examples/deferred_demo/src/main.rs` — RT shadow 통합
|
||||
|
||||
## Types
|
||||
|
||||
### RtAccel
|
||||
|
||||
```rust
|
||||
pub struct RtAccel {
|
||||
pub blas_list: Vec<wgpu::Blas>,
|
||||
pub tlas_package: wgpu::TlasPackage,
|
||||
}
|
||||
```
|
||||
|
||||
**Methods:**
|
||||
- `new(device, meshes: &[(vertex_buffer, index_buffer, vertex_count, index_count)], transforms: &[[f32; 12]])` — BLAS 빌드, TLAS 구성
|
||||
- BLAS: 메시별 삼각형 지오메트리 (BlasTriangleGeometry)
|
||||
- TLAS: 인스턴스 배열 (TlasInstance with transform, blas index)
|
||||
|
||||
**BLAS 생성:**
|
||||
1. BlasTriangleGeometrySizeDescriptor (vertex_count, index_count, vertex_format: Float32x3)
|
||||
2. device.create_blas(size, flags: PREFER_FAST_TRACE)
|
||||
3. encoder.build_acceleration_structures with BlasBuildEntry (vertex_buffer, index_buffer, geometry)
|
||||
|
||||
**TLAS 생성:**
|
||||
1. device.create_tlas(max_instances: transform_count)
|
||||
2. TlasPackage에 TlasInstance 채움 (transform [3x4 row-major], blas_index, mask: 0xFF)
|
||||
3. encoder.build_acceleration_structures with tlas_package
|
||||
|
||||
### RtShadowResources
|
||||
|
||||
```rust
|
||||
pub struct RtShadowResources {
|
||||
pub shadow_view: TextureView, // R8Unorm, STORAGE_BINDING
|
||||
pub shadow_texture: Texture,
|
||||
pub uniform_buffer: Buffer, // RtShadowUniform
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
```
|
||||
|
||||
### RtShadowUniform
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
pub struct RtShadowUniform {
|
||||
pub light_direction: [f32; 3],
|
||||
pub _pad0: f32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub _pad1: [u32; 2],
|
||||
}
|
||||
```
|
||||
|
||||
## RT Shadow Compute Shader
|
||||
|
||||
### 바인드 그룹
|
||||
|
||||
**Group 0: G-Buffer**
|
||||
- binding 0: position texture (Float, non-filterable)
|
||||
- binding 1: normal texture (Float, filterable)
|
||||
|
||||
**Group 1: RT Data**
|
||||
- binding 0: TLAS (acceleration_structure)
|
||||
- binding 1: RT shadow output (storage texture, r32float, write)
|
||||
- binding 2: RtShadowUniform
|
||||
|
||||
### 셰이더 로직
|
||||
|
||||
```wgsl
|
||||
@compute @workgroup_size(8, 8)
|
||||
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
||||
if id.x >= uniforms.width || id.y >= uniforms.height { return; }
|
||||
|
||||
let world_pos = textureLoad(t_position, id.xy, 0).xyz;
|
||||
|
||||
// Skip background
|
||||
if dot(world_pos, world_pos) < 0.001 {
|
||||
textureStore(t_shadow_out, id.xy, vec4(1.0));
|
||||
return;
|
||||
}
|
||||
|
||||
let normal = normalize(textureLoad(t_normal, id.xy, 0).xyz * 2.0 - 1.0);
|
||||
let ray_origin = world_pos + normal * 0.01; // bias off surface
|
||||
let ray_dir = normalize(-uniforms.light_direction);
|
||||
|
||||
var rq: ray_query;
|
||||
rayQueryInitialize(&rq, tlas, RAY_FLAG_TERMINATE_ON_FIRST_HIT,
|
||||
0xFFu, ray_origin, 0.001, ray_dir, 1000.0);
|
||||
rayQueryProceed(&rq);
|
||||
|
||||
var shadow = 1.0; // lit by default
|
||||
if rayQueryGetCommittedIntersectionType(&rq) != RAY_QUERY_COMMITTED_INTERSECTION_NONE {
|
||||
shadow = 0.0; // occluded
|
||||
}
|
||||
|
||||
textureStore(t_shadow_out, id.xy, vec4(shadow, 0.0, 0.0, 0.0));
|
||||
}
|
||||
```
|
||||
|
||||
## Lighting Pass 수정
|
||||
|
||||
RT shadow 텍스처를 기존 shadow_factor 대신 사용:
|
||||
|
||||
```wgsl
|
||||
// 기존: let shadow_factor = calculate_shadow(world_pos);
|
||||
// 변경: RT shadow map에서 직접 읽기
|
||||
let rt_shadow = textureSample(t_rt_shadow, s_rt_shadow, uv).r;
|
||||
let shadow_factor = rt_shadow;
|
||||
```
|
||||
|
||||
기존 PCF shadow map 관련 바인딩은 유지하되 사용하지 않음 (호환성).
|
||||
RT shadow 텍스처를 Group 2의 추가 바인딩(7, 8)으로 추가.
|
||||
|
||||
## Device Creation 변경
|
||||
|
||||
RT feature를 요청해야 함:
|
||||
```rust
|
||||
let (device, queue) = adapter.request_device(&DeviceDescriptor {
|
||||
required_features: Features::EXPERIMENTAL_RAY_QUERY,
|
||||
..
|
||||
}).await;
|
||||
```
|
||||
|
||||
기존 GpuContext::new()는 features를 요청하지 않으므로, deferred_demo에서 직접 device를 생성하거나 GpuContext에 optional features 파라미터를 추가.
|
||||
|
||||
## Bind Group Details
|
||||
|
||||
### RT Shadow Compute
|
||||
|
||||
**Group 0:**
|
||||
- binding 0: position texture (texture_2d<f32>)
|
||||
- binding 1: normal texture (texture_2d<f32>)
|
||||
|
||||
**Group 1:**
|
||||
- binding 0: acceleration_structure (TLAS)
|
||||
- binding 1: storage texture (r32float, write)
|
||||
- binding 2: uniform buffer (RtShadowUniform)
|
||||
|
||||
### Lighting Pass Group 2 (확장)
|
||||
|
||||
기존 7 bindings (0-6: shadow+IBL+SSGI) + 추가:
|
||||
- binding 7: RT shadow texture (Float, filterable)
|
||||
- binding 8: RT shadow sampler (Filtering)
|
||||
|
||||
## Test Plan
|
||||
|
||||
- rt_accel.rs: 빌드 확인만 (GPU 의존)
|
||||
- rt_shadow.rs: RtShadowUniform 크기, 리소스 생성
|
||||
- 통합: deferred_demo에서 RT shadow ON, 기존 PCF OFF → 날카로운 그림자 확인
|
||||
Reference in New Issue
Block a user