Skip to content

Commit

Permalink
Added Normal mapping to lights (#17)
Browse files Browse the repository at this point in the history
* added normal mapping to lighting

* fixed opt-level
  • Loading branch information
Sycrosity authored Aug 31, 2022
1 parent dff2e17 commit 6453e3e
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 74 deletions.
5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,4 @@ crate-type = ["cdylib", "rlib"]
[build-dependencies]
anyhow = "1.0"
fs_extra = "1.2"
glob = "0.3"

[profile.dev]
opt-level = 1
glob = "0.3"
43 changes: 16 additions & 27 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,11 +360,8 @@ struct State {
size: winit::dpi::PhysicalSize<u32>,
//describes the actions our gpu will perform when acting on a set of data (like a set of verticies)
render_pipeline: wgpu::RenderPipeline,
//our imported model
obj_model: model::Model,
//describes how a set of textures can be accessed by the shader
diffuse_bind_group: wgpu::BindGroup,
//aa texture generated from texture.rs
diffuse_texture: texture::Texture,
//a view into our scene that can move around (using rasterization) and give the perception of depth
camera: Camera,
//the camera matrix data for use in the buffer
Expand Down Expand Up @@ -456,12 +453,6 @@ impl State {
};
surface.configure(&device, &config);

//collect the bytes from happy-tree.png
let diffuse_bytes = include_bytes!("assets/happy-tree.png");
//create a texture using our texture.rs file and our image bytes
let diffuse_texture: texture::Texture =
texture::Texture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").unwrap();

//[TODO] really very black box
//used to create a bind group with the specified config, so that bind groups can be swapped in and out (as long as they share the same BindGroupLayout)
let texture_bind_group_layout: wgpu::BindGroupLayout =
Expand Down Expand Up @@ -490,22 +481,22 @@ impl State {
//we only have one so this isn't needed
count: None,
},
],
});

//describes how a set of textures can be accessed by a shader
let diffuse_bind_group: wgpu::BindGroup =
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("diffuse_bind_group"),
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
// normal map
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
Expand Down Expand Up @@ -723,8 +714,6 @@ impl State {
size,
render_pipeline,
obj_model,
diffuse_bind_group,
diffuse_texture,
depth_texture,
camera,
camera_uniform,
Expand Down
58 changes: 57 additions & 1 deletion src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ pub struct ModelVertex {
pub position: [f32; 3],
//texture coordinates - 2 f32's as textures are 2d only (for now)
pub tex_coords: [f32; 2],
//for lighting (will be used later)
//for lighting
pub normal: [f32; 3],
//to represent lighting via a coordinate system instead of a world system
pub tangent: [f32; 3],
pub bitangent: [f32; 3],
}

impl Vertex for ModelVertex {
Expand Down Expand Up @@ -56,6 +59,18 @@ impl Vertex for ModelVertex {
shader_location: 2,
format: wgpu::VertexFormat::Float32x3,
},
//tangent
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Float32x3,
},
//bitangent
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32x3,
},
],
}
}
Expand All @@ -71,9 +86,50 @@ pub struct Model {
pub struct Material {
pub label: String,
pub diffuse_texture: texture::Texture,
pub normal_texture: texture::Texture,
pub bind_group: wgpu::BindGroup,
}

impl Material {
pub fn new(
device: &wgpu::Device,
label: &str,
diffuse_texture: texture::Texture,
normal_texture: texture::Texture, // NEW!
layout: &wgpu::BindGroupLayout,
) -> Self {
let bind_group: wgpu::BindGroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&normal_texture.view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&normal_texture.sampler),
},
],
});

Self {
label: String::from(label),
diffuse_texture,
normal_texture,
bind_group,
}
}
}

//all the vertices and indices data of the model
pub struct Mesh {
pub label: String,
Expand Down
107 changes: 84 additions & 23 deletions src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ pub async fn load_texture(
file_name: &str,
device: &wgpu::Device,
queue: &wgpu::Queue,
is_normal_map: bool,
) -> anyhow::Result<texture::Texture> {
let data: Vec<u8> = load_binary(file_name).await?;
texture::Texture::from_bytes(device, queue, &data, file_name)
texture::Texture::from_bytes(device, queue, &data, file_name, is_normal_map)
}

pub async fn load_obj_model(
Expand Down Expand Up @@ -108,28 +109,18 @@ pub async fn load_obj_model(
//consatruct the actual texture materials from the file and index references in the .mtl file
for mat in obj_materials? {
let diffuse_texture: texture::Texture =
load_texture(&mat.diffuse_texture, device, queue).await?;
let bind_group: wgpu::BindGroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
//the inputed BindGroupLayout
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
},
],
});

