use macroquad::prelude::*; use macroquad::ui::{ hash, root_ui, widgets::{self, Label, TreeNode}, }; use macroquad::color; enum Uniform { Float1(String), Float2(String, String), Float3(String, String, String), Color(Vec3), } impl Uniform { fn uniform_type(&self) -> UniformType { match self { Uniform::Float1(_) => UniformType::Float1, Uniform::Float2(_, _) => UniformType::Float2, Uniform::Float3(_, _, _) => UniformType::Float3, Uniform::Color(_) => UniformType::Float3, } } } fn color_picker_texture(w: usize, h: usize) -> (Texture2D, Image) { let ratio = 1.0 / h as f32; let mut image = Image::gen_image_color(w as u16, h as u16, WHITE); let image_data = image.get_image_data_mut(); for j in 0..h { for i in 0..w { let lightness = 1.0 - i as f32 * ratio; let hue = j as f32 * ratio; image_data[i + j * w] = color::hsl_to_rgb(hue, 1.0, lightness).into(); } } (Texture2D::from_image(&image), image) } #[macroquad::main("Shadertoy")] async fn main() { let ferris = load_texture("examples/rust.png").await.unwrap(); let (color_picker_texture, color_picker_image) = color_picker_texture(200, 200); let mut fragment_shader = DEFAULT_FRAGMENT_SHADER.to_string(); let mut vertex_shader = DEFAULT_VERTEX_SHADER.to_string(); let pipeline_params = PipelineParams { depth_write: true, depth_test: Comparison::LessOrEqual, ..Default::default() }; let mut material = load_material( ShaderSource::Glsl { vertex: &vertex_shader, fragment: &fragment_shader, }, MaterialParams { pipeline_params, ..Default::default() }, ) .unwrap(); let mut error: Option = None; enum Mesh { Sphere, Cube, Plane, } let mut mesh = Mesh::Sphere; let mut camera = Camera3D { position: vec3(-15., 15., -5.), up: vec3(0., 1., 0.), target: vec3(0., 5., -5.), ..Default::default() }; let mut colorpicker_window = false; let mut color_picking_uniform = None; let mut new_uniform_window = false; let mut new_uniform_name = String::new(); let mut uniforms: Vec<(String, Uniform)> = vec![]; loop { clear_background(WHITE); set_camera(&camera); draw_grid( 20, 1., Color::new(0.55, 0.55, 0.55, 0.75), Color::new(0.75, 0.75, 0.75, 0.75), ); gl_use_material(&material); match mesh { Mesh::Plane => draw_plane(vec3(0., 2., 0.), vec2(5., 5.), Some(&ferris), WHITE), Mesh::Sphere => draw_sphere(vec3(0., 6., 0.), 5., Some(&ferris), WHITE), Mesh::Cube => draw_cube(vec3(0., 5., 0.), vec3(10., 10., 10.), Some(&ferris), WHITE), } gl_use_default_material(); set_default_camera(); let mut need_update = false; widgets::Window::new(hash!(), vec2(20., 20.), vec2(470., 650.)) .label("Shader") .ui(&mut *root_ui(), |ui| { ui.label(None, "Camera: "); ui.same_line(0.0); if ui.button(None, "Ortho") { camera.projection = Projection::Orthographics; } ui.same_line(0.0); if ui.button(None, "Perspective") { camera.projection = Projection::Perspective; } ui.label(None, "Mesh: "); ui.same_line(0.0); if ui.button(None, "Sphere") { mesh = Mesh::Sphere; } ui.same_line(0.0); if ui.button(None, "Cube") { mesh = Mesh::Cube; } ui.same_line(0.0); if ui.button(None, "Plane") { mesh = Mesh::Plane; } ui.label(None, "Uniforms:"); ui.separator(); for (i, (name, uniform)) in uniforms.iter_mut().enumerate() { ui.label(None, &format!("{}", name)); ui.same_line(120.0); match uniform { Uniform::Float1(x) => { widgets::InputText::new(hash!(hash!(), i)) .size(vec2(200.0, 19.0)) .filter_numbers() .ui(ui, x); if let Ok(x) = x.parse::() { material.set_uniform(name, x); } } Uniform::Float2(x, y) => { widgets::InputText::new(hash!(hash!(), i)) .size(vec2(99.0, 19.0)) .filter_numbers() .ui(ui, x); ui.same_line(0.0); widgets::InputText::new(hash!(hash!(), i)) .size(vec2(99.0, 19.0)) .filter_numbers() .ui(ui, y); if let (Ok(x), Ok(y)) = (x.parse::(), y.parse::()) { material.set_uniform(name, (x, y)); } } Uniform::Float3(x, y, z) => { widgets::InputText::new(hash!(hash!(), i)) .size(vec2(65.0, 19.0)) .filter_numbers() .ui(ui, x); ui.same_line(0.0); widgets::InputText::new(hash!(hash!(), i)) .size(vec2(65.0, 19.0)) .filter_numbers() .ui(ui, y); ui.same_line(0.0); widgets::InputText::new(hash!(hash!(), i)) .size(vec2(65.0, 19.0)) .filter_numbers() .ui(ui, z); if let (Ok(x), Ok(y), Ok(z)) = (x.parse::(), y.parse::(), z.parse::()) { material.set_uniform(name, (x, y, z)); } } Uniform::Color(color) => { let mut canvas = ui.canvas(); let cursor = canvas.cursor(); canvas.rect( Rect::new(cursor.x + 20.0, cursor.y, 50.0, 18.0), Color::new(0.2, 0.2, 0.2, 1.0), Color::new(color.x, color.y, color.z, 1.0), ); if ui.button(None, "change") { colorpicker_window = true; color_picking_uniform = Some(name.to_owned()); } material.set_uniform(name, (color.x, color.y, color.z)); } } } ui.separator(); if ui.button(None, "New uniform") { new_uniform_window = true; } TreeNode::new(hash!(), "Fragment shader") .init_unfolded() .ui(ui, |ui| { if ui.editbox(hash!(), vec2(440., 200.), &mut fragment_shader) { need_update = true; }; }); ui.tree_node(hash!(), "Vertex shader", |ui| { if ui.editbox(hash!(), vec2(440., 300.), &mut vertex_shader) { need_update = true; }; }); if let Some(ref error) = error { Label::new(error).multiline(14.0).ui(ui); } }); if new_uniform_window { widgets::Window::new(hash!(), vec2(100., 100.), vec2(200., 80.)) .label("New uniform") .ui(&mut *root_ui(), |ui| { if ui.active_window_focused() == false { new_uniform_window = false; } ui.input_text(hash!(), "Name", &mut new_uniform_name); let uniform_type = ui.combo_box( hash!(), "Type", &["Float1", "Float2", "Float3", "Color"], None, ); if ui.button(None, "Add") { if new_uniform_name.is_empty() == false { let uniform = match uniform_type { 0 => Uniform::Float1("0".to_string()), 1 => Uniform::Float2("0".to_string(), "0".to_string()), 2 => Uniform::Float3( "0".to_string(), "0".to_string(), "0".to_string(), ), 3 => Uniform::Color(vec3(0.0, 0.0, 0.0)), _ => unreachable!(), }; uniforms.push((new_uniform_name.clone(), uniform)); new_uniform_name.clear(); need_update = true; } new_uniform_window = false; } ui.same_line(0.0); if ui.button(None, "Cancel") { new_uniform_window = false; } }); } if colorpicker_window { colorpicker_window &= widgets::Window::new(hash!(), vec2(140., 100.), vec2(210., 240.)) .label("Colorpicker") .ui(&mut *root_ui(), |ui| { if ui.active_window_focused() == false { colorpicker_window = false; } let mut canvas = ui.canvas(); let cursor = canvas.cursor(); let mouse = mouse_position(); let x = mouse.0 as i32 - cursor.x as i32; let y = mouse.1 as i32 - (cursor.y as i32 + 20); let color = color_picker_image .get_pixel(x.max(0).min(199) as u32, y.max(0).min(199) as u32); canvas.rect( Rect::new(cursor.x, cursor.y, 200.0, 18.0), Color::new(0.0, 0.0, 0.0, 1.0), Color::new(color.r, color.g, color.b, 1.0), ); canvas.image( Rect::new(cursor.x, cursor.y + 20.0, 200.0, 200.0), &color_picker_texture, ); if x >= 0 && x < 200 && y >= 0 && y < 200 { canvas.rect( Rect::new(mouse.0 - 3.5, mouse.1 - 3.5, 7.0, 7.0), Color::new(0.3, 0.3, 0.3, 1.0), Color::new(1.0, 1.0, 1.0, 1.0), ); if is_mouse_button_down(MouseButton::Left) { colorpicker_window = false; let uniform_name = color_picking_uniform.take().unwrap(); uniforms .iter_mut() .find(|(name, _)| name == &uniform_name) .unwrap() .1 = Uniform::Color(vec3(color.r, color.g, color.b)); } } }); } if need_update { let uniforms = uniforms .iter() .map(|(name, uniform)| UniformDesc::new(name, uniform.uniform_type())) .collect::>(); match load_material( ShaderSource::Glsl { vertex: &vertex_shader, fragment: &fragment_shader, }, MaterialParams { pipeline_params, uniforms, textures: vec![], }, ) { Ok(new_material) => { material = new_material; error = None; } Err(err) => { error = Some(format!("{:#?}", err)); } } } next_frame().await } } const DEFAULT_FRAGMENT_SHADER: &'static str = "#version 100 precision lowp float; varying vec2 uv; uniform sampler2D Texture; void main() { gl_FragColor = texture2D(Texture, uv); } "; const DEFAULT_VERTEX_SHADER: &'static str = "#version 100 precision lowp float; attribute vec3 position; attribute vec2 texcoord; varying vec2 uv; uniform mat4 Model; uniform mat4 Projection; void main() { gl_Position = Projection * Model * vec4(position, 1); uv = texcoord; } ";