Skip to main content

engvis_renderer/
lighting.rs

1use wgpu::util::DeviceExt;
2use engvis_core::LightingEnvironment;
3
4pub const MAX_DIR_LIGHTS: usize = 4;
5pub const MAX_POINT_LIGHTS: usize = 16;
6
7/// GPU layout for lighting uniform data
8#[repr(C)]
9#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
10pub struct LightingUniforms {
11    pub ambient_color: [f32; 4],
12    pub dir_light_count: u32,
13    pub point_light_count: u32,
14    pub _pad0: u32,
15    pub _pad1: u32,
16}
17
18/// GPU layout for a single directional light
19#[repr(C)]
20#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
21pub struct DirectionalLightData {
22    pub direction: [f32; 4],
23    pub color: [f32; 4],
24}
25
26/// GPU layout for a single point light
27#[repr(C)]
28#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
29pub struct PointLightData {
30    pub position: [f32; 4],
31    pub color: [f32; 4],
32}
33
34pub struct LightingBuffer {
35    pub uniform_buffer: wgpu::Buffer,
36    pub dir_light_buffer: wgpu::Buffer,
37    pub point_light_buffer: wgpu::Buffer,
38    pub bind_group: wgpu::BindGroup,
39    pub bind_group_layout: wgpu::BindGroupLayout,
40}
41
42impl LightingBuffer {
43    pub fn new(device: &wgpu::Device, lighting: &LightingEnvironment) -> Self {
44        let uniform_data = Self::build_uniforms(lighting);
45        let dir_lights = Self::build_dir_lights(lighting);
46        let point_lights = Self::build_point_lights(lighting);
47
48        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
49            label: Some("Lighting Uniform Buffer"),
50            contents: bytemuck::cast_slice(&[uniform_data]),
51            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
52        });
53
54        let dir_light_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
55            label: Some("Dir Light Storage Buffer"),
56            contents: bytemuck::cast_slice(&dir_lights),
57            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
58        });
59
60        let point_light_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
61            label: Some("Point Light Storage Buffer"),
62            contents: bytemuck::cast_slice(&point_lights),
63            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
64        });
65
66        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
67            label: Some("Lighting Bind Group Layout"),
68            entries: &[
69                wgpu::BindGroupLayoutEntry {
70                    binding: 0,
71                    visibility: wgpu::ShaderStages::FRAGMENT,
72                    ty: wgpu::BindingType::Buffer {
73                        ty: wgpu::BufferBindingType::Uniform,
74                        has_dynamic_offset: false,
75                        min_binding_size: None,
76                    },
77                    count: None,
78                },
79                wgpu::BindGroupLayoutEntry {
80                    binding: 1,
81                    visibility: wgpu::ShaderStages::FRAGMENT,
82                    ty: wgpu::BindingType::Buffer {
83                        ty: wgpu::BufferBindingType::Storage { read_only: true },
84                        has_dynamic_offset: false,
85                        min_binding_size: None,
86                    },
87                    count: None,
88                },
89                wgpu::BindGroupLayoutEntry {
90                    binding: 2,
91                    visibility: wgpu::ShaderStages::FRAGMENT,
92                    ty: wgpu::BindingType::Buffer {
93                        ty: wgpu::BufferBindingType::Storage { read_only: true },
94                        has_dynamic_offset: false,
95                        min_binding_size: None,
96                    },
97                    count: None,
98                },
99            ],
100        });
101
102        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
103            label: Some("Lighting Bind Group"),
104            layout: &bind_group_layout,
105            entries: &[
106                wgpu::BindGroupEntry {
107                    binding: 0,
108                    resource: uniform_buffer.as_entire_binding(),
109                },
110                wgpu::BindGroupEntry {
111                    binding: 1,
112                    resource: dir_light_buffer.as_entire_binding(),
113                },
114                wgpu::BindGroupEntry {
115                    binding: 2,
116                    resource: point_light_buffer.as_entire_binding(),
117                },
118            ],
119        });
120
121        Self {
122            uniform_buffer,
123            dir_light_buffer,
124            point_light_buffer,
125            bind_group,
126            bind_group_layout,
127        }
128    }
129
130    pub fn update(&self, queue: &wgpu::Queue, lighting: &LightingEnvironment) {
131        let uniforms = Self::build_uniforms(lighting);
132        queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
133
134        let dir_lights = Self::build_dir_lights(lighting);
135        queue.write_buffer(&self.dir_light_buffer, 0, bytemuck::cast_slice(&dir_lights));
136
137        let point_lights = Self::build_point_lights(lighting);
138        queue.write_buffer(
139            &self.point_light_buffer,
140            0,
141            bytemuck::cast_slice(&point_lights),
142        );
143    }
144
145    fn build_uniforms(lighting: &LightingEnvironment) -> LightingUniforms {
146        LightingUniforms {
147            ambient_color: [
148                lighting.ambient.color[0],
149                lighting.ambient.color[1],
150                lighting.ambient.color[2],
151                lighting.ambient.intensity,
152            ],
153            dir_light_count: lighting.directional_lights.len().min(MAX_DIR_LIGHTS) as u32,
154            point_light_count: lighting.point_lights.len().min(MAX_POINT_LIGHTS) as u32,
155            _pad0: 0,
156            _pad1: 0,
157        }
158    }
159
160    fn build_dir_lights(lighting: &LightingEnvironment) -> Vec<DirectionalLightData> {
161        let mut lights = vec![
162            DirectionalLightData {
163                direction: [0.0; 4],
164                color: [0.0; 4],
165            };
166            MAX_DIR_LIGHTS
167        ];
168        for (i, l) in lighting
169            .directional_lights
170            .iter()
171            .take(MAX_DIR_LIGHTS)
172            .enumerate()
173        {
174            lights[i] = DirectionalLightData {
175                direction: [l.direction.x, l.direction.y, l.direction.z, l.intensity],
176                color: [l.color[0], l.color[1], l.color[2], 0.0],
177            };
178        }
179        lights
180    }
181
182    fn build_point_lights(lighting: &LightingEnvironment) -> Vec<PointLightData> {
183        let mut lights = vec![
184            PointLightData {
185                position: [0.0; 4],
186                color: [0.0; 4],
187            };
188            MAX_POINT_LIGHTS
189        ];
190        for (i, l) in lighting
191            .point_lights
192            .iter()
193            .take(MAX_POINT_LIGHTS)
194            .enumerate()
195        {
196            lights[i] = PointLightData {
197                position: [l.position.x, l.position.y, l.position.z, l.range],
198                color: [l.color[0], l.color[1], l.color[2], l.intensity],
199            };
200        }
201        lights
202    }
203}