From bae051e4eede4a9b0e21af86a4bc943905d1755f Mon Sep 17 00:00:00 2001 From: altunenes Date: Sun, 27 Oct 2024 22:29:09 +0300 Subject: [PATCH] Adelson's Checker-Shadow Illusion --- .github/workflows/release.yml | 2 +- Cargo.toml | 6 +- shaders/orbittraps.wgsl | 121 ++++++++++++ src/orbittraps.rs | 342 ++++++++++++++++++++++++++++++++++ 4 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 shaders/orbittraps.wgsl create mode 100644 src/orbittraps.rs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e12206..dd91eba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - bin: [footsteps, blobs, fibonacci, triangles, peace, attractors, waves, waves2, cafe_wall, munker, pdiamond, lilac, fourier, nblur, pixelrain, pina, hole,neuralnet, bhole, oscillation, chladni, hilbert, hilbertimg, snowflake, mandelbrot, leviant, eyes, imgblob, scramble, pixelate, rainbowimage,gaborill,munkerclock, voronoi, lorenz, ulam, dfft, butter2d, gaborwgpu, galaxy, chladniwgpu, snowflakewgpu, spiralimgwgpu, neuralnetwgpu, imlenswgpu, fbmflowgpu, lovewgpu, neurons, asahi, voronoiwgpu, fluid, asahi2, sinh, tree, expmandelbrotgpu, pupils, pixelflow,darkclouds, tunnel, neurons2, nebula, pixelsum, smoothneurons,gaussiansplat,gaborimage,rorschach,stripes,psychology,3dneuron,mandelbulb,dottedlines,ornaments,faketunnel,smoothvoro,wrapper,galaxy2,GPUattractor,peaceGPU,kleinian,adelson] + bin: [footsteps, blobs, fibonacci, triangles, peace, attractors, waves, waves2, cafe_wall, munker, pdiamond, lilac, fourier, nblur, pixelrain, pina, hole,neuralnet, bhole, oscillation, chladni, hilbert, hilbertimg, snowflake, mandelbrot, leviant, eyes, imgblob, scramble, pixelate, rainbowimage,gaborill,munkerclock, voronoi, lorenz, ulam, dfft, butter2d, gaborwgpu, galaxy, chladniwgpu, snowflakewgpu, spiralimgwgpu, neuralnetwgpu, imlenswgpu, fbmflowgpu, lovewgpu, neurons, asahi, voronoiwgpu, fluid, asahi2, sinh, tree, expmandelbrotgpu, pupils, pixelflow,darkclouds, tunnel, neurons2, nebula, pixelsum, smoothneurons,gaussiansplat,gaborimage,rorschach,stripes,psychology,3dneuron,mandelbulb,dottedlines,ornaments,faketunnel,smoothvoro,wrapper,galaxy2,GPUattractor,peaceGPU,kleinian,adelson,orbittraps] include: - target: x86_64-unknown-linux-gnu ext: "" diff --git a/Cargo.toml b/Cargo.toml index 37d5f1f..95ea17e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -336,4 +336,8 @@ path = "src/kleinian.rs" [[bin]] name= "adelson" -path = "src/adelson.rs" \ No newline at end of file +path = "src/adelson.rs" + +[[bin]] +name= "orbittraps" +path = "src/orbittraps.rs" \ No newline at end of file diff --git a/shaders/orbittraps.wgsl b/shaders/orbittraps.wgsl new file mode 100644 index 0000000..f86ff89 --- /dev/null +++ b/shaders/orbittraps.wgsl @@ -0,0 +1,121 @@ +const PI: f32 = 3.141592653589793; +struct TimeUniform { + time: f32, +}; +@group(1) @binding(0) +var u_time: TimeUniform; +fn osc(minValue: f32, maxValue: f32, interval: f32, currentTime: f32) -> f32 { + return minValue + (maxValue - minValue) * 0.5 * (sin(2.0 * PI * currentTime / interval) + 1.0); +} +struct Params { + lambda: f32, + theta: f32, + alpha:f32, + sigma: f32, + gamma: f32, + blue:f32, + aa:f32, + iter:f32, + bound:f32, + tt:f32, + a:f32, + b:f32, + c:f32, + d:f32, + e:f32, + f:f32, + g:f32, +}; +@group(0) @binding(1) +var params: Params; +fn implicit(c: vec2, trap1: vec2, trap2: vec2, currentTime: f32) -> vec4 { + var z: vec2 = vec2(0.0, 0.0); + var dz: vec2 = vec2(1.0, 0.0); + var trap1_min: f32 = 1e20; + var trap2_min: f32 = 1e20; + var MAX_ITER: i32 = i32(params.iter); + var BOUND: f32 = params.bound; + + + var i: i32 = 0; + for (i = 0; i < MAX_ITER; i = i + 1) { + dz = 2.0 * vec2(z.x * dz.x - z.y * dz.y, z.x * dz.y + z.y * dz.x) + vec2(1.0, 0.0); + let xnew: f32 = z.x * z.x - z.y * z.y + c.x; + z.y = 2.0 * z.x * z.y + c.y; + z.x = xnew; + let dampenedTime: f32 = currentTime * 0.001; + z = z + 0.1 * vec2(sin(0.001 * dampenedTime), cos(0.001 * dampenedTime)); + // Orbit traps: https://iquilezles.org/articles/ftrapsgeometric/ + trap1_min = min(trap1_min, length(z - trap1)); + trap2_min = min(trap2_min, dot(z - trap2, z - trap2)); + + if (dot(z, z) > BOUND) { + break; + } + } + let d: f32 = sqrt(dot(z, z) / dot(dz, dz)) * log(dot(z, z)); + return vec4(f32(i), d, trap1_min, trap2_min); +} +fn gamma(color: vec3, gamma: f32) -> vec3 { + return pow(color, vec3(1.0 / gamma)); +} +@fragment +fn main(@builtin(position) FragCoord: vec4) -> @location(0) vec4 { + let screen_size = vec2(1920.0, 1080.0); + let fragCoord = vec2(FragCoord.x, screen_size.y - FragCoord.y); + let uv_base = 0.4 * (fragCoord - 0.5 * screen_size) / screen_size.y; + let AA: i32 = i32(params.aa); + var MAX_ITER: i32 = i32(params.iter); + var BOUND: f32 = params.bound; + let camSpeed = vec2(0.0002, 0.0002); + let camPath = vec2( + sin(camSpeed.x * u_time.time / 10.0), + cos(camSpeed.y * u_time.time / 10.0) + ); + + var pan: vec2 = vec2(params.theta, params.alpha); + + if (u_time.time > 2.0) { + let timeSince14 = u_time.time - 45.0; + pan.y = pan.y + 0.00002 * timeSince14; + } + + let zoomLevel: f32 = osc(params.lambda, params.lambda, 20.0, u_time.time * params.tt); + let trap1 = vec2(params.blue, params.sigma); + let trap2 = vec2(-0.5, 2.0) + 0.5 * vec2( + cos(0.13 * u_time.time), + sin(0.13 * u_time.time) + ); + + var col = vec3(params.a,params.b,params.c); + + for (var m: i32 = 0; m < AA; m = m + 1) { + for (var n: i32 = 0; n < AA; n = n + 1) { + let sample_offset = vec2(f32(m), f32(n)) / f32(AA); + let min_res = min(screen_size.x, screen_size.y); + let uv_sample = ((fragCoord + sample_offset - 0.5 * screen_size) / min_res * zoomLevel + pan + camPath) * 2.033 - vec2(2.14278, 2.14278); + + let z_data = implicit(uv_sample, trap1, trap2, u_time.time* params.tt); + let iter_ratio = z_data.x / f32(MAX_ITER); + let d = z_data.y; + let trap1_dist = z_data.z; + let trap2_dist = z_data.w; + + if (iter_ratio < 1.0) { + let c1 = pow(clamp(2.00 * d / zoomLevel, 0.0, 1.0), 0.5); + let c2 = pow(clamp(1.5 * trap1_dist, 0.0, 1.0), 2.0); + let c3 = pow(clamp(0.4 * trap2_dist, 0.0, 1.0), 0.25); + + let col1 = 0.5 + 0.5 * sin(vec3(3.0) + 2.0 * c2 + vec3(0.0, 0.5, 1.0)); + let col2 = 0.5 + 0.5 * sin(vec3(4.1) + 2.0 * c3 + vec3(1.0, 0.5, 0.0)); + let osc_val = osc(12.0, 12.0, 10.0, u_time.time); + let exteriorColor = 0.5 + 0.5 * sin(params.g * trap1_dist + vec3(params.d, params.e, params.f) + PI * vec3(3.0 * iter_ratio) + osc_val); + + col = col + col1 + exteriorColor; + } + } + } + + col = gamma(col / f32(AA * AA), params.gamma); + return vec4(col, 1.0); +} \ No newline at end of file diff --git a/src/orbittraps.rs b/src/orbittraps.rs new file mode 100644 index 0000000..a9129fd --- /dev/null +++ b/src/orbittraps.rs @@ -0,0 +1,342 @@ +use nannou::prelude::*; +use nannou_egui::{self, egui, Egui}; +struct Model { + render_pipeline: wgpu::RenderPipeline, + vertex_buffer: wgpu::Buffer, + time_uniform: wgpu::Buffer, + time_bind_group: wgpu::BindGroup, + params_uniform: wgpu::Buffer, + params_bind_group: wgpu::BindGroup, + settings:Settings, + egui:Egui, +} +struct Settings { + lambda: f32, + theta: f32, + alpha:f32, + sigma: f32, + gamma:f32, + blue:f32, + a:f32, + b:f32, + c:f32, + d:f32, + e:f32, + f:f32, + g:f32, + iter:f32, + bound:f32, + aa:f32, + tt:f32, + show_ui: bool, +} +#[repr(C)] +#[derive(Clone, Copy)] +struct Vertex { + position: [f32; 2], +} +const VERTICES: [Vertex; 6] = [ + Vertex { position: [-1.0, -1.0] }, + Vertex { position: [ 1.0, -1.0] }, + Vertex { position: [-1.0, 1.0] }, + Vertex { position: [ 1.0, -1.0] }, + Vertex { position: [ 1.0, 1.0] }, + Vertex { position: [-1.0, 1.0] }, +]; +fn main() { + nannou::app(model) + .update(update) + .run(); +} +fn update(app: &App, model: &mut Model, update: Update) { + static mut REVERSE_COLORS: bool = false; + + let egui = &mut model.egui; + egui.set_elapsed_time(update.since_start); + let ctx = egui.begin_frame(); + if app.keys.down.contains(&Key::H) { + model.settings.show_ui = !model.settings.show_ui; + } + egui::Window::new("Shader Settings").show(&ctx, |ui| { + ui.horizontal(|ui| { + ui.add(egui::Slider::new(&mut model.settings.lambda, 0.00001..=2.0).text("zoom")); + if ui.button("-").clicked() { + model.settings.lambda -= 0.00001; + if model.settings.lambda < 0.00001 { + model.settings.lambda = 0.00001; + } + } + if ui.button("+").clicked() { + model.settings.lambda += 0.00001; + if model.settings.lambda > 2.0 { + model.settings.lambda = 2.0; + } + } + }); + + ui.horizontal(|ui| { + ui.add(egui::Slider::new(&mut model.settings.theta, 0.0..=1.5).text("x_axis")); + if ui.button("-").clicked() { + model.settings.theta -= 0.0001; + if model.settings.theta < 0.0 { + model.settings.theta = 0.0; + } + } + if ui.button("+").clicked() { + model.settings.theta += 0.0001; + if model.settings.theta > 1.5 { + model.settings.theta = 1.5; + } + } + }); + + ui.horizontal(|ui| { + ui.add(egui::Slider::new(&mut model.settings.alpha, -0.5..=0.5).text("y_axis")); + if ui.button("-").clicked() { + model.settings.alpha -= 0.0001; + if model.settings.alpha < -0.5 { + model.settings.alpha = -0.5; + } + } + if ui.button("+").clicked() { + model.settings.alpha += 0.0001; + if model.settings.alpha > 0.5 { + model.settings.alpha = 0.5; + } + } + }); + ui.add(egui::Slider::new(&mut model.settings.sigma, 0.0..=4.0).text("trap1")); + ui.add(egui::Slider::new(&mut model.settings.gamma, 0.1..=1.0).text("gamma")); + ui.add(egui::Slider::new(&mut model.settings.blue, 0.0..=4.0).text("trap2")); + ui.add(egui::Slider::new(&mut model.settings.a, 0.0..=1.0).text("R")); + ui.add(egui::Slider::new(&mut model.settings.b, 0.0..=1.0).text("G")); + ui.add(egui::Slider::new(&mut model.settings.c, 0.0..=1.0).text("B")); + ui.add(egui::Slider::new(&mut model.settings.d, 0.0..=1.0).text("e4")); + ui.add(egui::Slider::new(&mut model.settings.g, 1.0..=8.00).text("Sinext")); + ui.add(egui::Slider::new(&mut model.settings.e, 0.0..=1.0).text("c1")); + ui.add(egui::Slider::new(&mut model.settings.f, 0.0..=1.0).text("c2")); + ui.add(egui::Slider::new(&mut model.settings.iter, 1.0..=2000.0).text("iter")); + ui.add(egui::Slider::new(&mut model.settings.bound, 1.0..=2000.0).text("bound")); + ui.add(egui::Slider::new(&mut model.settings.aa, 0.0..=10.0).text("smart AA")); + ui.add(egui::Slider::new(&mut model.settings.tt, 0.0..=1.0).text("time")); + if ui.button("alternative view").clicked() { + unsafe { + REVERSE_COLORS = !REVERSE_COLORS; + if REVERSE_COLORS { + model.settings.iter = 350.0; + model.settings.bound = 50.0; + model.settings.a = -0.5; + model.settings.b = 0.0; + model.settings.c = 0.0; + model.settings.d = 2.0; + } else { + model.settings.iter = 855.0; + model.settings.bound = 3.5; + model.settings.a = 0.1; + model.settings.b = 0.5; + model.settings.c = 1.0; + model.settings.d = 8.0; + } + } + } + ui.horizontal(|ui| { + if ui.button("Hide UI").clicked() { + model.settings.show_ui = false; + } + ui.label("Press H to revert"); + }); + }); + + let params_data = [ + model.settings.lambda, model.settings.theta, model.settings.alpha, + model.settings.sigma, model.settings.gamma, model.settings.blue, + model.settings.aa, model.settings.iter, model.settings.bound, + model.settings.tt, model.settings.a, model.settings.b, + model.settings.c, model.settings.d, model.settings.e, + model.settings.f, model.settings.g + ]; + let params_bytes = bytemuck::cast_slice(¶ms_data); + app.main_window().queue().write_buffer(&model.params_uniform, 0, ¶ms_bytes); +} +fn raw_window_event(app: &App, model: &mut Model, event: &nannou::winit::event::WindowEvent) { + model.egui.handle_raw_event(event); + if let nannou::winit::event::WindowEvent::KeyboardInput { input, .. } = event { + if let (Some(nannou::winit::event::VirtualKeyCode::F), true) = + (input.virtual_keycode, input.state == nannou::winit::event::ElementState::Pressed) + { + let window = app.main_window(); + let fullscreen = window.fullscreen().is_some(); + window.set_fullscreen(!fullscreen); + } + } +} +fn model(app: &App) -> Model { + let w_id = app.new_window().raw_event(raw_window_event). + size(512, 512).view(view).build().unwrap(); + // The gpu device associated with the window's swapchain + let window = app.window(w_id).unwrap(); + let device = window.device(); + let format = Frame::TEXTURE_FORMAT; + let sample_count = window.msaa_samples(); + let vs_desc = wgpu::include_wgsl!("../shaders/vs.wgsl"); + let fs_desc = wgpu::include_wgsl!("../shaders/orbittraps.wgsl"); + let vs_mod = device.create_shader_module(vs_desc); + let fs_mod = device.create_shader_module(fs_desc); + let vertices_bytes = vertices_as_bytes(&VERTICES[..]); + let usage = wgpu::BufferUsages::VERTEX; + let vertex_buffer = device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: vertices_bytes, + usage, + }); + let time_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new(std::mem::size_of::() as _), + }, + count: None, + }, + ], + label: Some("time_bind_group_layout"), + }); + let params_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("params_bind_group_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new((std::mem::size_of::() * 17) as _), + }, + count: None, + }], + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Pipeline Layout"), + bind_group_layouts: &[¶ms_bind_group_layout, &time_bind_group_layout], + push_constant_ranges: &[], + }); + let render_pipeline = wgpu::RenderPipelineBuilder::from_layout(&pipeline_layout, &vs_mod) + .fragment_shader(&fs_mod) + .color_format(format) + .add_vertex_buffer::(&wgpu::vertex_attr_array![0 => Float32x2]) + .sample_count(sample_count) + .build(device); + let time_uniform = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Time Uniform Buffer"), + size: std::mem::size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let time_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &time_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: time_uniform.as_entire_binding(), + }, + ], + label: Some("time_bind_group"), + }); + let settings = Settings { + lambda: 0.0004, + theta:0.8030, + alpha:0.2585, + sigma:1.0, + gamma:0.5, + blue:0.0, + show_ui:true, + aa: 2.0, + iter:355.0, + bound:35.5, + tt:0.1, + a:0.0, + b:0.0, + c:0.0, + + d:0.0, + e:0.5, + f:1.0, + + g:2.0, + }; + let params_data = [settings.lambda, settings.theta, settings.alpha,settings.sigma,settings.gamma,settings.blue,settings.aa,settings.iter,settings.bound,settings.tt,settings.a,settings.b,settings.c,settings.d,settings.e,settings.f,settings.g]; + let params_bytes = bytemuck::cast_slice(¶ms_data); + let params_uniform = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Params Uniform"), + contents: params_bytes, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let params_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: ¶ms_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 1, + resource: params_uniform.as_entire_binding(), + }, + ], + label: Some("params_bind_group"), + }); + let window = app.window(w_id).unwrap(); + let egui = Egui::from_window(&window); + Model { + params_bind_group, + settings, + params_uniform, + egui, + vertex_buffer, + render_pipeline, + time_uniform, + time_bind_group, + } +} +fn view(app: &App, model: &Model, frame: Frame) { + let draw = app.draw(); + draw.background().color(BLACK); + let time = app.time; + let time_bytes = time.to_ne_bytes(); + let binding = app.main_window(); + let queue = binding.queue(); + { + let mut encoder = frame.command_encoder(); + queue.write_buffer(&model.time_uniform, 0, &time_bytes); + let mut render_pass = wgpu::RenderPassBuilder::new() + .color_attachment(frame.texture_view(), |color| color) + .begin(&mut encoder); + render_pass.set_bind_group(0, &model.params_bind_group, &[]); + render_pass.set_bind_group(1, &model.time_bind_group, &[]); + render_pass.set_pipeline(&model.render_pipeline); + render_pass.set_vertex_buffer(0, model.vertex_buffer.slice(..)); + let vertex_range = 0..VERTICES.len() as u32; + let instance_range = 0..1; + render_pass.draw(vertex_range, instance_range); + } + if model.settings.show_ui { + model.egui.draw_to_frame(&frame).unwrap(); + } + if app.keys.down.contains(&Key::Space) { + match app.project_path() { + Ok(project_path) => { + let frames_path = project_path.join("frames"); + if let Err(e) = std::fs::create_dir_all(&frames_path) { + eprintln!("Failed to create frames directory: {:?}", e); + return; + } + let file_path = frames_path.join(format!("{:0}.png", app.elapsed_frames())); + app.main_window().capture_frame(file_path); + }, + Err(e) => { + eprintln!("Failed to locate project directory: {:?}", e); + } + } + } +} +fn vertices_as_bytes(data: &[Vertex]) -> &[u8] { + unsafe { wgpu::bytes::from_slice(data) } +} \ No newline at end of file