finished camera changes
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "tutorial12-camera"
|
||||
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.23"
|
||||
winit = "0.22"
|
||||
shaderc = "0.6"
|
||||
failure = "0.1"
|
||||
tobj = "1"
|
||||
bytemuck = "1.2"
|
||||
futures = "0.3.4"
|
||||
wgpu = "0.5.0"
|
||||
|
||||
[dependencies.cgmath]
|
||||
version = "0.17"
|
||||
features = ["swizzle"]
|
@ -0,0 +1,202 @@
|
||||
use cgmath::*;
|
||||
use winit::event::*;
|
||||
use winit::dpi::LogicalPosition;
|
||||
use std::time::Duration;
|
||||
use std::f32::consts::FRAC_PI_2;
|
||||
|
||||
#[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,
|
||||
);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Camera {
|
||||
pub position: Point3<f32>,
|
||||
yaw: Rad<f32>,
|
||||
pitch: Rad<f32>,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn new<
|
||||
V: Into<Point3<f32>>,
|
||||
Y: Into<Rad<f32>>,
|
||||
P: Into<Rad<f32>>,
|
||||
>(
|
||||
position: V,
|
||||
yaw: Y,
|
||||
pitch: P,
|
||||
) -> Self {
|
||||
Self {
|
||||
position: position.into(),
|
||||
yaw: yaw.into(),
|
||||
pitch: pitch.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calc_matrix(&self) -> Matrix4<f32> {
|
||||
Matrix4::look_at_dir(
|
||||
self.position,
|
||||
Vector3::new(
|
||||
self.yaw.0.cos(),
|
||||
self.pitch.0.sin(),
|
||||
self.yaw.0.sin(),
|
||||
).normalize(),
|
||||
Vector3::unit_y(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Projection {
|
||||
aspect: f32,
|
||||
fovy: Rad<f32>,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
}
|
||||
|
||||
impl Projection {
|
||||
pub fn new<F: Into<Rad<f32>>>(
|
||||
width: u32,
|
||||
height: u32,
|
||||
fovy: F,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
) -> Self {
|
||||
Self {
|
||||
aspect: width as f32 / height as f32,
|
||||
fovy: fovy.into(),
|
||||
znear,
|
||||
zfar,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.aspect = width as f32 / height as f32;
|
||||
}
|
||||
|
||||
pub fn calc_matrix(&self) -> Matrix4<f32> {
|
||||
OPENGL_TO_WGPU_MATRIX * perspective(self.fovy, self.aspect, self.znear, self.zfar)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CameraController {
|
||||
amount_left: f32,
|
||||
amount_right: f32,
|
||||
amount_forward: f32,
|
||||
amount_backward: f32,
|
||||
amount_up: f32,
|
||||
amount_down: f32,
|
||||
rotate_horizontal: f32,
|
||||
rotate_vertical: f32,
|
||||
scroll: f32,
|
||||
speed: f32,
|
||||
sensitivity: f32,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
pub fn new(speed: f32, sensitivity: f32) -> Self {
|
||||
Self {
|
||||
amount_left: 0.0,
|
||||
amount_right: 0.0,
|
||||
amount_forward: 0.0,
|
||||
amount_backward: 0.0,
|
||||
amount_up: 0.0,
|
||||
amount_down: 0.0,
|
||||
rotate_horizontal: 0.0,
|
||||
rotate_vertical: 0.0,
|
||||
scroll: 0.0,
|
||||
speed,
|
||||
sensitivity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool{
|
||||
let amount = if state == ElementState::Pressed { 1.0 } else { 0.0 };
|
||||
match key {
|
||||
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
||||
self.amount_forward = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::S | VirtualKeyCode::Down => {
|
||||
self.amount_backward = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::A | VirtualKeyCode::Left => {
|
||||
self.amount_left = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::D | VirtualKeyCode::Right => {
|
||||
self.amount_right = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::Space => {
|
||||
self.amount_up = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::LShift => {
|
||||
self.amount_down = amount;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
|
||||
self.rotate_horizontal = mouse_dx as f32;
|
||||
self.rotate_vertical = mouse_dy as f32;
|
||||
}
|
||||
|
||||
pub fn process_scroll(&mut self, delta: &MouseScrollDelta) {
|
||||
self.scroll = -match delta {
|
||||
// I'm assuming a line is about 100 pixels
|
||||
MouseScrollDelta::LineDelta(_, scroll) => scroll * 100.0,
|
||||
MouseScrollDelta::PixelDelta(LogicalPosition {
|
||||
y: scroll,
|
||||
..
|
||||
}) => *scroll as f32,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn update_camera(&mut self, camera: &mut Camera, dt: Duration) {
|
||||
let dt = dt.as_secs_f32();
|
||||
|
||||
// Move forward/backward and left/right
|
||||
let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos();
|
||||
let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize();
|
||||
let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize();
|
||||
camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt;
|
||||
camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt;
|
||||
|
||||
// Move in/out (aka. "zoom")
|
||||
// Note: this isn't an actual zoom. The camera's position
|
||||
// changes when zooming. I've added this to make it easier
|
||||
// to get closer to an object you want to focus on.
|
||||
let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos();
|
||||
let scrollward = Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize();
|
||||
camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt;
|
||||
|
||||
// Move up/down. Since we don't use roll, we can just
|
||||
// modify the y coordinate directly.
|
||||
camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt;
|
||||
|
||||
// Rotate
|
||||
camera.yaw += Rad(self.rotate_horizontal) * self.sensitivity * dt;
|
||||
camera.pitch += Rad(-self.rotate_vertical) * self.sensitivity * dt;
|
||||
|
||||
// If process_mouse isn't called every frame, these values
|
||||
// will not get set to zero, and the camera will rotate
|
||||
// when moving in a non cardinal direction.
|
||||
self.rotate_horizontal = 0.0;
|
||||
self.rotate_vertical = 0.0;
|
||||
|
||||
// Keep the camera's angle from going too high/low.
|
||||
if camera.pitch < -Rad(FRAC_PI_2) {
|
||||
camera.pitch = -Rad(FRAC_PI_2);
|
||||
} else if camera.pitch > Rad(FRAC_PI_2) {
|
||||
camera.pitch = Rad(FRAC_PI_2);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 v_color;
|
||||
layout(location=0) out vec4 f_color;
|
||||
|
||||
void main() {
|
||||
f_color = vec4(v_color, 1.0);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
|
||||
layout(location=0) out vec3 v_color;
|
||||
|
||||
layout(set=0, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Light {
|
||||
vec3 u_position;
|
||||
vec3 u_color;
|
||||
};
|
||||
|
||||
// Let's keep our light smaller than our other objects
|
||||
float scale = 0.25;
|
||||
|
||||
void main() {
|
||||
vec3 v_position = a_position * scale + u_position;
|
||||
gl_Position = u_view_proj * vec4(v_position, 1);
|
||||
|
||||
v_color = u_color;
|
||||
}
|
@ -0,0 +1,652 @@
|
||||
use cgmath::prelude::*;
|
||||
use winit::{
|
||||
dpi::PhysicalPosition,
|
||||
event::*,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
mod model;
|
||||
mod texture;
|
||||
mod camera; // NEW!
|
||||
|
||||
use model::{DrawLight, DrawModel, Vertex};
|
||||
|
||||
const NUM_INSTANCES_PER_ROW: u32 = 10;
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct Uniforms {
|
||||
view_position: cgmath::Vector4<f32>,
|
||||
view_proj: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
view_position: Zero::zero(),
|
||||
view_proj: cgmath::Matrix4::identity(),
|
||||
}
|
||||
}
|
||||
|
||||
// UPDATED!
|
||||
fn update_view_proj(&mut self, camera: &camera::Camera, projection: &camera::Projection) {
|
||||
self.view_position = camera.position.to_homogeneous();
|
||||
self.view_proj = projection.calc_matrix() * camera.calc_matrix()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for Uniforms {}
|
||||
unsafe impl bytemuck::Pod for Uniforms {}
|
||||
|
||||
struct Instance {
|
||||
position: cgmath::Vector3<f32>,
|
||||
rotation: cgmath::Quaternion<f32>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
fn to_raw(&self) -> InstanceRaw {
|
||||
InstanceRaw {
|
||||
model: cgmath::Matrix4::from_translation(self.position) * cgmath::Matrix4::from(self.rotation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct InstanceRaw {
|
||||
#[allow(dead_code)]
|
||||
model: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for InstanceRaw {}
|
||||
unsafe impl bytemuck::Zeroable for InstanceRaw {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Light {
|
||||
position: cgmath::Vector3<f32>,
|
||||
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
|
||||
_padding: u32,
|
||||
color: cgmath::Vector3<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for Light {}
|
||||
unsafe impl bytemuck::Pod for Light {}
|
||||
|
||||
struct State {
|
||||
surface: wgpu::Surface,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
sc_desc: wgpu::SwapChainDescriptor,
|
||||
swap_chain: wgpu::SwapChain,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
obj_model: model::Model,
|
||||
camera: camera::Camera, // UPDATED!
|
||||
projection: camera::Projection, // NEW!
|
||||
camera_controller: camera::CameraController, // UPDATED!
|
||||
uniforms: Uniforms,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
instances: Vec<Instance>,
|
||||
#[allow(dead_code)]
|
||||
instance_buffer: wgpu::Buffer,
|
||||
depth_texture: texture::Texture,
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
light: Light,
|
||||
light_buffer: wgpu::Buffer,
|
||||
light_bind_group: wgpu::BindGroup,
|
||||
light_render_pipeline: wgpu::RenderPipeline,
|
||||
#[allow(dead_code)]
|
||||
debug_material: model::Material,
|
||||
// NEW!
|
||||
last_mouse_pos: PhysicalPosition<f64>,
|
||||
mouse_pressed: bool,
|
||||
}
|
||||
|
||||
fn create_render_pipeline(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::PipelineLayout,
|
||||
color_format: wgpu::TextureFormat,
|
||||
depth_format: Option<wgpu::TextureFormat>,
|
||||
vertex_descs: &[wgpu::VertexBufferDescriptor],
|
||||
vs_src: &str,
|
||||
fs_src: &str,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let mut compiler = shaderc::Compiler::new().unwrap();
|
||||
let vs_spirv = compiler.compile_into_spirv(vs_src, shaderc::ShaderKind::Vertex, "shader.vert", "main", None).unwrap();
|
||||
let fs_spirv = compiler.compile_into_spirv(fs_src, shaderc::ShaderKind::Fragment, "shader.frag", "main", None).unwrap();
|
||||
let vs_data = wgpu::read_spirv(std::io::Cursor::new(vs_spirv.as_binary_u8())).unwrap();
|
||||
let fs_data = wgpu::read_spirv(std::io::Cursor::new(fs_spirv.as_binary_u8())).unwrap();
|
||||
let vs_module = device.create_shader_module(&vs_data);
|
||||
let fs_module = device.create_shader_module(&fs_data);
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
layout: &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: color_format,
|
||||
color_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
alpha_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
}],
|
||||
depth_stencil_state: depth_format.map(|format| wgpu::DepthStencilStateDescriptor {
|
||||
format,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil_front: wgpu::StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_back: wgpu::StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_read_mask: 0,
|
||||
stencil_write_mask: 0,
|
||||
}),
|
||||
sample_count: 1,
|
||||
sample_mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
vertex_state: wgpu::VertexStateDescriptor {
|
||||
index_format: wgpu::IndexFormat::Uint32,
|
||||
vertex_buffers: vertex_descs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
impl State {
|
||||
async fn new(window: &Window) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
let surface = wgpu::Surface::create(window);
|
||||
|
||||
let adapter = wgpu::Adapter::request(
|
||||
&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::Default,
|
||||
compatible_surface: Some(&surface),
|
||||
},
|
||||
wgpu::BackendBit::PRIMARY, // Vulkan + Metal + DX12 + Browser WebGPU
|
||||
).await.unwrap();
|
||||
|
||||
let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
extensions: wgpu::Extensions {
|
||||
anisotropic_filtering: false,
|
||||
},
|
||||
limits: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
let sc_desc = wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
|
||||
let swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||
|
||||
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
component_type: wgpu::TextureComponentType::Float,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler { comparison: false },
|
||||
},
|
||||
// normal map
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
component_type: wgpu::TextureComponentType::Float,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler { comparison: false },
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
// UPDATED!
|
||||
let camera = camera::Camera::new((0.0, 2.0, 10.0), cgmath::Deg(-90.0), cgmath::Deg(-20.0));
|
||||
let projection = camera::Projection::new(sc_desc.width, sc_desc.height, cgmath::Deg(45.0), 0.1, 100.0);
|
||||
let camera_controller = camera::CameraController::new(4.0, 0.4);
|
||||
|
||||
let mut uniforms = Uniforms::new();
|
||||
uniforms.update_view_proj(&camera, &projection);
|
||||
|
||||
let uniform_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[uniforms]),
|
||||
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
const SPACE_BETWEEN: f32 = 3.0;
|
||||
let instances = (0..NUM_INSTANCES_PER_ROW)
|
||||
.flat_map(|z| {
|
||||
(0..NUM_INSTANCES_PER_ROW).map(move |x| {
|
||||
let x = SPACE_BETWEEN * (x as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
|
||||
let z = SPACE_BETWEEN * (z as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
|
||||
|
||||
let position = cgmath::Vector3 { x, y: 0.0, z };
|
||||
|
||||
let rotation = if position.is_zero() {
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
cgmath::Vector3::unit_z(),
|
||||
cgmath::Deg(0.0),
|
||||
)
|
||||
} else {
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
position.clone().normalize(),
|
||||
cgmath::Deg(45.0),
|
||||
)
|
||||
};
|
||||
|
||||
Instance { position, rotation }
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance_data = instances
|
||||
.iter()
|
||||
.map(Instance::to_raw)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance_buffer_size =
|
||||
instance_data.len() * std::mem::size_of::<cgmath::Matrix4<f32>>();
|
||||
let instance_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&instance_data),
|
||||
wgpu::BufferUsage::STORAGE_READ | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
let uniform_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::VERTEX,
|
||||
ty: wgpu::BindingType::StorageBuffer {
|
||||
dynamic: false,
|
||||
readonly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &instance_buffer,
|
||||
range: 0..instance_buffer_size as wgpu::BufferAddress,
|
||||
},
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let (obj_model, cmds) =
|
||||
model::Model::load(&device, &texture_bind_group_layout, "code/intermediate/tutorial10-lighting/src/res/cube.obj").unwrap();
|
||||
|
||||
queue.submit(&cmds);
|
||||
|
||||
let light = Light {
|
||||
position: (2.0, 2.0, 2.0).into(),
|
||||
_padding: 0,
|
||||
color: (1.0, 1.0, 1.0).into(),
|
||||
};
|
||||
|
||||
let light_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[light]),
|
||||
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
let light_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
|
||||
}],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let light_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &light_bind_group_layout,
|
||||
bindings: &[wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &light_buffer,
|
||||
range: 0..std::mem::size_of_val(&light) as wgpu::BufferAddress,
|
||||
},
|
||||
}],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let depth_texture = texture::Texture::create_depth_texture(&device, &sc_desc, "depth_texture");
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[
|
||||
&texture_bind_group_layout,
|
||||
&uniform_bind_group_layout,
|
||||
&light_bind_group_layout,
|
||||
],
|
||||
});
|
||||
|
||||
let render_pipeline = {
|
||||
let vs_src = include_str!("shader.vert");
|
||||
let fs_src = include_str!("shader.frag");
|
||||
|
||||
create_render_pipeline(
|
||||
&device,
|
||||
&render_pipeline_layout,
|
||||
sc_desc.format,
|
||||
Some(texture::Texture::DEPTH_FORMAT),
|
||||
&[model::ModelVertex::desc()],
|
||||
vs_src,
|
||||
fs_src,
|
||||
)
|
||||
};
|
||||
|
||||
let light_render_pipeline = {
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[&uniform_bind_group_layout, &light_bind_group_layout],
|
||||
});
|
||||
|
||||
let vs_src = include_str!("light.vert");
|
||||
let fs_src = include_str!("light.frag");
|
||||
|
||||
create_render_pipeline(
|
||||
&device,
|
||||
&layout,
|
||||
sc_desc.format,
|
||||
Some(texture::Texture::DEPTH_FORMAT),
|
||||
&[model::ModelVertex::desc()],
|
||||
vs_src,
|
||||
fs_src,
|
||||
)
|
||||
};
|
||||
|
||||
let debug_material = {
|
||||
let diffuse_bytes = include_bytes!("res/cobble-diffuse.png");
|
||||
let normal_bytes = include_bytes!("res/cobble-normal.png");
|
||||
|
||||
let mut command_buffers = vec![];
|
||||
let (diffuse_texture, cmds) = texture::Texture::from_bytes(&device, diffuse_bytes, "res/alt-diffuse.png", false).unwrap();
|
||||
command_buffers.push(cmds);
|
||||
let (normal_texture, cmds) = texture::Texture::from_bytes(&device, normal_bytes, "res/alt-normal.png", true).unwrap();
|
||||
command_buffers.push(cmds);
|
||||
queue.submit(&command_buffers);
|
||||
|
||||
model::Material::new(&device, "alt-material", diffuse_texture, normal_texture, &texture_bind_group_layout)
|
||||
};
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
sc_desc,
|
||||
swap_chain,
|
||||
render_pipeline,
|
||||
obj_model,
|
||||
camera,
|
||||
projection,
|
||||
camera_controller,
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
uniforms,
|
||||
instances,
|
||||
instance_buffer,
|
||||
depth_texture,
|
||||
size,
|
||||
light,
|
||||
light_buffer,
|
||||
light_bind_group,
|
||||
light_render_pipeline,
|
||||
#[allow(dead_code)]
|
||||
debug_material,
|
||||
// NEW!
|
||||
last_mouse_pos: (0.0, 0.0).into(),
|
||||
mouse_pressed: false,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
// UPDATED!
|
||||
self.projection.resize(new_size.width, new_size.height);
|
||||
self.size = new_size;
|
||||
self.sc_desc.width = new_size.width;
|
||||
self.sc_desc.height = new_size.height;
|
||||
self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
|
||||
self.depth_texture = texture::Texture::create_depth_texture(&self.device, &self.sc_desc, "depth_texture");
|
||||
}
|
||||
|
||||
// UPDATED!
|
||||
fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
input: KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => self.camera_controller.process_keyboard(*key, *state),
|
||||
WindowEvent::MouseWheel {
|
||||
delta,
|
||||
..
|
||||
} => {
|
||||
self.camera_controller.process_scroll(delta);
|
||||
true
|
||||
}
|
||||
WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
state,
|
||||
..
|
||||
} => {
|
||||
self.mouse_pressed = *state == ElementState::Pressed;
|
||||
true
|
||||
}
|
||||
WindowEvent::CursorMoved {
|
||||
position,
|
||||
..
|
||||
} => {
|
||||
let mouse_dx = position.x - self.last_mouse_pos.x;
|
||||
let mouse_dy = position.y - self.last_mouse_pos.y;
|
||||
self.last_mouse_pos = *position;
|
||||
if self.mouse_pressed {
|
||||
self.camera_controller.process_mouse(mouse_dx, mouse_dy);
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, dt: std::time::Duration) {
|
||||
// UPDATED!
|
||||
self.camera_controller.update_camera(&mut self.camera, dt);
|
||||
self.uniforms.update_view_proj(&self.camera, &self.projection);
|
||||
|
||||
let mut encoder =
|
||||
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
|
||||
let staging_buffer = self.device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[self.uniforms]),
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&staging_buffer,
|
||||
0,
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
// Update the light
|
||||
let old_position = self.light.position;
|
||||
self.light.position =
|
||||
cgmath::Quaternion::from_axis_angle((0.0, 1.0, 0.0).into(), cgmath::Deg(60.0 * dt.as_secs_f32()))
|
||||
* old_position; // UPDATED!
|
||||
|
||||
let staging_buffer = self.device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[self.light]),
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&staging_buffer,
|
||||
0,
|
||||
&self.light_buffer,
|
||||
0,
|
||||
std::mem::size_of::<Light>() as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
|
||||
fn render(&mut self) {
|
||||
let frame = self.swap_chain.get_next_texture()
|
||||
.expect("Timeout getting texture");
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: None
|
||||
});
|
||||
|
||||
{
|
||||
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: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
|
||||
attachment: &self.depth_texture.view,
|
||||
depth_load_op: wgpu::LoadOp::Clear,
|
||||
depth_store_op: wgpu::StoreOp::Store,
|
||||
clear_depth: 1.0,
|
||||
stencil_load_op: wgpu::LoadOp::Clear,
|
||||
stencil_store_op: wgpu::StoreOp::Store,
|
||||
clear_stencil: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.light_render_pipeline);
|
||||
render_pass.draw_light_model(
|
||||
&self.obj_model,
|
||||
&self.uniform_bind_group,
|
||||
&self.light_bind_group,
|
||||
);
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.draw_model_instanced(
|
||||
&self.obj_model,
|
||||
0..self.instances.len() as u32,
|
||||
&self.uniform_bind_group,
|
||||
&self.light_bind_group,
|
||||
);
|
||||
}
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
let title = env!("CARGO_PKG_NAME");
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
use futures::executor::block_on;
|
||||
let mut state = block_on(State::new(&window)); // NEW!
|
||||
let mut last_render_time = std::time::Instant::now();
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
match event {
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
Event::WindowEvent {
|
||||
ref event,
|
||||
window_id,
|
||||
} if window_id == window.id() => {
|
||||
if !state.input(event) {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
state.resize(*physical_size);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
state.resize(**new_inner_size);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// UPDATED!
|
||||
Event::RedrawRequested(_) => {
|
||||
let now = std::time::Instant::now();
|
||||
let dt = now - last_render_time;
|
||||
last_render_time = now;
|
||||
state.update(dt);
|
||||
state.render();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,441 @@
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::texture;
|
||||
|
||||
pub trait Vertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a>;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ModelVertex {
|
||||
position: cgmath::Vector3<f32>,
|
||||
tex_coords: cgmath::Vector2<f32>,
|
||||
normal: cgmath::Vector3<f32>,
|
||||
tangent: cgmath::Vector3<f32>,
|
||||
bitangent: cgmath::Vector3<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for ModelVertex {}
|
||||
unsafe impl bytemuck::Pod for ModelVertex {}
|
||||
|
||||
impl Vertex for ModelVertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferDescriptor {
|
||||
stride: mem::size_of::<ModelVertex>() 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,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
// Tangent and bitangent
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Material {
|
||||
pub name: String,
|
||||
pub diffuse_texture: texture::Texture,
|
||||
pub normal_texture: texture::Texture,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl Material {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
name: &str,
|
||||
diffuse_texture: texture::Texture,
|
||||
normal_texture: texture::Texture,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::TextureView(&normal_texture.view),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::Sampler(&normal_texture.sampler),
|
||||
},
|
||||
],
|
||||
label: Some(name),
|
||||
});
|
||||
|
||||
Self {
|
||||
name: String::from(name),
|
||||
diffuse_texture,
|
||||
normal_texture,
|
||||
bind_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Mesh {
|
||||
pub name: String,
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub num_elements: u32,
|
||||
pub material: usize,
|
||||
}
|
||||
|
||||
pub struct Model {
|
||||
pub meshes: Vec<Mesh>,
|
||||
pub materials: Vec<Material>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn load<P: AsRef<Path>>(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
path: P,
|
||||
) -> Result<(Self, Vec<wgpu::CommandBuffer>), failure::Error> {
|
||||
let (obj_models, obj_materials) = tobj::load_obj(path.as_ref())?;
|
||||
|
||||
// We're assuming that the texture files are stored with the obj file
|
||||
let containing_folder = path.as_ref().parent().unwrap();
|
||||
|
||||
// Our `Texure` struct currently returns a `CommandBuffer` when it's created so we need to collect those and return them.
|
||||
let mut command_buffers = Vec::new();
|
||||
|
||||
let mut materials = Vec::new();
|
||||
for mat in obj_materials {
|
||||
let diffuse_path = mat.diffuse_texture;
|
||||
let (diffuse_texture, cmds) = texture::Texture::load(device, containing_folder.join(diffuse_path), false)?;
|
||||
command_buffers.push(cmds);
|
||||
|
||||
let normal_path = match mat.unknown_param.get("map_Bump") {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(failure::err_msg("Unable to find normal map"))
|
||||
};
|
||||
let (normal_texture, cmds) = texture::Texture::load(device, containing_folder.join(normal_path?), true)?;
|
||||
command_buffers.push(cmds);
|
||||
|
||||
materials.push(Material::new(
|
||||
device,
|
||||
&mat.name,
|
||||
diffuse_texture,
|
||||
normal_texture,
|
||||
layout,
|
||||
));
|
||||
}
|
||||
|
||||
let mut meshes = Vec::new();
|
||||
for m in obj_models {
|
||||
let mut vertices = Vec::new();
|
||||
for i in 0..m.mesh.positions.len() / 3 {
|
||||
vertices.push(ModelVertex {
|
||||
position: [
|
||||
m.mesh.positions[i * 3],
|
||||
m.mesh.positions[i * 3 + 1],
|
||||
m.mesh.positions[i * 3 + 2],
|
||||
].into(),
|
||||
tex_coords: [
|
||||
m.mesh.texcoords[i * 2],
|
||||
m.mesh.texcoords[i * 2 + 1]
|
||||
].into(),
|
||||
normal: [
|
||||
m.mesh.normals[i * 3],
|
||||
m.mesh.normals[i * 3 + 1],
|
||||
m.mesh.normals[i * 3 + 2],
|
||||
].into(),
|
||||
// We'll calculate these later
|
||||
tangent: [0.0; 3].into(),
|
||||
bitangent: [0.0; 3].into(),
|
||||
});
|
||||
}
|
||||
|
||||
let indices = &m.mesh.indices;
|
||||
|
||||
// Calculate tangents and bitangets. We're going to
|
||||
// use the triangles, so we need to loop through the
|
||||
// indices in chunks of 3
|
||||
for c in indices.chunks(3) {
|
||||
let v0 = vertices[c[0] as usize];
|
||||
let v1 = vertices[c[1] as usize];
|
||||
let v2 = vertices[c[2] as usize];
|
||||
|
||||
let pos0 = v0.position;
|
||||
let pos1 = v1.position;
|
||||
let pos2 = v2.position;
|
||||
|
||||
let uv0 = v0.tex_coords;
|
||||
let uv1 = v1.tex_coords;
|
||||
let uv2 = v2.tex_coords;
|
||||
|
||||
// Calculate the edges of the triangle
|
||||
let delta_pos1 = pos1 - pos0;
|
||||
let delta_pos2 = pos2 - pos0;
|
||||
|
||||
// This will give us a direction to calculate the
|
||||
// tangent and bitangent
|
||||
let delta_uv1 = uv1 - uv0;
|
||||
let delta_uv2 = uv2 - uv0;
|
||||
|
||||
// Solving the following system of equations will
|
||||
// give us the tangent and bitangent.
|
||||
// delta_pos1 = delta_uv1.x * T + delta_u.y * B
|
||||
// delta_pos2 = delta_uv2.x * T + delta_uv2.y * B
|
||||
// Luckily, the place I found this equation provided
|
||||
// the solution!
|
||||
let r = 1.0 / (delta_uv1 .x * delta_uv2.y - delta_uv1.y * delta_uv2.x);
|
||||
let tangent = (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r;
|
||||
let bitangent = (delta_pos2 * delta_uv1.x - delta_pos1 * delta_uv2.x) * r;
|
||||
|
||||
// We'll use the same tangent/bitangent for each vertex in the triangle
|
||||
vertices[c[0] as usize].tangent = tangent;
|
||||
vertices[c[1] as usize].tangent = tangent;
|
||||
vertices[c[2] as usize].tangent = tangent;
|
||||
|
||||
vertices[c[0] as usize].bitangent = bitangent;
|
||||
vertices[c[1] as usize].bitangent = bitangent;
|
||||
vertices[c[2] as usize].bitangent = bitangent;
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&vertices),
|
||||
wgpu::BufferUsage::VERTEX,
|
||||
);
|
||||
|
||||
let index_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(indices),
|
||||
wgpu::BufferUsage::INDEX,
|
||||
);
|
||||
|
||||
meshes.push(Mesh {
|
||||
name: m.name,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_elements: m.mesh.indices.len() as u32,
|
||||
material: m.mesh.material_id.unwrap_or(0),
|
||||
});
|
||||
}
|
||||
|
||||
Ok((Self { meshes, materials }, command_buffers))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DrawModel<'a, 'b>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
|
||||
fn draw_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_model_instanced_with_material(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawModel<'a, 'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_mesh_instanced(mesh, material, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.set_vertex_buffer(0, &mesh.vertex_buffer, 0, 0);
|
||||
self.set_index_buffer(&mesh.index_buffer, 0, 0);
|
||||
self.set_bind_group(0, &material.bind_group, &[]);
|
||||
self.set_bind_group(1, &uniforms, &[]);
|
||||
self.set_bind_group(2, &light, &[]);
|
||||
self.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_model_instanced(model, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
let material = &model.materials[mesh.material];
|
||||
self.draw_mesh_instanced(mesh, material, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_model_instanced_with_material(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
self.draw_mesh_instanced(mesh, material, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DrawLight<'a, 'b>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_light_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_light_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) where
|
||||
'b: 'a;
|
||||
|
||||
fn draw_light_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_light_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawLight<'a, 'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_light_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_light_mesh_instanced(mesh, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_light_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.set_vertex_buffer(0, &mesh.vertex_buffer, 0, 0);
|
||||
self.set_index_buffer(&mesh.index_buffer, 0, 0);
|
||||
self.set_bind_group(0, uniforms, &[]);
|
||||
self.set_bind_group(1, light, &[]);
|
||||
self.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_light_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_light_model_instanced(model, 0..1, uniforms, light);
|
||||
}
|
||||
fn draw_light_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
self.draw_light_mesh_instanced(mesh, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 117 KiB |
@ -0,0 +1,14 @@
|
||||
# Blender MTL File: 'cube.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material.001
|
||||
Ns 323.999994
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Bump cube-normal.png
|
||||
map_Kd cube-diffuse.jpg
|
@ -0,0 +1,60 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
layout(location=2) in vec3 a_normal;
|
||||
layout(location=3) in vec3 a_tangent;
|
||||
layout(location=4) in vec3 a_bitangent;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
layout(location=1) out vec3 v_position; // UPDATED!
|
||||
layout(location=2) out vec3 v_light_position; // NEW!
|
||||
layout(location=3) out vec3 v_view_position; // NEW!
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=1)
|
||||
buffer Instances {
|
||||
mat4 s_models[];
|
||||
};
|
||||
|
||||
// NEW!
|
||||
layout(set=2, binding=0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
|
||||
mat4 model_matrix = s_models[gl_InstanceIndex];
|
||||
|
||||
mat3 normal_matrix = mat3(transpose(inverse(model_matrix)));
|
||||
vec3 normal = normalize(normal_matrix * a_normal);
|
||||
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||
|
||||
// UDPATED!
|
||||
mat3 tangent_matrix = transpose(mat3(
|
||||
tangent,
|
||||
bitangent,
|
||||
normal
|
||||
));
|
||||
|
||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||
v_position = model_space.xyz;
|
||||
|
||||
// NEW!
|
||||
// v_position = tangent_matrix * model_space.xyz;
|
||||
// v_light_position = tangent_matrix * light_position;
|
||||
// v_view_position = tangent_matrix * u_view_position;
|
||||
v_position = model_space.xyz;
|
||||
v_light_position = light_position;
|
||||
v_view_position = u_view_position;
|
||||
|
||||
gl_Position = u_view_proj * model_space;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec2 v_tex_coords;
|
||||
layout(location=1) in vec3 v_position; // UPDATED!
|
||||
layout(location=2) in vec3 v_light_position; // NEW!
|
||||
layout(location=3) in vec3 v_view_position; // NEW!
|
||||
|
||||
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;
|
||||
layout(set = 0, binding = 2) uniform texture2D t_normal;
|
||||
layout(set = 0, binding = 3) uniform sampler s_normal;
|
||||
|
||||
layout(set = 2, binding = 0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec4 object_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords);
|
||||
vec4 object_normal = texture(sampler2D(t_normal, s_normal), v_tex_coords);
|
||||
|
||||
float ambient_strength = 0.1;
|
||||
vec3 ambient_color = light_color * ambient_strength;
|
||||
|
||||
vec3 normal = normalize(object_normal.rgb * 2.0 - 1.0); // UPDATED!
|
||||
vec3 light_dir = normalize(v_light_position - v_position); // UPDATED!
|
||||
|
||||
float diffuse_strength = max(dot(normal, light_dir), 0.0);
|
||||
vec3 diffuse_color = light_color * diffuse_strength;
|
||||
|
||||
vec3 view_dir = normalize(v_view_position - v_position); // UPDATED!
|
||||
vec3 half_dir = normalize(view_dir + light_dir);
|
||||
float specular_strength = pow(max(dot(normal, half_dir), 0.0), 32);
|
||||
vec3 specular_color = specular_strength * light_color;
|
||||
|
||||
vec3 result = (ambient_color + diffuse_color + specular_color) * object_color.xyz;
|
||||
f_color = vec4(result, object_color.a);
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
layout(location=2) in vec3 a_normal;
|
||||
layout(location=3) in vec3 a_tangent;
|
||||
layout(location=4) in vec3 a_bitangent;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
layout(location=1) out vec3 v_position; // UPDATED!
|
||||
layout(location=2) out vec3 v_light_position; // NEW!
|
||||
layout(location=3) out vec3 v_view_position; // NEW!
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=1)
|
||||
buffer Instances {
|
||||
mat4 s_models[];
|
||||
};
|
||||
|
||||
// NEW!
|
||||
layout(set=2, binding=0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
|
||||
mat4 model_matrix = s_models[gl_InstanceIndex];
|
||||
|
||||
mat3 normal_matrix = mat3(transpose(inverse(model_matrix)));
|
||||
vec3 normal = normalize(normal_matrix * a_normal);
|
||||
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||
|
||||
// UDPATED!
|
||||
mat3 tangent_matrix = transpose(mat3(
|
||||
tangent,
|
||||
bitangent,
|
||||
normal
|
||||
));
|
||||
|
||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||
v_position = model_space.xyz;
|
||||
|
||||
// NEW!
|
||||
v_position = tangent_matrix * model_space.xyz;
|
||||
v_light_position = tangent_matrix * light_position;
|
||||
v_view_position = tangent_matrix * u_view_position;
|
||||
|
||||
gl_Position = u_view_proj * model_space;
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
use image::GenericImageView;
|
||||
use std::path::Path;
|
||||
|
||||
|
||||
pub struct Texture {
|
||||
pub texture: wgpu::Texture,
|
||||
pub view: wgpu::TextureView,
|
||||
pub sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||
|
||||
pub fn load<P: AsRef<Path>>(
|
||||
device: &wgpu::Device,
|
||||
path: P,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
// Needed to appease the borrow checker
|
||||
let path_copy = path.as_ref().to_path_buf();
|
||||
let label = path_copy.to_str();
|
||||
|
||||
let img = image::open(path)?;
|
||||
Self::from_image(device, &img, label, is_normal_map)
|
||||
}
|
||||
|
||||
pub fn create_depth_texture(device: &wgpu::Device, sc_desc: &wgpu::SwapChainDescriptor, label: &str) -> Self {
|
||||
let size = wgpu::Extent3d {
|
||||
width: sc_desc.width,
|
||||
height: sc_desc.height,
|
||||
depth: 1,
|
||||
};
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
array_layer_count: 1,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: Self::DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT
|
||||
| wgpu::TextureUsage::SAMPLED
|
||||
| wgpu::TextureUsage::COPY_SRC,
|
||||
};
|
||||
let texture = device.create_texture(&desc);
|
||||
|
||||
let view = texture.create_default_view();
|
||||
let 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::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
lod_min_clamp: -100.0,
|
||||
lod_max_clamp: 100.0,
|
||||
compare: wgpu::CompareFunction::LessEqual,
|
||||
});
|
||||
|
||||
Self { texture, view, sampler }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_bytes(
|
||||
device: &wgpu::Device,
|
||||
bytes: &[u8],
|
||||
label: &str,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
let img = image::load_from_memory(bytes)?;
|
||||
Self::from_image(device, &img, Some(label), is_normal_map)
|
||||
}
|
||||
|
||||
pub fn from_image(
|
||||
device: &wgpu::Device,
|
||||
img: &image::DynamicImage,
|
||||
label: Option<&str>,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
let rgba = img.to_rgba();
|
||||
let dimensions = img.dimensions();
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
depth: 1,
|
||||
};
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label,
|
||||
size,
|
||||
array_layer_count: 1,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: if is_normal_map {
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
} else {
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||
},
|
||||
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||
});
|
||||
|
||||
let buffer = device.create_buffer_with_data(
|
||||
&rgba,
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("texture_buffer_copy_encoder"),
|
||||
});
|
||||
|
||||
encoder.copy_buffer_to_texture(
|
||||
wgpu::BufferCopyView {
|
||||
buffer: &buffer,
|
||||
offset: 0,
|
||||
bytes_per_row: 4 * dimensions.0,
|
||||
rows_per_image: dimensions.1,
|
||||
},
|
||||
wgpu::TextureCopyView {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
array_layer: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
size,
|
||||
);
|
||||
|
||||
let cmd_buffer = encoder.finish();
|
||||
|
||||
let view = texture.create_default_view();
|
||||
let 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: wgpu::CompareFunction::Always,
|
||||
});
|
||||
|
||||
Ok((Self { texture, view, sampler }, cmd_buffer))
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec2 v_tex_coords;
|
||||
layout(location=1) in vec3 v_position; // UPDATED!
|
||||
layout(location=2) in mat3 v_tangent_matrix; // NEW!
|
||||
|
||||
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;
|
||||
layout(set = 0, binding = 2) uniform texture2D t_normal;
|
||||
layout(set = 0, binding = 3) uniform sampler s_normal;
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj; // unused
|
||||
};
|
||||
|
||||
layout(set = 2, binding = 0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec4 object_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords);
|
||||
vec4 object_normal = texture(sampler2D(t_normal, s_normal), v_tex_coords);
|
||||
|
||||
float ambient_strength = 0.1;
|
||||
vec3 ambient_color = light_color * ambient_strength;
|
||||
|
||||
vec3 normal = normalize(v_tangent_matrix * (object_normal.rgb * 2.0 - 1.0));
|
||||
vec3 light_dir = normalize(light_position - v_position);
|
||||
|
||||
float diffuse_strength = max(dot(normal, light_dir), 0.0);
|
||||
vec3 diffuse_color = light_color * diffuse_strength;
|
||||
|
||||
vec3 view_dir = normalize(u_view_position - v_position);
|
||||
vec3 half_dir = normalize(view_dir + light_dir);
|
||||
float specular_strength = pow(max(dot(normal, half_dir), 0.0), 32);
|
||||
vec3 specular_color = specular_strength * light_color;
|
||||
|
||||
vec3 result = (ambient_color + diffuse_color + specular_color) * object_color.xyz;
|
||||
f_color = vec4(result, object_color.a);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
layout(location=2) in vec3 a_normal;
|
||||
// NEW!
|
||||
layout(location=3) in vec3 a_tangent;
|
||||
layout(location=4) in vec3 a_bitangent;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
layout(location=1) out vec3 v_position; // UPDATED!
|
||||
layout(location=2) out mat3 v_tangent_matrix; // NEW!
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=1)
|
||||
buffer Instances {
|
||||
mat4 s_models[];
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
|
||||
mat4 model_matrix = s_models[gl_InstanceIndex];
|
||||
|
||||
mat3 normal_matrix = mat3(transpose(inverse(model_matrix)));
|
||||
vec3 normal = normalize(normal_matrix * a_normal);
|
||||
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||
v_tangent_matrix = mat3(
|
||||
tangent,
|
||||
bitangent,
|
||||
normal
|
||||
);
|
||||
|
||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||
v_position = model_space.xyz;
|
||||
|
||||
gl_Position = u_view_proj * model_space;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "tutorial11-normals"
|
||||
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.23"
|
||||
winit = "0.22"
|
||||
shaderc = "0.6"
|
||||
# cgmath = "0.17"
|
||||
failure = "0.1"
|
||||
tobj = "1"
|
||||
bytemuck = "1.2"
|
||||
futures = "0.3.4"
|
||||
wgpu = "0.5.0"
|
||||
|
||||
[dependencies.cgmath]
|
||||
version = "0.17"
|
||||
features = ["swizzle"]
|
||||
|
||||
[[bin]]
|
||||
name = "tutorial11-normals"
|
||||
path = "src/main.rs"
|
||||
|
||||
|
||||
# [[bin]]
|
||||
# name = "tutorial10-challenge"
|
||||
# path = "src/challenge.rs"
|
@ -0,0 +1,8 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 v_color;
|
||||
layout(location=0) out vec4 f_color;
|
||||
|
||||
void main() {
|
||||
f_color = vec4(v_color, 1.0);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
|
||||
layout(location=0) out vec3 v_color;
|
||||
|
||||
layout(set=0, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Light {
|
||||
vec3 u_position;
|
||||
vec3 u_color;
|
||||
};
|
||||
|
||||
// Let's keep our light smaller than our other objects
|
||||
float scale = 0.25;
|
||||
|
||||
void main() {
|
||||
vec3 v_position = a_position * scale + u_position;
|
||||
gl_Position = u_view_proj * vec4(v_position, 1);
|
||||
|
||||
v_color = u_color;
|
||||
}
|
@ -0,0 +1,728 @@
|
||||
use cgmath::prelude::*;
|
||||
use winit::{
|
||||
event::*,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
mod model;
|
||||
mod texture;
|
||||
|
||||
use model::{DrawLight, DrawModel, Vertex};
|
||||
|
||||
#[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,
|
||||
);
|
||||
|
||||
const NUM_INSTANCES_PER_ROW: u32 = 10;
|
||||
|
||||
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(Copy, Clone)]
|
||||
struct Uniforms {
|
||||
view_position: cgmath::Vector4<f32>,
|
||||
view_proj: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
view_position: Zero::zero(),
|
||||
view_proj: cgmath::Matrix4::identity(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_view_proj(&mut self, camera: &Camera) {
|
||||
self.view_position = camera.eye.to_homogeneous();
|
||||
self.view_proj = OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for Uniforms {}
|
||||
unsafe impl bytemuck::Pod for Uniforms {}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Instance {
|
||||
position: cgmath::Vector3<f32>,
|
||||
rotation: cgmath::Quaternion<f32>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
fn to_raw(&self) -> InstanceRaw {
|
||||
InstanceRaw {
|
||||
model: cgmath::Matrix4::from_translation(self.position) * cgmath::Matrix4::from(self.rotation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct InstanceRaw {
|
||||
#[allow(dead_code)]
|
||||
model: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for InstanceRaw {}
|
||||
unsafe impl bytemuck::Zeroable for InstanceRaw {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Light {
|
||||
position: cgmath::Vector3<f32>,
|
||||
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
|
||||
_padding: u32,
|
||||
color: cgmath::Vector3<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for Light {}
|
||||
unsafe impl bytemuck::Pod for Light {}
|
||||
|
||||
struct State {
|
||||
surface: wgpu::Surface,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
sc_desc: wgpu::SwapChainDescriptor,
|
||||
swap_chain: wgpu::SwapChain,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
obj_model: model::Model,
|
||||
camera: Camera,
|
||||
camera_controller: CameraController,
|
||||
uniforms: Uniforms,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
instances: Vec<Instance>,
|
||||
#[allow(dead_code)]
|
||||
instance_buffer: wgpu::Buffer,
|
||||
depth_texture: texture::Texture,
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
light: Light,
|
||||
light_buffer: wgpu::Buffer,
|
||||
light_bind_group: wgpu::BindGroup,
|
||||
light_render_pipeline: wgpu::RenderPipeline,
|
||||
#[allow(dead_code)]
|
||||
debug_material: model::Material,
|
||||
}
|
||||
|
||||
fn create_render_pipeline(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::PipelineLayout,
|
||||
color_format: wgpu::TextureFormat,
|
||||
depth_format: Option<wgpu::TextureFormat>,
|
||||
vertex_descs: &[wgpu::VertexBufferDescriptor],
|
||||
vs_src: &str,
|
||||
fs_src: &str,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let mut compiler = shaderc::Compiler::new().unwrap();
|
||||
let vs_spirv = compiler.compile_into_spirv(vs_src, shaderc::ShaderKind::Vertex, "shader.vert", "main", None).unwrap();
|
||||
let fs_spirv = compiler.compile_into_spirv(fs_src, shaderc::ShaderKind::Fragment, "shader.frag", "main", None).unwrap();
|
||||
let vs_data = wgpu::read_spirv(std::io::Cursor::new(vs_spirv.as_binary_u8())).unwrap();
|
||||
let fs_data = wgpu::read_spirv(std::io::Cursor::new(fs_spirv.as_binary_u8())).unwrap();
|
||||
let vs_module = device.create_shader_module(&vs_data);
|
||||
let fs_module = device.create_shader_module(&fs_data);
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
layout: &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: color_format,
|
||||
color_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
alpha_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
}],
|
||||
depth_stencil_state: depth_format.map(|format| wgpu::DepthStencilStateDescriptor {
|
||||
format,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil_front: wgpu::StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_back: wgpu::StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_read_mask: 0,
|
||||
stencil_write_mask: 0,
|
||||
}),
|
||||
sample_count: 1,
|
||||
sample_mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
vertex_state: wgpu::VertexStateDescriptor {
|
||||
index_format: wgpu::IndexFormat::Uint32,
|
||||
vertex_buffers: vertex_descs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
impl State {
|
||||
async fn new(window: &Window) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
let surface = wgpu::Surface::create(window);
|
||||
|
||||
let adapter = wgpu::Adapter::request(
|
||||
&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::Default,
|
||||
compatible_surface: Some(&surface),
|
||||
},
|
||||
wgpu::BackendBit::PRIMARY, // Vulkan + Metal + DX12 + Browser WebGPU
|
||||
).await.unwrap();
|
||||
|
||||
let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
extensions: wgpu::Extensions {
|
||||
anisotropic_filtering: false,
|
||||
},
|
||||
limits: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
let sc_desc = wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
|
||||
let swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||
|
||||
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
component_type: wgpu::TextureComponentType::Float,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler { comparison: false },
|
||||
},
|
||||
// normal map
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
component_type: wgpu::TextureComponentType::Float,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler { comparison: false },
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let camera = Camera {
|
||||
eye: (0.0, 5.0, -10.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_with_data(
|
||||
bytemuck::cast_slice(&[uniforms]),
|
||||
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
const SPACE_BETWEEN: f32 = 3.0;
|
||||
let instances = (0..NUM_INSTANCES_PER_ROW)
|
||||
.flat_map(|z| {
|
||||
(0..NUM_INSTANCES_PER_ROW).map(move |x| {
|
||||
let x = SPACE_BETWEEN * (x as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
|
||||
let z = SPACE_BETWEEN * (z as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
|
||||
|
||||
let position = cgmath::Vector3 { x, y: 0.0, z };
|
||||
|
||||
let rotation = if position.is_zero() {
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
cgmath::Vector3::unit_z(),
|
||||
cgmath::Deg(0.0),
|
||||
)
|
||||
} else {
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
position.clone().normalize(),
|
||||
cgmath::Deg(45.0),
|
||||
)
|
||||
};
|
||||
|
||||
Instance { position, rotation }
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance_data = instances
|
||||
.iter()
|
||||
.map(Instance::to_raw)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance_buffer_size =
|
||||
instance_data.len() * std::mem::size_of::<cgmath::Matrix4<f32>>();
|
||||
let instance_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&instance_data),
|
||||
wgpu::BufferUsage::STORAGE_READ | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
let uniform_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::VERTEX,
|
||||
ty: wgpu::BindingType::StorageBuffer {
|
||||
dynamic: false,
|
||||
readonly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &instance_buffer,
|
||||
range: 0..instance_buffer_size as wgpu::BufferAddress,
|
||||
},
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let (obj_model, cmds) =
|
||||
model::Model::load(&device, &texture_bind_group_layout, "code/intermediate/tutorial10-lighting/src/res/cube.obj").unwrap();
|
||||
|
||||
queue.submit(&cmds);
|
||||
|
||||
let light = Light {
|
||||
position: (2.0, 2.0, 2.0).into(),
|
||||
_padding: 0,
|
||||
color: (1.0, 1.0, 1.0).into(),
|
||||
};
|
||||
|
||||
let light_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[light]),
|
||||
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
let light_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
|
||||
}],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let light_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &light_bind_group_layout,
|
||||
bindings: &[wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &light_buffer,
|
||||
range: 0..std::mem::size_of_val(&light) as wgpu::BufferAddress,
|
||||
},
|
||||
}],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let depth_texture = texture::Texture::create_depth_texture(&device, &sc_desc, "depth_texture");
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[
|
||||
&texture_bind_group_layout,
|
||||
&uniform_bind_group_layout,
|
||||
&light_bind_group_layout,
|
||||
],
|
||||
});
|
||||
|
||||
let render_pipeline = {
|
||||
let vs_src = include_str!("shader.vert");
|
||||
let fs_src = include_str!("shader.frag");
|
||||
|
||||
create_render_pipeline(
|
||||
&device,
|
||||
&render_pipeline_layout,
|
||||
sc_desc.format,
|
||||
Some(texture::Texture::DEPTH_FORMAT),
|
||||
&[model::ModelVertex::desc()],
|
||||
vs_src,
|
||||
fs_src,
|
||||
)
|
||||
};
|
||||
|
||||
let light_render_pipeline = {
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[&uniform_bind_group_layout, &light_bind_group_layout],
|
||||
});
|
||||
|
||||
let vs_src = include_str!("light.vert");
|
||||
let fs_src = include_str!("light.frag");
|
||||
|
||||
create_render_pipeline(
|
||||
&device,
|
||||
&layout,
|
||||
sc_desc.format,
|
||||
Some(texture::Texture::DEPTH_FORMAT),
|
||||
&[model::ModelVertex::desc()],
|
||||
vs_src,
|
||||
fs_src,
|
||||
)
|
||||
};
|
||||
|
||||
let debug_material = {
|
||||
let diffuse_bytes = include_bytes!("res/cobble-diffuse.png");
|
||||
let normal_bytes = include_bytes!("res/cobble-normal.png");
|
||||
|
||||
let mut command_buffers = vec![];
|
||||
let (diffuse_texture, cmds) = texture::Texture::from_bytes(&device, diffuse_bytes, "res/alt-diffuse.png", false).unwrap();
|
||||
command_buffers.push(cmds);
|
||||
let (normal_texture, cmds) = texture::Texture::from_bytes(&device, normal_bytes, "res/alt-normal.png", true).unwrap();
|
||||
command_buffers.push(cmds);
|
||||
queue.submit(&command_buffers);
|
||||
|
||||
model::Material::new(&device, "alt-material", diffuse_texture, normal_texture, &texture_bind_group_layout)
|
||||
};
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
sc_desc,
|
||||
swap_chain,
|
||||
render_pipeline,
|
||||
obj_model,
|
||||
camera,
|
||||
camera_controller,
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
uniforms,
|
||||
instances,
|
||||
instance_buffer,
|
||||
depth_texture,
|
||||
size,
|
||||
light,
|
||||
light_buffer,
|
||||
light_bind_group,
|
||||
light_render_pipeline,
|
||||
#[allow(dead_code)]
|
||||
debug_material,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
self.camera.aspect = self.sc_desc.width as f32 / self.sc_desc.height as f32;
|
||||
self.size = new_size;
|
||||
self.sc_desc.width = new_size.width;
|
||||
self.sc_desc.height = new_size.height;
|
||||
self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
|
||||
self.depth_texture = texture::Texture::create_depth_texture(&self.device, &self.sc_desc, "depth_texture");
|
||||
}
|
||||
|
||||
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 { label: None });
|
||||
|
||||
let staging_buffer = self.device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[self.uniforms]),
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&staging_buffer,
|
||||
0,
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
// Update the light
|
||||
let old_position = self.light.position;
|
||||
self.light.position =
|
||||
cgmath::Quaternion::from_axis_angle((0.0, 1.0, 0.0).into(), cgmath::Deg(1.0))
|
||||
* old_position;
|
||||
|
||||
let staging_buffer = self.device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[self.light]),
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&staging_buffer,
|
||||
0,
|
||||
&self.light_buffer,
|
||||
0,
|
||||
std::mem::size_of::<Light>() as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
|
||||
fn render(&mut self) {
|
||||
let frame = self.swap_chain.get_next_texture()
|
||||
.expect("Timeout getting texture");
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: None
|
||||
});
|
||||
|
||||
{
|
||||
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: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
|
||||
attachment: &self.depth_texture.view,
|
||||
depth_load_op: wgpu::LoadOp::Clear,
|
||||
depth_store_op: wgpu::StoreOp::Store,
|
||||
clear_depth: 1.0,
|
||||
stencil_load_op: wgpu::LoadOp::Clear,
|
||||
stencil_store_op: wgpu::StoreOp::Store,
|
||||
clear_stencil: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.light_render_pipeline);
|
||||
render_pass.draw_light_model(
|
||||
&self.obj_model,
|
||||
&self.uniform_bind_group,
|
||||
&self.light_bind_group,
|
||||
);
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.draw_model_instanced(
|
||||
&self.obj_model,
|
||||
0..self.instances.len() as u32,
|
||||
&self.uniform_bind_group,
|
||||
&self.light_bind_group,
|
||||
);
|
||||
}
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
let title = env!("CARGO_PKG_NAME");
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
use futures::executor::block_on;
|
||||
let mut state = block_on(State::new(&window));
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
match event {
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
Event::WindowEvent {
|
||||
ref event,
|
||||
window_id,
|
||||
} if window_id == window.id() => {
|
||||
if !state.input(event) {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
state.resize(*physical_size);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
state.resize(**new_inner_size);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
state.update();
|
||||
state.render();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,441 @@
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::texture;
|
||||
|
||||
pub trait Vertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a>;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ModelVertex {
|
||||
position: cgmath::Vector3<f32>,
|
||||
tex_coords: cgmath::Vector2<f32>,
|
||||
normal: cgmath::Vector3<f32>,
|
||||
tangent: cgmath::Vector3<f32>,
|
||||
bitangent: cgmath::Vector3<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for ModelVertex {}
|
||||
unsafe impl bytemuck::Pod for ModelVertex {}
|
||||
|
||||
impl Vertex for ModelVertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferDescriptor {
|
||||
stride: mem::size_of::<ModelVertex>() 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,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
// Tangent and bitangent
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Material {
|
||||
pub name: String,
|
||||
pub diffuse_texture: texture::Texture,
|
||||
pub normal_texture: texture::Texture,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl Material {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
name: &str,
|
||||
diffuse_texture: texture::Texture,
|
||||
normal_texture: texture::Texture,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::TextureView(&normal_texture.view),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::Sampler(&normal_texture.sampler),
|
||||
},
|
||||
],
|
||||
label: Some(name),
|
||||
});
|
||||
|
||||
Self {
|
||||
name: String::from(name),
|
||||
diffuse_texture,
|
||||
normal_texture,
|
||||
bind_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Mesh {
|
||||
pub name: String,
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub num_elements: u32,
|
||||
pub material: usize,
|
||||
}
|
||||
|
||||
pub struct Model {
|
||||
pub meshes: Vec<Mesh>,
|
||||
pub materials: Vec<Material>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn load<P: AsRef<Path>>(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
path: P,
|
||||
) -> Result<(Self, Vec<wgpu::CommandBuffer>), failure::Error> {
|
||||
let (obj_models, obj_materials) = tobj::load_obj(path.as_ref())?;
|
||||
|
||||
// We're assuming that the texture files are stored with the obj file
|
||||
let containing_folder = path.as_ref().parent().unwrap();
|
||||
|
||||
// Our `Texure` struct currently returns a `CommandBuffer` when it's created so we need to collect those and return them.
|
||||
let mut command_buffers = Vec::new();
|
||||
|
||||
let mut materials = Vec::new();
|
||||
for mat in obj_materials {
|
||||
let diffuse_path = mat.diffuse_texture;
|
||||
let (diffuse_texture, cmds) = texture::Texture::load(device, containing_folder.join(diffuse_path), false)?;
|
||||
command_buffers.push(cmds);
|
||||
|
||||
let normal_path = match mat.unknown_param.get("map_Bump") {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(failure::err_msg("Unable to find normal map"))
|
||||
};
|
||||
let (normal_texture, cmds) = texture::Texture::load(device, containing_folder.join(normal_path?), true)?;
|
||||
command_buffers.push(cmds);
|
||||
|
||||
materials.push(Material::new(
|
||||
device,
|
||||
&mat.name,
|
||||
diffuse_texture,
|
||||
normal_texture,
|
||||
layout,
|
||||
));
|
||||
}
|
||||
|
||||
let mut meshes = Vec::new();
|
||||
for m in obj_models {
|
||||
let mut vertices = Vec::new();
|
||||
for i in 0..m.mesh.positions.len() / 3 {
|
||||
vertices.push(ModelVertex {
|
||||
position: [
|
||||
m.mesh.positions[i * 3],
|
||||
m.mesh.positions[i * 3 + 1],
|
||||
m.mesh.positions[i * 3 + 2],
|
||||
].into(),
|
||||
tex_coords: [
|
||||
m.mesh.texcoords[i * 2],
|
||||
m.mesh.texcoords[i * 2 + 1]
|
||||
].into(),
|
||||
normal: [
|
||||
m.mesh.normals[i * 3],
|
||||
m.mesh.normals[i * 3 + 1],
|
||||
m.mesh.normals[i * 3 + 2],
|
||||
].into(),
|
||||
// We'll calculate these later
|
||||
tangent: [0.0; 3].into(),
|
||||
bitangent: [0.0; 3].into(),
|
||||
});
|
||||
}
|
||||
|
||||
let indices = &m.mesh.indices;
|
||||
|
||||
// Calculate tangents and bitangets. We're going to
|
||||
// use the triangles, so we need to loop through the
|
||||
// indices in chunks of 3
|
||||
for c in indices.chunks(3) {
|
||||
let v0 = vertices[c[0] as usize];
|
||||
let v1 = vertices[c[1] as usize];
|
||||
let v2 = vertices[c[2] as usize];
|
||||
|
||||
let pos0 = v0.position;
|
||||
let pos1 = v1.position;
|
||||
let pos2 = v2.position;
|
||||
|
||||
let uv0 = v0.tex_coords;
|
||||
let uv1 = v1.tex_coords;
|
||||
let uv2 = v2.tex_coords;
|
||||
|
||||
// Calculate the edges of the triangle
|
||||
let delta_pos1 = pos1 - pos0;
|
||||
let delta_pos2 = pos2 - pos0;
|
||||
|
||||
// This will give us a direction to calculate the
|
||||
// tangent and bitangent
|
||||
let delta_uv1 = uv1 - uv0;
|
||||
let delta_uv2 = uv2 - uv0;
|
||||
|
||||
// Solving the following system of equations will
|
||||
// give us the tangent and bitangent.
|
||||
// delta_pos1 = delta_uv1.x * T + delta_u.y * B
|
||||
// delta_pos2 = delta_uv2.x * T + delta_uv2.y * B
|
||||
// Luckily, the place I found this equation provided
|
||||
// the solution!
|
||||
let r = 1.0 / (delta_uv1 .x * delta_uv2.y - delta_uv1.y * delta_uv2.x);
|
||||
let tangent = (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r;
|
||||
let bitangent = (delta_pos2 * delta_uv1.x - delta_pos1 * delta_uv2.x) * r;
|
||||
|
||||
// We'll use the same tangent/bitangent for each vertex in the triangle
|
||||
vertices[c[0] as usize].tangent = tangent;
|
||||
vertices[c[1] as usize].tangent = tangent;
|
||||
vertices[c[2] as usize].tangent = tangent;
|
||||
|
||||
vertices[c[0] as usize].bitangent = bitangent;
|
||||
vertices[c[1] as usize].bitangent = bitangent;
|
||||
vertices[c[2] as usize].bitangent = bitangent;
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&vertices),
|
||||
wgpu::BufferUsage::VERTEX,
|
||||
);
|
||||
|
||||
let index_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(indices),
|
||||
wgpu::BufferUsage::INDEX,
|
||||
);
|
||||
|
||||
meshes.push(Mesh {
|
||||
name: m.name,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_elements: m.mesh.indices.len() as u32,
|
||||
material: m.mesh.material_id.unwrap_or(0),
|
||||
});
|
||||
}
|
||||
|
||||
Ok((Self { meshes, materials }, command_buffers))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DrawModel<'a, 'b>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
|
||||
fn draw_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_model_instanced_with_material(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawModel<'a, 'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_mesh_instanced(mesh, material, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.set_vertex_buffer(0, &mesh.vertex_buffer, 0, 0);
|
||||
self.set_index_buffer(&mesh.index_buffer, 0, 0);
|
||||
self.set_bind_group(0, &material.bind_group, &[]);
|
||||
self.set_bind_group(1, &uniforms, &[]);
|
||||
self.set_bind_group(2, &light, &[]);
|
||||
self.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_model_instanced(model, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
let material = &model.materials[mesh.material];
|
||||
self.draw_mesh_instanced(mesh, material, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_model_instanced_with_material(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
self.draw_mesh_instanced(mesh, material, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DrawLight<'a, 'b>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_light_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_light_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) where
|
||||
'b: 'a;
|
||||
|
||||
fn draw_light_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_light_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawLight<'a, 'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_light_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_light_mesh_instanced(mesh, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_light_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.set_vertex_buffer(0, &mesh.vertex_buffer, 0, 0);
|
||||
self.set_index_buffer(&mesh.index_buffer, 0, 0);
|
||||
self.set_bind_group(0, uniforms, &[]);
|
||||
self.set_bind_group(1, light, &[]);
|
||||
self.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_light_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_light_model_instanced(model, 0..1, uniforms, light);
|
||||
}
|
||||
fn draw_light_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
self.draw_light_mesh_instanced(mesh, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 117 KiB |
@ -0,0 +1,14 @@
|
||||
# Blender MTL File: 'cube.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material.001
|
||||
Ns 323.999994
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Bump cube-normal.png
|
||||
map_Kd cube-diffuse.jpg
|
@ -0,0 +1,60 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
layout(location=2) in vec3 a_normal;
|
||||
layout(location=3) in vec3 a_tangent;
|
||||
layout(location=4) in vec3 a_bitangent;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
layout(location=1) out vec3 v_position; // UPDATED!
|
||||
layout(location=2) out vec3 v_light_position; // NEW!
|
||||
layout(location=3) out vec3 v_view_position; // NEW!
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=1)
|
||||
buffer Instances {
|
||||
mat4 s_models[];
|
||||
};
|
||||
|
||||
// NEW!
|
||||
layout(set=2, binding=0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
|
||||
mat4 model_matrix = s_models[gl_InstanceIndex];
|
||||
|
||||
mat3 normal_matrix = mat3(transpose(inverse(model_matrix)));
|
||||
vec3 normal = normalize(normal_matrix * a_normal);
|
||||
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||
|
||||
// UDPATED!
|
||||
mat3 tangent_matrix = transpose(mat3(
|
||||
tangent,
|
||||
bitangent,
|
||||
normal
|
||||
));
|
||||
|
||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||
v_position = model_space.xyz;
|
||||
|
||||
// NEW!
|
||||
// v_position = tangent_matrix * model_space.xyz;
|
||||
// v_light_position = tangent_matrix * light_position;
|
||||
// v_view_position = tangent_matrix * u_view_position;
|
||||
v_position = model_space.xyz;
|
||||
v_light_position = light_position;
|
||||
v_view_position = u_view_position;
|
||||
|
||||
gl_Position = u_view_proj * model_space;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec2 v_tex_coords;
|
||||
layout(location=1) in vec3 v_position; // UPDATED!
|
||||
layout(location=2) in vec3 v_light_position; // NEW!
|
||||
layout(location=3) in vec3 v_view_position; // NEW!
|
||||
|
||||
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;
|
||||
layout(set = 0, binding = 2) uniform texture2D t_normal;
|
||||
layout(set = 0, binding = 3) uniform sampler s_normal;
|
||||
|
||||
layout(set = 2, binding = 0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec4 object_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords);
|
||||
vec4 object_normal = texture(sampler2D(t_normal, s_normal), v_tex_coords);
|
||||
|
||||
float ambient_strength = 0.1;
|
||||
vec3 ambient_color = light_color * ambient_strength;
|
||||
|
||||
vec3 normal = normalize(object_normal.rgb * 2.0 - 1.0); // UPDATED!
|
||||
vec3 light_dir = normalize(v_light_position - v_position); // UPDATED!
|
||||
|
||||
float diffuse_strength = max(dot(normal, light_dir), 0.0);
|
||||
vec3 diffuse_color = light_color * diffuse_strength;
|
||||
|
||||
vec3 view_dir = normalize(v_view_position - v_position); // UPDATED!
|
||||
vec3 half_dir = normalize(view_dir + light_dir);
|
||||
float specular_strength = pow(max(dot(normal, half_dir), 0.0), 32);
|
||||
vec3 specular_color = specular_strength * light_color;
|
||||
|
||||
vec3 result = (ambient_color + diffuse_color + specular_color) * object_color.xyz;
|
||||
f_color = vec4(result, object_color.a);
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
layout(location=2) in vec3 a_normal;
|
||||
layout(location=3) in vec3 a_tangent;
|
||||
layout(location=4) in vec3 a_bitangent;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
layout(location=1) out vec3 v_position; // UPDATED!
|
||||
layout(location=2) out vec3 v_light_position; // NEW!
|
||||
layout(location=3) out vec3 v_view_position; // NEW!
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=1)
|
||||
buffer Instances {
|
||||
mat4 s_models[];
|
||||
};
|
||||
|
||||
// NEW!
|
||||
layout(set=2, binding=0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
|
||||
mat4 model_matrix = s_models[gl_InstanceIndex];
|
||||
|
||||
mat3 normal_matrix = mat3(transpose(inverse(model_matrix)));
|
||||
vec3 normal = normalize(normal_matrix * a_normal);
|
||||
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||
|
||||
// UDPATED!
|
||||
mat3 tangent_matrix = transpose(mat3(
|
||||
tangent,
|
||||
bitangent,
|
||||
normal
|
||||
));
|
||||
|
||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||
v_position = model_space.xyz;
|
||||
|
||||
// NEW!
|
||||
v_position = tangent_matrix * model_space.xyz;
|
||||
v_light_position = tangent_matrix * light_position;
|
||||
v_view_position = tangent_matrix * u_view_position;
|
||||
|
||||
gl_Position = u_view_proj * model_space;
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
use image::GenericImageView;
|
||||
use std::path::Path;
|
||||
|
||||
|
||||
pub struct Texture {
|
||||
pub texture: wgpu::Texture,
|
||||
pub view: wgpu::TextureView,
|
||||
pub sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||
|
||||
pub fn load<P: AsRef<Path>>(
|
||||
device: &wgpu::Device,
|
||||
path: P,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
// Needed to appease the borrow checker
|
||||
let path_copy = path.as_ref().to_path_buf();
|
||||
let label = path_copy.to_str();
|
||||
|
||||
let img = image::open(path)?;
|
||||
Self::from_image(device, &img, label, is_normal_map)
|
||||
}
|
||||
|
||||
pub fn create_depth_texture(device: &wgpu::Device, sc_desc: &wgpu::SwapChainDescriptor, label: &str) -> Self {
|
||||
let size = wgpu::Extent3d {
|
||||
width: sc_desc.width,
|
||||
height: sc_desc.height,
|
||||
depth: 1,
|
||||
};
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
array_layer_count: 1,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: Self::DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT
|
||||
| wgpu::TextureUsage::SAMPLED
|
||||
| wgpu::TextureUsage::COPY_SRC,
|
||||
};
|
||||
let texture = device.create_texture(&desc);
|
||||
|
||||
let view = texture.create_default_view();
|
||||
let 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::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
lod_min_clamp: -100.0,
|
||||
lod_max_clamp: 100.0,
|
||||
compare: wgpu::CompareFunction::LessEqual,
|
||||
});
|
||||
|
||||
Self { texture, view, sampler }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_bytes(
|
||||
device: &wgpu::Device,
|
||||
bytes: &[u8],
|
||||
label: &str,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
let img = image::load_from_memory(bytes)?;
|
||||
Self::from_image(device, &img, Some(label), is_normal_map)
|
||||
}
|
||||
|
||||
pub fn from_image(
|
||||
device: &wgpu::Device,
|
||||
img: &image::DynamicImage,
|
||||
label: Option<&str>,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
let rgba = img.to_rgba();
|
||||
let dimensions = img.dimensions();
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
depth: 1,
|
||||
};
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label,
|
||||
size,
|
||||
array_layer_count: 1,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: if is_normal_map {
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
} else {
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||
},
|
||||
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||
});
|
||||
|
||||
let buffer = device.create_buffer_with_data(
|
||||
&rgba,
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("texture_buffer_copy_encoder"),
|
||||
});
|
||||
|
||||
encoder.copy_buffer_to_texture(
|
||||
wgpu::BufferCopyView {
|
||||
buffer: &buffer,
|
||||
offset: 0,
|
||||
bytes_per_row: 4 * dimensions.0,
|
||||
rows_per_image: dimensions.1,
|
||||
},
|
||||
wgpu::TextureCopyView {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
array_layer: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
size,
|
||||
);
|
||||
|
||||
let cmd_buffer = encoder.finish();
|
||||
|
||||
let view = texture.create_default_view();
|
||||
let 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: wgpu::CompareFunction::Always,
|
||||
});
|
||||
|
||||
Ok((Self { texture, view, sampler }, cmd_buffer))
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec2 v_tex_coords;
|
||||
layout(location=1) in vec3 v_position; // UPDATED!
|
||||
layout(location=2) in mat3 v_tangent_matrix; // NEW!
|
||||
|
||||
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;
|
||||
layout(set = 0, binding = 2) uniform texture2D t_normal;
|
||||
layout(set = 0, binding = 3) uniform sampler s_normal;
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj; // unused
|
||||
};
|
||||
|
||||
layout(set = 2, binding = 0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec4 object_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords);
|
||||
vec4 object_normal = texture(sampler2D(t_normal, s_normal), v_tex_coords);
|
||||
|
||||
float ambient_strength = 0.1;
|
||||
vec3 ambient_color = light_color * ambient_strength;
|
||||
|
||||
vec3 normal = normalize(v_tangent_matrix * (object_normal.rgb * 2.0 - 1.0));
|
||||
vec3 light_dir = normalize(light_position - v_position);
|
||||
|
||||
float diffuse_strength = max(dot(normal, light_dir), 0.0);
|
||||
vec3 diffuse_color = light_color * diffuse_strength;
|
||||
|
||||
vec3 view_dir = normalize(u_view_position - v_position);
|
||||
vec3 half_dir = normalize(view_dir + light_dir);
|
||||
float specular_strength = pow(max(dot(normal, half_dir), 0.0), 32);
|
||||
vec3 specular_color = specular_strength * light_color;
|
||||
|
||||
vec3 result = (ambient_color + diffuse_color + specular_color) * object_color.xyz;
|
||||
f_color = vec4(result, object_color.a);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
layout(location=2) in vec3 a_normal;
|
||||
// NEW!
|
||||
layout(location=3) in vec3 a_tangent;
|
||||
layout(location=4) in vec3 a_bitangent;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
layout(location=1) out vec3 v_position; // UPDATED!
|
||||
layout(location=2) out mat3 v_tangent_matrix; // NEW!
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=1)
|
||||
buffer Instances {
|
||||
mat4 s_models[];
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
|
||||
mat4 model_matrix = s_models[gl_InstanceIndex];
|
||||
|
||||
mat3 normal_matrix = mat3(transpose(inverse(model_matrix)));
|
||||
vec3 normal = normalize(normal_matrix * a_normal);
|
||||
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||
v_tangent_matrix = mat3(
|
||||
tangent,
|
||||
bitangent,
|
||||
normal
|
||||
);
|
||||
|
||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||
v_position = model_space.xyz;
|
||||
|
||||
gl_Position = u_view_proj * model_space;
|
||||
}
|
@ -0,0 +1,407 @@
|
||||
# A Better Camera
|
||||
|
||||
I've been putting this off for a while. Implementing a camera isn't specifically related to using WGPU properly, but it's been bugging me so let's do it.
|
||||
|
||||
`main.rs` is getting a little crowded, so let's create a `camera.rs` file to put our camera code. The first thing we're going to put in it in is some imports and our `OPENGL_TO_WGPU_MATRIX`.
|
||||
|
||||
```rust
|
||||
use cgmath::*;
|
||||
use winit::event::*;
|
||||
use winit::dpi::LogicalPosition;
|
||||
use std::time::Duration;
|
||||
use std::f32::consts::FRAC_PI_2;
|
||||
|
||||
#[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,
|
||||
);
|
||||
```
|
||||
|
||||
## The Camera
|
||||
|
||||
Next we need create a new `Camera` struct. We're going to be using a FPS style camera, so we'll store the position and the yaw (horizontal rotation), and pitch (vertical rotation). We'll have a `calc_matrix` method to create our view matrix.
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
pub struct Camera {
|
||||
pub position: Point3<f32>,
|
||||
yaw: Rad<f32>,
|
||||
pitch: Rad<f32>,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn new<
|
||||
V: Into<Point3<f32>>,
|
||||
Y: Into<Rad<f32>>,
|
||||
P: Into<Rad<f32>>,
|
||||
>(
|
||||
position: V,
|
||||
yaw: Y,
|
||||
pitch: P,
|
||||
) -> Self {
|
||||
Self {
|
||||
position: position.into(),
|
||||
yaw: yaw.into(),
|
||||
pitch: pitch.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calc_matrix(&self) -> Matrix4<f32> {
|
||||
Matrix4::look_at_dir(
|
||||
self.position,
|
||||
Vector3::new(
|
||||
self.yaw.0.cos(),
|
||||
self.pitch.0.sin(),
|
||||
self.yaw.0.sin(),
|
||||
).normalize(),
|
||||
Vector3::unit_y(),
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## The Projection
|
||||
|
||||
I've decided to split the projection from the camera. The projection only really needs to change if the window resizes, so let's create a `Projection` struct.
|
||||
|
||||
```rust
|
||||
pub struct Projection {
|
||||
aspect: f32,
|
||||
fovy: Rad<f32>,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
}
|
||||
|
||||
impl Projection {
|
||||
pub fn new<F: Into<Rad<f32>>>(
|
||||
width: u32,
|
||||
height: u32,
|
||||
fovy: F,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
) -> Self {
|
||||
Self {
|
||||
aspect: width as f32 / height as f32,
|
||||
fovy: fovy.into(),
|
||||
znear,
|
||||
zfar,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.aspect = width as f32 / height as f32;
|
||||
}
|
||||
|
||||
pub fn calc_matrix(&self) -> Matrix4<f32> {
|
||||
OPENGL_TO_WGPU_MATRIX * perspective(self.fovy, self.aspect, self.znear, self.zfar)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
On thing to note: `cgmath` currently returns a right-handed projection matrix from the `perspective` function. This means that the z-axis points out of the screen. If you want the z-axis to be *into* the screen (aka. a left-handed projection matrix), you'll have to code your own.
|
||||
|
||||
You can tell the difference between a right-handed coordinate system and a left-handed one by using your hands. Point your thumb to the right. This is the x-axis. Point your pointer finger up. This is the y-axis. Extend your middle finger. This is the z-axis. On your right hand your middle finger should be pointing towards you. On your left hand it should be pointing away.
|
||||
|
||||
![left_right_hand.gif]()
|
||||
|
||||
# The Camera Controller
|
||||
|
||||
As our camera is different, so we'll need a new camera controller. Add the following to `camera.rs`.
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
pub struct CameraController {
|
||||
amount_left: f32,
|
||||
amount_right: f32,
|
||||
amount_forward: f32,
|
||||
amount_backward: f32,
|
||||
amount_up: f32,
|
||||
amount_down: f32,
|
||||
rotate_horizontal: f32,
|
||||
rotate_vertical: f32,
|
||||
scroll: f32,
|
||||
speed: f32,
|
||||
sensitivity: f32,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
pub fn new(speed: f32, sensitivity: f32) -> Self {
|
||||
Self {
|
||||
amount_left: 0.0,
|
||||
amount_right: 0.0,
|
||||
amount_forward: 0.0,
|
||||
amount_backward: 0.0,
|
||||
amount_up: 0.0,
|
||||
amount_down: 0.0,
|
||||
rotate_horizontal: 0.0,
|
||||
rotate_vertical: 0.0,
|
||||
scroll: 0.0,
|
||||
speed,
|
||||
sensitivity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool{
|
||||
let amount = if state == ElementState::Pressed { 1.0 } else { 0.0 };
|
||||
match key {
|
||||
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
||||
self.amount_forward = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::S | VirtualKeyCode::Down => {
|
||||
self.amount_backward = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::A | VirtualKeyCode::Left => {
|
||||
self.amount_left = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::D | VirtualKeyCode::Right => {
|
||||
self.amount_right = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::Space => {
|
||||
self.amount_up = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::LShift => {
|
||||
self.amount_down = amount;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
|
||||
self.rotate_horizontal = mouse_dx as f32;
|
||||
self.rotate_vertical = mouse_dy as f32;
|
||||
}
|
||||
|
||||
pub fn process_scroll(&mut self, delta: &MouseScrollDelta) {
|
||||
self.scroll = -match delta {
|
||||
// I'm assuming a line is about 100 pixels
|
||||
MouseScrollDelta::LineDelta(_, scroll) => scroll * 100.0,
|
||||
MouseScrollDelta::PixelDelta(LogicalPosition {
|
||||
y: scroll,
|
||||
..
|
||||
}) => *scroll as f32,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn update_camera(&mut self, camera: &mut Camera, dt: Duration) {
|
||||
let dt = dt.as_secs_f32();
|
||||
|
||||
// Move forward/backward and left/right
|
||||
let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos();
|
||||
let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize();
|
||||
let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize();
|
||||
camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt;
|
||||
camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt;
|
||||
|
||||
// Move in/out (aka. "zoom")
|
||||
// Note: this isn't an actual zoom. The camera's position
|
||||
// changes when zooming. I've added this to make it easier
|
||||
// to get closer to an object you want to focus on.
|
||||
let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos();
|
||||
let scrollward = Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize();
|
||||
camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt;
|
||||
|
||||
// Move up/down. Since we don't use roll, we can just
|
||||
// modify the y coordinate directly.
|
||||
camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt;
|
||||
|
||||
// Rotate
|
||||
camera.yaw += Rad(self.rotate_horizontal) * self.sensitivity * dt;
|
||||
camera.pitch += Rad(-self.rotate_vertical) * self.sensitivity * dt;
|
||||
|
||||
// If process_mouse isn't called every frame, these values
|
||||
// will not get set to zero, and the camera will rotate
|
||||
// when moving in a non cardinal direction.
|
||||
self.rotate_horizontal = 0.0;
|
||||
self.rotate_vertical = 0.0;
|
||||
|
||||
// Keep the camera's angle from going too high/low.
|
||||
if camera.pitch < -Rad(FRAC_PI_2) {
|
||||
camera.pitch = -Rad(FRAC_PI_2);
|
||||
} else if camera.pitch > Rad(FRAC_PI_2) {
|
||||
camera.pitch = Rad(FRAC_PI_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cleaning up `main.rs`
|
||||
|
||||
First things first we need to delete `Camera` and `CameraController` as well as the extra `OPENGL_TO_WGPU_MATRIX` from `main.rs`. Once you've done that import `camera.rs`.
|
||||
|
||||
```rust
|
||||
mod model;
|
||||
mod texture;
|
||||
mod camera; // NEW!
|
||||
```
|
||||
|
||||
We need to update `update_view_proj` to use our new `Camera` and `Projection`.
|
||||
|
||||
```rust
|
||||
|
||||
impl Uniforms {
|
||||
// ...
|
||||
|
||||
// UPDATED!
|
||||
fn update_view_proj(&mut self, camera: &camera::Camera, projection: &camera::Projection) {
|
||||
self.view_position = camera.position.to_homogeneous();
|
||||
self.view_proj = projection.calc_matrix() * camera.calc_matrix()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We need to change our `State` to use our `Camera`, `CameraProjection` and `Projection` as well. We'll also add two fields for later: `last_mouse_pos`, and `mouse_pressed`.
|
||||
|
||||
```rust
|
||||
struct State {
|
||||
// ...
|
||||
camera: camera::Camera, // UPDATED!
|
||||
projection: camera::Projection, // NEW!
|
||||
camera_controller: camera::CameraController, // UPDATED!
|
||||
// ...
|
||||
// NEW!
|
||||
last_mouse_pos: PhysicalPosition<f64>,
|
||||
mouse_pressed: bool,
|
||||
}
|
||||
```
|
||||
|
||||
You'll need to import `winit::dpi::PhysicalPosition` if you haven't already.
|
||||
|
||||
We need to update `new()` as well.
|
||||
|
||||
```rust
|
||||
impl State {
|
||||
async fn new(window: &Window) -> Self {
|
||||
// ...
|
||||
|
||||
// UPDATED!
|
||||
let camera = camera::Camera::new((0.0, 2.0, 10.0), cgmath::Deg(-90.0), cgmath::Deg(-20.0));
|
||||
let projection = camera::Projection::new(sc_desc.width, sc_desc.height, cgmath::Deg(45.0), 0.1, 100.0);
|
||||
let camera_controller = camera::CameraController::new(4.0, 0.4);
|
||||
|
||||
// ...
|
||||
|
||||
Self {
|
||||
// ...
|
||||
camera,
|
||||
projection,
|
||||
camera_controller,
|
||||
// ...
|
||||
// NEW!
|
||||
last_mouse_pos: (0.0, 0.0).into(),
|
||||
mouse_pressed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We need to change our `projection` in `resize` as well.
|
||||
|
||||
```rust
|
||||
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
// UPDATED!
|
||||
self.projection.resize(new_size.width, new_size.height);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`input` will need to be updated as well.
|
||||
|
||||
```rust
|
||||
// UPDATED!
|
||||
fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
input: KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => self.camera_controller.process_keyboard(*key, *state),
|
||||
WindowEvent::MouseWheel {
|
||||
delta,
|
||||
..
|
||||
} => {
|
||||
self.camera_controller.process_scroll(delta);
|
||||
true
|
||||
}
|
||||
WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
state,
|
||||
..
|
||||
} => {
|
||||
self.mouse_pressed = *state == ElementState::Pressed;
|
||||
true
|
||||
}
|
||||
WindowEvent::CursorMoved {
|
||||
position,
|
||||
..
|
||||
} => {
|
||||
let mouse_dx = position.x - self.last_mouse_pos.x;
|
||||
let mouse_dy = position.y - self.last_mouse_pos.y;
|
||||
self.last_mouse_pos = *position;
|
||||
if self.mouse_pressed {
|
||||
self.camera_controller.process_mouse(mouse_dx, mouse_dy);
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `update` function requires a bit more explanation. The `update_camera` function on the `CameraController` has a parameter `dt: Duration` which is the delta time or time between frames. This is to help smooth out the camera movement so that it's not locked be the framerate. Currently we aren't calculating `dt`, so I decided to pass it into `update` as a parameter.
|
||||
|
||||
```rust
|
||||
fn update(&mut self, dt: std::time::Duration) {
|
||||
// UPDATED!
|
||||
self.camera_controller.update_camera(&mut self.camera, dt);
|
||||
self.uniforms.update_view_proj(&self.camera, &self.projection);
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
While we're at it, let's use `dt` for the light's rotation as well.
|
||||
|
||||
```rust
|
||||
self.light.position =
|
||||
cgmath::Quaternion::from_axis_angle((0.0, 1.0, 0.0).into(), cgmath::Deg(60.0 * dt.as_secs_f32()))
|
||||
* old_position; // UPDATED!
|
||||
```
|
||||
|
||||
We still need to calculate `dt`. Let's do that in the `main` function.
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// ..
|
||||
let mut state = block_on(State::new(&window));
|
||||
let mut last_render_time = std::time::Instant::now();
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
match event {
|
||||
// ...
|
||||
// UPDATED!
|
||||
Event::RedrawRequested(_) => {
|
||||
let now = std::time::Instant::now();
|
||||
let dt = now - last_render_time;
|
||||
last_render_time = now;
|
||||
state.update(dt);
|
||||
state.render();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
With that we should be able to move our camera wherever we want.
|
After Width: | Height: | Size: 11 KiB |