started normals tutorial

pull/33/head
Ben Hansen 4 years ago
parent 107d64a70e
commit ec5339bd3b

15
Cargo.lock generated

@ -1635,6 +1635,21 @@ dependencies = [
"winit 0.22.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tutorial11-normals"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"glsl-to-spirv 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.4 (registry+https://github.com/rust-lang/crates.io-index)",
"tobj 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wgpu 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winit 0.22.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tutorial2-swapchain"
version = "0.1.0"

@ -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"
glsl-to-spirv = "0.1"
# 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 vec4 v_color;
layout(set=0, binding=0)
uniform Uniforms {
mat4 u_view;
mat4 u_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_proj * u_view * vec4(v_position, 1);
v_color = vec4(u_color, 0);
}

@ -0,0 +1,705 @@
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_matrices(&self) -> (cgmath::Matrix4<f32>, 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 (view, proj);
}
}
#[repr(C)]
#[derive(Copy, Clone)]
struct Uniforms {
// view_position: cgmath::Vector4<f32>,
view: cgmath::Matrix4<f32>,
proj: cgmath::Matrix4<f32>,
}
impl Uniforms {
fn new() -> Self {
Self {
// view_position: Zero::zero(),
view: cgmath::Matrix4::identity(),
proj: cgmath::Matrix4::identity(),
// view_3x3: cgmath::Matrix3::identity(),
}
}
fn update_view_proj(&mut self, camera: &Camera) {
// self.view_position = camera.eye.to_homogeneous();
let matrices = camera.build_matrices();
self.view = matrices.0;
self.proj = OPENGL_TO_WGPU_MATRIX * matrices.1;
}
}
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).normalize();
if self.is_forward_pressed {
camera.eye += forward * self.speed;
}
if self.is_backward_pressed {
camera.eye -= forward * self.speed;
}
let right = forward.cross(camera.up);
if self.is_right_pressed {
camera.eye += right * self.speed;
}
if self.is_left_pressed {
camera.eye -= right * self.speed;
}
}
}
struct 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,
}
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 vs_spirv = glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex).unwrap();
let fs_spirv = glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment).unwrap();
let vs_data = wgpu::read_spirv(vs_spirv).unwrap();
let fs_data = wgpu::read_spirv(fs_spirv).unwrap();
let vs_module = device.create_shader_module(&vs_data);
let fs_module = device.create_shader_module(&fs_data);
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,
);
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,
)
};
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,
}
}
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
self.depth_texture = texture::Texture::create_depth_texture(&self.device, &self.sc_desc, "depth_texture");
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);
}
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,400 @@
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,
}
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))?;
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?))?;
command_buffers.push(cmds);
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: None,
});
materials.push(Material {
name: mat.name,
diffuse_texture,
normal_texture,
bind_group,
});
}
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
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,
);
}
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);
}
}
}
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);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

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

File diff suppressed because it is too large Load Diff

@ -0,0 +1,54 @@
#version 450
layout(location=0) in vec2 v_tex_coords;
layout(location=1) in vec3 v_normal;
layout(location=2) in vec3 v_position_model_tangent_space;
layout(location=3) in vec3 v_light_position_tangent_space;
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.01;
vec3 ambient_color = light_color * ambient_strength;
// We're storing our normal map as Rgba8UnormSrgb, if we were
// using Rgba8Unorm or another format without Srgb, then we wouldn't
// be able to use the normal data directly, and we would have to
// transform it for the range [0, 1] to [-1, 1].
vec3 normal = normalize(object_normal.rgb * 2.0 - vec3(1.0));
// vec3 normal = normalize(object_normal.rgb);
vec3 light_dir = normalize(v_light_position_tangent_space - v_position_model_tangent_space);
float diffuse_strength = max(dot(normal, light_dir), 0.0);
vec3 diffuse_color = light_color * diffuse_strength;
vec3 view_dir = normalize(-v_position_model_tangent_space);
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;
vec3 result = (ambient_color + diffuse_color) * object_color.xyz;
f_color = vec4(result, object_color.a);
// f_color = vec4(normal, object_color.a);
}

