295 lines
10 KiB
Markdown
295 lines
10 KiB
Markdown
# PBR Texture Maps Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Add metallic/roughness/AO (ORM) and emissive texture map sampling to PBR shaders, extending bind group 1 from 4 to 8 bindings.
|
|
|
|
**Architecture:** Extend `pbr_texture_bind_group_layout` with 4 new bindings (ORM texture+sampler, emissive texture+sampler). Update forward PBR shader and deferred G-Buffer shader. Default 1x1 white/black textures when maps not provided. MaterialUniform values become multipliers for texture values.
|
|
|
|
**Tech Stack:** wgpu 28.0, WGSL shaders. No new Rust crates.
|
|
|
|
---
|
|
|
|
### Task 1: Texture Utility Additions
|
|
|
|
**Files:**
|
|
- Modify: `crates/voltex_renderer/src/texture.rs`
|
|
|
|
- [ ] **Step 1: Write test for black_1x1**
|
|
|
|
```rust
|
|
#[test]
|
|
fn test_black_1x1_exists() {
|
|
// This is a compile/API test — GPU tests need device
|
|
// We verify the function signature exists and the module compiles
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Add black_1x1 and extended layout functions**
|
|
|
|
Add to `texture.rs`:
|
|
|
|
```rust
|
|
/// 1x1 black texture for emissive default (no emission).
|
|
pub fn black_1x1(
|
|
device: &wgpu::Device,
|
|
queue: &wgpu::Queue,
|
|
layout: &wgpu::BindGroupLayout,
|
|
) -> GpuTexture {
|
|
Self::from_rgba(device, queue, 1, 1, &[0, 0, 0, 255], layout)
|
|
}
|
|
|
|
/// Extended PBR texture layout: albedo + normal + ORM + emissive (8 bindings).
|
|
pub fn pbr_full_texture_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
label: Some("PBR Full Texture Bind Group Layout"),
|
|
entries: &[
|
|
// 0-1: albedo (existing)
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 0,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Texture {
|
|
multisampled: false,
|
|
view_dimension: wgpu::TextureViewDimension::D2,
|
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
|
},
|
|
count: None,
|
|
},
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 1,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
|
count: None,
|
|
},
|
|
// 2-3: normal map (existing)
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 2,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Texture {
|
|
multisampled: false,
|
|
view_dimension: wgpu::TextureViewDimension::D2,
|
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
|
},
|
|
count: None,
|
|
},
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 3,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
|
count: None,
|
|
},
|
|
// 4-5: ORM (AO/Roughness/Metallic) — NEW
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 4,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Texture {
|
|
multisampled: false,
|
|
view_dimension: wgpu::TextureViewDimension::D2,
|
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
|
},
|
|
count: None,
|
|
},
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 5,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
|
count: None,
|
|
},
|
|
// 6-7: Emissive — NEW
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 6,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Texture {
|
|
multisampled: false,
|
|
view_dimension: wgpu::TextureViewDimension::D2,
|
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
|
},
|
|
count: None,
|
|
},
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 7,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
|
count: None,
|
|
},
|
|
],
|
|
})
|
|
}
|
|
|
|
/// Create bind group for full PBR textures (albedo + normal + ORM + emissive).
|
|
pub fn create_pbr_full_texture_bind_group(
|
|
device: &wgpu::Device,
|
|
layout: &wgpu::BindGroupLayout,
|
|
albedo_view: &wgpu::TextureView,
|
|
albedo_sampler: &wgpu::Sampler,
|
|
normal_view: &wgpu::TextureView,
|
|
normal_sampler: &wgpu::Sampler,
|
|
orm_view: &wgpu::TextureView,
|
|
orm_sampler: &wgpu::Sampler,
|
|
emissive_view: &wgpu::TextureView,
|
|
emissive_sampler: &wgpu::Sampler,
|
|
) -> wgpu::BindGroup {
|
|
// ... 8 entries at bindings 0-7
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Add exports to lib.rs**
|
|
|
|
```rust
|
|
pub use texture::{pbr_full_texture_bind_group_layout, create_pbr_full_texture_bind_group};
|
|
```
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add crates/voltex_renderer/src/texture.rs crates/voltex_renderer/src/lib.rs
|
|
git commit -m "feat(renderer): add ORM and emissive texture bind group layout"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: Update PBR Forward Shader
|
|
|
|
**Files:**
|
|
- Modify: `crates/voltex_renderer/src/pbr_shader.wgsl`
|
|
|
|
- [ ] **Step 1: Add ORM and emissive bindings**
|
|
|
|
Add after existing normal map bindings:
|
|
|
|
```wgsl
|
|
@group(1) @binding(4) var t_orm: texture_2d<f32>;
|
|
@group(1) @binding(5) var s_orm: sampler;
|
|
@group(1) @binding(6) var t_emissive: texture_2d<f32>;
|
|
@group(1) @binding(7) var s_emissive: sampler;
|
|
```
|
|
|
|
- [ ] **Step 2: Update fragment shader to sample ORM**
|
|
|
|
Replace lines that read material params directly:
|
|
|
|
```wgsl
|
|
// Before:
|
|
// let metallic = material.metallic;
|
|
// let roughness = material.roughness;
|
|
// let ao = material.ao;
|
|
|
|
// After:
|
|
let orm_sample = textureSample(t_orm, s_orm, in.uv);
|
|
let ao = orm_sample.r * material.ao;
|
|
let roughness = orm_sample.g * material.roughness;
|
|
let metallic = orm_sample.b * material.metallic;
|
|
```
|
|
|
|
- [ ] **Step 3: Add emissive to final color**
|
|
|
|
```wgsl
|
|
let emissive = textureSample(t_emissive, s_emissive, in.uv).rgb;
|
|
|
|
// Before tone mapping:
|
|
var color = ambient + Lo + emissive;
|
|
```
|
|
|
|
- [ ] **Step 4: Verify existing examples still compile**
|
|
|
|
The existing examples use `pbr_texture_bind_group_layout` (4 bindings). They should continue to work with the OLD layout — the new `pbr_full_texture_bind_group_layout` is a separate function. The shader needs to match whichever layout is used.
|
|
|
|
**Strategy:** Create a NEW shader variant `pbr_shader_full.wgsl` with ORM+emissive support, keeping the old shader untouched for backward compatibility. OR add a preprocessor approach.
|
|
|
|
**Simpler approach:** Just update `pbr_shader.wgsl` to include the new bindings AND update ALL examples that use it to use the full layout with default white/black textures. This avoids shader duplication.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add crates/voltex_renderer/src/pbr_shader.wgsl
|
|
git commit -m "feat(renderer): add ORM and emissive texture sampling to PBR shader"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: Update Deferred G-Buffer Shader
|
|
|
|
**Files:**
|
|
- Modify: `crates/voltex_renderer/src/deferred_gbuffer.wgsl`
|
|
- Modify: `crates/voltex_renderer/src/deferred_lighting.wgsl`
|
|
|
|
- [ ] **Step 1: Add ORM/emissive bindings to G-Buffer shader**
|
|
|
|
Same pattern as forward shader — add bindings 4-7, sample ORM for metallic/roughness/ao, store in material_data output.
|
|
|
|
- [ ] **Step 2: Store emissive in G-Buffer**
|
|
|
|
Option: Pack emissive into existing G-Buffer outputs or add a 5th MRT.
|
|
|
|
**Recommended:** Store emissive in material_data.a (was padding=1.0). Simple, no extra MRT.
|
|
|
|
```wgsl
|
|
// G-Buffer output location(3): [metallic, roughness, ao, emissive_intensity]
|
|
// Emissive color stored as luminance for simplicity, or use location(2).a
|
|
out.material_data = vec4<f32>(metallic, roughness, ao, emissive_luminance);
|
|
```
|
|
|
|
Actually simpler: add emissive directly in the lighting pass as a texture sample pass-through. But that requires the emissive texture in the lighting pass too.
|
|
|
|
**Simplest approach:** Write emissive to albedo output additively, since emissive bypasses lighting.
|
|
|
|
```wgsl
|
|
// In deferred_gbuffer.wgsl:
|
|
let emissive = textureSample(t_emissive, s_emissive, in.uv).rgb;
|
|
// Store emissive luminance in material_data.w (was 1.0 padding)
|
|
let emissive_lum = dot(emissive, vec3<f32>(0.299, 0.587, 0.114));
|
|
out.material_data = vec4<f32>(metallic, roughness, ao, emissive_lum);
|
|
```
|
|
|
|
Then in `deferred_lighting.wgsl`, read `material_data.w` as emissive intensity and add `albedo * emissive_lum` to final color.
|
|
|
|
- [ ] **Step 3: Update deferred_lighting.wgsl**
|
|
|
|
```wgsl
|
|
let emissive_lum = textureSample(g_material, s_gbuffer, uv).w;
|
|
// After lighting calculation:
|
|
color += albedo * emissive_lum;
|
|
```
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add crates/voltex_renderer/src/deferred_gbuffer.wgsl crates/voltex_renderer/src/deferred_lighting.wgsl
|
|
git commit -m "feat(renderer): add ORM and emissive to deferred rendering pipeline"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: Update Examples + Pipeline Creation
|
|
|
|
**Files:**
|
|
- Modify: `crates/voltex_renderer/src/pbr_pipeline.rs`
|
|
- Modify: `examples/pbr_demo/src/main.rs`
|
|
- Modify: `examples/ibl_demo/src/main.rs`
|
|
- Modify: `crates/voltex_renderer/src/deferred_pipeline.rs`
|
|
|
|
- [ ] **Step 1: Update examples to use full texture layout**
|
|
|
|
In each example that uses `pbr_texture_bind_group_layout`:
|
|
- Switch to `pbr_full_texture_bind_group_layout`
|
|
- Create default ORM texture (white_1x1 = ao=1, roughness=1, metallic=1 — but material multipliers control actual values)
|
|
- Create default emissive texture (black_1x1 = no emission)
|
|
- Pass all 4 texture pairs to `create_pbr_full_texture_bind_group`
|
|
|
|
- [ ] **Step 2: Update deferred_pipeline.rs**
|
|
|
|
Update gbuffer pipeline layout to use the full texture layout.
|
|
|
|
- [ ] **Step 3: Build and test**
|
|
|
|
```bash
|
|
cargo build --workspace
|
|
cargo test --workspace
|
|
```
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git commit -m "feat(renderer): update examples and pipelines for full PBR texture maps"
|
|
```
|