Extract camera

This commit is contained in:
numzero 2024-09-25 23:09:07 +03:00
parent d3d4048a5c
commit 2e2c93792b
2 changed files with 138 additions and 109 deletions

123
src/bin/wireframe/camera.rs Normal file
View File

@ -0,0 +1,123 @@
use std::mem;
use glam::{mat4, vec3, vec4, Mat4, Vec2, Vec3};
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CameraUniform {
mvp: [[f32; 4]; 4],
scale: [f32; 2],
pad: [u32; 2],
}
pub struct Camera {
buf: wgpu::Buffer,
bind: wgpu::BindGroup,
layout: wgpu::BindGroupLayout,
}
impl Camera {
pub fn new(device: &wgpu::Device) -> Camera {
let buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Camera Buffer"),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
size: mem::size_of::<CameraUniform>() as u64,
mapped_at_creation: false,
});
let 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 BindGroupLayout"),
});
let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buf.as_entire_binding(),
}],
label: Some("Camera BindGroup"),
});
Camera { buf, bind, layout }
}
pub fn bind_group_layout(&self) -> &wgpu::BindGroupLayout {
&self.layout
}
pub fn bind_group(&self) -> &wgpu::BindGroup {
&self.bind
}
fn uniform(view_mtx: Mat4, view_size: Vec2) -> CameraUniform {
const M: Mat4 = mat4(
vec4(0., 0., 1., 0.),
vec4(-1., 0., 0., 0.),
vec4(0., 1., 0., 0.),
vec4(0., 0., 0., 1.),
);
let size = view_size.normalize() * std::f32::consts::SQRT_2;
let proj = make_proj_matrix(vec3(size.x, size.y, 2.), (1., 4096.)) * M;
let mvp = proj * view_mtx;
CameraUniform {
mvp: mvp.to_cols_array_2d(),
scale: (1. / size).to_array(),
pad: [0; 2],
}
}
pub fn set(&self, queue: &wgpu::Queue, view_mtx: Mat4, view_size: Vec2) {
let uniform = Self::uniform(view_mtx, view_size);
queue.write_buffer(&self.buf, 0, bytemuck::bytes_of(&uniform));
}
}
/// Make a projection matrix, assuming input coordinates are (right, up, forward).
///
/// `corner` is a vector that will be mapped to (x=1, y=1) after the perspective division.
/// `zrange` is the Z range that will be mapped to z∈[-1, 1]. It has no other effect. Both ends have to be positive though.
fn make_proj_matrix(corner: Vec3, zrange: (f32, f32)) -> Mat4 {
let scale = 1.0 / corner;
let zspan = zrange.1 - zrange.0;
mat4(
scale.x * vec4(1., 0., 0., 0.),
scale.y * vec4(0., 1., 0., 0.),
scale.z * vec4(0., 0., (zrange.0 + zrange.1) / zspan, 1.),
scale.z * vec4(0., 0., -2. * zrange.0 * zrange.1 / zspan, 0.),
)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use glam::vec3;
#[test]
fn test_proj_matrix() {
let m = make_proj_matrix(vec3(2., 3., 4.), (0.5, 20.0));
let v = m * vec4(2., 3., 4., 1.);
assert_abs_diff_eq!(v.x / v.w, 1.0);
assert_abs_diff_eq!(v.y / v.w, 1.0);
assert!(-v.w < v.z && v.z < v.w, "z out of range in {v}");
let v = m * vec4(2., 3., 0.5, 1.);
assert_abs_diff_eq!(v.x / v.w, 8.0);
assert_abs_diff_eq!(v.y / v.w, 8.0);
assert_abs_diff_eq!(v.z / v.w, -1.0);
let v = m * vec4(2., 3., 20.0, 1.);
assert_abs_diff_eq!(v.x / v.w, 0.2);
assert_abs_diff_eq!(v.y / v.w, 0.2);
assert_abs_diff_eq!(v.z / v.w, 1.0);
}
}

View File

