From b976aeb8a4503ca48f658adc00f8aee1fcf38d28 Mon Sep 17 00:00:00 2001 From: altunenes Date: Tue, 5 Mar 2024 13:12:24 +0300 Subject: [PATCH] VORO+FBM+WGPU --- Cargo.toml | 4 + shaders/voronoiwgpu2.wgsl | 88 +++++++++++++++++ src/voronoiwgpu2.rs | 198 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 shaders/voronoiwgpu2.wgsl create mode 100644 src/voronoiwgpu2.rs diff --git a/Cargo.toml b/Cargo.toml index 279e9ef..f74066d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -304,3 +304,7 @@ path = "src/asahi.rs" [[bin]] name = "voronoiwgpu" path = "src/voronoiwgpu.rs" + +[[bin]] +name = "voronoiwgpu2" +path = "src/voronoiwgpu2.rs" diff --git a/shaders/voronoiwgpu2.wgsl b/shaders/voronoiwgpu2.wgsl new file mode 100644 index 0000000..02e9569 --- /dev/null +++ b/shaders/voronoiwgpu2.wgsl @@ -0,0 +1,88 @@ +@group(0) @binding(0) var tex: texture_2d; +@group(0) @binding(1) var tex_sampler: sampler; +struct TimeUniform { + time: f32, +}; + +@group(1) @binding(0) +var u_time: TimeUniform; +const PI: f32 = 3.14; + +fn hash3(p: vec3) -> vec3 { + let q: vec3 = vec3( + dot(p, vec3(127.1, 311.7, 189.2)), + dot(p, vec3(269.5, 183.3, 324.7)), + dot(p, vec3(419.2, 371.9, 128.5)) + ); + return fract(sin(q) * 43758.5453); +} + +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); +} + +fn noise(x: vec3, v: f32) -> f32 { + let p: vec3 = floor(x); + let f: vec3 = fract(x); + let s: f32 = 1.0 + 444.0 * v; + var va: f32 = 0.0; + var wt: f32 = 0.0; + var k: i32 = -2; + while (k <= 1) { + var j: i32 = -2; + while (j <= 1) { + var i: i32 = -2; + while (i <= 1) { + let g: vec3 = vec3(f32(i), f32(j), f32(k)); + let o: vec3 = hash3(p + g); + let r: vec3 = g - f + o + 0.5; + let d: f32 = dot(r, r); + let w: f32 = pow(1.0 - smoothstep(0.0, 1.414, sqrt(d)), s); + va += o.z * w; + wt += w; + i += 1; + } + j += 1; + } + k += 1; + } + return va / wt; +} + +fn fBm(p: vec3, v: f32) -> f32 { + var sum: f32 = 0.0; + let scramb: f32 = osc(0.0, 5.0, 10.0, u_time.time); + let scramb2: f32 = osc(0.0, 10.0, 10.0, u_time.time); + + var amp: f32 = scramb; + var mutable_p = p; + var i: i32 = 0; + while (i < 4) { + sum += amp * noise(mutable_p, v); + amp *= 0.5; + mutable_p *= 2.0; + i += 1; + } + return sum; +} + +@fragment +fn main(@builtin(position) FragCoord: vec4) -> @location(0) vec4 { + let resolution: vec2 = vec2(800.0, 800.0); // Use the actual resolution (with image I recommend) + let uv: vec2 = FragCoord.xy / resolution; + let p: vec2 = uv * 2.0 - 1.0; / + let rd: vec3 = normalize(vec3(p.x, p.y, 1.0)); + let pos: vec3 = vec3(0.0, 0.0, 1.0) * u_time.time + rd * 25.0; + + let center: vec2 = vec2(0.5, 0.5); + let toCenter: vec2 = center - uv; + let distanceFromCenter: f32 = length(toCenter); + let adjustedDistance: f32 = distanceFromCenter * 5.0 - 5.0; + + let distortionStrength: f32 = fBm(pos, 0.1) * 0.1; + let distortionDirection: vec2 = normalize(toCenter) * adjustedDistance; + let distortedUV: vec2 = uv + distortionDirection * distortionStrength; + + let texColor: vec4 = textureSample(tex, tex_sampler, distortedUV); + return vec4(texColor.rgb, 1.0); +} \ No newline at end of file diff --git a/src/voronoiwgpu2.rs b/src/voronoiwgpu2.rs new file mode 100644 index 0000000..785eaa6 --- /dev/null +++ b/src/voronoiwgpu2.rs @@ -0,0 +1,198 @@ +use nannou::image; +use nannou::image::GenericImageView; +use nannou::prelude::*; +use std::path::PathBuf; +struct Model { + bind_group: wgpu::BindGroup, + render_pipeline: wgpu::RenderPipeline, + vertex_buffer: wgpu::Buffer, + time_uniform: wgpu::Buffer, + time_bind_group: wgpu::BindGroup, +} +fn get_image_path(relative_path: &str) -> PathBuf { + let current_dir = std::env::current_dir().unwrap(); + current_dir.join(relative_path) +} +#[repr(C)] +#[derive(Clone, Copy)] +struct Vertex { + position: [f32; 2], +} +//this time not triangles :-D +const VERTICES: [Vertex; 4] = [ + 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).run(); +} +fn model(app: &App) -> Model { + let img_path = get_image_path("images/mona.jpg"); + let image = image::open(img_path).unwrap(); + let (img_w, img_h) = image.dimensions(); + let w_id = app.new_window().size(img_w, img_h).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 msaa_samples = window.msaa_samples(); + let vs_desc = wgpu::include_wgsl!("../shaders/verteximg.wgsl"); + let fs_desc = wgpu::include_wgsl!("../shaders/voronoiwgpu.wgsl"); + let vs_mod = device.create_shader_module(vs_desc); + let fs_mod = device.create_shader_module(fs_desc); + let texture = wgpu::Texture::from_image(&window, &image); + let texture_view = texture.view().build(); + let sampler_desc = wgpu::SamplerBuilder::new().into_descriptor(); + let sampler_filtering = wgpu::sampler_filtering(&sampler_desc); + let sampler = device.create_sampler(&sampler_desc); + 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 texture_bind_group_layout = create_bind_group_layout(device, texture_view.sample_type(), sampler_filtering); + let bind_group_layout = + create_bind_group_layout(device, texture_view.sample_type(), sampler_filtering); + let bind_group = create_bind_group(device, &bind_group_layout, &texture_view, &sampler); + let pipeline_layout = create_pipeline_layout(device, &[&texture_bind_group_layout, &time_bind_group_layout]); + let render_pipeline = create_render_pipeline( + device, + &pipeline_layout, + &vs_mod, + &fs_mod, + format, + msaa_samples, + ); + 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_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"), + }); + Model { + bind_group, + vertex_buffer, + render_pipeline, + time_uniform, + time_bind_group, + } +} +fn view(app: &App, model: &Model, frame: Frame) { + let mut encoder = frame.command_encoder(); + let time = app.time as f32; + let time_bytes = time.to_ne_bytes(); + let binding = app.main_window(); + let queue = binding.queue(); + 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.bind_group, &[]); + render_pass.set_pipeline(&model.render_pipeline); + render_pass.set_bind_group(1, &model.time_bind_group, &[]); + 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 app.keys.down.contains(&Key::Space) { + let file_path = app + .project_path() + .expect("failed to locate project directory") + .join("frames") + .join(format!("{:0}.png", app.elapsed_frames())); + app.main_window().capture_frame(file_path); + } +} +fn create_bind_group_layout( + device: &wgpu::Device, + texture_sample_type: wgpu::TextureSampleType, + sampler_filtering: bool, +) -> wgpu::BindGroupLayout { + wgpu::BindGroupLayoutBuilder::new() + .texture( + wgpu::ShaderStages::FRAGMENT, + false, + wgpu::TextureViewDimension::D2, + texture_sample_type, + ) + .sampler(wgpu::ShaderStages::FRAGMENT, sampler_filtering) + .build(device) +} +fn create_bind_group( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + texture: &wgpu::TextureView, + sampler: &wgpu::Sampler, +) -> wgpu::BindGroup { + wgpu::BindGroupBuilder::new() + .texture_view(texture) + .sampler(sampler) + .build(device, layout) +} +fn create_pipeline_layout( + device: &wgpu::Device, + bind_group_layouts: &[&wgpu::BindGroupLayout], +) -> wgpu::PipelineLayout { + let desc = wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts, + push_constant_ranges: &[], + }; + device.create_pipeline_layout(&desc) +} +fn create_render_pipeline( + device: &wgpu::Device, + layout: &wgpu::PipelineLayout, + vs_mod: &wgpu::ShaderModule, + fs_mod: &wgpu::ShaderModule, + dst_format: wgpu::TextureFormat, + sample_count: u32, +) -> wgpu::RenderPipeline { + wgpu::RenderPipelineBuilder::from_layout(layout, vs_mod) + .fragment_shader(fs_mod) + .color_format(dst_format) + .add_vertex_buffer::(&wgpu::vertex_attr_array![0 => Float32x2]) + .sample_count(sample_count) + .primitive_topology(wgpu::PrimitiveTopology::TriangleStrip) + .build(device) +} +fn vertices_as_bytes(data: &[Vertex]) -> &[u8] { + unsafe { wgpu::bytes::from_slice(data) } +} \ No newline at end of file