tutorial11-normals
This commit is contained in:
parent
0137cd6e22
commit
129619bb3d
7 changed files with 160 additions and 37 deletions
|
@ -1,8 +1,8 @@
|
||||||
# doing the wgpu-rs tutorial
|
# doing the wgpu-rs tutorial
|
||||||
|
|
||||||
[current chapter](https://sotrh.github.io/learn-wgpu/intermediate/tutorial11-normals/#tangent-space-to-world-space)
|
[current chapter](https://sotrh.github.io/learn-wgpu/intermediate/tutorial12-camera/)
|
||||||
|
|
||||||
## stuff to maybe do after the tutorial is done
|
## stuff to maybe do after the tutorial is done
|
||||||
|
|
||||||
* [glsl preprocessor using tera](https://codecrash.me/an-opengl-preprocessor-for-rust)
|
* [glsl preprocessor using tera](https://codecrash.me/an-opengl-preprocessor-for-rust)
|
||||||
* [line drawing](https://vladjuckov.github.io/hqlines/)
|
* [line drawing](https://vladjuckov.github.io/hqlines/)
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -184,6 +184,7 @@ impl State {
|
||||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("texture_bind_group_layout"),
|
label: Some("texture_bind_group_layout"),
|
||||||
entries: &[
|
entries: &[
|
||||||
|
// Diffuse texture
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
@ -200,6 +201,23 @@ impl State {
|
||||||
ty: wgpu::BindingType::Sampler { comparison: false },
|
ty: wgpu::BindingType::Sampler { comparison: false },
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
|
// Normal map texture
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::SampledTexture {
|
||||||
|
dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
component_type: wgpu::TextureComponentType::Uint,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
|
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler { comparison: false },
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
118
src/model.rs
118
src/model.rs
|
@ -14,6 +14,8 @@ pub struct ModelVertex {
|
||||||
position: cgmath::Vector3<f32>,
|
position: cgmath::Vector3<f32>,
|
||||||
tex_coords: cgmath::Vector2<f32>,
|
tex_coords: cgmath::Vector2<f32>,
|
||||||
normal: cgmath::Vector3<f32>,
|
normal: cgmath::Vector3<f32>,
|
||||||
|
tangent: cgmath::Vector3<f32>,
|
||||||
|
bitangent: cgmath::Vector3<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vertex for ModelVertex {
|
impl Vertex for ModelVertex {
|
||||||
|
@ -29,17 +31,26 @@ impl Vertex for ModelVertex {
|
||||||
format: wgpu::VertexFormat::Float3,
|
format: wgpu::VertexFormat::Float3,
|
||||||
},
|
},
|
||||||
wgpu::VertexAttributeDescriptor {
|
wgpu::VertexAttributeDescriptor {
|
||||||
offset: mem::size_of::<cgmath::Vector3<f32>>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||||
shader_location: 1,
|
shader_location: 1,
|
||||||
format: wgpu::VertexFormat::Float2,
|
format: wgpu::VertexFormat::Float2,
|
||||||
},
|
},
|
||||||
wgpu::VertexAttributeDescriptor {
|
wgpu::VertexAttributeDescriptor {
|
||||||
offset: (mem::size_of::<cgmath::Vector3<f32>>()
|
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
||||||
+ mem::size_of::<cgmath::Vector2<f32>>())
|
|
||||||
as wgpu::BufferAddress,
|
|
||||||
shader_location: 2,
|
shader_location: 2,
|
||||||
format: wgpu::VertexFormat::Float3,
|
format: wgpu::VertexFormat::Float3,
|
||||||
},
|
},
|
||||||
|
// Tangent & 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,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,9 +62,50 @@ unsafe impl bytemuck::Zeroable for ModelVertex {}
|
||||||
pub struct Material {
|
pub struct Material {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub diffuse_texture: texture::Texture,
|
pub diffuse_texture: texture::Texture,
|
||||||
|
pub normal_texture: texture::Texture,
|
||||||
pub bind_group: wgpu::BindGroup,
|
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 {
|
||||||
|
label: Some(name),
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
Material {
|
||||||
|
name: name.to_string(),
|
||||||
|
diffuse_texture,
|
||||||
|
normal_texture,
|
||||||
|
bind_group,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Mesh {
|
pub struct Mesh {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub vertex_buffer: wgpu::Buffer,
|
pub vertex_buffer: wgpu::Buffer,
|
||||||
|
@ -85,28 +137,22 @@ impl Model {
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
containing_folder.join(mat.diffuse_texture),
|
containing_folder.join(mat.diffuse_texture),
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
let normal_texture = texture::Texture::load(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
containing_folder.join(mat.normal_texture),
|
||||||
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
Ok(Material::new(
|
||||||
label: None,
|
device,
|
||||||
layout,
|
&mat.name,
|
||||||
entries: &[
|
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
|
|
||||||
},
|
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 1,
|
|
||||||
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(Material {
|
|
||||||
name: mat.name,
|
|
||||||
diffuse_texture,
|
diffuse_texture,
|
||||||
bind_group,
|
normal_texture,
|
||||||
})
|
layout,
|
||||||
|
))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -129,9 +175,35 @@ impl Model {
|
||||||
m.mesh.normals[i * 3 + 2],
|
m.mesh.normals[i * 3 + 2],
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
|
tangent: [0.0; 3].into(),
|
||||||
|
bitangent: [0.0; 3].into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for c in m.mesh.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 delta_pos1 = v1.position - v0.position;
|
||||||
|
let delta_pos2 = v2.position - v0.position;
|
||||||
|
|
||||||
|
let delta_uv1 = v1.tex_coords - v0.tex_coords;
|
||||||
|
let delta_uv2 = v2.tex_coords - v0.tex_coords;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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_init(&wgpu::util::BufferInitDescriptor {
|
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some(&format!("{:?} Vertex Buffer", path.as_ref())),
|
label: Some(&format!("{:?} Vertex Buffer", path.as_ref())),
|
||||||
contents: bytemuck::cast_slice(&vertices),
|
contents: bytemuck::cast_slice(&vertices),
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
layout(location=0) in vec2 v_tex_coords;
|
layout(location=0) in vec2 v_tex_coords;
|
||||||
layout(location=1) in vec3 v_normal;
|
layout(location=1) in vec3 v_position;
|
||||||
layout(location=2) in vec3 v_position;
|
layout(location=2) in vec3 v_light_position;
|
||||||
|
layout(location=3) in vec3 v_view_position;
|
||||||
|
|
||||||
layout(location=0) out vec4 f_color;
|
layout(location=0) out vec4 f_color;
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform texture2D t_diffuse;
|
layout(set = 0, binding = 0) uniform texture2D t_diffuse;
|
||||||
layout(set = 0, binding = 1) uniform sampler s_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)
|
layout(set=1, binding=0)
|
||||||
uniform Uniforms {
|
uniform Uniforms {
|
||||||
|
@ -22,11 +25,13 @@ layout(set=2, binding=0) uniform Light {
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 object_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords);
|
vec4 object_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords);
|
||||||
vec3 normal = normalize(v_normal);
|
vec4 object_normal = texture(sampler2D(t_normal, s_normal), v_tex_coords);
|
||||||
vec3 light_dir = normalize(light_position - v_position);
|
vec3 normal = normalize(object_normal.rgb);
|
||||||
vec3 view_dir = normalize(u_view_position - v_position);
|
vec3 light_dir = normalize(v_light_position - v_position);
|
||||||
|
vec3 view_dir = normalize(v_view_position - v_position);
|
||||||
vec3 half_dir = normalize(view_dir + light_dir);
|
vec3 half_dir = normalize(view_dir + light_dir);
|
||||||
|
|
||||||
|
|
||||||
float specular_strength = pow(max(dot(normal, half_dir), 0.0), 32);
|
float specular_strength = pow(max(dot(normal, half_dir), 0.0), 32);
|
||||||
vec3 specular_color = specular_strength * light_color;
|
vec3 specular_color = specular_strength * light_color;
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
layout(location=0) in vec3 a_position;
|
layout(location=0) in vec3 a_position;
|
||||||
layout(location=1) in vec2 a_tex_coords;
|
layout(location=1) in vec2 a_tex_coords;
|
||||||
layout(location=2) in vec3 a_normal;
|
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=0) out vec2 v_tex_coords;
|
||||||
layout(location=1) out vec3 v_normal;
|
layout(location=1) out vec3 v_position;
|
||||||
layout(location=2) out vec3 v_position;
|
layout(location=2) out vec3 v_light_position;
|
||||||
|
layout(location=3) out vec3 v_view_position;
|
||||||
|
|
||||||
layout(set=1, binding=0) uniform Uniforms {
|
layout(set=1, binding=0) uniform Uniforms {
|
||||||
vec3 u_view_position;
|
vec3 u_view_position;
|
||||||
|
@ -17,12 +20,30 @@ layout(set=1, binding=1) buffer Instances {
|
||||||
mat4 s_models[];
|
mat4 s_models[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
layout(set=2, binding=0) uniform Light {
|
||||||
|
vec3 light_position;
|
||||||
|
vec3 light_color;
|
||||||
|
};
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
v_tex_coords = a_tex_coords;
|
v_tex_coords = a_tex_coords;
|
||||||
mat4 model_matrix = s_models[gl_InstanceIndex];
|
mat4 model_matrix = s_models[gl_InstanceIndex];
|
||||||
mat3 normal_matrix = mat3(transpose(inverse(model_matrix))); // TODO: calculate this on CPU and pass in as an uniform
|
mat3 normal_matrix = mat3(transpose(inverse(model_matrix))); // TODO: calculate this on CPU and pass in as an uniform
|
||||||
|
|
||||||
|
vec3 normal = normalize(normal_matrix * a_normal);
|
||||||
|
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||||
|
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||||
|
|
||||||
|
mat3 tangent_matrix = transpose(mat3(
|
||||||
|
tangent,
|
||||||
|
bitangent,
|
||||||
|
normal
|
||||||
|
));
|
||||||
|
|
||||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||||
v_normal = normal_matrix * a_normal;
|
|
||||||
v_position = model_space.xyz;
|
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;
|
gl_Position = u_view_proj * model_space;
|
||||||
}
|
}
|
||||||
|
|
BIN
src/snubben.png
BIN
src/snubben.png
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
|
@ -14,16 +14,18 @@ impl Texture {
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
|
is_normal_map: bool,
|
||||||
label: &str,
|
label: &str,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let img = image::load_from_memory(bytes)?;
|
let img = image::load_from_memory(bytes)?;
|
||||||
Self::from_image(device, queue, &img, Some(label))
|
Self::from_image(device, queue, &img, is_normal_map, Some(label))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_image(
|
pub fn from_image(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
img: &image::DynamicImage,
|
img: &image::DynamicImage,
|
||||||
|
is_normal_map: bool,
|
||||||
label: Option<&str>,
|
label: Option<&str>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let rgba = img.to_rgba();
|
let rgba = img.to_rgba();
|
||||||
|
@ -41,7 +43,11 @@ impl Texture {
|
||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: wgpu::TextureDimension::D2,
|
dimension: wgpu::TextureDimension::D2,
|
||||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
format: if is_normal_map {
|
||||||
|
wgpu::TextureFormat::Rgba8Unorm
|
||||||
|
} else {
|
||||||
|
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||||
|
},
|
||||||
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -147,12 +153,13 @@ impl Texture {
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
path: P,
|
path: P,
|
||||||
|
is_normal_map: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// Needed to appease the borrow checker
|
// Needed to appease the borrow checker
|
||||||
let path_copy = path.as_ref().to_path_buf();
|
let path_copy = path.as_ref().to_path_buf();
|
||||||
let label = path_copy.to_str();
|
let label = path_copy.to_str();
|
||||||
|
|
||||||
let img = image::open(path)?;
|
let img = image::open(path)?;
|
||||||
Self::from_image(device, queue, &img, label)
|
Self::from_image(device, queue, &img, is_normal_map, label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue