Skip to main content

lego/
lego.rs

1use cuneus::compute::*;
2use cuneus::prelude::*;
3use log::error;
4
5cuneus::uniform_params! {
6    struct LegoParams {
7    brick_scale: f32,
8    lightdir_x: f32,
9    lightdir_y: f32,
10    grain: f32,
11    gamma: f32,
12    shadow_str: f32,
13    shadow_dist: f32,
14    ao_str: f32,
15    spec_pow: f32,
16    spec_str: f32,
17    edge_enh: f32,
18    stud_h: f32,
19    base_h: f32,
20    rim_str: f32,
21    res_scale_mult: f32,
22    stud_h_mult: f32,
23    light_r: f32,
24    light_g: f32,
25    light_b: f32,
26    depth_scale: f32,
27    edge_blend: f32,
28    _pad: f32,
29    _pad2: f32,
30    _pad3: f32}
31}
32
33struct LegoShader {
34    base: RenderKit,
35    compute_shader: ComputeShader,
36    current_params: LegoParams}
37
38impl ShaderManager for LegoShader {
39    fn init(core: &Core) -> Self {
40        let initial_params = LegoParams {
41            brick_scale: 0.01,
42            lightdir_x: 0.8,
43            lightdir_y: 0.6,
44            grain: 0.04,
45            gamma: 0.4,
46            shadow_str: 0.5,
47            shadow_dist: 1.25,
48            ao_str: 0.85,
49            spec_pow: 12.0,
50            spec_str: 0.3,
51            edge_enh: 0.15,
52            stud_h: 0.045,
53            base_h: 0.2,
54            rim_str: 0.5,
55            res_scale_mult: 0.2,
56            stud_h_mult: 1.0,
57            light_r: 0.8,
58            light_g: 0.75,
59            light_b: 0.7,
60            depth_scale: 0.85,
61            edge_blend: 0.3,
62            _pad: 0.0,
63            _pad2: 0.0,
64            _pad3: 0.0};
65        let base = RenderKit::new(core);
66
67        let config = ComputeShader::builder()
68            .with_entry_point("main_image")
69            .with_channels(1)
70            .with_custom_uniforms::<LegoParams>()
71            .with_workgroup_size([16, 16, 1])
72            .with_texture_format(COMPUTE_TEXTURE_FORMAT_RGBA16)
73            .with_label("LEGO Effect")
74            .build();
75
76        let compute_shader = cuneus::compute_shader!(core, "shaders/lego.wgsl", config);
77
78        compute_shader.set_custom_params(initial_params, &core.queue);
79
80        Self {
81            base,
82            compute_shader,
83            current_params: initial_params}
84    }
85
86    fn update(&mut self, core: &Core) {
87        self.base.update_current_texture(core, &core.queue);
88
89        if let Some(texture_manager) = self.base.get_current_texture_manager() {
90            self.compute_shader.update_channel_texture(
91                0,
92                &texture_manager.view,
93                &texture_manager.sampler,
94                &core.device,
95                &core.queue,
96            );
97        }
98
99        let current_time = self.base.controls.get_time(&self.base.start_time);
100        let delta = 1.0 / 60.0;
101        self.compute_shader
102            .set_time(current_time, delta, &core.queue);
103        self.compute_shader.handle_export(core, &mut self.base);
104    }
105
106    fn resize(&mut self, core: &Core) {
107        self.base.default_resize(core, &mut self.compute_shader);
108    }
109
110    fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
111        let mut frame = self.base.begin_frame(core)?;
112
113        let mut params = self.current_params;
114        let mut changed = false;
115        let mut should_start_export = false;
116        let mut export_request = self.base.export_manager.get_ui_request();
117        let mut controls_request = self
118            .base
119            .controls
120            .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
121
122        let using_video_texture = self.base.using_video_texture;
123        let using_hdri_texture = self.base.using_hdri_texture;
124        let using_webcam_texture = self.base.using_webcam_texture;
125        let video_info = self.base.get_video_info();
126        let hdri_info = self.base.get_hdri_info();
127        let webcam_info = self.base.get_webcam_info();
128
129        let current_fps = self.base.fps_tracker.fps();
130
131        let full_output = if self.base.key_handler.show_ui {
132            self.base.render_ui(core, |ctx| {
133                RenderKit::apply_default_style(ctx);
134
135                egui::Window::new("LEGO Effect")
136                    .collapsible(true)
137                    .resizable(true)
138                    .default_width(320.0)
139                    .show(ctx, |ui| {
140                        ShaderControls::render_media_panel(
141                            ui,
142                            &mut controls_request,
143                            using_video_texture,
144                            video_info,
145                            using_hdri_texture,
146                            hdri_info,
147                            using_webcam_texture,
148                            webcam_info,
149                        );
150
151                        ui.separator();
152                        egui::CollapsingHeader::new("Brick Geom")
153                            .default_open(true)
154                            .show(ui, |ui| {
155                                changed |= ui
156                                    .add(
157                                        egui::Slider::new(&mut params.brick_scale, 0.005..=0.05)
158                                            .text("Scale"),
159                                    )
160                                    .changed();
161                                changed |= ui
162                                    .add(
163                                        egui::Slider::new(&mut params.stud_h, 0.01..=0.1)
164                                            .text("Stud H"),
165                                    )
166                                    .changed();
167                                changed |= ui
168                                    .add(
169                                        egui::Slider::new(&mut params.base_h, 0.05..=0.3)
170                                            .text("Base H"),
171                                    )
172                                    .changed();
173                                changed |= ui
174                                    .add(
175                                        egui::Slider::new(&mut params.stud_h_mult, 1.0..=12.0)
176                                            .text("Stud Mult"),
177                                    )
178                                    .changed();
179                            });
180
181                        egui::CollapsingHeader::new("Lighting")
182                            .default_open(false)
183                            .show(ui, |ui| {
184                                changed |= ui
185                                    .add(
186                                        egui::Slider::new(&mut params.lightdir_x, -1.0..=1.0)
187                                            .text("Dir X"),
188                                    )
189                                    .changed();
190                                changed |= ui
191                                    .add(
192                                        egui::Slider::new(&mut params.lightdir_y, -1.0..=1.0)
193                                            .text("Dir Y"),
194                                    )
195                                    .changed();
196
197                                ui.label("Light Color");
198                                let mut color = [params.light_r, params.light_g, params.light_b];
199                                if ui.color_edit_button_rgb(&mut color).changed() {
200                                    params.light_r = color[0];
201                                    params.light_g = color[1];
202                                    params.light_b = color[2];
203                                    changed = true;
204                                }
205
206                                changed |= ui
207                                    .add(
208                                        egui::Slider::new(&mut params.spec_pow, 2.0..=50.0)
209                                            .text("Spec Pow"),
210                                    )
211                                    .changed();
212                                changed |= ui
213                                    .add(
214                                        egui::Slider::new(&mut params.spec_str, 0.0..=1.0)
215                                            .text("Spec Str"),
216                                    )
217                                    .changed();
218                                changed |= ui
219                                    .add(
220                                        egui::Slider::new(&mut params.rim_str, 0.0..=1.0)
221                                            .text("Rim Str"),
222                                    )
223                                    .changed();
224                            });
225
226                        egui::CollapsingHeader::new("Shadows & AO")
227                            .default_open(false)
228                            .show(ui, |ui| {
229                                changed |= ui
230                                    .add(
231                                        egui::Slider::new(&mut params.shadow_str, 0.0..=1.0)
232                                            .text("Shd Str"),
233                                    )
234                                    .changed();
235                                changed |= ui
236                                    .add(
237                                        egui::Slider::new(&mut params.shadow_dist, 0.1..=3.0)
238                                            .text("Shd Dist"),
239                                    )
240                                    .changed();
241                                changed |= ui
242                                    .add(
243                                        egui::Slider::new(&mut params.ao_str, 0.5..=1.5)
244                                            .text("AO Cntrst"),
245                                    )
246                                    .changed();
247                            });
248
249                        egui::CollapsingHeader::new("Post-FX")
250                            .default_open(false)
251                            .show(ui, |ui| {
252                                changed |= ui
253                                    .add(
254                                        egui::Slider::new(&mut params.edge_enh, 0.0..=0.5)
255                                            .text("Edge Enh"),
256                                    )
257                                    .changed();
258                                changed |= ui
259                                    .add(
260                                        egui::Slider::new(&mut params.edge_blend, 0.01..=0.3)
261                                            .text("Edge Blend"),
262                                    )
263                                    .changed();
264                                changed |= ui
265                                    .add(
266                                        egui::Slider::new(&mut params.grain, 0.0..=0.1)
267                                            .text("Grain"),
268                                    )
269                                    .changed();
270                                changed |= ui
271                                    .add(
272                                        egui::Slider::new(&mut params.gamma, 0.1..=1.4)
273                                            .text("Gamma"),
274                                    )
275                                    .changed();
276                            });
277
278                        egui::CollapsingHeader::new("Advanced")
279                            .default_open(false)
280                            .show(ui, |ui| {
281                                changed |= ui
282                                    .add(
283                                        egui::Slider::new(&mut params.res_scale_mult, 0.01..=2.0)
284                                            .text("Res Scale"),
285                                    )
286                                    .changed();
287                                changed |= ui
288                                    .add(
289                                        egui::Slider::new(&mut params.depth_scale, 0.5..=1.0)
290                                            .text("Depth Scl"),
291                                    )
292                                    .changed();
293                            });
294
295                        ui.separator();
296                        ShaderControls::render_controls_widget(ui, &mut controls_request);
297                        ui.separator();
298                        should_start_export =
299                            ExportManager::render_export_ui_widget(ui, &mut export_request);
300                        ui.separator();
301                        ui.label(format!(
302                            "Resolution: {}x{}",
303                            core.size.width, core.size.height
304                        ));
305                        ui.label(format!("FPS: {current_fps:.1}"));
306                    });
307            })
308        } else {
309            self.base.render_ui(core, |_ctx| {})
310        };
311
312        self.base.apply_media_requests(core, &controls_request);
313
314        self.base.export_manager.apply_ui_request(export_request);
315        if should_start_export {
316            self.base.export_manager.start_export();
317        }
318
319        if changed {
320            self.current_params = params;
321            self.compute_shader.set_custom_params(params, &core.queue);
322        }
323
324        self.compute_shader.dispatch_stage(&mut frame.encoder, core, 0);
325
326        self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
327
328        self.base.end_frame(core, frame, full_output);
329
330        Ok(())
331    }
332
333    fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
334        if self.base.default_handle_input(core, event) {
335            return true;
336        }
337        if let WindowEvent::DroppedFile(path) = event {
338            if let Err(e) = self.base.load_media(core, path) {
339                error!("Failed to load dropped file: {e:?}");
340            }
341            return true;
342        }
343        false
344    }
345}
346
347fn main() -> Result<(), Box<dyn std::error::Error>> {
348    cuneus::gst::init()?;
349    env_logger::init();
350    let (app, event_loop) = ShaderApp::new("LEGO Effect", 1280, 720);
351
352    app.run(event_loop, LegoShader::init)
353}