From b48cdbfff590b391949468d4f01475f6517fc252 Mon Sep 17 00:00:00 2001 From: Guilherme Werner Date: Fri, 16 Feb 2024 20:57:34 -0300 Subject: [PATCH] Uniform buffers and a 3d camera --- Cargo.toml | 1 + src/camera.rs | 142 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 78 ++++++++++++++++++++++++-- src/shader.wgsl | 10 +++- 4 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 src/camera.rs diff --git a/Cargo.toml b/Cargo.toml index cd16757..44b5575 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ path = "src/main.rs" anyhow = "1.0" bytemuck = { version = "1.12", features = ["derive"] } cfg-if = "1" +cgmath = "0.18" env_logger = "0.10" log = "0.4" wgpu = "0.18" diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 0000000..f588826 --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,142 @@ +use winit::event::{ElementState, KeyboardInput, VirtualKeyCode, WindowEvent}; + +pub struct Camera { + pub eye: cgmath::Point3, + pub target: cgmath::Point3, + pub up: cgmath::Vector3, + pub aspect: f32, + pub fovy: f32, + pub znear: f32, + pub zfar: f32, +} + +impl Camera { + pub fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { + // 1. + let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up); + // 2. + let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); + + // 3. + return OPENGL_TO_WGPU_MATRIX * proj * view; + } +} + +#[rustfmt::skip] +pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0, +); + +// We need this for Rust to store our data correctly for the shaders +#[repr(C)] +// This is so we can store this in a buffer +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct CameraUniform { + // We can't use cgmath with bytemuck directly, so we'll have + // to convert the Matrix4 into a 4x4 f32 array + pub view_proj: [[f32; 4]; 4], +} + +impl CameraUniform { + pub fn new() -> Self { + use cgmath::SquareMatrix; + Self { + view_proj: cgmath::Matrix4::identity().into(), + } + } + + pub fn update_view_proj(&mut self, camera: &Camera) { + self.view_proj = camera.build_view_projection_matrix().into(); + } +} + +pub struct CameraController { + pub speed: f32, + pub is_forward_pressed: bool, + pub is_backward_pressed: bool, + pub is_left_pressed: bool, + pub is_right_pressed: bool, +} + +impl CameraController { + pub fn new(speed: f32) -> Self { + Self { + speed, + is_forward_pressed: false, + is_backward_pressed: false, + is_left_pressed: false, + is_right_pressed: false, + } + } + + pub fn process_events(&mut self, event: &WindowEvent) -> bool { + match event { + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state, + virtual_keycode: Some(keycode), + .. + }, + .. + } => { + let is_pressed = *state == ElementState::Pressed; + match keycode { + VirtualKeyCode::W | VirtualKeyCode::Up => { + self.is_forward_pressed = is_pressed; + true + } + VirtualKeyCode::A | VirtualKeyCode::Left => { + self.is_left_pressed = is_pressed; + true + } + VirtualKeyCode::S | VirtualKeyCode::Down => { + self.is_backward_pressed = is_pressed; + true + } + VirtualKeyCode::D | VirtualKeyCode::Right => { + self.is_right_pressed = is_pressed; + true + } + _ => false, + } + } + _ => false, + } + } + + pub fn update_camera(&self, camera: &mut Camera) { + use cgmath::InnerSpace; + let forward = camera.target - camera.eye; + let forward_norm = forward.normalize(); + let forward_mag = forward.magnitude(); + + // Prevents glitching when the camera gets too close to the + // center of the scene. + if self.is_forward_pressed && forward_mag > self.speed { + camera.eye += forward_norm * self.speed; + } + if self.is_backward_pressed { + camera.eye -= forward_norm * self.speed; + } + + let right = forward_norm.cross(camera.up); + + // Redo radius calc in case the forward/backward is pressed. + let forward = camera.target - camera.eye; + let forward_mag = forward.magnitude(); + + if self.is_right_pressed { + // Rescale the distance between the target and the eye so + // that it doesn't change. The eye, therefore, still + // lies on the circle made by the target and eye. + camera.eye = camera.target - (forward + right * self.speed).normalize() * forward_mag; + } + if self.is_left_pressed { + camera.eye = camera.target - (forward - right * self.speed).normalize() * forward_mag; + } + } +} diff --git a/src/lib.rs b/src/lib.rs index edf0b49..e5f88dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,9 @@ use winit::{ #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +mod camera; mod texture; +pub use camera::*; pub use texture::*; #[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] @@ -114,6 +116,11 @@ pub struct State { num_indices: u32, diffuse_bind_group: wgpu::BindGroup, diffuse_texture: texture::Texture, // NEW + camera: Camera, + camera_uniform: CameraUniform, + camera_buffer: wgpu::Buffer, + camera_bind_group: wgpu::BindGroup, + camera_controller: CameraController, } impl State { @@ -179,7 +186,7 @@ impl State { format: surface_format, width: size.width, height: size.height, - present_mode: wgpu::PresentMode::AutoNoVsync, //surface_caps.present_modes[0], + present_mode: wgpu::PresentMode::AutoVsync, //surface_caps.present_modes[0], alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], }; @@ -251,10 +258,59 @@ impl State { label: Some("diffuse_bind_group"), }); + let camera_controller = CameraController::new(0.2); + + let camera = Camera { + // position the camera 1 unit up and 2 units back + // +z is out of the screen + eye: (0.0, 1.0, 2.0).into(), + // have it look at the origin + target: (0.0, 0.0, 0.0).into(), + // which way is "up" + up: cgmath::Vector3::unit_y(), + aspect: config.width as f32 / config.height as f32, + fovy: 45.0, + znear: 0.1, + zfar: 100.0, + }; + + let mut camera_uniform = CameraUniform::new(); + camera_uniform.update_view_proj(&camera); + + let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Camera Buffer"), + contents: bytemuck::cast_slice(&[camera_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let camera_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("camera_bind_group_layout"), + }); + + let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &camera_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera_buffer.as_entire_binding(), + }], + label: Some("camera_bind_group"), + }); + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&texture_bind_group_layout], // NEW! + bind_group_layouts: &[&texture_bind_group_layout, &camera_bind_group_layout], push_constant_ranges: &[], }); @@ -311,6 +367,11 @@ impl State { num_indices, diffuse_bind_group, diffuse_texture, // NEW + camera, + camera_uniform, + camera_buffer, + camera_bind_group, + camera_controller, } } @@ -328,10 +389,18 @@ impl State { } fn input(&mut self, event: &WindowEvent) -> bool { - false + self.camera_controller.process_events(event) } - fn update(&mut self) {} + fn update(&mut self) { + self.camera_controller.update_camera(&mut self.camera); + self.camera_uniform.update_view_proj(&self.camera); + self.queue.write_buffer( + &self.camera_buffer, + 0, + bytemuck::cast_slice(&[self.camera_uniform]), + ); + } fn render(&mut self) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; @@ -369,6 +438,7 @@ impl State { render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, &self.camera_bind_group, &[]); render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); render_pass.draw_indexed(0..self.num_indices, 0, 0..1); diff --git a/src/shader.wgsl b/src/shader.wgsl index 2970397..7d10e3f 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,5 +1,12 @@ // Vertex shader +struct CameraUniform { + view_proj: mat4x4, +}; + +@group(1) @binding(0) // 1. +var camera: CameraUniform; + struct VertexInput { @location(0) position: vec3, @location(1) tex_coords: vec2, @@ -16,7 +23,7 @@ fn vs_main( ) -> VertexOutput { var out: VertexOutput; out.tex_coords = model.tex_coords; - out.clip_position = vec4(model.position, 1.0); + out.clip_position = camera.view_proj * vec4(model.position, 1.0); // 2. return out; } @@ -24,6 +31,7 @@ fn vs_main( @group(0) @binding(0) var t_diffuse: texture_2d; + @group(0) @binding(1) var s_diffuse: sampler;