updated intermediate tutorial docs and removing tutorial13 for maintenance

web2
Ben Hansen 2 years ago
parent dbb24ac7ce
commit 1135fead3a

996
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -12,4 +12,6 @@ wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial8_dept
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial9_models code/beginner/tutorial9-models &
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial10_lighting code/intermediate/tutorial10-lighting &
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial11_normals code/intermediate/tutorial11-normals
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial12_camera code/intermediate/tutorial12-camera
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial13_threading code/intermediate/tutorial13-threading
)

@ -1,6 +1,6 @@
use anyhow::*;
use image::GenericImageView;
use std::{num::NonZeroU32, path::Path};
use std::num::NonZeroU32;
pub struct Texture {
pub texture: wgpu::Texture,
@ -11,20 +11,6 @@ pub struct Texture {
impl Texture {
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
pub fn load<P: AsRef<Path>>(
device: &wgpu::Device,
queue: &wgpu::Queue,
path: P,
is_normal_map: bool,
) -> Result<Self> {
// 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, is_normal_map)
}
pub fn create_depth_texture(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,

@ -20,6 +20,7 @@ tobj = { version = "3.2", features = ["async"]}
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko"}
winit = "0.26"
instant = "0.1"
async-std = "1"
[dependencies.image]
version = "0.24"

@ -1,5 +1,5 @@
use tutorial13_threading::run;
fn main() {
pollster::block_on(run());
async_std::task::block_on(run());
}

@ -2,6 +2,8 @@ use std::io::{BufReader, Cursor};
use cfg_if::cfg_if;
use wgpu::util::DeviceExt;
#[cfg(not(target_arch="wasm32"))]
use rayon::iter::IntoParallelIterator;
use crate::{model, texture};

@ -1,6 +1,6 @@
use anyhow::*;
use image::GenericImageView;
use std::{num::NonZeroU32, path::Path};
use std::num::NonZeroU32;
pub struct Texture {
pub texture: wgpu::Texture,

@ -39,7 +39,7 @@ module.exports = {
'/intermediate/tutorial10-lighting/',
'/intermediate/tutorial11-normals/',
'/intermediate/tutorial12-camera/',
'/intermediate/tutorial13-threading/',
// '/intermediate/tutorial13-threading/',
],
},
{

@ -1,5 +1,21 @@
# Working with Lights
<div class="warn">
The shaders used in this example don't compile on WASM using version 0.12.0 of wgpu. They are working on the "gecko" branch, so to get the code working for WASM, change the wgpu entries in Cargo.toml to be the following.
```toml
[dependencies]
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko"}
[target.'cfg(target_arch = "wasm32")'.dependencies]
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko", features = ["webgl"]}
```
Once 0.13 comes out I'll revert to using the version published on crates.io.
</div>
While we can tell that our scene is 3d because of our camera, it still feels very flat. That's because our model stays the same color regardless of how it's oriented. If we want to change that we need to add lighting to our scene.
In the real world, a light source emits photons which bounce around until they enter into our eyes. The color we see is the light's original color minus whatever energy it lost while it was bouncing around.

@ -1,5 +1,21 @@
# Normal Mapping
<div class="warn">
The shaders used in this example don't compile on WASM using version 0.12.0 of wgpu. They are working on the "gecko" branch, so to get the code working for WASM, change the wgpu entries in Cargo.toml to be the following.
```toml
[dependencies]
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko"}
[target.'cfg(target_arch = "wasm32")'.dependencies]
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko", features = ["webgl"]}
```
Once 0.13 comes out I'll revert to using the version published on crates.io.
</div>
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 be would hard to know where to add 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.

@ -1,5 +1,21 @@
# A Better Camera
<div class="warn">
The shaders used in this example don't compile on WASM using version 0.12.0 of wgpu. They are working on the "gecko" branch, so to get the code working for WASM, change the wgpu entries in Cargo.toml to be the following.
```toml
[dependencies]
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko"}
[target.'cfg(target_arch = "wasm32")'.dependencies]
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko", features = ["webgl"]}
```
Once 0.13 comes out I'll revert to using the version published on crates.io.
</div>
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`.
@ -8,7 +24,7 @@ I've been putting this off for a while. Implementing a camera isn't specifically
use cgmath::*;
use winit::event::*;
use winit::dpi::PhysicalPosition;
use std::time::Duration;
use instant::Duration;
use std::f32::consts::FRAC_PI_2;
#[rustfmt::skip]
@ -22,6 +38,16 @@ pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001;
```
<div class="note">
`std::time::Instant` panics on WASM, so we'll use the [instant crate](https://docs.rs/instant). You'll want to include it in your `Cargo.toml`:
```toml
instant = "0.1"
```
</div>
## 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.
@ -317,42 +343,41 @@ fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
}
```
`input()` will need to be updated as well. Up to this point we have been using `WindowEvent`s for our camera controls. While this works, it's not the best solution. The [winit docs](https://docs.rs/winit/0.24.0/winit/event/enum.WindowEvent.html?search=#variant.CursorMoved) inform us that OS will often transform the data for the `CursorMoved` event to allow effects such as cursor acceleration. Because of this, we're going to change our `input()` function to use `DeviceEvent` instead of `WindowEvent`.
`input()` will need to be updated as well. Up to this point we have been using `WindowEvent`s for our camera controls. While this works, it's not the best solution. The [winit docs](https://docs.rs/winit/0.24.0/winit/event/enum.WindowEvent.html?search=#variant.CursorMoved) inform us that OS will often transform the data for the `CursorMoved` event to allow effects such as cursor acceleration.
Now to fix this we could change the `input()` function to process `DeviceEvent` instead of `WindowEvent`, but keyboard and button presses don't get emitted as `DeviceEvent`s on MacOS and WASM. Instead, we'll just remove the `CursorMoved` check in `input()`, and a manual call to `camera_controller.process_mouse()` in the `run()` function.
```rust
// UPDATED!
fn input(&mut self, event: &DeviceEvent) -> bool {
fn input(&mut self, event: &WindowEvent) -> bool {
match event {
DeviceEvent::Key(
KeyboardInput {
virtual_keycode: Some(key),
state,
..
}
) => self.camera_controller.process_keyboard(*key, *state),
DeviceEvent::MouseWheel { delta, .. } => {
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
}
DeviceEvent::Button {
button: 1, // Left Mouse Button
WindowEvent::MouseInput {
button: MouseButton::Left,
state,
..
} => {
self.mouse_pressed = *state == ElementState::Pressed;
true
}
DeviceEvent::MouseMotion { delta } => {
if self.mouse_pressed {
self.camera_controller.process_mouse(delta.0, delta.1);
}
true
}
_ => false,
}
}
```
This change means will have to modify the event loop in `main()` as well.
Here are the changes to `run()`:
```rust
fn main() {
@ -363,17 +388,18 @@ fn main() {
// ...
// NEW!
Event::DeviceEvent {
ref event,
event: DeviceEvent::MouseMotion{ delta, },
.. // We're not using device_id currently
} => {
state.input(event);
} => if state.mouse_pressed {
state.camera_controller.process_mouse(delta.0, delta.1)
}
// UPDATED!
Event::WindowEvent {
ref event,
window_id,
} if window_id == window.id() => {
} if window_id == window.id() && !state.input(event) => {
match event {
#[cfg(not(target_arch="wasm32"))]
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
input:
@ -402,7 +428,7 @@ fn main() {
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) {
fn update(&mut self, dt: instant::Duration) {
// UPDATED!
self.camera_controller.update_camera(&mut self.camera, dt);
self.camera_uniform.update_view_proj(&self.camera, &self.projection);
@ -425,14 +451,14 @@ We still need to calculate `dt`. Let's do that in the `main` function.
fn main() {
// ...
let mut state = State::new(&window).await;
let mut last_render_time = std::time::Instant::now(); // NEW!
let mut last_render_time = instant::Instant::now(); // NEW!
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;
match event {
// ...
// UPDATED!
Event::RedrawRequested(window_id) if window_id == window.id() => {
let now = std::time::Instant::now();
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
@ -448,4 +474,6 @@ With that we should be able to move our camera wherever we want.
![./screenshot.png](./screenshot.png)
<WasmExample example="tutorial12_camera"></WasmExample>
<AutoGithubLink/>

@ -1,5 +1,21 @@
# Multi-threading with Wgpu and Rayon
<div class="warn">
The shaders used in this example don't compile on WASM using version 0.12.0 of wgpu. They are working on the "gecko" branch, so to get the code working for WASM, change the wgpu entries in Cargo.toml to be the following.
```toml
[dependencies]
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko"}
[target.'cfg(target_arch = "wasm32")'.dependencies]
wgpu = { version = "0.12", git="https://github.com/gfx-rs/wgpu", branch="gecko", features = ["webgl"]}
```
Once 0.13 comes out I'll revert to using the version published on crates.io.
</div>
The main selling point of Vulkan, DirectX 12, Metal, and by extension Wgpu is that these APIs is that they designed from the ground up to be thread safe. Up to this point we have been doing everything on a single thread. That's about to change.
<div class="note">
@ -15,7 +31,10 @@ We won't go over multithreading rendering as we don't have enough different type
Currently we load the materials and meshes of our model one at a time. This is a perfect opportunity for multithreading! All our changes will be in `model.rs`. Let's first start with the materials. We'll convert the regular for loop into a `par_iter().map()`.
```rust
// model.rs
// resources.rs
#[cfg(not(target_arch="wasm32"))]
use rayon::iter::IntoParallelIterator;
impl Model {
pub fn load<P: AsRef<Path>>(
@ -128,4 +147,6 @@ Elapsed (Threaded): 199.645027ms
We're not loading that many resources, so the speed up is minimal. We'll be doing more stuff with threading, but this is a good introduction.
<WasmExample example="tutorial12_camera"></WasmExample>
<AutoGithubLink/>
Loading…
Cancel
Save