New WGSL files:
mask.wgsl Sun-cone × sky-alpha mask at ¼ resolution. Marks sky
pixels (scene_color.a == 0) that fall within a tight
cone around the sun direction. Sun direction derived
from scene time via the injected DAY_PERIOD /
SUN_OFFSET constants — same source as sim::lighting,
so visual rays align with the mechanical sun.
shafts.wgsl Radial blur at ¼ resolution. Projects the sun
direction-at-infinity to screen space via view_proj,
steps 32 samples from each pixel toward the
sun_screen_pos accumulating mask intensity with
exponential decay, outputs sun-tinted ray color.
shader.wgsl:
- fs_sky now writes alpha = 0 so the mask pass can identify sky
pixels without a separate occluder pass. Terrain / outline /
remote-player pipelines continue writing alpha = 1.
post.wgsl (rewritten):
- Reads scene_color + shafts.
- Cheap edge-aware FXAA (5-tap diagonal blur, blends toward neighbor
average where luminance gradient exceeds threshold). Catches the
axis-aligned staircase aliasing that voxel games produce.
- Adds shafts additively before tonemap so rays go through the
filmic curve and don't blow out.
render/scene_target.rs:
- create_image_bind_group (single texture + sampler) — used by
mask pass (binds scene_color) and shafts pass (binds mask_view).
- create_composite_bind_group (scene + shafts + sampler) — used by
the final post pass.
- create_quarter_res_view (¹⁄₁₆ fillrate, RENDER_ATTACHMENT +
TEXTURE_BINDING) — used for both mask and shafts targets.
render/pipelines.rs:
- image_bgl / composite_bgl as separate layouts.
- fullscreen_pipeline factory replaces the post-specific one;
takes vs/fs entry-point names so mask, shafts, and post all
build through the same shape.
render/mod.rs:
- Renderer grows mask_view, shafts_view, image_bgl, composite_bgl,
mask_pipeline, shafts_pipeline, mask_bg, shafts_bg fields. The
old single-pass post_pipeline becomes the final composite pass.
- render() now does scene → mask → shafts → post (each in its own
encoder block).
- resize() recreates all three render targets and all three bind
groups in the right order.
Tests: 63 passing (added 2 for mask/shafts source assembly). Native
+ wasm release clean.
Visual change: when you face roughly toward the sun and there's
geometry blocking the disc, you'll see warm light shafts radiating
outward. FXAA softens the worst pixel-step edges. Tonemap (from
Round A) is now the final step of the new pipeline.
129 lines
4.2 KiB
Rust
129 lines
4.2 KiB
Rust
//! Off-screen render targets: depth buffer, scene-color HDR texture,
|
|
//! and the post-pass bind group that samples scene-color back into the
|
|
//! surface. All factories take device + dimensions and return a fresh
|
|
//! resource — no hidden mutation. Renderer::resize calls these to
|
|
//! recreate after a surface change.
|
|
use wgpu::{Device, Sampler, TextureFormat, TextureView};
|
|
|
|
pub fn create_depth_view(device: &Device, w: u32, h: u32) -> TextureView {
|
|
let tex = device.create_texture(&wgpu::TextureDescriptor {
|
|
label: Some("depth"),
|
|
size: wgpu::Extent3d {
|
|
width: w.max(1),
|
|
height: h.max(1),
|
|
depth_or_array_layers: 1,
|
|
},
|
|
mip_level_count: 1,
|
|
sample_count: 1,
|
|
dimension: wgpu::TextureDimension::D2,
|
|
format: wgpu::TextureFormat::Depth32Float,
|
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
view_formats: &[],
|
|
});
|
|
tex.create_view(&wgpu::TextureViewDescriptor::default())
|
|
}
|
|
|
|
/// Off-screen color target the world is rendered into. Same format as
|
|
/// the surface so the post pass can sample it and write the result back
|
|
/// without any conversion cost.
|
|
pub fn create_scene_color_view(
|
|
device: &Device,
|
|
w: u32,
|
|
h: u32,
|
|
format: TextureFormat,
|
|
) -> TextureView {
|
|
let tex = device.create_texture(&wgpu::TextureDescriptor {
|
|
label: Some("scene color"),
|
|
size: wgpu::Extent3d {
|
|
width: w.max(1),
|
|
height: h.max(1),
|
|
depth_or_array_layers: 1,
|
|
},
|
|
mip_level_count: 1,
|
|
sample_count: 1,
|
|
dimension: wgpu::TextureDimension::D2,
|
|
format,
|
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
|
view_formats: &[],
|
|
});
|
|
tex.create_view(&wgpu::TextureViewDescriptor::default())
|
|
}
|
|
|
|
/// One-image bind group: a single texture + a sampler. Used by the
|
|
/// mask pass (binds scene_color) and the shafts pass (binds the mask).
|
|
pub fn create_image_bind_group(
|
|
device: &Device,
|
|
layout: &wgpu::BindGroupLayout,
|
|
image: &TextureView,
|
|
sampler: &Sampler,
|
|
) -> wgpu::BindGroup {
|
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
label: Some("image bg"),
|
|
layout,
|
|
entries: &[
|
|
wgpu::BindGroupEntry {
|
|
binding: 0,
|
|
resource: wgpu::BindingResource::TextureView(image),
|
|
},
|
|
wgpu::BindGroupEntry {
|
|
binding: 1,
|
|
resource: wgpu::BindingResource::Sampler(sampler),
|
|
},
|
|
],
|
|
})
|
|
}
|
|
|
|
/// Composite bind group for the final post pass: scene_color + shafts
|
|
/// + shared sampler.
|
|
pub fn create_composite_bind_group(
|
|
device: &Device,
|
|
layout: &wgpu::BindGroupLayout,
|
|
scene: &TextureView,
|
|
shafts: &TextureView,
|
|
sampler: &Sampler,
|
|
) -> wgpu::BindGroup {
|
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
label: Some("composite bg"),
|
|
layout,
|
|
entries: &[
|
|
wgpu::BindGroupEntry {
|
|
binding: 0,
|
|
resource: wgpu::BindingResource::TextureView(scene),
|
|
},
|
|
wgpu::BindGroupEntry {
|
|
binding: 1,
|
|
resource: wgpu::BindingResource::TextureView(shafts),
|
|
},
|
|
wgpu::BindGroupEntry {
|
|
binding: 2,
|
|
resource: wgpu::BindingResource::Sampler(sampler),
|
|
},
|
|
],
|
|
})
|
|
}
|
|
|
|
/// Quarter-resolution render target for the god-rays mask and shafts
|
|
/// passes. ¼ dimensions = ¹⁄₁₆ fillrate of full screen — cheap enough
|
|
/// to run 32-sample radial blur each frame.
|
|
pub fn create_quarter_res_view(
|
|
device: &Device,
|
|
w: u32,
|
|
h: u32,
|
|
format: TextureFormat,
|
|
) -> TextureView {
|
|
let tex = device.create_texture(&wgpu::TextureDescriptor {
|
|
label: Some("¼-res shafts target"),
|
|
size: wgpu::Extent3d {
|
|
width: (w / 4).max(1),
|
|
height: (h / 4).max(1),
|
|
depth_or_array_layers: 1,
|
|
},
|
|
mip_level_count: 1,
|
|
sample_count: 1,
|
|
dimension: wgpu::TextureDimension::D2,
|
|
format,
|
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
|
view_formats: &[],
|
|
});
|
|
tex.create_view(&wgpu::TextureViewDescriptor::default())
|
|
}
|