1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! Sky / atmosphere render pipeline.
//!
//! A fullscreen-triangle pipeline that renders a Rayleigh + Mie
//! atmospheric scattering sky behind all scene geometry. The sky
//! is drawn at depth = 1.0 with depth-test but no depth-write so
//! scene geometry always wins.
use bytemuck::{Pod, Zeroable};
/// GPU-side uniform for the sky shader.
///
/// Layout (std140):
/// - `inv_view_proj`: mat4x4<f32>
/// - `sun_dir`: vec4<f32> (xyz = direction toward sun, w = intensity)
/// - `rayleigh_tint`: vec4<f32> (rgb = tint, w = sky_enabled 1/0)
/// - `mie_tint`: vec4<f32> (rgb = tint, w = opacity)
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct SkyUniform {
/// Inverse view-projection matrix for ray unprojection.
pub inv_view_proj: [[f32; 4]; 4],
/// Sun direction (xyz) and intensity (w).
pub sun_dir: [f32; 4],
/// Rayleigh tint (rgb) and sky-enabled flag (w).
pub rayleigh_tint: [f32; 4],
/// Mie tint (rgb) and opacity (w).
pub mie_tint: [f32; 4],
}
impl Default for SkyUniform {
fn default() -> Self {
Self {
inv_view_proj: glam::Mat4::IDENTITY.to_cols_array_2d(),
sun_dir: [0.0, 0.707, 0.707, 0.0],
rayleigh_tint: [1.0, 1.0, 1.0, 0.0], // w=0 → disabled
mie_tint: [1.0, 1.0, 1.0, 1.0],
}
}
}
/// The sky render pipeline.
pub struct SkyPipeline {
/// Render pipeline for the fullscreen sky triangle.
pub pipeline: wgpu::RenderPipeline,
/// Bind group layout for the sky uniform (group 0).
pub uniform_bind_group_layout: wgpu::BindGroupLayout,
}
impl SkyPipeline {
/// Create the sky pipeline.
pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("sky_shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/sky.wgsl").into()),
});
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("sky_uniform_bgl"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
std::mem::size_of::<SkyUniform>() as u64
),
},
count: None,
}],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("sky_pipeline_layout"),
bind_group_layouts: &[&uniform_bind_group_layout],
push_constant_ranges: &[],
});
// Alpha blending so the sky can be partially transparent.
let blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("sky_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[], // fullscreen triangle from vertex_index
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: Some(blend),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
// Depth: test against existing (LessEqual) but do NOT write.
// The sky triangle sits at z=1.0 (far plane) so only pixels
// not covered by scene geometry are shaded.
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::LessEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
Self {
pipeline,
uniform_bind_group_layout,
}
}
}