From 5d8cb19212ee56230c6310b94b1b3cc870079a86 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Mon, 15 May 2023 01:02:45 -0400 Subject: [PATCH] add simple 3d camera, rename Model2dComponent to MeshComponent, rename base shader file --- res/shader/{base_2d.wgsl => base.wgsl} | 9 ++- src/ecs/components/camera.rs | 56 +++++++++++++++ src/ecs/components/{model_2d.rs => mesh.rs} | 4 +- src/ecs/components/mod.rs | 5 +- src/game.rs | 8 +-- src/main.rs | 11 ++- src/math/mod.rs | 11 ++- src/render/camera.rs | 21 ++++++ src/render/mod.rs | 3 +- src/render/renderer.rs | 80 ++++++++++++++++++--- 10 files changed, 180 insertions(+), 28 deletions(-) rename res/shader/{base_2d.wgsl => base.wgsl} (77%) create mode 100755 src/ecs/components/camera.rs rename src/ecs/components/{model_2d.rs => mesh.rs} (85%) create mode 100755 src/render/camera.rs diff --git a/res/shader/base_2d.wgsl b/res/shader/base.wgsl similarity index 77% rename from res/shader/base_2d.wgsl rename to res/shader/base.wgsl index 01e6b99..c8e6ce1 100755 --- a/res/shader/base_2d.wgsl +++ b/res/shader/base.wgsl @@ -10,16 +10,23 @@ struct VertexOutput { @location(0) tex_coords: vec2, } +struct CameraUniform { + view_proj: mat4x4, +}; + @group(1) @binding(0) var u_model_transform: mat4x4; +@group(2) @binding(0) +var camera: CameraUniform; + @vertex fn vs_main( model: VertexInput, ) -> VertexOutput { var out: VertexOutput; out.tex_coords = model.tex_coords; - out.clip_position = u_model_transform * vec4(model.position, 1.0); + out.clip_position = camera.view_proj * u_model_transform * vec4(model.position, 1.0); return out; } diff --git a/src/ecs/components/camera.rs b/src/ecs/components/camera.rs new file mode 100755 index 0000000..646e30b --- /dev/null +++ b/src/ecs/components/camera.rs @@ -0,0 +1,56 @@ +use winit::dpi::PhysicalSize; + +use crate::math::{Angle, OPENGL_TO_WGPU_MATRIX}; + +pub struct CameraComponent { + eye: glam::Vec3, + target: glam::Vec3, + up: glam::Vec3, + aspect_ratio: Option, + fov_y: Angle, + znear: f32, + zfar: f32, +} + +impl Default for CameraComponent { + fn default() -> Self { + Self { + // 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: (0.0, 1.0, 0.0).into(), + aspect_ratio: None,//config.width as f32 / config.height as f32, + fov_y: Angle::Degrees(45.0), + znear: 0.1, + zfar: 100.0, + } + } +} + +impl CameraComponent { + pub fn new() -> Self { + Self::default() + } + + /// Set the camera aspect ratio. This should only be used internally. + pub(crate) fn set_aspect_ratio(&mut self, size: PhysicalSize) { + self.aspect_ratio = Some(size.width as f32 / size.height as f32); + } + + /// Check if the camera is ready to be used in rendering. + pub fn is_ready(&self) -> bool { + self.aspect_ratio.is_some() + } + + pub fn get_view_projection_matrix(&self) -> glam::Mat4 { + let aspect_ratio = self.aspect_ratio.expect("ERROR: Camera aspect ratio was not set!"); + + let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up); + let projection = glam::Mat4::perspective_rh_gl(self.fov_y.to_radians(), aspect_ratio, self.znear, self.zfar); + + OPENGL_TO_WGPU_MATRIX * projection * view + } +} \ No newline at end of file diff --git a/src/ecs/components/model_2d.rs b/src/ecs/components/mesh.rs similarity index 85% rename from src/ecs/components/model_2d.rs rename to src/ecs/components/mesh.rs index ece71f9..b263ae6 100755 --- a/src/ecs/components/model_2d.rs +++ b/src/ecs/components/mesh.rs @@ -3,12 +3,12 @@ use specs::{Component, DenseVecStorage}; use crate::render::{vertex::Vertex, mesh::Mesh, material::Material}; #[derive(Component, Clone)] -pub struct Model2dComponent { +pub struct MeshComponent { pub mesh: Mesh, pub material: Material, } -impl Model2dComponent { +impl MeshComponent { pub fn new(mesh: Mesh, material: Material) -> Self { Self { mesh, diff --git a/src/ecs/components/mod.rs b/src/ecs/components/mod.rs index c199d73..93b9a2b 100755 --- a/src/ecs/components/mod.rs +++ b/src/ecs/components/mod.rs @@ -1,2 +1,3 @@ -pub mod model_2d; -pub mod transform; \ No newline at end of file +pub mod mesh; +pub mod transform; +pub mod camera; \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index 04da05e..429a343 100755 --- a/src/game.rs +++ b/src/game.rs @@ -12,7 +12,7 @@ use tracing_subscriber::{ use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode}, event_loop::{EventLoop, ControlFlow}}; -use crate::{render::{renderer::{Renderer, BasicRenderer}, render_job::RenderJob}, input_event::InputEvent, ecs::components::{model_2d::Model2dComponent, transform::TransformComponent}}; +use crate::{render::{renderer::{Renderer, BasicRenderer}, render_job::RenderJob}, input_event::InputEvent, ecs::components::{mesh::MeshComponent, transform::TransformComponent}}; struct GameLoop { window: Arc, @@ -122,15 +122,15 @@ impl GameLoop { let mut world = self.world.lock().await; - for (ent, (transform,)) in world.query_mut::<(&mut TransformComponent,)>() { + /* for (ent, (transform,)) in world.query_mut::<(&mut TransformComponent,)>() { transform.transform.translate_vec3(glam::Vec3::new(0.0, 0.001, 0.0)); } for (ent, (transform,)) in world.query::<(&TransformComponent,)>().iter() { println!("transform pos: {:?}", transform.transform); - } + } */ - self.renderer.as_mut().prepare(&world).await; + self.renderer.as_mut().prepare(&mut world).await; drop(world); match self.renderer.as_mut().render().await { diff --git a/src/main.rs b/src/main.rs index 27f1eea..3776dbb 100755 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod resources; mod ecs; mod math; -use ecs::components::model_2d::Model2dComponent; +use ecs::components::mesh::MeshComponent; use ecs::components::transform::TransformComponent; use game::Game; @@ -13,6 +13,7 @@ use hecs::World; use crate::render::material::Material; use crate::render::texture::Texture; +use crate::ecs::components::camera::CameraComponent; //use specs::*; #[derive(Debug, Default)] @@ -63,11 +64,11 @@ impl Point3d { async fn main() { let mut world = World::new(); - world.spawn((Point2d::new(10, 10), Point3d::new(50, 50, 50))); + //world.spawn((Point2d::new(10, 10), Point3d::new(50, 50, 50))); let diffuse_bytes = include_bytes!("../res/happy-tree.png"); let diffuse_texture = Texture::from_bytes(diffuse_bytes).unwrap(); - world.spawn((Model2dComponent::new( + world.spawn((MeshComponent::new( render::mesh::Mesh { vertices: crate::render::vertex::VERTICES.to_vec(), indices: Some(crate::render::vertex::INDICES.to_vec()) @@ -78,9 +79,7 @@ async fn main() { TransformComponent::new(), )); - for (id, (point2d,)) in world.query::<(&Point2d,)>().iter() { - println!("Entity {} is at 2d position: {:?}", id.id(), point2d); - } + world.spawn((CameraComponent::new(),)); Game::initialize().await .with_world(world) diff --git a/src/math/mod.rs b/src/math/mod.rs index 1b7e551..f1ae129 100755 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -5,4 +5,13 @@ pub mod angle; pub use angle::*; pub mod transform; -pub use transform::*; \ No newline at end of file +pub use transform::*; + +/// A matrix used to convert an OpenGL matrix to a matrix compatible with Wgpu +#[rustfmt::skip] +pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = glam::Mat4::from_cols_array(&[ + 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, +]); \ No newline at end of file diff --git a/src/render/camera.rs b/src/render/camera.rs new file mode 100755 index 0000000..a34e7f1 --- /dev/null +++ b/src/render/camera.rs @@ -0,0 +1,21 @@ +use winit::dpi::PhysicalSize; + +use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, ecs::components::camera::CameraComponent}; + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Camera { + projection: glam::Mat4 +} + +impl Camera { + pub fn new() -> Self { + Self { + projection: glam::Mat4::IDENTITY, + } + } + + pub fn update_view_projection(&mut self, camera: &CameraComponent) { + self.projection = camera.get_view_projection_matrix().into(); + } +} \ No newline at end of file diff --git a/src/render/mod.rs b/src/render/mod.rs index 02da5a3..739a771 100755 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -7,4 +7,5 @@ pub mod render_job; pub mod mesh; pub mod texture; pub mod shader_loader; -pub mod material; \ No newline at end of file +pub mod material; +pub mod camera; \ No newline at end of file diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 5ed6a52..b91f9b6 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -6,25 +6,27 @@ use std::borrow::Cow; use async_std::sync::Mutex; use async_trait::async_trait; -use tracing::debug; +use tracing::{debug, warn}; use wgpu::{BindGroup, BindGroupLayout}; use wgpu::util::DeviceExt; use winit::window::Window; use hecs::{World, Entity}; -use crate::ecs::components::model_2d::Model2dComponent; +use crate::ecs::components::camera::CameraComponent; +use crate::ecs::components::mesh::MeshComponent; use crate::ecs::components::transform::TransformComponent; use crate::math::Transform; use crate::resources; +use super::camera::Camera; use super::desc_buf_lay::DescVertexBufferLayout; use super::texture::RenderTexture; use super::{render_pipeline::FullRenderPipeline, vertex::{VERTICES}, render_buffer::BufferStorage, render_job::RenderJob, mesh::Mesh}; #[async_trait] pub trait Renderer { - async fn prepare(&mut self, main_world: &World); + async fn prepare(&mut self, main_world: &mut World); async fn render(&mut self) -> Result<(), wgpu::SurfaceError>; async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize); @@ -56,8 +58,12 @@ pub struct BasicRenderer { buffer_storage: HashMap, // TODO: clean up left over buffers from deleted entities/components - transform_bind_group: wgpu::BindGroup, transform_buffer: wgpu::Buffer, + transform_bind_group: wgpu::BindGroup, + + inuse_camera: Camera, + camera_buffer: wgpu::Buffer, + camera_bind_group: wgpu::BindGroup, } impl BasicRenderer { @@ -140,7 +146,7 @@ impl BasicRenderer { label: Some("texture_bind_group_layout"), }); - let shader_src = resources::load_string("shader/base_2d.wgsl").await.expect("Failed to load shader!"); + let shader_src = resources::load_string("shader/base.wgsl").await.expect("Failed to load shader!"); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(&shader_src)), @@ -181,9 +187,45 @@ impl BasicRenderer { label: Some("transform_bind_group"), }); + let camera_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Camera Buffer"), + contents: bytemuck::cast_slice(&[Camera::new()]), + 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 mut pipelines = HashMap::new(); pipelines.insert(0, Arc::new(FullRenderPipeline::new(&device, &config, &shader, - vec![super::vertex::Vertex::desc(),], vec![&texture_bind_group_layout, &transform_bind_group_layout]))); + vec![super::vertex::Vertex::desc(),], + vec![&texture_bind_group_layout, &transform_bind_group_layout, &camera_bind_group_layout]))); Self { window, @@ -204,10 +246,14 @@ impl BasicRenderer { transform_buffer, transform_bind_group, + + inuse_camera: Camera::new(), + camera_buffer, + camera_bind_group, } } - fn create_model_buffers(&mut self, model_2d: &Model2dComponent) -> RenderBufferStorage { + fn create_model_buffers(&mut self, model_2d: &MeshComponent) -> RenderBufferStorage { let mesh = &model_2d.mesh; let vertex_buffer = self.device.create_buffer_init( @@ -294,8 +340,8 @@ impl BasicRenderer { #[async_trait] impl Renderer for BasicRenderer { - async fn prepare(&mut self, main_world: &World) { - for (entity, (model, transform)) in main_world.query::<(&Model2dComponent, Option<&TransformComponent>)>().iter() { + async fn prepare(&mut self, main_world: &mut World) { + for (entity, (model, transform)) in main_world.query::<(&MeshComponent, Option<&TransformComponent>)>().iter() { let transform = match transform { Some(transform) => { Some(transform.transform) @@ -315,10 +361,19 @@ impl Renderer for BasicRenderer { if self.buffer_storage.get(&entity).is_none() { let buffers = self.create_model_buffers(model); self.buffer_storage.insert(entity, buffers); - } else { - } } + + if let Some((_e, (camera,))) = main_world.query_mut::<(&mut CameraComponent,)>().into_iter().next() { + //if !camera.is_ready() { + camera.set_aspect_ratio(self.size); + //} + + self.inuse_camera.update_view_projection(camera); + self.queue.write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[self.inuse_camera])); + } else { + warn!("Missing camera!"); + } } fn add_render_pipeline(&mut self, shader_id: u32, pipeline: Arc) { @@ -369,6 +424,9 @@ impl Renderer for BasicRenderer { } else { debug!("//TODO: clear transform uniform if the RenderJob doesn't have a transform."); } + + // There will always be a camera (hopefully) + render_pass.set_bind_group(2, &self.camera_bind_group, &[]); if let Some(indices) = buffers.buffer_indices.as_ref() { let indices_len = indices.count().unwrap(); // index buffers will have count, if not thats a bug