migrated tutorial9

pull/90/head
Ben Hansen 4 years ago
parent cfd36577bb
commit 02921f7ffa

@ -22,7 +22,7 @@ impl Texture {
let label = path_copy.to_str();
let img = image::open(path)?;
Self::from_image(device, queue, img, label)
Self::from_image(device, queue, &img, label)
}
pub fn create_depth_texture(device: &wgpu::Device, sc_desc: &wgpu::SwapChainDescriptor, label: &str) -> Self {
@ -69,17 +69,17 @@ impl Texture {
label: &str
) -> Result<Self> {
let img = image::load_from_memory(bytes)?;
Self::from_image(device, queue, img, Some(label))
Self::from_image(device, queue, &img, Some(label))
}
pub fn from_image(
device: &wgpu::Device,
queue: &wgpu::Queue,
img: image::DynamicImage,
img: &image::DynamicImage,
label: Option<&str>
) -> Result<Self> {
let dimensions = img.dimensions();
let rgba = img.into_rgba();
let rgba = img.to_rgba();
let size = wgpu::Extent3d {
width: dimensions.0,

@ -60,33 +60,31 @@ let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescrip
});
```
With all that in place we need a model to render. If you have one already that's great, but I've supplied a [zip file](https://github.com/sotrh/learn-wgpu/blob/master/code/beginner/tutorial9-models/src/res/cube.zip) with the model and all of it's textures. We're going to put this model in a new `res` folder.
With all that in place we need a model to render. If you have one already that's great, but I've supplied a [zip file](https://github.com/sotrh/learn-wgpu/blob/master/code/beginner/tutorial9-models/src/res/cube.zip) with the model and all of it's textures. We're going to put this model in a new `res` folder next to the existing `src` folder.
Speaking of textures, let's add a `load()` method to `Texture` in `texture.rs`.
## Accessing files in the res folder
```rust
use std::path::Path;
When cargo builds and runs our program it sets what's known as the current working directory. This directory is usually the folder containing your projects root `Cargo.toml`. The path to our res folder may differ depending on the structure of the project. In the `res` folder for the example code for this section tutorial is at `code/beginner/tutorial9-models/res/`. When loading our model we could use this path, and just append `cube.obj`, but our program will break if we try to run the executable generated by cargo directly. This is because the running executable is in charge of setting the current working directory.
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)
}
We're going to fix that by modifying our build script to copy our `res` folder to where cargo creates our executable, and we'll reference it from there. Add the following lines to `build.rs` after you compile the shaders.
```rust
// This tells cargo to rerun this script if something in /res/ changes.
println!("cargo:rerun-if-changed=res/*");
let out_dir = env::var("OUT_DIR")?;
let mut copy_options = CopyOptions::new();
copy_options.overwrite = true;
let mut paths_to_copy = Vec::new();
paths_to_copy.push("res/");
copy_items(&paths_to_copy, out_dir, &copy_options)?;
```
The `load` method will be useful when we load the textures for our models, as `include_bytes!` requires that we know the name of the file at compile time which we can't really guarantee with model textures.
<div class="note">
While we're at it let's import `texture.rs` in `model.rs`.
The `OUT_DIR` is an environment variable that cargo uses to specify where our application will be built.
```rust
use crate::texture;
```
</div>
## Loading models with TOBJ
@ -120,6 +118,39 @@ pub struct Mesh {
The `Material` is pretty simple, it's just the name and one texture. Our cube obj actually has 2 textures, but one is a normal map, and we'll get to those [later](../../intermediate/normal-mapping). The name is more for debugging purposes.
Speaking of textures, we'll need to add a `load()` method to `Texture` in `texture.rs`.
```rust
use std::path::Path;
pub fn load<P: AsRef<Path>>(
device: &wgpu::Device,
queue: &wgpu::Queue,
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, queue, &img, label)
}
```
The `load` method will be useful when we load the textures for our models, as `include_bytes!` requires that we know the name of the file at compile time which we can't really guarantee with model textures.
While we're at it let's import `texture.rs` in `model.rs`.
```rust
use crate::texture;
```
We also need to make a subtle change on `from_image()` method in `texture.rs`. PNGs work fine with `as_rgba8()`, as they have an alpha channel. But, JPEGs don't have an alpha channel, and the code would panic if we try to call `as_rgba8()` on the JPEG texture image we are going to use. Instead, we can use `to_rgba()` to handle such an image.
```rust
let rgba = img.to_rgba();
```
`Mesh` holds a vertex buffer, an index buffer, and the number of indices in the mesh. We're using an `usize` for the material. This `usize` will be used to index the `materials` list when it comes time to draw.
With all that out of the way, we can get to loading our model.
@ -128,31 +159,29 @@ With all that out of the way, we can get to loading our model.
impl Model {
pub fn load<P: AsRef<Path>>(
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
path: P,
) -> Result<(Self, Vec<wgpu::CommandBuffer>), failure::Error> {
let (obj_models, obj_materials) = tobj::load_obj(path.as_ref())?;
) -> Result<Self> {
let (obj_models, obj_materials) = tobj::load_obj(path.as_ref(), true)?;
// 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 containing_folder = path.as_ref().parent()
.context("Directory has no parent")?;
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))?;
let diffuse_texture = texture::Texture::load(device, queue, containing_folder.join(diffuse_path))?;
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout,
bindings: &[
wgpu::Binding {
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::Binding {
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
},
@ -163,8 +192,8 @@ impl Model {
materials.push(Material {
name: mat.name,
diffuse_texture,
bind_group,
});
command_buffers.push(cmds);
}
let mut meshes = Vec::new();
@ -186,13 +215,19 @@ impl Model {
});
}
let vertex_buffer = device.create_buffer_with_data(
bytemuck::cast_slice(&vertices),
wgpu::BufferUsage::VERTEX,
let vertex_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Vertex Buffer", path.as_ref())),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsage::VERTEX,
}
);
let index_buffer = device.create_buffer_with_data(
bytemuck::cast_slice(&m.mesh.indices),
wgpu::BufferUsage::INDEX,
let index_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Index Buffer", path.as_ref())),
contents: bytemuck::cast_slice(&m.mesh.indices),
usage: wgpu::BufferUsage::INDEX,
}
);
meshes.push(Mesh {
@ -204,7 +239,7 @@ impl Model {
});
}
Ok((Self { meshes, materials }, command_buffers))
Ok(Self { meshes, materials })
}
}
```
@ -250,8 +285,8 @@ where
mesh: &'b Mesh,
instances: Range<u32>,
){
self.set_vertex_buffer(0, &mesh.vertex_buffer, 0, 0);
self.set_index_buffer(&mesh.index_buffer, 0, 0);
self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
self.set_index_buffer(mesh.index_buffer.slice(..));
self.draw_indexed(0..mesh.num_elements, 0, instances);
}
}
@ -272,14 +307,17 @@ render_pass.draw_mesh_instanced(&self.obj_model.meshes[0], 0..self.instances.len
Before that though we need to actually load the model and save it to `State`. Put the following in `State::new()`.
```rust
let (obj_model, cmds) = model::Model::load(
let res_dir = std::path::Path::new(env!("OUT_DIR")).join("res");
let obj_model = model::Model::load(
&device,
&queue,
&texture_bind_group_layout,
"code/beginner/tutorial9-models/src/res/cube.obj",
res_dir.join("cube.obj"),
).unwrap();
```
The path to the obj will be different for you, so keep that in mind.
Our new model is a bit bigger than our previous one so we're gonna need to adjust the spacing on our instances a bit.
@ -310,6 +348,7 @@ With all that done, you should get something like this.
![cubes.png](./cubes.png)
## Using the correct textures
If you look at the texture files for our obj, you'll see that they don't match up to our obj. The texture we want to see is this one,
![cube-diffuse.jpg](./cube-diffuse.jpg)
@ -359,8 +398,8 @@ where
instances: Range<u32>,
uniforms: &'b wgpu::BindGroup,
) {
self.set_vertex_buffer(0, &mesh.vertex_buffer, 0, 0);
self.set_index_buffer(&mesh.index_buffer, 0, 0);
self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
self.set_index_buffer(mesh.index_buffer.slice(..));
self.set_bind_group(0, &material.bind_group, &[]);
self.set_bind_group(1, &uniforms, &[]);
self.draw_indexed(0..mesh.num_elements, 0, instances);
@ -378,12 +417,6 @@ let material = &self.obj_model.materials[mesh.material];
render_pass.draw_mesh_instanced(mesh, material, 0..self.instances.len() as u32, &self.uniform_bind_group);
```
We also need to make a subtle change on `from_image()` method in `texture.rs`. PNGs work fine with `as_rgba8()`, as they have an alpha channel. But, JPEGs don't have an alpha channel, and the code would panic if we try to call `as_rgba8()` on the JPEG texture image we are going to use. Instead, we can use `to_rgba()` to handle such an image.
```rust
let rgba = img.to_rgba();
```
With all that in place we should get the following.
![cubes-correct.png](./cubes-correct.png)

Loading…
Cancel
Save