From 19b9c8c14864c0dbadfd8c99bdd8dc9e9b9e4f32 Mon Sep 17 00:00:00 2001 From: Werner Date: Wed, 3 Nov 2021 19:05:49 -0300 Subject: [PATCH] 3d camera --- Shaders/Texture.wgsl | 10 ++- Source/Camera.rs | 154 +++++++++++++++++++++++++++++++++++++++++++ Source/Main.rs | 85 ++++++++++++++++++++++-- 3 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 Source/Camera.rs diff --git a/Shaders/Texture.wgsl b/Shaders/Texture.wgsl index b464d9d..11f288f 100644 --- a/Shaders/Texture.wgsl +++ b/Shaders/Texture.wgsl @@ -1,5 +1,13 @@ // Vertex shader +[[block]] +struct CameraUniform { + view_proj: mat4x4; +}; + +[[group(1), binding(0)]] +var camera: CameraUniform; + struct VertexInput { [[location(0)]] position: vec3; [[location(1)]] tex_coords: vec2; @@ -16,7 +24,7 @@ fn 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); // 3. return out; } diff --git a/Source/Camera.rs b/Source/Camera.rs new file mode 100644 index 0000000..6695b1e --- /dev/null +++ b/Source/Camera.rs @@ -0,0 +1,154 @@ +use bytemuck::{Pod, Zeroable}; +use winit::event::*; + +#[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.0, + 0.0, 0.0, 0.5, 1.0, +); + +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 { + let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up); + let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); + + OPENGL_TO_WGPU_MATRIX * proj * view + } +} + +// This is so we can store this in a buffer +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod, 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_up_pressed: bool, + pub is_down_pressed: bool, + 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_up_pressed: false, + is_down_pressed: false, + 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::Space => { + self.is_up_pressed = is_pressed; + true + } + VirtualKeyCode::LShift => { + self.is_down_pressed = is_pressed; + true + } + 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 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 up/ down 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 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/Source/Main.rs b/Source/Main.rs index e7326cd..1f377d5 100644 --- a/Source/Main.rs +++ b/Source/Main.rs @@ -1,8 +1,12 @@ #![allow(non_snake_case)] +#[path = "Camera.rs"] +mod _Camera; +use _Camera::*; + #[path = "Texture.rs"] mod _Texture; -use _Texture::Texture; +use _Texture::*; use bytemuck::{Pod, Zeroable}; use image::GenericImageView; @@ -83,6 +87,11 @@ struct State { num_indices: u32, diffuse_bind_group: wgpu::BindGroup, diffuse_texture: Texture, + camera: Camera, + camera_uniform: CameraUniform, + camera_buffer: wgpu::Buffer, + camera_bind_group: wgpu::BindGroup, + camera_controller: CameraController, } impl State { @@ -223,6 +232,57 @@ impl State { label: Some("diffuse_bind_group"), }); + // Camera + + let camera = Camera { + // position the camera one 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 camera_controller = CameraController::new(0.2); + + 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"), + }); + // Shader let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { @@ -235,7 +295,7 @@ impl State { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&texture_bind_group_layout], + bind_group_layouts: &[&texture_bind_group_layout, &camera_bind_group_layout], push_constant_ranges: &[], }); @@ -306,6 +366,11 @@ impl State { num_indices, diffuse_bind_group, diffuse_texture, + camera, + camera_uniform, + camera_buffer, + camera_bind_group, + camera_controller, } } @@ -319,10 +384,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()?; @@ -359,9 +432,13 @@ 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); }