@ -1,6 +1,6 @@
use std::{iter, mem, time::Instant}; use std::{iter, mem, time::Instant};
use glam::{mat4, vec2, vec3, vec4, Mat4, Vec3}; use glam::{vec2, vec3, Vec3};
use wgpu::{util::DeviceExt, ShaderStages}; use wgpu::{util::DeviceExt, ShaderStages};
use winit::{ use winit::{
event::*, event::*,
@ -9,6 +9,7 @@ use winit::{
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
mod camera;
mod scene; mod scene;
// The coordinate system: // The coordinate system:
@ -140,14 +141,6 @@ static KEYS_ROTATE: &'static [(PhysicalKey, Vec3)] = &[
(PhysicalKey::Code(KeyCode::Numpad6), vec3(0., 0., -1.)), (PhysicalKey::Code(KeyCode::Numpad6), vec3(0., 0., -1.)),
]; ];
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CameraUniform {
mvp: [[f32; 4]; 4],
scale: [f32; 2],
pad: [u32; 2],
}
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct LineUniform { struct LineUniform {
@ -164,11 +157,10 @@ struct State<'a> {
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
kbd: keyctl::Keyboard, kbd: keyctl::Keyboard,
cam: camctl::CameraLocation,
t1: Instant, t1: Instant,
camera_buffer: wgpu::Buffer, cam_loc: camctl::CameraLocation,
camera_bind_group: wgpu::BindGroup, cam_obj: camera::Camera,
scene: Vec<Wireframe>, scene: Vec<Wireframe>,
@ -227,37 +219,10 @@ impl<'a> State<'a> {
}; };
let kbd = keyctl::Keyboard::new(); let kbd = keyctl::Keyboard::new();
let cam = camctl::CameraLocation::new(); let cam_loc = camctl::CameraLocation::new();
let t1 = Instant::now(); let t1 = Instant::now();
let camera_buffer = device.create_buffer(&wgpu::BufferDescriptor { let cam_obj = camera::Camera::new(&device);
label: Some("Camera Buffer"),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
size: mem::size_of::<CameraUniform>() as u64,
mapped_at_creation: false,
});
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 shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"), label: Some("Shader"),
@ -272,7 +237,7 @@ impl<'a> State<'a> {
let render_pipeline_layout = let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"), label: Some("Render Pipeline Layout"),
bind_group_layouts: &[&camera_bind_group_layout], bind_group_layouts: &[cam_obj.bind_group_layout()],
push_constant_ranges: &[line_push_constant_range], push_constant_ranges: &[line_push_constant_range],
}); });
@ -349,11 +314,10 @@ impl<'a> State<'a> {
size, size,
render_pipeline, render_pipeline,
kbd, kbd,
cam, cam_loc,
cam_obj,
t1, t1,
scene, scene,
camera_buffer,
camera_bind_group,
window, window,
} }
} }
@ -378,29 +342,13 @@ impl<'a> State<'a> {
self.t1 = t2; self.t1 = t2;
dt.as_secs_f32() dt.as_secs_f32()
}; };
self.cam.move_rel(100. * dt * self.kbd.control(&KEYS_MOVE));
self.cam
.rotate_rel_ypr(2. * dt * self.kbd.control(&KEYS_ROTATE));
let size = vec2(self.config.width as f32, self.config.height as f32); let size = vec2(self.config.width as f32, self.config.height as f32);
let size = size.normalize() * std::f32::consts::SQRT_2;
let proj = make_proj_matrix(vec3(size.x, size.y, 2.), (1., 4096.));
let my_to_gl = mat4( self.cam_loc
vec4(0., 0., 1., 0.), .move_rel(100. * dt * self.kbd.control(&KEYS_MOVE));
vec4(-1., 0., 0., 0.), self.cam_loc
vec4(0., 1., 0., 0.), .rotate_rel_ypr(2. * dt * self.kbd.control(&KEYS_ROTATE));
vec4(0., 0., 0., 1.), self.cam_obj.set(&self.queue, self.cam_loc.view_mtx(), size);
);
let view = my_to_gl * self.cam.view_mtx();
let mvp = proj * view;
let camera_uniform = CameraUniform {
mvp: mvp.to_cols_array_2d(),
scale: (1. / size).to_array(),
pad: [0; 2],
};
self.queue
.write_buffer(&self.camera_buffer, 0, bytemuck::bytes_of(&camera_uniform));
} }
fn render(&mut self) -> Result<(), wgpu::SurfaceError> { fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
@ -437,7 +385,7 @@ impl<'a> State<'a> {
}); });
render_pass.set_pipeline(&self.render_pipeline); render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.camera_bind_group, &[]); render_pass.set_bind_group(0, self.cam_obj.bind_group(), &[]);
for wireframe in &self.scene { for wireframe in &self.scene {
let line = LineUniform { let line = LineUniform {
color: wireframe.color.to_array(), color: wireframe.color.to_array(),
@ -526,45 +474,3 @@ pub async fn run() {
fn main() { fn main() {
pollster::block_on(run()); pollster::block_on(run());
} }
/// Make a projection matrix, assuming input coordinates are (right, up, forward).
///
/// `corner` is a vector that will be mapped to (x=1, y=1) after the perspective division.
/// `zrange` is the Z range that will be mapped to z∈[-1, 1]. It has no other effect. Both ends have to be positive though.
fn make_proj_matrix(corner: Vec3, zrange: (f32, f32)) -> Mat4 {
let scale = 1.0 / corner;
let zspan = zrange.1 - zrange.0;
mat4(
scale.x * vec4(1., 0., 0., 0.),
scale.y * vec4(0., 1., 0., 0.),
scale.z * vec4(0., 0., (zrange.0 + zrange.1) / zspan, 1.),
scale.z * vec4(0., 0., -2. * zrange.0 * zrange.1 / zspan, 0.),
)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use glam::vec3;
#[test]
fn test_proj_matrix() {
let m = make_proj_matrix(vec3(2., 3., 4.), (0.5, 20.0));
let v = m * vec4(2., 3., 4., 1.);
assert_abs_diff_eq!(v.x / v.w, 1.0);
assert_abs_diff_eq!(v.y / v.w, 1.0);
assert!(-v.w < v.z && v.z < v.w, "z out of range in {v}");
let v = m * vec4(2., 3., 0.5, 1.);
assert_abs_diff_eq!(v.x / v.w, 8.0);
assert_abs_diff_eq!(v.y / v.w, 8.0);
assert_abs_diff_eq!(v.z / v.w, -1.0);
let v = m * vec4(2., 3., 20.0, 1.);
assert_abs_diff_eq!(v.x / v.w, 0.2);
assert_abs_diff_eq!(v.y / v.w, 0.2);
assert_abs_diff_eq!(v.z / v.w, 1.0);
}
}