materials.push(model::Material {
label: mat.name,
load_texture(&mat.diffuse_texture, device, queue, true).await?;

let normal_texture: texture::Texture =
load_texture(&mat.normal_texture, device, queue, true).await?;

materials.push(model::Material::new(
device,
&mat.name,
diffuse_texture,
bind_group,
})
normal_texture,
layout,
));
}

let meshes: Vec<model::Mesh> = models
Expand All @@ -139,7 +130,7 @@ pub async fn load_obj_model(
// println!("{}", mat.mesh.positions.len() / 3);

//divide the mesh positions from the .obj file into groups of 3 f32 for the ModelVertex struct (as they are flattened and must be re-grouped into their 3d space positions)
let vertices: Vec<model::ModelVertex> = (0..mat.mesh.positions.len() / 3)
let mut vertices: Vec<model::ModelVertex> = (0..mat.mesh.positions.len() / 3)
.map(|i| model::ModelVertex {
position: [
//as they are in groups of 3, the i * 3 is needed to ensure we are skipping properly over positions
Expand All @@ -155,9 +146,79 @@ pub async fn load_obj_model(
mat.mesh.normals[i * 3 + 1],
mat.mesh.normals[i * 3 + 2],
],
// We'll calculate these later
tangent: [0.0; 3],
bitangent: [0.0; 3],
})
.collect::<Vec<_>>();

let indices: &Vec<u32> = &mat.mesh.indices;
let mut triangles_included: Vec<i32> = vec![0; vertices.len()];

//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: model::ModelVertex = vertices[c[0] as usize];
let v1: model::ModelVertex = vertices[c[1] as usize];
let v2: model::ModelVertex = vertices[c[2] as usize];

let pos0: cgmath::Vector3<_> = v0.position.into();
let pos1: cgmath::Vector3<_> = v1.position.into();
let pos2: cgmath::Vector3<_> = v2.position.into();

let uv0: cgmath::Vector2<_> = v0.tex_coords.into();
let uv1: cgmath::Vector2<_> = v1.tex_coords.into();
let uv2: cgmath::Vector2<_> = v2.tex_coords.into();

// Calculate the edges of the triangle
let delta_pos1: cgmath::Vector3<f32> = pos1 - pos0;
let delta_pos2: cgmath::Vector3<f32> = pos2 - pos0;

// This will give us a direction to calculate the
// tangent and bitangent
let delta_uv1: cgmath::Vector2<f32> = uv1 - uv0;
let delta_uv2: cgmath::Vector2<f32> = uv2 - uv0;

//black box of complicated maths

//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
let r: f32 = 1.0 / (delta_uv1.x * delta_uv2.y - delta_uv1.y * delta_uv2.x);
let tangent: cgmath::Vector3<f32> =
(delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r;
// We flip the bitangent to enable right-handed normal
// maps with wgpu texture coordinate system
let bitangent: cgmath::Vector3<f32> =
(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 + cgmath::Vector3::from(vertices[c[0] as usize].tangent)).into();
vertices[c[1] as usize].tangent =
(tangent + cgmath::Vector3::from(vertices[c[1] as usize].tangent)).into();
vertices[c[2] as usize].tangent =
(tangent + cgmath::Vector3::from(vertices[c[2] as usize].tangent)).into();
vertices[c[0] as usize].bitangent =
(bitangent + cgmath::Vector3::from(vertices[c[0] as usize].bitangent)).into();
vertices[c[1] as usize].bitangent =
(bitangent + cgmath::Vector3::from(vertices[c[1] as usize].bitangent)).into();
vertices[c[2] as usize].bitangent =
(bitangent + cgmath::Vector3::from(vertices[c[2] as usize].bitangent)).into();

// Used to average the tangents/bitangents
triangles_included[c[0] as usize] += 1;
triangles_included[c[1] as usize] += 1;
triangles_included[c[2] as usize] += 1;
}

//average the tangents/bitangents
for (i, n) in triangles_included.into_iter().enumerate() {
let denom: f32 = 1.0 / n as f32;
let mut v: &mut model::ModelVertex = &mut vertices[i];
v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into();
v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into();
}

//a buffer to store the vertex data we want to draw (so we don't have to expensively recomplie the shader on every update)
let vertex_buffer: wgpu::Buffer =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
Expand Down
Loading

0 comments on commit 6453e3e

Please sign in to comment.