@ -0,0 +1,54 @@
#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_normal;
layout(location=2) out vec3 v_position_model_tangent_space;
layout(location=3) out vec3 v_light_position_tangent_space;
layout(set=1, binding=0)
uniform Uniforms {
mat4 u_view;
mat4 u_proj;
};
layout(set=1, binding=1)
buffer Instances {
mat4 s_models[];
};
layout(set = 2, binding = 0) uniform Light {
vec3 light_position;
vec3 light_color; // used in fragment shader
};
void main() {
v_tex_coords = a_tex_coords;
mat4 model_matrix = s_models[gl_InstanceIndex];
mat4 model_view_matrix = u_view * model_matrix;
vec4 light_position_view_space = u_view * vec4(light_position, 1.0);
vec4 position_model_view_space = model_view_matrix * vec4(a_position, 1.0);
mat3 MV3x3 = mat3(model_view_matrix);
vec3 normal_view_space = MV3x3 * normalize(a_normal);
vec3 tangent_view_space = MV3x3 * normalize(a_tangent);
vec3 bitangent_view_space = MV3x3 * normalize(a_bitangent);
mat3 TBN = transpose(mat3(
tangent_view_space,
bitangent_view_space,
normal_view_space
));
gl_Position = u_proj * position_model_view_space;
v_light_position_tangent_space = TBN * light_position_view_space.xyz;
v_position_model_tangent_space = TBN * position_model_view_space.xyz;
}

@ -0,0 +1,134 @@
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,
) -> 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)
}
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) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
let img = image::load_from_memory(bytes)?;
Self::from_image(device, &img, Some(label))
}
pub fn from_image(
device: &wgpu::Device,
img: &image::DynamicImage,
label: Option<&str>,
) -> 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: 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))
}
}

@ -37,6 +37,7 @@ module.exports = {
collapsable: false,
children: [
'/intermediate/tutorial10-lighting/',
'/intermediate/tutorial11-normals/',
],
},
{

@ -1 +1 @@
Subproject commit 2f3cc4c8ca1b42921ba05cc1e08a23351345dd54
Subproject commit 6d424eea0a6cc5317d986de103519f10beb93415

@ -0,0 +1,123 @@
# Normal Mapping
With just lighting, our scene is already looking pretty good. Still, our models are still overly smooth. This is understandable because we are using a very simple model. If we were using a texture that was supposed to be smooth, this wouldn't be a problem, but our brick texture is supposed to be rougher. We could solve this by adding more geometry, but that would slow our scene down, and it would hard to know where to new polygons. This is were normal mapping comes in.
Remember in [the instancing tutorial](/beginner/tutorial7-instancing/#a-different-way-textures), we experimented with storing instance data in a texture? A normal map is doing just that with normal data! We'll use the normals in the normal map in our lighting calculation in addition to the vertex normal.
The brick texture I found came with a normal map. Let's take a look at it!
![./cube-normal.png](./cube-normal.png)
The r, g, and b components of the texture correspond to the x, y, and z components or the normals. All the z values should be positive, that's why the normal map has a bluish tint.
We'll need to modify our `Material` struct in `model.rs` to include a `normal_texture`.
```rust
pub struct Material {
pub name: String,
pub diffuse_texture: texture::Texture,
pub normal_texture: texture::Texture,
pub bind_group: wgpu::BindGroup,
}
```
We'll have to update the `texture_bind_group_layout` to include the normal map as well.
```rust
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
bindings: &[
// ...
// 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,
});
```
We'll need to actually the normal map itself. We'll do this in the loop we create the materials in.
```rust
let diffuse_path = mat.diffuse_texture;
let (diffuse_texture, cmds) = texture::Texture::load(device, containing_folder.join(diffuse_path))?;
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?))?;
command_buffers.push(cmds);
```
* Note: I duplicated and moved teh `command_buffers.push(cmds);` line. This means we can reuse the `cmds` variable for both the normal map and diffuse/color map.
Our `Material`'s `bind_group` will have to change as well.
```rust
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout,
bindings: &[
// ...
wgpu::Binding {
binding: 2,
resource: wgpu::BindingResource::TextureView(&normal_texture.view),
},
wgpu::Binding {
binding: 3,
resource: wgpu::BindingResource::Sampler(&normal_texture.sampler),
},
],
label: None,
});
```
Don't forget to pass the `normal_texture` into the `Material` struct!
```rust
materials.push(Material {
name: mat.name,
diffuse_texture,
normal_texture, // NEW!
bind_group,
});
```
Now we can add use the texture in the fragment shader. Color values are by default in the range `[0, 1]`, so we'll have to convert the normal values to `[-1, 1]`.
```glsl
// shader.frag
// ...
layout(set = 0, binding = 2) uniform texture2D t_normal;
layout(set = 0, binding = 3) uniform sampler s_normal;
// ...
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); // NEW!
// ...
vec3 normal = normalize(object_normal.rgb * 2.0 - 1.0); // UPDATED!
// ...
}
```
## View Space
I mentioned it briefly in the [lighting tutorial](/intermediate/tutorial10-lighting/#the-normal-matrix), that we were doing our lighting calculation in "world space". This meant that the entire scene was oriented with respect to the *world's* coordinate system.

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Loading…
Cancel
Save