From b4b1805ce286c17ea41bbfd4d827839ecea15657 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 23 Dec 2024 22:25:11 +0300 Subject: [PATCH] Basic noise generator --- .kateproject.build | 5 ++ Cargo.toml | 1 + src/bin/envmap/main.rs | 129 +++++++++++++++++++++++++++++++++++++ src/bin/envmap/perlin.rs | 125 +++++++++++++++++++++++++++++++++++ src/bin/envmap/perlin.wgsl | 90 ++++++++++++++++++++++++++ 5 files changed, 350 insertions(+) create mode 100644 src/bin/envmap/main.rs create mode 100644 src/bin/envmap/perlin.rs create mode 100644 src/bin/envmap/perlin.wgsl diff --git a/.kateproject.build b/.kateproject.build index f720f87..23ab294 100644 --- a/.kateproject.build +++ b/.kateproject.build @@ -11,6 +11,11 @@ "build_cmd": "cargo build", "name": "build", "run_cmd": "cargo run" + }, + { + "build_cmd": "cargo build --bin envmap", + "name": "envmap", + "run_cmd": "cargo run --bin envmap" } ] } diff --git a/Cargo.toml b/Cargo.toml index 0aef428..f57a2ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "raytracing3" version = "0.1.0" edition = "2021" +default-run = "raytracing3" [dependencies] bytemuck = { version = "1.21.0", features = ["derive"] } diff --git a/src/bin/envmap/main.rs b/src/bin/envmap/main.rs new file mode 100644 index 0000000..7952348 --- /dev/null +++ b/src/bin/envmap/main.rs @@ -0,0 +1,129 @@ +use std::error::Error; + +use glam::{vec2, vec3}; +use perlin::{Pipeline, Vertex}; +use winit::{ + event::{Event, WindowEvent}, + event_loop::EventLoop, + window::{Window, WindowAttributes}, +}; + +mod perlin; + +fn make_viewport(w: u32, h: u32) -> [Vertex; 4] { + let w = w as f32; + let h = h as f32; + let (w, h) = (1.0f32.max(w / h), 1.0f32.max(h / w)); + let screen_coord = [vec2(-1., -1.), vec2(1., -1.), vec2(-1., 1.), vec2(1., 1.)]; + let world_coord = [vec3(-w, -h, 0.), vec3(w, -h, 0.), vec3(-w, h, 0.), vec3(w, h, 0.)]; + [0, 1, 2, 3].map(|k| Vertex { + world: 10. * world_coord[k], + screen: screen_coord[k], + }) +} + +fn main() { + let event_loop = EventLoop::new().unwrap(); + + #[allow(deprecated)] + let window = &event_loop + .create_window(WindowAttributes::new().with_title("Noise generation test")) + .unwrap(); + + let (device, queue, surface) = pollster::block_on(init_gpu(window)).unwrap(); + + let mut noiser = Pipeline::new(&device); + noiser.set_params( + &queue, + perlin::Params { + seed: 42, + layers: 4, + persistence: 0.5, + }, + ); + + let mut surface_configured = false; + #[allow(deprecated)] + event_loop + .run(move |event, control_flow| match event { + Event::WindowEvent { ref event, window_id } if window_id == window.id() => match event { + WindowEvent::CloseRequested => control_flow.exit(), + WindowEvent::Resized(physical_size) => { + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, + format: wgpu::TextureFormat::Bgra8Unorm, + width: physical_size.width, + height: physical_size.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], + desired_maximum_frame_latency: 2, + }, + ); + noiser.set_view(&queue, &make_viewport(physical_size.width, physical_size.height)); + surface_configured = true; + } + WindowEvent::RedrawRequested => { + if !surface_configured { + return; + } + let output = surface.get_current_texture().unwrap(); + let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + noiser.render(&mut render_pass); + + drop(render_pass); + queue.submit(std::iter::once(encoder.finish())); + output.present(); + } + _ => {} + }, + _ => {} + }) + .unwrap(); +} + +async fn init_gpu(wnd: &Window) -> Result<(wgpu::Device, wgpu::Queue, wgpu::Surface), Box> { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + let surface = instance.create_surface(wnd)?; + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: Default::default(), + }, + None, + ) + .await + .unwrap(); + Ok((device, queue, surface)) +} diff --git a/src/bin/envmap/perlin.rs b/src/bin/envmap/perlin.rs new file mode 100644 index 0000000..c366db2 --- /dev/null +++ b/src/bin/envmap/perlin.rs @@ -0,0 +1,125 @@ +use std::mem::{offset_of, size_of}; + +use bytemuck::{bytes_of, Pod, Zeroable}; +use glam::{Vec2, Vec3}; + +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct Params { + pub seed: u32, + pub layers: u32, + pub persistence: f32, +} + +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct Vertex { + pub world: Vec3, + pub screen: Vec2, +} + +pub struct Pipeline { + view_buf: wgpu::Buffer, + params_buf: wgpu::Buffer, + bindings: wgpu::BindGroup, + pipeline: wgpu::RenderPipeline, +} + +static SHADER: &str = include_str!("perlin.wgsl"); + +impl Pipeline { + pub fn new(device: &wgpu::Device) -> Self { + let view_buf = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: (4 * size_of::()) as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let params_buf = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(SHADER.into()), + }); + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + module: &shader, + entry_point: None, + compilation_options: wgpu::PipelineCompilationOptions::default(), + buffers: &[wgpu::VertexBufferLayout { + array_stride: size_of::() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + shader_location: 0, + offset: offset_of!(Vertex, screen) as u64, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + shader_location: 1, + offset: offset_of!(Vertex, world) as u64, + format: wgpu::VertexFormat::Float32x3, + }, + ], + }], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: None, + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Bgra8Unorm, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + cache: None, + }); + let bindings = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &pipeline.get_bind_group_layout(0), + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: params_buf.as_entire_binding(), + }], + }); + Self { + view_buf, + params_buf, + bindings, + pipeline, + } + } + + pub fn set_params(&mut self, queue: &wgpu::Queue, params: Params) { + queue.write_buffer(&self.params_buf, 0, bytes_of(¶ms)); + } + + pub fn set_view(&mut self, queue: &wgpu::Queue, vertices: &[Vertex; 4]) { + queue.write_buffer(&self.view_buf, 0, bytes_of(vertices)); + } + + pub fn render(&self, pass: &mut wgpu::RenderPass) { + pass.set_pipeline(&self.pipeline); + pass.set_vertex_buffer(0, self.view_buf.slice(..)); + pass.set_bind_group(0, &self.bindings, &[]); + pass.draw(0..4, 0..1); + } +} diff --git a/src/bin/envmap/perlin.wgsl b/src/bin/envmap/perlin.wgsl new file mode 100644 index 0000000..08ea11b --- /dev/null +++ b/src/bin/envmap/perlin.wgsl @@ -0,0 +1,90 @@ +struct Params { + seed: u32, + layers: u32, + persistence: f32, +} + +struct Vertex { + @location(0) screen: vec2f, + @location(1) world: vec3f, +} + +struct Varying { + @location(0) world: vec3f, + @builtin(position) screen: vec4f, +} + +@group(0) @binding(0) var params: Params; + +@vertex +fn on_vertex(in: Vertex) -> Varying { + return Varying(in.world, vec4(in.screen, 0.0, 1.0)); +} + +@fragment +fn on_fragment(in: Varying) -> @location(0) vec4f { + return vec4(0.5 + 0.5 * noise(in.world)); +} + +fn noise(coords: vec3f) -> f32 { + let s = split(coords); + var ret = 0.0; + for (var i = 0u; i < 2; i++) { + for (var j = 0u; j < 2; j++) { + for (var k = 0u; k < 2; k++) { + ret += part(params.seed, s, vec3u(i, j, k)); + } + } + } + return ret; +} + +fn part(seed: u32, pos: Split, off: vec3u) -> f32 { + let base_vec = base(seed, pos.int + off); + let to_node = vec3f(off) - pos.frac; + let base_val = dot(base_vec, to_node); + let scale = smoothstep(vec3(0.0), vec3(1.0), 1.0 - abs(to_node)); + return scale.x * scale.y * scale.z * base_val; +} + +fn base(base_seed: u32, key: vec3u) -> vec3f { + var seed = hash(hash(hash(hash(base_seed) ^ key.x) ^ key.y) ^ key.z); + return rand_sphere(&seed); +} + +struct Split { + int: vec3u, + frac: vec3f, +} + +fn split(val: vec3f) -> Split { + let int = floor(val); + return Split(vec3u(vec3i(int)), val - int); +} + +fn hash(key : u32) -> u32 { + var v = key; + v *= 0xb384af1bu; + v ^= v >> 15u; + return v; +} + +fn rand(state: ptr) -> u32 { + *state = hash(*state); + return *state; +} + +fn rand_float(state: ptr) -> f32 { + return f32(rand(state)) / 0x1p32; +} + +fn rand_sphere(state: ptr) -> vec3f { + loop { + let v = vec3f(rand_float(state), rand_float(state), rand_float(state)) - 0.5; + let l = length(v); + if (length(v) <= 0.5) { + return v / l; + } + } + return vec3f(0.0); // unreachable +}