testing some things

web2
Ben Hansen 2 years ago
parent b8b8444030
commit c269f46a0e

@ -1,32 +0,0 @@
matrix:
include:
- language: rust
addons:
apt:
packages:
- libasound2-dev
rust:
- stable
# - beta
# - nightly
jobs:
# allow_failures:
# - rust: nightly
fast_finish: true
scripts:
- cargo build --verbose
- language: node_js
node_js:
- lts/*
install:
- npm ci
script:
- npm run build
deploy:
provider: pages
skip_cleanup: true
local_dir: docs/.vuepress/dist
github_token: $GITHUB_TOKEN # A token generated on GitHub allowing Travis to push code on you repository. Set in the Travis settings page of your repository, as a secure variable
keep_history: true
on:
branch: master

28
Cargo.lock generated

@ -2870,6 +2870,7 @@ dependencies = [
"image 0.24.0", "image 0.24.0",
"log", "log",
"pollster", "pollster",
"reqwest",
"tobj 3.2.0", "tobj 3.2.0",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -2884,14 +2885,21 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytemuck", "bytemuck",
"cfg-if 1.0.0",
"cgmath", "cgmath",
"console_error_panic_hook",
"console_log",
"env_logger", "env_logger",
"fs_extra", "fs_extra",
"glob", "glob",
"image 0.23.14", "image 0.24.0",
"log", "log",
"pollster", "pollster",
"reqwest",
"tobj 3.2.0", "tobj 3.2.0",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"wgpu", "wgpu",
"winit", "winit",
] ]
@ -2902,14 +2910,21 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytemuck", "bytemuck",
"cfg-if 1.0.0",
"cgmath", "cgmath",
"console_error_panic_hook",
"console_log",
"env_logger", "env_logger",
"fs_extra", "fs_extra",
"glob", "glob",
"image 0.23.14", "image 0.24.0",
"log", "log",
"pollster", "pollster",
"reqwest",
"tobj 3.2.0", "tobj 3.2.0",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"wgpu", "wgpu",
"winit", "winit",
] ]
@ -2920,15 +2935,22 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytemuck", "bytemuck",
"cfg-if 1.0.0",
"cgmath", "cgmath",
"console_error_panic_hook",
"console_log",
"env_logger", "env_logger",
"fs_extra", "fs_extra",
"glob", "glob",
"image 0.23.14", "image 0.24.0",
"log", "log",
"pollster", "pollster",
"rayon", "rayon",
"reqwest",
"tobj 3.2.0", "tobj 3.2.0",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"wgpu", "wgpu",
"winit", "winit",
] ]

@ -1,4 +1,4 @@
(trap 'kill 0' SIGINT; (trap 'killall background' INT;
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/pong code/showcase/pong & wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/pong code/showcase/pong &
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial1_window code/beginner/tutorial1-window & wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial1_window code/beginner/tutorial1-window &
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial2_surface code/beginner/tutorial2-surface & wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial2_surface code/beginner/tutorial2-surface &
@ -8,5 +8,7 @@ wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial5_text
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial6_uniforms code/beginner/tutorial6-uniforms & wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial6_uniforms code/beginner/tutorial6-uniforms &
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial7_instancing code/beginner/tutorial7-instancing & wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial7_instancing code/beginner/tutorial7-instancing &
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial8_depth code/beginner/tutorial8-depth & wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial8_depth code/beginner/tutorial8-depth &
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/tutorial9_models code/beginner/tutorial9-models 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
) )

@ -8,7 +8,7 @@ use winit::{
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[cfg_attr(target_arch="wasm32", wasm_bindgen(start))] #[cfg_attr(target_arch="wasm32", wasm_bindgen(start))]
pub async fn run() { pub fn run() {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); std::panic::set_hook(Box::new(console_error_panic_hook::hook));

@ -23,7 +23,7 @@ impl State {
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::Backends::all()); let instance = wgpu::Instance::new(wgpu::Backends::GL);
let surface = unsafe { instance.create_surface(window) }; let surface = unsafe { instance.create_surface(window) };
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {

@ -596,7 +596,6 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
log::warn!("Creating State");
let mut state = State::new(&window).await; let mut state = State::new(&window).await;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {

@ -4,6 +4,9 @@ version = "0.1.0"
authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"] authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"]
edition = "2018" edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
cfg-if = "1" cfg-if = "1"
anyhow = "1.0" anyhow = "1.0"
@ -22,6 +25,7 @@ default-features = false
features = ["png", "jpeg"] features = ["png", "jpeg"]
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
reqwest = { version = "0.11" }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "0.2" console_log = "0.2"
wgpu = { version = "0.12", features = ["webgl"]} wgpu = { version = "0.12", features = ["webgl"]}

@ -14,7 +14,7 @@
</div> </div>
<script type="module"> <script type="module">
import init from "./pkg/tutorial9_models.js"; import init from "./pkg/tutorial10_lighting.js";
init().then(() => { init().then(() => {
console.log("WASM Loaded"); console.log("WASM Loaded");
}); });

@ -9,4 +9,4 @@ class CORSRequestHandler(SimpleHTTPRequestHandler):
SimpleHTTPRequestHandler.end_headers(self) SimpleHTTPRequestHandler.end_headers(self)
if __name__ == '__main__': if __name__ == '__main__':
test(CORSRequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000) test(CORSRequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8080)

@ -695,15 +695,47 @@ impl State {
} }
} }
#[cfg_attr(target_arch="wasm32", wasm_bindgen(start))]
pub async fn run() { pub async fn run() {
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Warn).expect("Could't initialize logger");
} else {
env_logger::init(); env_logger::init();
}
}
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
#[cfg(target_arch = "wasm32")]
{
// Winit prevents sizing with CSS, so we have to set
// the size manually when on web.
use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys;
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas());
dst.append_child(&canvas).ok()?;
Some(())
})
.expect("Couldn't append canvas to document body.");
}
// State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(&window).await; let mut state = State::new(&window).await;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll; *control_flow = ControlFlow::Poll;
match event { match event {

@ -1,8 +0,0 @@
#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);
}

@ -1,27 +0,0 @@
#version 450
layout(location=0) in vec3 a_position;
layout(location=0) out vec3 v_color;
layout(set=0, binding=0)
uniform Camera {
vec3 u_view_position; // unused
mat4 u_view_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_view_proj * vec4(v_position, 1);
v_color = u_color;
}

@ -59,7 +59,6 @@ pub async fn load_model(
queue: &wgpu::Queue, queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout, layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Model> { ) -> anyhow::Result<model::Model> {
log::warn!("Loading obj text");
let obj_text = load_string(file_name).await?; let obj_text = load_string(file_name).await?;
let obj_cursor = Cursor::new(obj_text); let obj_cursor = Cursor::new(obj_text);
let mut obj_reader = BufReader::new(obj_cursor); let mut obj_reader = BufReader::new(obj_cursor);

@ -1,47 +0,0 @@
#version 450
layout(location=0) in vec2 v_tex_coords;
layout(location=1) in vec3 v_normal;
layout(location=2) in vec3 v_position;
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=1, binding=0)
uniform Camera {
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);
// We don't need (or want) much ambient light, so 0.1 is fine
float ambient_strength = 0.1;
vec3 ambient_color = light_color * ambient_strength;
vec3 normal = normalize(v_normal);
vec3 light_dir = normalize(light_position - v_position);
float diffuse_strength = max(dot(normal, light_dir), 0.0);
vec3 diffuse_color = light_color * diffuse_strength;
vec3 view_dir = normalize(u_view_position - v_position);
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;
// Since lights don't typically (afaik) cast transparency, so we use
// the alpha here at the end.
f_color = vec4(result, object_color.a);
}

@ -1,37 +0,0 @@
#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=0) out vec2 v_tex_coords;
layout(location=1) out vec3 v_normal;
layout(location=2) out vec3 v_position;
layout(set=1, binding=0)
uniform Camera {
vec3 u_view_position; // unused
mat4 u_view_proj;
};
layout(location=5) in vec4 model_matrix_0;
layout(location=6) in vec4 model_matrix_1;
layout(location=7) in vec4 model_matrix_2;
layout(location=8) in vec4 model_matrix_3;
void main() {
mat4 model_matrix = mat4(
model_matrix_0,
model_matrix_1,
model_matrix_2,
model_matrix_3
);
v_tex_coords = a_tex_coords;
mat3 normal_matrix = mat3(transpose(inverse(model_matrix)));
v_normal = normal_matrix * a_normal;
vec4 model_space = model_matrix * vec4(a_position, 1.0);
v_position = model_space.xyz;
gl_Position = u_view_proj * model_space;
}

@ -4,18 +4,39 @@ version = "0.1.0"
authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"] authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"]
edition = "2018" edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
cfg-if = "1"
anyhow = "1.0" anyhow = "1.0"
bytemuck = { version = "1.4", features = [ "derive" ] } bytemuck = { version = "1.4", features = [ "derive" ] }
cgmath = "0.18" cgmath = "0.18"
env_logger = "0.9" env_logger = "0.9"
pollster = "0.2" pollster = "0.2"
image = "0.23"
log = "0.4" log = "0.4"
tobj = "3.0" tobj = { version = "3.2", features = ["async"]}
wgpu = "0.12" wgpu = "0.12"
winit = "0.26" winit = "0.26"
[dependencies.image]
version = "0.24"
default-features = false
features = ["png", "jpeg"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
reqwest = { version = "0.11" }
console_error_panic_hook = "0.1"
console_log = "0.2"
wgpu = { version = "0.12", features = ["webgl"]}
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [
"Document",
"Window",
"Element",
]}
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"
fs_extra = "1.2" fs_extra = "1.2"

@ -8,7 +8,11 @@ use winit::{
window::Window, window::Window,
}; };
#[cfg(target_arch="wasm32")]
use wasm_bindgen::prelude::*;
mod model; mod model;
mod resources;
mod texture; mod texture;
use model::{DrawLight, DrawModel, Vertex}; use model::{DrawLight, DrawModel, Vertex};
@ -493,14 +497,12 @@ impl State {
label: Some("camera_bind_group"), label: Some("camera_bind_group"),
}); });
let res_dir = std::path::Path::new(env!("OUT_DIR")).join("res"); let obj_model = resources::load_model(
let obj_model = model::Model::load( "cube.obj",
&device, &device,
&queue, &queue,
&texture_bind_group_layout, &texture_bind_group_layout,
res_dir.join("cube.obj"), ).await.unwrap();
)
.unwrap();
let light_uniform = LightUniform { let light_uniform = LightUniform {
position: [2.0, 2.0, 2.0], position: [2.0, 2.0, 2.0],
@ -742,15 +744,47 @@ impl State {
} }
} }
#[cfg_attr(target_arch="wasm32", wasm_bindgen(start))]
pub async fn run() { pub async fn run() {
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Warn).expect("Could't initialize logger");
} else {
env_logger::init(); env_logger::init();
}
}
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
#[cfg(target_arch = "wasm32")]
{
// Winit prevents sizing with CSS, so we have to set
// the size manually when on web.
use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys;
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas());
dst.append_child(&canvas).ok()?;
Some(())
})
.expect("Couldn't append canvas to document body.");
}
// State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(&window).await; let mut state = State::new(&window).await;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll; *control_flow = ControlFlow::Poll;
match event { match event {

@ -1,9 +1,4 @@
use anyhow::*;
use cgmath::InnerSpace;
use std::ops::Range; use std::ops::Range;
use std::path::Path;
use tobj::LoadOptions;
use wgpu::util::DeviceExt;
use crate::texture; use crate::texture;
@ -14,11 +9,11 @@ pub trait Vertex {
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ModelVertex { pub struct ModelVertex {
position: [f32; 3], pub position: [f32; 3],
tex_coords: [f32; 2], pub tex_coords: [f32; 2],
normal: [f32; 3], pub normal: [f32; 3],
tangent: [f32; 3], pub tangent: [f32; 3],
bitangent: [f32; 3], pub bitangent: [f32; 3],
} }
impl Vertex for ModelVertex { impl Vertex for ModelVertex {
@ -119,164 +114,6 @@ pub struct Model {
pub materials: Vec<Material>, pub materials: Vec<Material>,
} }
impl Model {
pub fn load<P: AsRef<Path>>(
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
path: P,
) -> Result<Self> {
let (obj_models, obj_materials) = tobj::load_obj(
path.as_ref(),
&LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
},
)?;
let obj_materials = obj_materials?;
// We're assuming that the texture files are stored with the obj file
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 =
texture::Texture::load(device, queue, containing_folder.join(diffuse_path), false)?;
let normal_path = mat.normal_texture;
let normal_texture =
texture::Texture::load(device, queue, containing_folder.join(normal_path), true)?;
materials.push(Material::new(
device,
&mat.name,
diffuse_texture,
normal_texture,
layout,
));
}
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;
let mut triangles_included = (0..vertices.len()).collect::<Vec<_>>();
// 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 = vertices[c[0] as usize];
let v1 = vertices[c[1] as usize];
let v2 = 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 = 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 + 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 = 1.0 / n as f32;
let mut v = &mut vertices[i];
v.tangent = (cgmath::Vector3::from(v.tangent) * denom)
.normalize()
.into();
v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom)
.normalize()
.into();
}
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::BufferUsages::VERTEX,
});
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::BufferUsages::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 })
}
}
pub trait DrawModel<'a> { pub trait DrawModel<'a> {
fn draw_mesh( fn draw_mesh(
&mut self, &mut self,

@ -0,0 +1,205 @@
use std::io::{BufReader, Cursor};
use cfg_if::cfg_if;
use wgpu::util::DeviceExt;
use crate::{model, texture};
pub async fn load_string(file_name: &str) -> anyhow::Result<String> {
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
let url = format!("http://127.0.0.1:8080/learn-wgpu/{}", file_name);
let txt = reqwest::get(&url)
.await?
.text()
.await?;
} else {
let path = std::path::Path::new(env!("OUT_DIR"))
.join("res")
.join(file_name);
let txt = std::fs::read_to_string(path)?;
}
}
Ok(txt)
}
pub async fn load_binary(file_name: &str) -> anyhow::Result<Vec<u8>> {
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
let url = format!("http://127.0.0.1:8080/learn-wgpu/{}", file_name);
let data = reqwest::get(url)
.await?
.bytes()
.await?
.to_vec();
} else {
let path = std::path::Path::new(env!("OUT_DIR"))
.join("res")
.join(file_name);
let data = std::fs::read(path)?;
}
}
Ok(data)
}
pub async fn load_texture(
file_name: &str,
is_normal_map: bool,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> anyhow::Result<texture::Texture> {
let data = load_binary(file_name).await?;
texture::Texture::from_bytes(device, queue, &data, file_name, is_normal_map)
}
pub async fn load_model(
file_name: &str,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Model> {
let obj_text = load_string(file_name).await?;
let obj_cursor = Cursor::new(obj_text);
let mut obj_reader = BufReader::new(obj_cursor);
let (models, obj_materials) = tobj::load_obj_buf_async(
&mut obj_reader,
&tobj::LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
},
|p| async move {
let mat_text = load_string(&p).await.unwrap();
tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text)))
},
)
.await?;
let mut materials = Vec::new();
for m in obj_materials? {
let diffuse_texture = load_texture(&m.diffuse_texture, false, device, queue).await?;
let normal_texture = load_texture(&m.normal_texture, true, device, queue).await?;
materials.push(model::Material::new(
device,
&m.name,
diffuse_texture,
normal_texture,
layout,
));
}
let meshes = models
.into_iter()
.map(|m| {
let mut vertices = (0..m.mesh.positions.len() / 3)
.map(|i| model::ModelVertex {
position: [
m.mesh.positions[i * 3],
m.mesh.positions[i * 3 + 1],
m.mesh.positions[i * 3 + 2],
],
tex_coords: [m.mesh.texcoords[i * 2], m.mesh.texcoords[i * 2 + 1]],
normal: [
m.mesh.normals[i * 3],
m.mesh.normals[i * 3 + 1],
m.mesh.normals[i * 3 + 2],
],
// We'll calculate these later
tangent: [0.0; 3],
bitangent: [0.0; 3],
})
.collect::<Vec<_>>();
let indices = &m.mesh.indices;
let mut triangles_included = 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 = vertices[c[0] as usize];
let v1 = vertices[c[1] as usize];
let v2 = 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 = 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 + 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 = 1.0 / n as f32;
let mut v = &mut vertices[i];
v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into();
v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into();
}
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Vertex Buffer", file_name)),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Index Buffer", file_name)),
contents: bytemuck::cast_slice(&m.mesh.indices),
usage: wgpu::BufferUsages::INDEX,
});
model::Mesh {
name: file_name.to_string(),
vertex_buffer,
index_buffer,
num_elements: m.mesh.indices.len() as u32,
material: m.mesh.material_id.unwrap_or(0),
}
})
.collect::<Vec<_>>();
Ok(model::Model { meshes, materials })
}

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

@ -4,18 +4,39 @@ version = "0.1.0"
authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"] authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"]
edition = "2018" edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
cfg-if = "1"
anyhow = "1.0" anyhow = "1.0"
bytemuck = { version = "1.4", features = [ "derive" ] } bytemuck = { version = "1.4", features = [ "derive" ] }
cgmath = "0.18" cgmath = "0.18"
env_logger = "0.9" env_logger = "0.9"
pollster = "0.2" pollster = "0.2"
image = "0.23"
log = "0.4" log = "0.4"
tobj = "3.0" tobj = { version = "3.2", features = ["async"]}
wgpu = "0.12" wgpu = "0.12"
winit = "0.26" winit = "0.26"
[dependencies.image]
version = "0.24"
default-features = false
features = ["png", "jpeg"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
reqwest = { version = "0.11" }
console_error_panic_hook = "0.1"
console_log = "0.2"
wgpu = { version = "0.12", features = ["webgl"]}
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [
"Document",
"Window",
"Element",
]}
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"
fs_extra = "1.2" fs_extra = "1.2"

@ -8,6 +8,9 @@ use winit::{
window::Window, window::Window,
}; };
#[cfg(target_arch="wasm32")]
use wasm_bindgen::prelude::*;
mod camera; mod camera;
mod model; mod model;
mod texture; // NEW! mod texture; // NEW!

@ -4,19 +4,40 @@ version = "0.1.0"
authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"] authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"]
edition = "2018" edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
cfg-if = "1"
anyhow = "1.0" anyhow = "1.0"
bytemuck = { version = "1.4", features = [ "derive" ] } bytemuck = { version = "1.4", features = [ "derive" ] }
cgmath = "0.18" cgmath = "0.18"
env_logger = "0.9" env_logger = "0.9"
pollster = "0.2" pollster = "0.2"
image = "0.23"
log = "0.4" log = "0.4"
rayon = "1.4" # NEW! rayon = "1.4" # NEW!
tobj = "3.0" tobj = { version = "3.2", features = ["async"]}
wgpu = "0.12" wgpu = "0.12"
winit = "0.26" winit = "0.26"
[dependencies.image]
version = "0.24"
default-features = false
features = ["png", "jpeg"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
reqwest = { version = "0.11" }
console_error_panic_hook = "0.1"
console_log = "0.2"
wgpu = { version = "0.12", features = ["webgl"]}
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [
"Document",
"Window",
"Element",
]}
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"
fs_extra = "1.2" fs_extra = "1.2"

@ -8,6 +8,9 @@ use winit::{
window::Window, window::Window,
}; };
#[cfg(target_arch="wasm32")]
use wasm_bindgen::prelude::*;
mod camera; mod camera;
mod model; mod model;
mod texture; // NEW! mod texture; // NEW!

@ -1,28 +0,0 @@
#!/usr/bin/env sh
# abort on errors
set -e
# build
npm run build
# navigate into the build output directory
cd docs/.vuepress/dist
# if you are deploying to a custom domain
# echo 'www.example.com' > CNAME
git init
git add -A
git commit -m 'deploy'
# if you are deploying to https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master
# if you are deploying to https://<USERNAME>.github.io/<REPO>
git push -f git@github.com:sotrh/learn-wgpu.git master:gh-pages
# cleanup
rm -rf ./.git
cd -

@ -195,6 +195,11 @@ I'm using [reqwest](https://docs.rs/reqwest) to handle loading the requests when
reqwest = { version = "0.11" } reqwest = { version = "0.11" }
``` ```
Make sure to add `resources` as a module in `lib.rs`:
```rust
mod resources;
```
## Loading models with TOBJ ## Loading models with TOBJ

@ -805,7 +805,7 @@ Don't forget to update the `Camera` struct in `light.wgsl` as well, as if it doe
We're going to need to update the `CameraUniform` struct as well. We're going to need to update the `CameraUniform` struct as well.
```rust ```rust
// main.rs // lib.rs
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CameraUniform { struct CameraUniform {
@ -832,7 +832,7 @@ impl CameraUniform {
Since we want to use our uniforms in the fragment shader now, we need to change it's visibility. Since we want to use our uniforms in the fragment shader now, we need to change it's visibility.
```rust ```rust
// main.rs // lib.rs
let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
wgpu::BindGroupLayoutBinding { wgpu::BindGroupLayoutBinding {
@ -891,4 +891,6 @@ It's hard to tell the difference, but here's the results.
![./half_dir.png](./half_dir.png) ![./half_dir.png](./half_dir.png)
<WasmExample example="tutorial10_lighting"></WasmExample>
<AutoGithubLink/> <AutoGithubLink/>

@ -627,4 +627,6 @@ That gives us something like this.
You can find the textures I use in the Github Repository. You can find the textures I use in the Github Repository.
<WasmExample example="tutorial11_normals"></WasmExample>
<AutoGithubLink/> <AutoGithubLink/>

Loading…
Cancel
Save