mirror of https://github.com/sotrh/learn-wgpu
finished uniforms tutorial
parent
ac1543bea6
commit
58fa5be6d2
@ -1,12 +1,8 @@
|
||||
[workspace]
|
||||
members = [
|
||||
# beginner tutorials
|
||||
"code/beginner/tutorial1-window",
|
||||
"code/beginner/tutorial2-swapchain",
|
||||
"code/beginner/tutorial3-pipeline",
|
||||
"code/beginner/tutorial4-buffer",
|
||||
"code/beginner/tutorial5-textures",
|
||||
"code/beginner/*",
|
||||
|
||||
# intermediate tutorials
|
||||
"code/intermediate/windowless",
|
||||
"code/intermediate/*",
|
||||
]
|
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "tutorial6-uniforms"
|
||||
version = "0.1.0"
|
||||
authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
image = "0.22"
|
||||
winit = "0.20.0-alpha4"
|
||||
glsl-to-spirv = "0.1"
|
||||
wgpu = "0.4"
|
||||
cgmath = "0.17"
|
||||
|
||||
[[bin]]
|
||||
name = "tutorial6-uniforms"
|
||||
path = "main.rs"
|
||||
|
||||
|
||||
[[bin]]
|
||||
name = "tutorial6-challenge"
|
||||
path = "challenge.rs"
|
@ -0,0 +1,598 @@
|
||||
use winit::{
|
||||
event::*,
|
||||
event_loop::{EventLoop, ControlFlow},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Vertex {
|
||||
position: [f32; 3],
|
||||
tex_coords: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferDescriptor {
|
||||
stride: mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::InputStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float2,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const VERTICES: &[Vertex] = &[
|
||||
Vertex { position: [-0.0868241, -0.49240386, 0.0], tex_coords: [1.0 - 0.4131759, 1.0 - 0.00759614], }, // A
|
||||
Vertex { position: [-0.49513406, -0.06958647, 0.0], tex_coords: [1.0 - 0.0048659444, 1.0 - 0.43041354], }, // B
|
||||
Vertex { position: [-0.21918549, 0.44939706, 0.0], tex_coords: [1.0 - 0.28081453, 1.0 - 0.949397057], }, // C
|
||||
Vertex { position: [0.35966998, 0.3473291, 0.0], tex_coords: [1.0 - 0.85967, 1.0 - 0.84732911], }, // D
|
||||
Vertex { position: [0.44147372, -0.2347359, 0.0], tex_coords: [1.0 - 0.9414737, 1.0 - 0.2652641], }, // E
|
||||
];
|
||||
|
||||
const INDICES: &[u16] = &[
|
||||
0, 1, 4,
|
||||
1, 2, 4,
|
||||
2, 3, 4,
|
||||
];
|
||||
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = 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,
|
||||
);
|
||||
|
||||
struct Camera {
|
||||
eye: cgmath::Point3<f32>,
|
||||
target: cgmath::Point3<f32>,
|
||||
up: cgmath::Vector3<f32>,
|
||||
aspect: f32,
|
||||
fovy: f32,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
fn build_view_projection_matrix(&self) -> cgmath::Matrix4<f32> {
|
||||
let view = cgmath::Matrix4::look_at(self.eye, self.target, self.up);
|
||||
let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar);
|
||||
return proj * view;
|
||||
}
|
||||
}
|
||||
|
||||
struct UniformStaging {
|
||||
camera: Camera,
|
||||
model_rotation: cgmath::Deg<f32>,
|
||||
}
|
||||
|
||||
impl UniformStaging {
|
||||
fn new(camera: Camera) -> Self {
|
||||
Self { camera, model_rotation: cgmath::Deg(0.0) }
|
||||
}
|
||||
fn update_uniforms(&self, uniforms: &mut Uniforms) {
|
||||
uniforms.model_view_proj =
|
||||
OPENGL_TO_WGPU_MATRIX
|
||||
* self.camera.build_view_projection_matrix()
|
||||
* cgmath::Matrix4::from_angle_z(self.model_rotation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Uniforms {
|
||||
model_view_proj: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new() -> Self {
|
||||
use cgmath::SquareMatrix;
|
||||
Self {
|
||||
model_view_proj: cgmath::Matrix4::identity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CameraController {
|
||||
speed: f32,
|
||||
is_up_pressed: bool,
|
||||
is_down_pressed: bool,
|
||||
is_forward_pressed: bool,
|
||||
is_backward_pressed: bool,
|
||||
is_left_pressed: bool,
|
||||
is_right_pressed: bool,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_camera(&self, camera: &mut Camera) {
|
||||
use cgmath::InnerSpace;
|
||||
let forward = (camera.target - camera.eye).normalize();
|
||||
|
||||
if self.is_forward_pressed {
|
||||
camera.eye += forward * self.speed;
|
||||
}
|
||||
if self.is_backward_pressed {
|
||||
camera.eye -= forward * self.speed;
|
||||
}
|
||||
|
||||
let right = forward.cross(camera.up);
|
||||
|
||||
if self.is_right_pressed {
|
||||
camera.eye += right * self.speed;
|
||||
}
|
||||
if self.is_left_pressed {
|
||||
camera.eye -= right * self.speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
surface: wgpu::Surface,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
sc_desc: wgpu::SwapChainDescriptor,
|
||||
swap_chain: wgpu::SwapChain,
|
||||
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
|
||||
vertex_buffer: wgpu::Buffer,
|
||||
index_buffer: wgpu::Buffer,
|
||||
num_indices: u32,
|
||||
|
||||
diffuse_texture: wgpu::Texture,
|
||||
diffuse_texture_view: wgpu::TextureView,
|
||||
diffuse_sampler: wgpu::Sampler,
|
||||
diffuse_bind_group: wgpu::BindGroup,
|
||||
|
||||
camera_controller: CameraController,
|
||||
uniforms: Uniforms,
|
||||
uniform_staging: UniformStaging,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
|
||||
hidpi_factor: f64,
|
||||
size: winit::dpi::LogicalSize,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(window: &Window) -> Self {
|
||||
let hidpi_factor = window.hidpi_factor();
|
||||
let size = window.inner_size();
|
||||
let physical_size = size.to_physical(hidpi_factor);
|
||||
|
||||
let surface = wgpu::Surface::create(window);
|
||||
|
||||
let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions {
|
||||
..Default::default()
|
||||
}).unwrap();
|
||||
|
||||
let (device, mut queue) = adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
extensions: wgpu::Extensions {
|
||||
anisotropic_filtering: false,
|
||||
},
|
||||
limits: Default::default(),
|
||||
});
|
||||
|
||||
let sc_desc = wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
width: physical_size.width.round() as u32,
|
||||
height: physical_size.height.round() as u32,
|
||||
present_mode: wgpu::PresentMode::Vsync,
|
||||
};
|
||||
let swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||
|
||||
let diffuse_bytes = include_bytes!("happy-tree.png");
|
||||
let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap();
|
||||
let diffuse_rgba = diffuse_image.as_rgba8().unwrap();
|
||||
|
||||
use image::GenericImageView;
|
||||
let dimensions = diffuse_image.dimensions();
|
||||
|
||||
let size3d = wgpu::Extent3d {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
depth: 1,
|
||||
};
|
||||
let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
size: size3d,
|
||||
array_layer_count: 1,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||
});
|
||||
|
||||
let diffuse_buffer = device
|
||||
.create_buffer_mapped(diffuse_rgba.len(), wgpu::BufferUsage::COPY_SRC)
|
||||
.fill_from_slice(&diffuse_rgba);
|
||||
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
todo: 0,
|
||||
});
|
||||
|
||||
encoder.copy_buffer_to_texture(
|
||||
wgpu::BufferCopyView {
|
||||
buffer: &diffuse_buffer,
|
||||
offset: 0,
|
||||
row_pitch: 4 * dimensions.0,
|
||||
image_height: dimensions.1,
|
||||
},
|
||||
wgpu::TextureCopyView {
|
||||
texture: &diffuse_texture,
|
||||
mip_level: 0,
|
||||
array_layer: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
size3d,
|
||||
);
|
||||
|
||||
queue.submit(&[encoder.finish()]);
|
||||
|
||||
let diffuse_texture_view = diffuse_texture.create_default_view();
|
||||
let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
lod_min_clamp: -100.0,
|
||||
lod_max_clamp: 100.0,
|
||||
compare_function: wgpu::CompareFunction::Always,
|
||||
});
|
||||
|
||||
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
},
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &texture_bind_group_layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&diffuse_texture_view),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
let camera = Camera {
|
||||
eye: (0.0, 1.0, -2.0).into(),
|
||||
target: (0.0, 0.0, 0.0).into(),
|
||||
up: cgmath::Vector3::unit_y(),
|
||||
aspect: sc_desc.width as f32 / sc_desc.height as f32,
|
||||
fovy: 45.0,
|
||||
znear: 0.1,
|
||||
zfar: 100.0,
|
||||
};
|
||||
let camera_controller = CameraController::new(0.2);
|
||||
|
||||
let mut uniforms = Uniforms::new();
|
||||
let uniform_staging = UniformStaging::new(camera);
|
||||
uniform_staging.update_uniforms(&mut uniforms);
|
||||
|
||||
let uniform_buffer = device
|
||||
.create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST)
|
||||
.fill_from_slice(&[uniforms]);
|
||||
|
||||
let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX,
|
||||
ty: wgpu::BindingType::UniformBuffer {
|
||||
dynamic: false,
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &uniform_bind_group_layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &uniform_buffer,
|
||||
range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress,
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
let vs_src = include_str!("shader.vert");
|
||||
let fs_src = include_str!("shader.frag");
|
||||
let vs_spirv = glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex).unwrap();
|
||||
let fs_spirv = glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment).unwrap();
|
||||
let vs_data = wgpu::read_spirv(vs_spirv).unwrap();
|
||||
let fs_data = wgpu::read_spirv(fs_spirv).unwrap();
|
||||
let vs_module = device.create_shader_module(&vs_data);
|
||||
let fs_module = device.create_shader_module(&fs_data);
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
layout: &render_pipeline_layout,
|
||||
vertex_stage: wgpu::ProgrammableStageDescriptor {
|
||||
module: &vs_module,
|
||||
entry_point: "main",
|
||||
},
|
||||
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
|
||||
module: &fs_module,
|
||||
entry_point: "main",
|
||||
}),
|
||||
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: wgpu::CullMode::Back,
|
||||
depth_bias: 0,
|
||||
depth_bias_slope_scale: 0.0,
|
||||
depth_bias_clamp: 0.0,
|
||||
}),
|
||||
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
color_states: &[
|
||||
wgpu::ColorStateDescriptor {
|
||||
format: sc_desc.format,
|
||||
color_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
alpha_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
},
|
||||
],
|
||||
depth_stencil_state: None,
|
||||
index_format: wgpu::IndexFormat::Uint16,
|
||||
vertex_buffers: &[
|
||||
Vertex::desc(),
|
||||
],
|
||||
sample_count: 1,
|
||||
sample_mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
});
|
||||
|
||||
let vertex_buffer = device
|
||||
.create_buffer_mapped(VERTICES.len(), wgpu::BufferUsage::VERTEX)
|
||||
.fill_from_slice(VERTICES);
|
||||
let index_buffer = device
|
||||
.create_buffer_mapped(INDICES.len(), wgpu::BufferUsage::INDEX)
|
||||
.fill_from_slice(INDICES);
|
||||
let num_indices = INDICES.len() as u32;
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
sc_desc,
|
||||
swap_chain,
|
||||
render_pipeline,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices,
|
||||
diffuse_texture,
|
||||
diffuse_texture_view,
|
||||
diffuse_sampler,
|
||||
diffuse_bind_group,
|
||||
camera_controller,
|
||||
uniform_staging,
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
uniforms,
|
||||
hidpi_factor,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_hidpi_and_resize(&mut self, new_hidpi_factor: f64) {
|
||||
self.hidpi_factor = new_hidpi_factor;
|
||||
self.resize(self.size);
|
||||
}
|
||||
|
||||
fn resize(&mut self, new_size: winit::dpi::LogicalSize) {
|
||||
let physical_size = new_size.to_physical(self.hidpi_factor);
|
||||
self.size = new_size;
|
||||
self.sc_desc.width = physical_size.width.round() as u32;
|
||||
self.sc_desc.height = physical_size.height.round() as u32;
|
||||
self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
|
||||
|
||||
self.uniform_staging.camera.aspect = self.sc_desc.width as f32 / self.sc_desc.height as f32;
|
||||
}
|
||||
|
||||
fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
self.camera_controller.process_events(event)
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
self.camera_controller.update_camera(&mut self.uniform_staging.camera);
|
||||
self.uniform_staging.model_rotation += cgmath::Deg(2.0);
|
||||
self.uniform_staging.update_uniforms(&mut self.uniforms);
|
||||
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
todo: 0,
|
||||
});
|
||||
|
||||
let staging_buffer = self.device
|
||||
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
|
||||
.fill_from_slice(&[self.uniforms]);
|
||||
|
||||
encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::<Uniforms>() as wgpu::BufferAddress);
|
||||
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
|
||||
fn render(&mut self) {
|
||||
let frame = self.swap_chain.get_next_texture();
|
||||
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
todo: 0,
|
||||
});
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[
|
||||
wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &frame.view,
|
||||
resolve_target: None,
|
||||
load_op: wgpu::LoadOp::Clear,
|
||||
store_op: wgpu::StoreOp::Store,
|
||||
clear_color: wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
},
|
||||
}
|
||||
],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]);
|
||||
render_pass.set_bind_group(1, &self.uniform_bind_group, &[]);
|
||||
render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]);
|
||||
render_pass.set_index_buffer(&self.index_buffer, 0);
|
||||
render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
|
||||
}
|
||||
|
||||
self.queue.submit(&[
|
||||
encoder.finish()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut state = State::new(&window);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
ref event,
|
||||
window_id,
|
||||
} if window_id == window.id() => if state.input(event) {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
} else {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input,
|
||||
..
|
||||
} => {
|
||||
match input {
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
}
|
||||
WindowEvent::Resized(logical_size) => {
|
||||
state.resize(*logical_size);
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
WindowEvent::HiDpiFactorChanged(new_hidpi_factor) => {
|
||||
state.update_hidpi_and_resize(*new_hidpi_factor);
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
}
|
||||
Event::EventsCleared => {
|
||||
state.update();
|
||||
state.render();
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
@ -0,0 +1,582 @@
|
||||
use winit::{
|
||||
event::*,
|
||||
event_loop::{EventLoop, ControlFlow},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Vertex {
|
||||
position: [f32; 3],
|
||||
tex_coords: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferDescriptor {
|
||||
stride: mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::InputStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float2,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const VERTICES: &[Vertex] = &[
|
||||
Vertex { position: [-0.0868241, -0.49240386, 0.0], tex_coords: [1.0 - 0.4131759, 1.0 - 0.00759614], }, // A
|
||||
Vertex { position: [-0.49513406, -0.06958647, 0.0], tex_coords: [1.0 - 0.0048659444, 1.0 - 0.43041354], }, // B
|
||||
Vertex { position: [-0.21918549, 0.44939706, 0.0], tex_coords: [1.0 - 0.28081453, 1.0 - 0.949397057], }, // C
|
||||
Vertex { position: [0.35966998, 0.3473291, 0.0], tex_coords: [1.0 - 0.85967, 1.0 - 0.84732911], }, // D
|
||||
Vertex { position: [0.44147372, -0.2347359, 0.0], tex_coords: [1.0 - 0.9414737, 1.0 - 0.2652641], }, // E
|
||||
];
|
||||
|
||||
const INDICES: &[u16] = &[
|
||||
0, 1, 4,
|
||||
1, 2, 4,
|
||||
2, 3, 4,
|
||||
];
|
||||
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = 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,
|
||||
);
|
||||
|
||||
struct Camera {
|
||||
eye: cgmath::Point3<f32>,
|
||||
target: cgmath::Point3<f32>,
|
||||
up: cgmath::Vector3<f32>,
|
||||
aspect: f32,
|
||||
fovy: f32,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
fn build_view_projection_matrix(&self) -> cgmath::Matrix4<f32> {
|
||||
let view = cgmath::Matrix4::look_at(self.eye, self.target, self.up);
|
||||
let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar);
|
||||
return proj * view;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Uniforms {
|
||||
view_proj: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new() -> Self {
|
||||
use cgmath::SquareMatrix;
|
||||
Self {
|
||||
view_proj: cgmath::Matrix4::identity(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_view_proj(&mut self, camera: &Camera) {
|
||||
self.view_proj = OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix();
|
||||
}
|
||||
}
|
||||
|
||||
struct CameraController {
|
||||
speed: f32,
|
||||
is_up_pressed: bool,
|
||||
is_down_pressed: bool,
|
||||
is_forward_pressed: bool,
|
||||
is_backward_pressed: bool,
|
||||
is_left_pressed: bool,
|
||||
is_right_pressed: bool,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_camera(&self, camera: &mut Camera) {
|
||||
use cgmath::InnerSpace;
|
||||
let forward = (camera.target - camera.eye).normalize();
|
||||
|
||||
if self.is_forward_pressed {
|
||||
camera.eye += forward * self.speed;
|
||||
}
|
||||
if self.is_backward_pressed {
|
||||
camera.eye -= forward * self.speed;
|
||||
}
|
||||
|
||||
let right = forward.cross(camera.up);
|
||||
|
||||
if self.is_right_pressed {
|
||||
camera.eye += right * self.speed;
|
||||
}
|
||||
if self.is_left_pressed {
|
||||
camera.eye -= right * self.speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
surface: wgpu::Surface,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
sc_desc: wgpu::SwapChainDescriptor,
|
||||
swap_chain: wgpu::SwapChain,
|
||||
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
|
||||
vertex_buffer: wgpu::Buffer,
|
||||
index_buffer: wgpu::Buffer,
|
||||
num_indices: u32,
|
||||
|
||||
diffuse_texture: wgpu::Texture,
|
||||
diffuse_texture_view: wgpu::TextureView,
|
||||
diffuse_sampler: wgpu::Sampler,
|
||||
diffuse_bind_group: wgpu::BindGroup,
|
||||
|
||||
camera: Camera,
|
||||
camera_controller: CameraController,
|
||||
uniforms: Uniforms,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
|
||||
hidpi_factor: f64,
|
||||
size: winit::dpi::LogicalSize,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(window: &Window) -> Self {
|
||||
let hidpi_factor = window.hidpi_factor();
|
||||
let size = window.inner_size();
|
||||
let physical_size = size.to_physical(hidpi_factor);
|
||||
|
||||
let surface = wgpu::Surface::create(window);
|
||||
|
||||
let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions {
|
||||
..Default::default()
|
||||
}).unwrap();
|
||||
|
||||
let (device, mut queue) = adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
extensions: wgpu::Extensions {
|
||||
anisotropic_filtering: false,
|
||||
},
|
||||
limits: Default::default(),
|
||||
});
|
||||
|
||||
let sc_desc = wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
width: physical_size.width.round() as u32,
|
||||
height: physical_size.height.round() as u32,
|
||||
present_mode: wgpu::PresentMode::Vsync,
|
||||
};
|
||||
let swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||
|
||||
let diffuse_bytes = include_bytes!("happy-tree.png");
|
||||
let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap();
|
||||
let diffuse_rgba = diffuse_image.as_rgba8().unwrap();
|
||||
|
||||
use image::GenericImageView;
|
||||
let dimensions = diffuse_image.dimensions();
|
||||
|
||||
let size3d = wgpu::Extent3d {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
depth: 1,
|
||||
};
|
||||
let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
size: size3d,
|
||||
array_layer_count: 1,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||
});
|
||||
|
||||
let diffuse_buffer = device
|
||||
.create_buffer_mapped(diffuse_rgba.len(), wgpu::BufferUsage::COPY_SRC)
|
||||
.fill_from_slice(&diffuse_rgba);
|
||||
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
todo: 0,
|
||||
});
|
||||
|
||||
encoder.copy_buffer_to_texture(
|
||||
wgpu::BufferCopyView {
|
||||
buffer: &diffuse_buffer,
|
||||
offset: 0,
|
||||
row_pitch: 4 * dimensions.0,
|
||||
image_height: dimensions.1,
|
||||
},
|
||||
wgpu::TextureCopyView {
|
||||
texture: &diffuse_texture,
|
||||
mip_level: 0,
|
||||
array_layer: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
size3d,
|
||||
);
|
||||
|
||||
queue.submit(&[encoder.finish()]);
|
||||
|
||||
let diffuse_texture_view = diffuse_texture.create_default_view();
|
||||
let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
lod_min_clamp: -100.0,
|
||||
lod_max_clamp: 100.0,
|
||||
compare_function: wgpu::CompareFunction::Always,
|
||||
});
|
||||
|
||||
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
},
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &texture_bind_group_layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&diffuse_texture_view),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
let camera = Camera {
|
||||
eye: (0.0, 1.0, -2.0).into(),
|
||||
target: (0.0, 0.0, 0.0).into(),
|
||||
up: cgmath::Vector3::unit_y(),
|
||||
aspect: sc_desc.width as f32 / sc_desc.height as f32,
|
||||
fovy: 45.0,
|
||||
znear: 0.1,
|
||||
zfar: 100.0,
|
||||
};
|
||||
let camera_controller = CameraController::new(0.2);
|
||||
|
||||
let mut uniforms = Uniforms::new();
|
||||
uniforms.update_view_proj(&camera);
|
||||
|
||||
let uniform_buffer = device
|
||||
.create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST)
|
||||
.fill_from_slice(&[uniforms]);
|
||||
|
||||
let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX,
|
||||
ty: wgpu::BindingType::UniformBuffer {
|
||||
dynamic: false,
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &uniform_bind_group_layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &uniform_buffer,
|
||||
range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress,
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
let vs_src = include_str!("shader.vert");
|
||||
let fs_src = include_str!("shader.frag");
|
||||
let vs_spirv = glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex).unwrap();
|
||||
let fs_spirv = glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment).unwrap();
|
||||
let vs_data = wgpu::read_spirv(vs_spirv).unwrap();
|
||||
let fs_data = wgpu::read_spirv(fs_spirv).unwrap();
|
||||
let vs_module = device.create_shader_module(&vs_data);
|
||||
let fs_module = device.create_shader_module(&fs_data);
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
layout: &render_pipeline_layout,
|
||||
vertex_stage: wgpu::ProgrammableStageDescriptor {
|
||||
module: &vs_module,
|
||||
entry_point: "main",
|
||||
},
|
||||
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
|
||||
module: &fs_module,
|
||||
entry_point: "main",
|
||||
}),
|
||||
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: wgpu::CullMode::Back,
|
||||
depth_bias: 0,
|
||||
depth_bias_slope_scale: 0.0,
|
||||
depth_bias_clamp: 0.0,
|
||||
}),
|
||||
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
color_states: &[
|
||||
wgpu::ColorStateDescriptor {
|
||||
format: sc_desc.format,
|
||||
color_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
alpha_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
},
|
||||
],
|
||||
depth_stencil_state: None,
|
||||
index_format: wgpu::IndexFormat::Uint16,
|
||||
vertex_buffers: &[
|
||||
Vertex::desc(),
|
||||
],
|
||||
sample_count: 1,
|
||||
sample_mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
});
|
||||
|
||||
let vertex_buffer = device
|
||||
.create_buffer_mapped(VERTICES.len(), wgpu::BufferUsage::VERTEX)
|
||||
.fill_from_slice(VERTICES);
|
||||
let index_buffer = device
|
||||
.create_buffer_mapped(INDICES.len(), wgpu::BufferUsage::INDEX)
|
||||
.fill_from_slice(INDICES);
|
||||
let num_indices = INDICES.len() as u32;
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
sc_desc,
|
||||
swap_chain,
|
||||
render_pipeline,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices,
|
||||
diffuse_texture,
|
||||
diffuse_texture_view,
|
||||
diffuse_sampler,
|
||||
diffuse_bind_group,
|
||||
camera,
|
||||
camera_controller,
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
uniforms,
|
||||
hidpi_factor,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_hidpi_and_resize(&mut self, new_hidpi_factor: f64) {
|
||||
self.hidpi_factor = new_hidpi_factor;
|
||||
self.resize(self.size);
|
||||
}
|
||||
|
||||
fn resize(&mut self, new_size: winit::dpi::LogicalSize) {
|
||||
let physical_size = new_size.to_physical(self.hidpi_factor);
|
||||
self.size = new_size;
|
||||
self.sc_desc.width = physical_size.width.round() as u32;
|
||||
self.sc_desc.height = physical_size.height.round() as u32;
|
||||
self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
|
||||
|
||||
self.camera.aspect = self.sc_desc.width as f32 / self.sc_desc.height as f32;
|
||||
}
|
||||
|
||||
fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
self.camera_controller.process_events(event)
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
self.camera_controller.update_camera(&mut self.camera);
|
||||
self.uniforms.update_view_proj(&self.camera);
|
||||
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
todo: 0,
|
||||
});
|
||||
|
||||
let staging_buffer = self.device
|
||||
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
|
||||
.fill_from_slice(&[self.uniforms]);
|
||||
|
||||
encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::<Uniforms>() as wgpu::BufferAddress);
|
||||
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
|
||||
fn render(&mut self) {
|
||||
let frame = self.swap_chain.get_next_texture();
|
||||
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
todo: 0,
|
||||
});
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[
|
||||
wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &frame.view,
|
||||
resolve_target: None,
|
||||
load_op: wgpu::LoadOp::Clear,
|
||||
store_op: wgpu::StoreOp::Store,
|
||||
clear_color: wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
},
|
||||
}
|
||||
],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]);
|
||||
render_pass.set_bind_group(1, &self.uniform_bind_group, &[]);
|
||||
render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]);
|
||||
render_pass.set_index_buffer(&self.index_buffer, 0);
|
||||
render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
|
||||
}
|
||||
|
||||
self.queue.submit(&[
|
||||
encoder.finish()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut state = State::new(&window);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
ref event,
|
||||
window_id,
|
||||
} if window_id == window.id() => if state.input(event) {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
} else {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput {
|
||||
input,
|
||||
..
|
||||
} => {
|
||||
match input {
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
}
|
||||
WindowEvent::Resized(logical_size) => {
|
||||
state.resize(*logical_size);
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
WindowEvent::HiDpiFactorChanged(new_hidpi_factor) => {
|
||||
state.update_hidpi_and_resize(*new_hidpi_factor);
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
}
|
||||
Event::EventsCleared => {
|
||||
state.update();
|
||||
state.render();
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec2 v_tex_coords;
|
||||
layout(location=0) out vec4 f_color;
|
||||
|
||||
layout(set = 0, binding = 0) uniform texture2D t_diffuse;
|
||||
layout(set = 0, binding = 1) uniform sampler s_diffuse;
|
||||
|
||||
void main() {
|
||||
f_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
gl_Position = u_view_proj * vec4(a_position, 1.0);
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 102b0fc4dcbe440a27ee98b2bf6644205bd10713
|
||||
Subproject commit ca194e22df1850a6cc26fa3b21d068678dbaab85
|
@ -0,0 +1,433 @@
|
||||
# Uniform buffers and a 3d camera
|
||||
|
||||
While all of our previous work has seemed to be in 2d, we've actually been working in 3d the entire time! That's part of the reason why our `Vertex` structure has `position` be an array of 3 floats instead of just 2. We can't really see the 3d-ness of our scene, because we're viewing things head on. We're going to change our point of view by creating a `Camera`.
|
||||
|
||||
## A perspective camera
|
||||
|
||||
This tutorial is more about learning to use wgpu and less about linear algebra, so I'm going to gloss over a lot of the math involved. There's plenty of reading material online if you're interested in what's going on under the hood. The first thing to know is that we need `cgmath = "0.17"` in our `Cargo.toml`.
|
||||
|
||||
Now that we have a math library, let's put it to use! Create a `Camera` struct above the `State` struct.
|
||||
|
||||
```rust
|
||||
struct Camera {
|
||||
eye: cgmath::Point3<f32>,
|
||||
target: cgmath::Point3<f32>,
|
||||
up: cgmath::Vector3<f32>,
|
||||
aspect: f32,
|
||||
fovy: f32,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
fn build_view_projection_matrix(&self) -> cgmath::Matrix4<f32> {
|
||||
// 1.
|
||||
let view = cgmath::Matrix4::look_at(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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `build_view_projection_matrix` is where the magic happens.
|
||||
1. The `view` matrix moves the world to be at the position and rotation of the camera. It's essentialy an the inverse of whatever the transform matrix of the camera would be.
|
||||
2. The `proj` matrix warps the scene to give the effect of depth. Without this, objects up close would be the same size as objects far away.
|
||||
3. Cgmath is built with OpenGL in mind. If we try to use it as is, all our transformations will be out of wack. We'll define this matrix as follows.
|
||||
|
||||
```rust
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = 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,
|
||||
);
|
||||
```
|
||||
|
||||
I am honestly not equipped to explain how this works, but you can read https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ if you want to know more. All we need to know is that we need to use `OPENGL_TO_WGPU_MATRIX` only one time, when we create our projection matrix.
|
||||
|
||||
Using this conversion matrix means we're going to have to change
|
||||
|
||||
Now that that's done, let's add a `camera` field to `State`.
|
||||
|
||||
```rust
|
||||
struct State {
|
||||
// ...
|
||||
camera: Camera,
|
||||
// ...
|
||||
}
|
||||
|
||||
fn new(window: &Window) -> Self {
|
||||
// let diffuse_bind_group ...
|
||||
|
||||
let camera = Camera {
|
||||
// position the camera one unit up and 2 units back
|
||||
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: sc_desc.width as f32 / sc_desc.height as f32,
|
||||
fovy: 45.0,
|
||||
znear: 0.1,
|
||||
zfar: 100.0,
|
||||
};
|
||||
|
||||
Self {
|
||||
// ...
|
||||
camera,
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now that we have our camera, and it can make us a view projection matrix, we need somewhere to put it. We also need some way of getting it into our shaders.
|
||||
|
||||
## The uniform buffer
|
||||
|
||||
Up to this point we've used `Buffer`s to store our vertex and index data, and even to load our textures. We going to use them again to create what's known as a uniform buffer. A uniform is a blob of data that is available to every invocation of a set of shaders. We've technically already used uniforms for our texture and sampler. We're going to use them again to store our view projection matrix. To start let's create a struct to hold our `Uniforms`.
|
||||
|
||||
```rust
|
||||
#[repr(C)] // We need this for Rust to store our data correctly for the shaders
|
||||
#[derive(Debug, Copy, Clone)] // This is so we can store this in a buffer
|
||||
struct Uniforms {
|
||||
view_proj: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new() -> Self {
|
||||
use cgmath::SquareMatrix;
|
||||
Self {
|
||||
view_proj: cgmath::Matrix4::identity(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_view_proj(&mut self, camera: &Camera) {
|
||||
self.view_proj = camera.build_view_projection_matrix();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now that we have our data structured, let's make our `uniform_buffer`.
|
||||
|
||||
```rust
|
||||
// in new() after creating `camera`
|
||||
|
||||
let mut uniforms = Uniforms::new();
|
||||
uniforms.update_view_proj(&camera);
|
||||
|
||||
let uniform_buffer = device
|
||||
// The COPY_DST part will be important later
|
||||
.create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST)
|
||||
.fill_from_slice(&[uniforms]);
|
||||
```
|
||||
|
||||
## Uniform buffers and bind groups
|
||||
|
||||
Cool, now that we have a uniform buffer, what do we do with it? The answer is we create a bind group for it. First we have to create the bind group layout.
|
||||
|
||||
```rust
|
||||
let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutBinding {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX, // 1.
|
||||
ty: wgpu::BindingType::UniformBuffer {
|
||||
dynamic: false, // 2.
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
1. We only really need camera information in the vertex shader, as that's what we'll use to manipulate our vertices.
|
||||
2. The `dynamic` field indicates whether this buffer will change size or not. This is useful if we want to store an array of things in our uniforms.
|
||||
|
||||
Now we can create the actual bind group.
|
||||
|
||||
```rust
|
||||
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &uniform_bind_group_layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &uniform_buffer,
|
||||
// FYI: you can share a single buffer between bindings.
|
||||
range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress,
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Like with our texture, we need to register our `uniform_bind_group_layout` with the render pipeline.
|
||||
|
||||
```rust
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout],
|
||||
});
|
||||
```
|
||||
|
||||
Now we need to add `uniform_buffer` and `uniform_bind_group` to `State`
|
||||
|
||||
```rust
|
||||
struct State {
|
||||
camera: Camera,
|
||||
uniforms: Uniforms,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
fn new(window: &Window) -> Self {
|
||||
// ...
|
||||
Self {
|
||||
// ...
|
||||
camera,
|
||||
uniforms,
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The final thing we need to do before we get into shaders is use the bind group in `render()`.
|
||||
|
||||
```rust
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]);
|
||||
// NEW!
|
||||
render_pass.set_bind_group(1, &self.uniform_bind_group, &[]);
|
||||
render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]);
|
||||
render_pass.set_index_buffer(&self.index_buffer, 0);
|
||||
render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
|
||||
```
|
||||
|
||||
## Using the uniforms in the vertex shader
|
||||
|
||||
Modify `shader.vert` to include the following.
|
||||
|
||||
```glsl
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
|
||||
// NEW!
|
||||
layout(set=1, binding=0) // 1.
|
||||
uniform Uniforms {
|
||||
mat4 u_view_proj; // 2.
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
// UPDATED!
|
||||
gl_Position = u_view_proj * vec4(a_position, 1.0); // 3.
|
||||
}
|
||||
```
|
||||
|
||||
1. Because we've created a new bind group, we need to specify which one we're using in the shader. The number is determined by our `render_pipeline_layout`. The `texture_bind_group_layout` is listed first, thus it's `set=0`, and `uniform_bind_group` is second, so it's `set=1`.
|
||||
2. The `uniform` block requires us to specify global identifiers for all the fields we intend to use. It's important to only specify fields that are actually in our uniform buffer, as trying to access data that isn't there may lead to undefined behavior.
|
||||
3. Multiplication order is important when it comes to matrices. The vector always goes on the right, and the matrices gone on the left in order of importance.
|
||||
|
||||
## Changing our vertices again
|
||||
|
||||
Because our correction matrix makes positive y up. Our model is not only upside down, it's also facing the wrong way. This might make you think that adding our correction matrix was a mistake, and you'd have a point. The thing is, most models you find online, and most modeling software expects y to be up. If we try to render any models without the correction matrix, they will likely be upside-down and inside-out. Since our model is still very simple we can easily change it to work with our new setup.
|
||||
|
||||
```rust
|
||||
const VERTICES: &[Vertex] = &[
|
||||
Vertex { position: [-0.0868241, -0.49240386, 0.0], tex_coords: [1.0 - 0.4131759, 1.0 - 0.00759614], }, // A
|
||||
Vertex { position: [-0.49513406, -0.06958647, 0.0], tex_coords: [1.0 - 0.0048659444, 1.0 - 0.43041354], }, // B
|
||||
Vertex { position: [-0.21918549, 0.44939706, 0.0], tex_coords: [1.0 - 0.28081453, 1.0 - 0.949397057], }, // C
|
||||
Vertex { position: [0.35966998, 0.3473291, 0.0], tex_coords: [1.0 - 0.85967, 1.0 - 0.84732911], }, // D
|
||||
Vertex { position: [0.44147372, -0.2347359, 0.0], tex_coords: [1.0 - 0.9414737, 1.0 - 0.2652641], }, // E
|
||||
];
|
||||
|
||||
const INDICES: &[u16] = &[
|
||||
0, 1, 4,
|
||||
1, 2, 4,
|
||||
2, 3, 4,
|
||||
];
|
||||
```
|
||||
|
||||
## A controller for our camera
|
||||
|
||||
If you run the code right now, you should get something that looks like this.
|
||||
|
||||
![static_tree.png](static_tree.png)
|
||||
|
||||
The shape's less stretched now, but it's still pretty static. You can experiment with moving the camera position around, but most cameras in games move around. Since this tutorial is about using wgpu and not how to process user input, I'm just going to post the `CameraController` code below.
|
||||
|
||||
```rust
|
||||
struct CameraController {
|
||||
speed: f32,
|
||||
is_up_pressed: bool,
|
||||
is_down_pressed: bool,
|
||||
is_forward_pressed: bool,
|
||||
is_backward_pressed: bool,
|
||||
is_left_pressed: bool,
|
||||
is_right_pressed: bool,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_camera(&self, camera: &mut Camera) {
|
||||
use cgmath::InnerSpace;
|
||||
let forward = (camera.target - camera.eye).normalize();
|
||||
|
||||
if self.is_forward_pressed {
|
||||
camera.eye += forward * self.speed;
|
||||
}
|
||||
if self.is_backward_pressed {
|
||||
camera.eye -= forward * self.speed;
|
||||
}
|
||||
|
||||
let right = forward.cross(camera.up);
|
||||
|
||||
if self.is_right_pressed {
|
||||
camera.eye += right * self.speed;
|
||||
}
|
||||
if self.is_left_pressed {
|
||||
camera.eye -= right * self.speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This code is not perfect. The camera slowly moves back when you rotate it. It works for our purposes though. Feel free to improve it!
|
||||
|
||||
We still need to plug this into our existing code to make it do anything. Add the controller to `State` and create it in `new()`.
|
||||
|
||||
```rust
|
||||
struct State {
|
||||
// ...
|
||||
camera: Camera,
|
||||
// NEW!
|
||||
camera_controller: CameraController,
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
impl State {
|
||||
fn new(window: &Window) -> Self {
|
||||
// ...
|
||||
let camera_controller = CameraController::new(0.2);
|
||||
// ...
|
||||
|
||||
Self {
|
||||
// ...
|
||||
camera_controller,
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We're finally going to add some code to `input()` (assuming you haven't already)!
|
||||
|
||||
```rust
|
||||
fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
self.camera_controller.process_events(event)
|
||||
}
|
||||
```
|
||||
|
||||
Up to this point, the camera controller isn't actually doing anything. The values in our uniform buffer need to be updated. There are 2 main methods to do that.
|
||||
1. We can create a separate buffer and copy it's contents to our `uniform_buffer`. The new buffer is known as a staging buffer. This method is usually how it's done as it allows the contents of the main buffer (in this case `uniform_buffer`) to only be accessible by the gpu. The gpu can do some speed optimizations which it couldn't if we could access the buffer via the cpu.
|
||||
2. We can call on of the mapping method's `map_read_async`, and `map_write_async` on the buffer itself. These allow us to access a buffer's contents directly, but requires us to deal with the `async` aspect of these methods this also requires our buffer to use the `BufferUsage::MAP_READ` and/or `BufferUsage::MAP_WRITE`. We won't talk about it here, but you check out [Wgpu without a window](/intermediate/windowless) tutorial if you want to know more.
|
||||
|
||||
Enough about that though, let's get into actually implementing the code.
|
||||
|
||||
```rust
|
||||
fn update(&mut self) {
|
||||
self.camera_controller.update_camera(&mut self.camera);
|
||||
self.uniforms.update_view_proj(&self.camera);
|
||||
|
||||
// Copy operation's are performed on the gpu, so we'll need
|
||||
// a CommandEncoder for that
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
todo: 0,
|
||||
});
|
||||
|
||||
let staging_buffer = self.device
|
||||
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
|
||||
.fill_from_slice(&[self.uniforms]);
|
||||
|
||||
encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::<Uniforms>() as wgpu::BufferAddress);
|
||||
|
||||
// We need to remember to submit our CommandEncoder's output
|
||||
// otherwise we won't see any change.
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
```
|
||||
|
||||
That's all we need to do. If you run the code now you should see a pentagon with our tree texture that you can rotate around and zoom into with the wasd/arrow keys.
|
||||
|
||||
## Challenge
|
||||
|
||||
Have our model rotate on it's own independently of the the camera. *Hint: you'll need another matrix for this.*
|
||||
|
||||
<!-- TODO: add a gif/video for this -->
|
||||
|
||||
<!--
|
||||
[ThinMatrix](https://www.youtube.com/watch?v=DLKN0jExRIM)
|
||||
http://antongerdelan.net/opengl/raycasting.html
|
||||
-->
|
Binary file not shown.
After Width: | Height: | Size: 488 KiB |
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Loading…
Reference in New Issue