Migrate to tutorial 13 / threading
This commit is contained in:
parent
fe8a47d14d
commit
6f68444dc7
43 changed files with 4085 additions and 1163 deletions
1040
Cargo.lock
generated
1040
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
77
Cargo.toml
77
Cargo.toml
|
@ -1,30 +1,47 @@
|
|||
[package]
|
||||
name = "tuto1"
|
||||
version = "0.1.0"
|
||||
authors = ["Florian RICHER <florian.richer@unova.fr>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.17"
|
||||
image = { version = "0.24.2", features = [ "png", "jpeg" ], default-features = false }
|
||||
winit = "0.26.1"
|
||||
cgmath = "0.18"
|
||||
wgpu = "0.12.0"
|
||||
bytemuck = { version = "1.9.1", features = [ "derive" ] }
|
||||
anyhow = "1.0"
|
||||
simplelog = "0.12.0"
|
||||
pollster = "0.2"
|
||||
|
||||
# [target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# console_error_panic_hook = "0.1.6"
|
||||
# console_log = "0.2.0"
|
||||
# wgpu = { version = "0.12", features = ["webgl"]}
|
||||
# wasm-bindgen = "0.2"
|
||||
# wasm-bindgen-futures = "0.4"
|
||||
# web-sys = { version = "0.3", features = [
|
||||
# "Document",
|
||||
# "Window",
|
||||
# "Element",
|
||||
# ]}
|
||||
[package]
|
||||
name = "tuto1"
|
||||
version = "0.1.0"
|
||||
authors = ["Florian RICHER <florian.richer@unova.fr>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
anyhow = "1.0"
|
||||
bytemuck = { version = "1.4", features = [ "derive" ] }
|
||||
cgmath = "0.18"
|
||||
env_logger = "0.9"
|
||||
pollster = "0.2"
|
||||
log = "0.4"
|
||||
rayon = "1.4"
|
||||
tobj = { version = "3.2", features = ["async"]}
|
||||
wgpu = { version = "0.12"}
|
||||
winit = "0.26"
|
||||
instant = "0.1"
|
||||
async-std = "1"
|
||||
|
||||
[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",
|
||||
"Location",
|
||||
]}
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0"
|
||||
fs_extra = "1.2"
|
||||
glob = "0.3"
|
18
build.rs
Normal file
18
build.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use anyhow::*;
|
||||
use fs_extra::copy_items;
|
||||
use fs_extra::dir::CopyOptions;
|
||||
use std::env;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// 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, ©_options)?;
|
||||
|
||||
Ok(())
|
||||
}
|
29
index.html
Normal file
29
index.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tutorial 13 Threading</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wasm-example"></div>
|
||||
<script type="module">
|
||||
import init from "./pkg/tutorial13_threading.js";
|
||||
init().then(() => {
|
||||
console.log("WASM Loaded");
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #444;
|
||||
}
|
||||
canvas {
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
res/cobble-diffuse.png
Normal file
BIN
res/cobble-diffuse.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
BIN
res/cobble-normal.png
Normal file
BIN
res/cobble-normal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 MiB |
BIN
res/cube-diffuse.jpg
Normal file
BIN
res/cube-diffuse.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
res/cube-normal.png
Normal file
BIN
res/cube-normal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
14
res/cube.mtl
Normal file
14
res/cube.mtl
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Blender MTL File: 'cube.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material.001
|
||||
Ns 323.999994
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Bump cube-normal.png
|
||||
map_Kd cube-diffuse.jpg
|
1143
res/cube.obj
Normal file
1143
res/cube.obj
Normal file
File diff suppressed because it is too large
Load diff
BIN
res/cube.zip
Normal file
BIN
res/cube.zip
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
|
@ -1,49 +0,0 @@
|
|||
struct InstanceInput {
|
||||
[[location(5)]] model_matrix_0: vec4<f32>;
|
||||
[[location(6)]] model_matrix_1: vec4<f32>;
|
||||
[[location(7)]] model_matrix_2: vec4<f32>;
|
||||
[[location(8)]] model_matrix_3: vec4<f32>;
|
||||
};
|
||||
|
||||
struct CameraUniform {
|
||||
view_proj: mat4x4<f32>;
|
||||
};
|
||||
[[group(1), binding(0)]]
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
struct VertexInput {
|
||||
[[location(0)]] position: vec3<f32>;
|
||||
[[location(1)]] tex_coords: vec2<f32>;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] clip_position: vec4<f32>;
|
||||
[[location(0)]] tex_coords: vec2<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vs_main(
|
||||
model: VertexInput,
|
||||
instance: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
let model_matrix = mat4x4<f32>(
|
||||
instance.model_matrix_0,
|
||||
instance.model_matrix_1,
|
||||
instance.model_matrix_2,
|
||||
instance.model_matrix_3,
|
||||
);
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.clip_position = camera.view_proj * model_matrix * vec4<f32>(model.position, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
[[group(0), binding(1)]]
|
||||
var s_diffuse: sampler;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
}
|
196
src/camera.rs
Normal file
196
src/camera.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
use cgmath::*;
|
||||
use std::f32::consts::FRAC_PI_2;
|
||||
use std::time::Duration;
|
||||
use winit::dpi::PhysicalPosition;
|
||||
use winit::event::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.5, 0.0,
|
||||
0.0, 0.0, 0.5, 1.0,
|
||||
);
|
||||
|
||||
const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Camera {
|
||||
pub position: Point3<f32>,
|
||||
yaw: Rad<f32>,
|
||||
pitch: Rad<f32>,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn new<V: Into<Point3<f32>>, Y: Into<Rad<f32>>, P: Into<Rad<f32>>>(
|
||||
position: V,
|
||||
yaw: Y,
|
||||
pitch: P,
|
||||
) -> Self {
|
||||
Self {
|
||||
position: position.into(),
|
||||
yaw: yaw.into(),
|
||||
pitch: pitch.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calc_matrix(&self) -> Matrix4<f32> {
|
||||
let (sin_pitch, cos_pitch) = self.pitch.0.sin_cos();
|
||||
let (sin_yaw, cos_yaw) = self.yaw.0.sin_cos();
|
||||
|
||||
Matrix4::look_to_rh(
|
||||
self.position,
|
||||
Vector3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(),
|
||||
Vector3::unit_y(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Projection {
|
||||
aspect: f32,
|
||||
fovy: Rad<f32>,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
}
|
||||
|
||||
impl Projection {
|
||||
pub fn new<F: Into<Rad<f32>>>(width: u32, height: u32, fovy: F, znear: f32, zfar: f32) -> Self {
|
||||
Self {
|
||||
aspect: width as f32 / height as f32,
|
||||
fovy: fovy.into(),
|
||||
znear,
|
||||
zfar,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.aspect = width as f32 / height as f32;
|
||||
}
|
||||
|
||||
pub fn calc_matrix(&self) -> Matrix4<f32> {
|
||||
OPENGL_TO_WGPU_MATRIX * perspective(self.fovy, self.aspect, self.znear, self.zfar)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CameraController {
|
||||
amount_left: f32,
|
||||
amount_right: f32,
|
||||
amount_forward: f32,
|
||||
amount_backward: f32,
|
||||
amount_up: f32,
|
||||
amount_down: f32,
|
||||
rotate_horizontal: f32,
|
||||
rotate_vertical: f32,
|
||||
scroll: f32,
|
||||
speed: f32,
|
||||
sensitivity: f32,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
pub fn new(speed: f32, sensitivity: f32) -> Self {
|
||||
Self {
|
||||
amount_left: 0.0,
|
||||
amount_right: 0.0,
|
||||
amount_forward: 0.0,
|
||||
amount_backward: 0.0,
|
||||
amount_up: 0.0,
|
||||
amount_down: 0.0,
|
||||
rotate_horizontal: 0.0,
|
||||
rotate_vertical: 0.0,
|
||||
scroll: 0.0,
|
||||
speed,
|
||||
sensitivity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool {
|
||||
let amount = if state == ElementState::Pressed {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
match key {
|
||||
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
||||
self.amount_forward = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::S | VirtualKeyCode::Down => {
|
||||
self.amount_backward = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::A | VirtualKeyCode::Left => {
|
||||
self.amount_left = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::D | VirtualKeyCode::Right => {
|
||||
self.amount_right = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::Space => {
|
||||
self.amount_up = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::LShift => {
|
||||
self.amount_down = amount;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
|
||||
self.rotate_horizontal = mouse_dx as f32;
|
||||
self.rotate_vertical = mouse_dy as f32;
|
||||
}
|
||||
|
||||
pub fn process_scroll(&mut self, delta: &MouseScrollDelta) {
|
||||
self.scroll = match delta {
|
||||
// I'm assuming a line is about 100 pixels
|
||||
MouseScrollDelta::LineDelta(_, scroll) => -scroll * 0.5,
|
||||
MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => -*scroll as f32,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn update_camera(&mut self, camera: &mut Camera, dt: Duration) {
|
||||
let dt = dt.as_secs_f32();
|
||||
|
||||
// Move forward/backward and left/right
|
||||
let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos();
|
||||
let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize();
|
||||
let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize();
|
||||
camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt;
|
||||
camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt;
|
||||
|
||||
// Move in/out (aka. "zoom")
|
||||
// Note: this isn't an actual zoom. The camera's position
|
||||
// changes when zooming. I've added this to make it easier
|
||||
// to get closer to an object you want to focus on.
|
||||
let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos();
|
||||
let scrollward =
|
||||
Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize();
|
||||
camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt;
|
||||
self.scroll = 0.0;
|
||||
|
||||
// Move up/down. Since we don't use roll, we can just
|
||||
// modify the y coordinate directly.
|
||||
camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt;
|
||||
|
||||
// Rotate
|
||||
camera.yaw += Rad(self.rotate_horizontal) * self.sensitivity * dt;
|
||||
camera.pitch += Rad(-self.rotate_vertical) * self.sensitivity * dt;
|
||||
|
||||
// If process_mouse isn't called every frame, these values
|
||||
// will not get set to zero, and the camera will rotate
|
||||
// when moving in a non cardinal direction.
|
||||
self.rotate_horizontal = 0.0;
|
||||
self.rotate_vertical = 0.0;
|
||||
|
||||
// Keep the camera's angle from going too high/low.
|
||||
if camera.pitch < -Rad(SAFE_FRAC_PI_2) {
|
||||
camera.pitch = -Rad(SAFE_FRAC_PI_2);
|
||||
} else if camera.pitch > Rad(SAFE_FRAC_PI_2) {
|
||||
camera.pitch = Rad(SAFE_FRAC_PI_2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
use winit::event::WindowEvent;
|
||||
|
||||
pub trait Controllable {
|
||||
fn process_events(&mut self, event: &WindowEvent) -> bool;
|
||||
}
|
750
src/lib.rs
Normal file
750
src/lib.rs
Normal file
|
@ -0,0 +1,750 @@
|
|||
use cgmath::prelude::*;
|
||||
use rayon::prelude::*;
|
||||
use std::iter;
|
||||
use wgpu::util::DeviceExt;
|
||||
use winit::{
|
||||
event::*,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
mod camera;
|
||||
mod model;
|
||||
mod resources;
|
||||
mod texture;
|
||||
|
||||
use model::{DrawLight, DrawModel, Vertex};
|
||||
|
||||
const NUM_INSTANCES_PER_ROW: u32 = 10;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct CameraUniform {
|
||||
view_position: [f32; 4],
|
||||
view_proj: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl CameraUniform {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
view_position: [0.0; 4],
|
||||
view_proj: cgmath::Matrix4::identity().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_view_proj(&mut self, camera: &camera::Camera, projection: &camera::Projection) {
|
||||
self.view_position = camera.position.to_homogeneous().into();
|
||||
self.view_proj = (projection.calc_matrix() * camera.calc_matrix()).into()
|
||||
}
|
||||
}
|
||||
|
||||
struct Instance {
|
||||
position: cgmath::Vector3<f32>,
|
||||
rotation: cgmath::Quaternion<f32>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
fn to_raw(&self) -> InstanceRaw {
|
||||
InstanceRaw {
|
||||
model: (cgmath::Matrix4::from_translation(self.position)
|
||||
* cgmath::Matrix4::from(self.rotation))
|
||||
.into(),
|
||||
normal: cgmath::Matrix3::from(self.rotation).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[allow(dead_code)]
|
||||
struct InstanceRaw {
|
||||
model: [[f32; 4]; 4],
|
||||
normal: [[f32; 3]; 3],
|
||||
}
|
||||
|
||||
impl model::Vertex for InstanceRaw {
|
||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: mem::size_of::<InstanceRaw>() as wgpu::BufferAddress,
|
||||
// We need to switch from using a step mode of Vertex to Instance
|
||||
// This means that our shaders will only change to use the next
|
||||
// instance when the shader starts processing a new instance
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
// While our vertex shader only uses locations 0, and 1 now, in later tutorials we'll
|
||||
// be using 2, 3, and 4, for Vertex. We'll start at slot 5 not conflict with them later
|
||||
shader_location: 5,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
// A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot
|
||||
// for each vec4. We don't have to do this in code though.
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||
shader_location: 6,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||
shader_location: 7,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||
shader_location: 8,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
|
||||
shader_location: 9,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 19]>() as wgpu::BufferAddress,
|
||||
shader_location: 10,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 22]>() as wgpu::BufferAddress,
|
||||
shader_location: 11,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct LightUniform {
|
||||
position: [f32; 3],
|
||||
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
|
||||
_padding: u32,
|
||||
color: [f32; 3],
|
||||
_padding2: u32,
|
||||
}
|
||||
|
||||
struct State {
|
||||
surface: wgpu::Surface,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
obj_model: model::Model,
|
||||
camera: camera::Camera,
|
||||
projection: camera::Projection,
|
||||
camera_controller: camera::CameraController,
|
||||
camera_uniform: CameraUniform,
|
||||
camera_buffer: wgpu::Buffer,
|
||||
camera_bind_group: wgpu::BindGroup,
|
||||
instances: Vec<Instance>,
|
||||
#[allow(dead_code)]
|
||||
instance_buffer: wgpu::Buffer,
|
||||
depth_texture: texture::Texture,
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
light_uniform: LightUniform,
|
||||
light_buffer: wgpu::Buffer,
|
||||
light_bind_group: wgpu::BindGroup,
|
||||
light_render_pipeline: wgpu::RenderPipeline,
|
||||
#[allow(dead_code)]
|
||||
debug_material: model::Material,
|
||||
mouse_pressed: bool,
|
||||
}
|
||||
|
||||
fn create_render_pipeline(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::PipelineLayout,
|
||||
color_format: wgpu::TextureFormat,
|
||||
depth_format: Option<wgpu::TextureFormat>,
|
||||
vertex_layouts: &[wgpu::VertexBufferLayout],
|
||||
shader: wgpu::ShaderModuleDescriptor,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(&shader);
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some(&format!("{:?}", shader)),
|
||||
layout: Some(layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: vertex_layouts,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[wgpu::ColorTargetState {
|
||||
format: color_format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
alpha: wgpu::BlendComponent::REPLACE,
|
||||
color: wgpu::BlendComponent::REPLACE,
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
// Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
// Requires Features::DEPTH_CLIP_CONTROL
|
||||
unclipped_depth: false,
|
||||
// Requires Features::CONSERVATIVE_RASTERIZATION
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: depth_format.map(|format| wgpu::DepthStencilState {
|
||||
format,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
// If the pipeline will be used with a multiview render pass, this
|
||||
// indicates how many array layers the attachments will have.
|
||||
multiview: None,
|
||||
})
|
||||
}
|
||||
|
||||
impl State {
|
||||
async fn new(window: &Window) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
// The instance is a handle to our GPU
|
||||
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||
let surface = unsafe { instance.create_surface(window) };
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
// WebGL doesn't support all of wgpu's features, so if
|
||||
// we're building for the web we'll have to disable some.
|
||||
limits: if cfg!(target_arch = "wasm32") {
|
||||
wgpu::Limits::downlevel_webgl2_defaults()
|
||||
} else {
|
||||
wgpu::Limits::default()
|
||||
},
|
||||
},
|
||||
None, // Trace path
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface.get_preferred_format(&adapter).unwrap(),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let texture_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
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::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// 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::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group_layout"),
|
||||
});
|
||||
|
||||
// UPDATED!
|
||||
let camera = camera::Camera::new((0.0, 5.0, 10.0), cgmath::Deg(-90.0), cgmath::Deg(-20.0));
|
||||
let projection =
|
||||
camera::Projection::new(config.width, config.height, cgmath::Deg(45.0), 0.1, 100.0);
|
||||
let camera_controller = camera::CameraController::new(4.0, 0.4);
|
||||
|
||||
let mut camera_uniform = CameraUniform::new();
|
||||
camera_uniform.update_view_proj(&camera, &projection);
|
||||
|
||||
let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Camera Buffer"),
|
||||
contents: bytemuck::cast_slice(&[camera_uniform]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
const SPACE_BETWEEN: f32 = 3.0;
|
||||
let iter = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_arch = "wasm32")] {
|
||||
(0..NUM_INSTANCES_PER_ROW)
|
||||
.into_iter()
|
||||
} else {
|
||||
(0..NUM_INSTANCES_PER_ROW)
|
||||
.into_par_iter()
|
||||
}
|
||||
}
|
||||
};
|
||||
let instances = iter
|
||||
.clone()
|
||||
.flat_map(|z| {
|
||||
// UPDATED!
|
||||
iter.clone().map(move |x| {
|
||||
let x = SPACE_BETWEEN * (x as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
|
||||
let z = SPACE_BETWEEN * (z as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
|
||||
|
||||
let position = cgmath::Vector3 { x, y: 0.0, z };
|
||||
|
||||
let rotation = if position.is_zero() {
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
cgmath::Vector3::unit_z(),
|
||||
cgmath::Deg(0.0),
|
||||
)
|
||||
} else {
|
||||
cgmath::Quaternion::from_axis_angle(position.normalize(), cgmath::Deg(45.0))
|
||||
};
|
||||
|
||||
Instance { position, rotation }
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance_data = instances.iter().map(Instance::to_raw).collect::<Vec<_>>();
|
||||
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Instance Buffer"),
|
||||
contents: bytemuck::cast_slice(&instance_data),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let camera_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("camera_bind_group_layout"),
|
||||
});
|
||||
|
||||
let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &camera_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: camera_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("camera_bind_group"),
|
||||
});
|
||||
|
||||
let obj_model =
|
||||
resources::load_model("cube.obj", &device, &queue, &texture_bind_group_layout)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let light_uniform = LightUniform {
|
||||
position: [2.0, 2.0, 2.0],
|
||||
_padding: 0,
|
||||
color: [1.0, 1.0, 1.0],
|
||||
_padding2: 0,
|
||||
};
|
||||
|
||||
let light_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Light VB"),
|
||||
contents: bytemuck::cast_slice(&[light_uniform]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let light_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let light_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &light_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: light_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let depth_texture =
|
||||
texture::Texture::create_depth_texture(&device, &config, "depth_texture");
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &[
|
||||
&texture_bind_group_layout,
|
||||
&camera_bind_group_layout,
|
||||
&light_bind_group_layout,
|
||||
],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = {
|
||||
let shader = wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Normal Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
|
||||
};
|
||||
create_render_pipeline(
|
||||
&device,
|
||||
&render_pipeline_layout,
|
||||
config.format,
|
||||
Some(texture::Texture::DEPTH_FORMAT),
|
||||
&[model::ModelVertex::desc(), InstanceRaw::desc()],
|
||||
shader,
|
||||
)
|
||||
};
|
||||
|
||||
let light_render_pipeline = {
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Light Pipeline Layout"),
|
||||
bind_group_layouts: &[&camera_bind_group_layout, &light_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let shader = wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Light Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("light.wgsl").into()),
|
||||
};
|
||||
create_render_pipeline(
|
||||
&device,
|
||||
&layout,
|
||||
config.format,
|
||||
Some(texture::Texture::DEPTH_FORMAT),
|
||||
&[model::ModelVertex::desc()],
|
||||
shader,
|
||||
)
|
||||
};
|
||||
|
||||
let debug_material = {
|
||||
let diffuse_bytes = include_bytes!("../res/cobble-diffuse.png");
|
||||
let normal_bytes = include_bytes!("../res/cobble-normal.png");
|
||||
|
||||
let diffuse_texture = texture::Texture::from_bytes(
|
||||
&device,
|
||||
&queue,
|
||||
diffuse_bytes,
|
||||
"res/alt-diffuse.png",
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
let normal_texture = texture::Texture::from_bytes(
|
||||
&device,
|
||||
&queue,
|
||||
normal_bytes,
|
||||
"res/alt-normal.png",
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
model::Material::new(
|
||||
&device,
|
||||
"alt-material",
|
||||
diffuse_texture,
|
||||
normal_texture,
|
||||
&texture_bind_group_layout,
|
||||
)
|
||||
};
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
config,
|
||||
render_pipeline,
|
||||
obj_model,
|
||||
camera,
|
||||
projection,
|
||||
camera_controller,
|
||||
camera_buffer,
|
||||
camera_bind_group,
|
||||
camera_uniform,
|
||||
instances,
|
||||
instance_buffer,
|
||||
depth_texture,
|
||||
size,
|
||||
light_uniform,
|
||||
light_buffer,
|
||||
light_bind_group,
|
||||
light_render_pipeline,
|
||||
#[allow(dead_code)]
|
||||
debug_material,
|
||||
mouse_pressed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.projection.resize(new_size.width, new_size.height);
|
||||
self.size = new_size;
|
||||
self.config.width = new_size.width;
|
||||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
self.depth_texture =
|
||||
texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture");
|
||||
}
|
||||
}
|
||||
|
||||
fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
match event {
|
||||
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
|
||||
}
|
||||
WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
state,
|
||||
..
|
||||
} => {
|
||||
self.mouse_pressed = *state == ElementState::Pressed;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, dt: instant::Duration) {
|
||||
self.camera_controller.update_camera(&mut self.camera, dt);
|
||||
self.camera_uniform
|
||||
.update_view_proj(&self.camera, &self.projection);
|
||||
self.queue.write_buffer(
|
||||
&self.camera_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[self.camera_uniform]),
|
||||
);
|
||||
|
||||
// Update the light
|
||||
let old_position: cgmath::Vector3<_> = self.light_uniform.position.into();
|
||||
self.light_uniform.position =
|
||||
(cgmath::Quaternion::from_axis_angle((0.0, 1.0, 0.0).into(), cgmath::Deg(1.0))
|
||||
* old_position)
|
||||
.into();
|
||||
self.queue.write_buffer(
|
||||
&self.light_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[self.light_uniform]),
|
||||
);
|
||||
}
|
||||
|
||||
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_texture.view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
});
|
||||
|
||||
render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..));
|
||||
render_pass.set_pipeline(&self.light_render_pipeline);
|
||||
render_pass.draw_light_model(
|
||||
&self.obj_model,
|
||||
&self.camera_bind_group,
|
||||
&self.light_bind_group,
|
||||
);
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.draw_model_instanced(
|
||||
&self.obj_model,
|
||||
0..self.instances.len() as u32,
|
||||
&self.camera_bind_group,
|
||||
&self.light_bind_group,
|
||||
);
|
||||
}
|
||||
self.queue.submit(iter::once(encoder.finish()));
|
||||
output.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
|
||||
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::Info).expect("Could't initialize logger");
|
||||
} else {
|
||||
env_logger::init();
|
||||
}
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let title = env!("CARGO_PKG_NAME");
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.build(&event_loop)
|
||||
.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.");
|
||||
}
|
||||
|
||||
let mut state = State::new(&window).await; // NEW!
|
||||
let mut last_render_time = instant::Instant::now();
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
match event {
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
// NEW!
|
||||
Event::DeviceEvent {
|
||||
event: DeviceEvent::MouseMotion{ delta, },
|
||||
.. // We're not using device_id currently
|
||||
} => 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() && !state.input(event) => {
|
||||
match event {
|
||||
#[cfg(not(target_arch="wasm32"))]
|
||||
WindowEvent::CloseRequested
|
||||
| WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
state.resize(*physical_size);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
state.resize(**new_inner_size);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(window_id) if window_id == window.id() => {
|
||||
let now = instant::Instant::now();
|
||||
let dt = now - last_render_time;
|
||||
last_render_time = now;
|
||||
state.update(dt);
|
||||
match state.render() {
|
||||
Ok(_) => {}
|
||||
// Reconfigure the surface if it's lost or outdated
|
||||
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
|
||||
// The system is out of memory, we should probably quit
|
||||
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||
// We're ignoring timeouts
|
||||
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
8
src/light.frag
Normal file
8
src/light.frag
Normal file
|
@ -0,0 +1,8 @@
|
|||
#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);
|
||||
}
|
BIN
src/light.frag.spv
Normal file
BIN
src/light.frag.spv
Normal file
Binary file not shown.
27
src/light.vert
Normal file
27
src/light.vert
Normal file
|
@ -0,0 +1,27 @@
|
|||
#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;
|
||||
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;
|
||||
}
|
BIN
src/light.vert.spv
Normal file
BIN
src/light.vert.spv
Normal file
Binary file not shown.
42
src/light.wgsl
Normal file
42
src/light.wgsl
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Vertex shader
|
||||
|
||||
struct Camera {
|
||||
view_pos: vec4<f32>;
|
||||
view_proj: mat4x4<f32>;
|
||||
};
|
||||
[[group(0), binding(0)]]
|
||||
var<uniform> camera: Camera;
|
||||
|
||||
struct Light {
|
||||
position: vec3<f32>;
|
||||
color: vec3<f32>;
|
||||
};
|
||||
[[group(1), binding(0)]]
|
||||
var<uniform> light: Light;
|
||||
|
||||
struct VertexInput {
|
||||
[[location(0)]] position: vec3<f32>;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] clip_position: vec4<f32>;
|
||||
[[location(0)]] color: vec3<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vs_main(
|
||||
model: VertexInput,
|
||||
) -> VertexOutput {
|
||||
let scale = 0.25;
|
||||
var out: VertexOutput;
|
||||
out.clip_position = camera.view_proj * vec4<f32>(model.position * scale + light.position, 1.0);
|
||||
out.color = light.color;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment shader
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||
return vec4<f32>(in.color, 1.0);
|
||||
}
|
27
src/main.rs
27
src/main.rs
|
@ -1,22 +1,5 @@
|
|||
mod state;
|
||||
pub use state::State;
|
||||
|
||||
pub mod input;
|
||||
pub mod meshs;
|
||||
pub mod render;
|
||||
|
||||
use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = TermLogger::init(
|
||||
LevelFilter::Info,
|
||||
Config::default(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
) {
|
||||
println!("Failed to start logger : {}", err);
|
||||
}
|
||||
|
||||
let engine = render::Window::new("Test 123");
|
||||
pollster::block_on(engine.run());
|
||||
}
|
||||
use tuto1::run;
|
||||
|
||||
fn main() {
|
||||
async_std::task::block_on(run());
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use cgmath::prelude::*;
|
||||
use winit::event::{WindowEvent, KeyboardInput, VirtualKeyCode, ElementState};
|
||||
|
||||
use crate::{render::{Mesh, Renderable, Vertex, Instance}, input::Controllable};
|
||||
|
||||
const VERTICES: &[Vertex] = &[
|
||||
Vertex {
|
||||
position: [-0.0868241, 0.49240386, 0.0],
|
||||
tex_coords: [0.4131759, 0.00759614],
|
||||
}, // A
|
||||
Vertex {
|
||||
position: [-0.49513406, 0.06958647, 0.0],
|
||||
tex_coords: [0.0048659444, 0.43041354],
|
||||
}, // B
|
||||
Vertex {
|
||||
position: [-0.21918549, -0.44939706, 0.0],
|
||||
tex_coords: [0.28081453, 0.949397],
|
||||
}, // C
|
||||
Vertex {
|
||||
position: [0.35966998, -0.3473291, 0.0],
|
||||
tex_coords: [0.85967, 0.84732914],
|
||||
}, // D
|
||||
Vertex {
|
||||
position: [0.44147372, 0.2347359, 0.0],
|
||||
tex_coords: [0.9414737, 0.2652641],
|
||||
}, // E
|
||||
];
|
||||
|
||||
const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4];
|
||||
|
||||
const NUM_INSTANCES_PER_ROW: u32 = 10;
|
||||
const INSTANCE_DISPLACEMENT: cgmath::Vector3<f32> = cgmath::Vector3::new(
|
||||
NUM_INSTANCES_PER_ROW as f32 * 0.5,
|
||||
0.0,
|
||||
NUM_INSTANCES_PER_ROW as f32 * 0.5,
|
||||
);
|
||||
|
||||
const FRAME_TIME: f32 = 1.0 / 60.0;
|
||||
const ROTATION_SPEED: f32 = std::f32::consts::PI * FRAME_TIME * 0.5;
|
||||
|
||||
pub struct DefaultMesh {
|
||||
mesh: Mesh,
|
||||
toggle: bool,
|
||||
texture1_bind_group: Arc<wgpu::BindGroup>,
|
||||
texture2_bind_group: Arc<wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
impl DefaultMesh {
|
||||
pub fn new(texture1_bind_group: wgpu::BindGroup, texture2_bind_group: wgpu::BindGroup) -> Self {
|
||||
let texture1_bind_group = Arc::new(texture1_bind_group);
|
||||
let texture2_bind_group = Arc::new(texture2_bind_group);
|
||||
|
||||
let instances = (0..NUM_INSTANCES_PER_ROW)
|
||||
.flat_map(|z| {
|
||||
(0..NUM_INSTANCES_PER_ROW).map(move |x| {
|
||||
let position = cgmath::Vector3 {
|
||||
x: x as f32,
|
||||
y: 0.0,
|
||||
z: z as f32,
|
||||
} - INSTANCE_DISPLACEMENT;
|
||||
|
||||
let rotation = if position.is_zero() {
|
||||
// this is needed so an object at (0, 0, 0) won't get scaled to zero
|
||||
// as Quaternions can effect scale if they're not created correctly
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
cgmath::Vector3::unit_z(),
|
||||
cgmath::Deg(0.0),
|
||||
)
|
||||
} else {
|
||||
cgmath::Quaternion::from_axis_angle(position.normalize(), cgmath::Deg(45.0))
|
||||
};
|
||||
|
||||
Instance { position, rotation }
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mesh = Mesh {
|
||||
vertex_array: VERTICES.to_vec(),
|
||||
index_array: INDICES.to_vec(),
|
||||
num_indices: INDICES.len() as u32,
|
||||
instance_array: instances,
|
||||
texture_bind_group: Some(texture1_bind_group.clone()),
|
||||
vertex_buffer: None,
|
||||
index_buffer: None,
|
||||
instance_buffer: None,
|
||||
};
|
||||
DefaultMesh {
|
||||
mesh,
|
||||
toggle: false,
|
||||
texture1_bind_group,
|
||||
texture2_bind_group,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, toggle: bool) {
|
||||
self.toggle = toggle;
|
||||
if !self.toggle {
|
||||
self.mesh.texture_bind_group = Some(self.texture1_bind_group.clone());
|
||||
} else {
|
||||
self.mesh.texture_bind_group = Some(self.texture2_bind_group.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for DefaultMesh {
|
||||
fn initialize(&mut self, device: &wgpu::Device) {
|
||||
self.mesh.initialize(device);
|
||||
}
|
||||
|
||||
fn update_instances(&mut self, device: &wgpu::Queue) {
|
||||
for instance in self.mesh.instance_array.iter_mut() {
|
||||
let amount = cgmath::Quaternion::from_angle_y(cgmath::Rad(ROTATION_SPEED));
|
||||
let current = instance.rotation;
|
||||
instance.rotation = amount * current;
|
||||
}
|
||||
self.mesh.update_instances(device);
|
||||
}
|
||||
|
||||
fn prepare<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
self.mesh.prepare(render_pass);
|
||||
}
|
||||
|
||||
fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
self.mesh.draw(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
impl Controllable for DefaultMesh {
|
||||
fn process_events(&mut self, event: &winit::event::WindowEvent) -> bool {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state,
|
||||
virtual_keycode: Some(keycode),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let is_pressed = *state == ElementState::Pressed;
|
||||
match keycode {
|
||||
VirtualKeyCode::Space => {
|
||||
self.toggle(is_pressed);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
mod default_mesh;
|
||||
pub use default_mesh::DefaultMesh;
|
316
src/model.rs
Normal file
316
src/model.rs
Normal file
|
@ -0,0 +1,316 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use crate::texture;
|
||||
|
||||
pub trait Vertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a>;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct ModelVertex {
|
||||
pub position: [f32; 3],
|
||||
pub tex_coords: [f32; 2],
|
||||
pub normal: [f32; 3],
|
||||
pub tangent: [f32; 3],
|
||||
pub bitangent: [f32; 3],
|
||||
}
|
||||
|
||||
impl Vertex for ModelVertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: mem::size_of::<ModelVertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
// Tangent and bitangent
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Material {
|
||||
pub name: String,
|
||||
pub diffuse_texture: texture::Texture,
|
||||
pub normal_texture: texture::Texture,
|
||||
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 {
|
||||
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),
|
||||
},
|
||||
],
|
||||
label: Some(name),
|
||||
});
|
||||
|
||||
Self {
|
||||
name: String::from(name),
|
||||
diffuse_texture,
|
||||
normal_texture,
|
||||
bind_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Mesh {
|
||||
pub name: String,
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub num_elements: u32,
|
||||
pub material: usize,
|
||||
}
|
||||
|
||||
pub struct Model {
|
||||
pub meshes: Vec<Mesh>,
|
||||
pub materials: Vec<Material>,
|
||||
}
|
||||
|
||||
pub trait DrawModel<'a> {
|
||||
fn draw_mesh(
|
||||
&mut self,
|
||||
mesh: &'a Mesh,
|
||||
material: &'a Material,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'a Mesh,
|
||||
material: &'a Material,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
|
||||
fn draw_model(
|
||||
&mut self,
|
||||
model: &'a Model,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'a Model,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
fn draw_model_instanced_with_material(
|
||||
&mut self,
|
||||
model: &'a Model,
|
||||
material: &'a Material,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawModel<'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_mesh_instanced(mesh, material, 0..1, camera_bind_group, light_bind_group);
|
||||
}
|
||||
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||
self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
||||
self.set_bind_group(0, &material.bind_group, &[]);
|
||||
self.set_bind_group(1, camera_bind_group, &[]);
|
||||
self.set_bind_group(2, light_bind_group, &[]);
|
||||
self.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_model_instanced(model, 0..1, camera_bind_group, light_bind_group);
|
||||
}
|
||||
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
let material = &model.materials[mesh.material];
|
||||
self.draw_mesh_instanced(
|
||||
mesh,
|
||||
material,
|
||||
instances.clone(),
|
||||
camera_bind_group,
|
||||
light_bind_group,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_model_instanced_with_material(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
self.draw_mesh_instanced(
|
||||
mesh,
|
||||
material,
|
||||
instances.clone(),
|
||||
camera_bind_group,
|
||||
light_bind_group,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DrawLight<'a> {
|
||||
fn draw_light_mesh(
|
||||
&mut self,
|
||||
mesh: &'a Mesh,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
fn draw_light_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'a Mesh,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
|
||||
fn draw_light_model(
|
||||
&mut self,
|
||||
model: &'a Model,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
fn draw_light_model_instanced(
|
||||
&mut self,
|
||||
model: &'a Model,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'a wgpu::BindGroup,
|
||||
light_bind_group: &'a wgpu::BindGroup,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawLight<'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_light_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_light_mesh_instanced(mesh, 0..1, camera_bind_group, light_bind_group);
|
||||
}
|
||||
|
||||
fn draw_light_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||
self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
||||
self.set_bind_group(0, camera_bind_group, &[]);
|
||||
self.set_bind_group(1, light_bind_group, &[]);
|
||||
self.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_light_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_light_model_instanced(model, 0..1, camera_bind_group, light_bind_group);
|
||||
}
|
||||
fn draw_light_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
camera_bind_group: &'b wgpu::BindGroup,
|
||||
light_bind_group: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
self.draw_light_mesh_instanced(
|
||||
mesh,
|
||||
instances.clone(),
|
||||
camera_bind_group,
|
||||
light_bind_group,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
use wgpu::util::DeviceExt;
|
||||
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
|
||||
use crate::input::Controllable;
|
||||
|
||||
use super::Renderable;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.5, 0.0,
|
||||
0.0, 0.0, 0.5, 1.0,
|
||||
);
|
||||
|
||||
// We need this for Rust to store our data correctly for the shaders
|
||||
#[repr(C)]
|
||||
// This is so we can store this in a buffer
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||
struct CameraUniform {
|
||||
// We can't use cgmath with bytemuck directly so we'll have
|
||||
// to convert the Matrix4 into a 4x4 f32 array
|
||||
view_proj: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
pub struct Camera {
|
||||
eye: cgmath::Point3<f32>,
|
||||
target: cgmath::Point3<f32>,
|
||||
up: cgmath::Vector3<f32>,
|
||||
aspect: f32,
|
||||
fovy: f32,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
controller: CameraController,
|
||||
uniform: CameraUniform,
|
||||
bind_group: Option<wgpu::BindGroup>,
|
||||
bind_group_layout: Option<wgpu::BindGroupLayout>,
|
||||
buffer: Option<wgpu::Buffer>,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn new(width: f32, height: f32, speed: f32) -> Self {
|
||||
Self {
|
||||
eye: (0.0, 1.0, 2.0).into(),
|
||||
target: (0.0, 0.0, 0.0).into(),
|
||||
up: cgmath::Vector3::unit_y(),
|
||||
aspect: width / height,
|
||||
fovy: 45.0,
|
||||
znear: 0.1,
|
||||
zfar: 100.0,
|
||||
controller: CameraController::new(speed),
|
||||
bind_group: None,
|
||||
bind_group_layout: None,
|
||||
uniform: CameraUniform::default(),
|
||||
buffer: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_uniform(&mut self) {
|
||||
let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up);
|
||||
let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar);
|
||||
|
||||
self.uniform.view_proj = (OPENGL_TO_WGPU_MATRIX * proj * view).into();
|
||||
}
|
||||
|
||||
pub fn get_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
|
||||
&self.bind_group_layout.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn update_camera(&mut self) {
|
||||
use cgmath::InnerSpace;
|
||||
let forward = self.target - self.eye;
|
||||
let forward_norm = forward.normalize();
|
||||
let forward_mag = forward.magnitude();
|
||||
|
||||
if self.controller.is_forward_pressed && forward_mag > self.controller.speed {
|
||||
self.eye += forward_norm * self.controller.speed;
|
||||
}
|
||||
if self.controller.is_backward_pressed {
|
||||
self.eye -= forward_norm * self.controller.speed;
|
||||
}
|
||||
|
||||
let right = forward_norm.cross(self.up);
|
||||
|
||||
let forward = self.target - self.eye;
|
||||
let forward_mag = forward.magnitude();
|
||||
|
||||
if self.controller.is_right_pressed {
|
||||
self.eye = self.target - (forward + right * self.controller.speed).normalize() * forward_mag;
|
||||
}
|
||||
if self.controller.is_left_pressed {
|
||||
self.eye = self.target - (forward - right * self.controller.speed).normalize() * forward_mag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Camera {
|
||||
fn initialize(&mut self, device: &wgpu::Device) {
|
||||
self.update_uniform();
|
||||
|
||||
self.buffer = Some(
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Camera Buffer"),
|
||||
contents: bytemuck::cast_slice(&[self.uniform]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
}),
|
||||
);
|
||||
|
||||
self.bind_group_layout = Some(device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("camera_bind_group_layout"),
|
||||
},
|
||||
));
|
||||
|
||||
self.bind_group = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &self.bind_group_layout.as_ref().unwrap(),
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: self.buffer.as_ref().unwrap().as_entire_binding(),
|
||||
}],
|
||||
label: Some("camera_bind_group"),
|
||||
}));
|
||||
}
|
||||
|
||||
fn update_instances(&mut self, queue: &wgpu::Queue) {
|
||||
self.update_camera();
|
||||
self.update_uniform();
|
||||
queue.write_buffer(
|
||||
&self.buffer.as_ref().unwrap(),
|
||||
0,
|
||||
bytemuck::cast_slice(&[self.uniform]),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_bind_group(1, &self.bind_group.as_ref().unwrap(), &[]);
|
||||
}
|
||||
|
||||
fn draw<'a>(&'a self, _render_pass: &mut wgpu::RenderPass<'a>) { }
|
||||
}
|
||||
|
||||
impl Controllable for Camera {
|
||||
fn process_events(&mut self, event: &WindowEvent) -> bool {
|
||||
self.controller.process_events(event)
|
||||
}
|
||||
}
|
||||
|
||||
struct CameraController {
|
||||
speed: f32,
|
||||
is_forward_pressed: bool,
|
||||
is_backward_pressed: bool,
|
||||
is_left_pressed: bool,
|
||||
is_right_pressed: bool,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
pub fn new(speed: f32) -> Self {
|
||||
Self {
|
||||
speed,
|
||||
is_forward_pressed: false,
|
||||
is_backward_pressed: false,
|
||||
is_left_pressed: false,
|
||||
is_right_pressed: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_events(&mut self, event: &WindowEvent) -> bool {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state,
|
||||
virtual_keycode: Some(keycode),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let is_pressed = *state == ElementState::Pressed;
|
||||
match keycode {
|
||||
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
||||
self.is_forward_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::A | VirtualKeyCode::Left => {
|
||||
self.is_left_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::S | VirtualKeyCode::Down => {
|
||||
self.is_backward_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::D | VirtualKeyCode::Right => {
|
||||
self.is_right_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
pub struct Instance {
|
||||
pub position: cgmath::Vector3<f32>,
|
||||
pub rotation: cgmath::Quaternion<f32>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct InstanceRaw {
|
||||
model: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn to_raw(&self) -> InstanceRaw {
|
||||
InstanceRaw {
|
||||
model: (cgmath::Matrix4::from_translation(self.position)
|
||||
* cgmath::Matrix4::from(self.rotation))
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InstanceRaw {
|
||||
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: mem::size_of::<InstanceRaw>() as wgpu::BufferAddress,
|
||||
// We need to switch from using a step mode of Vertex to Instance
|
||||
// This means that our shaders will only change to use the next
|
||||
// instance when the shader starts processing a new instance
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
// While our vertex shader only uses locations 0, and 1 now, in later tutorials we'll
|
||||
// be using 2, 3, and 4, for Vertex. We'll start at slot 5 not conflict with them later
|
||||
shader_location: 5,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
// A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot
|
||||
// for each vec4. We'll have to reassemble the mat4 in
|
||||
// the shader.
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||
shader_location: 6,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||
shader_location: 7,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||
shader_location: 8,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use wgpu::{Device, util::DeviceExt, Queue};
|
||||
|
||||
use super::{Vertex, Renderable, Instance};
|
||||
|
||||
pub struct Mesh {
|
||||
pub vertex_array: Vec<Vertex>,
|
||||
pub index_array: Vec<u16>,
|
||||
pub num_indices: u32,
|
||||
pub instance_array: Vec<Instance>,
|
||||
pub texture_bind_group: Option<Arc<wgpu::BindGroup>>,
|
||||
pub vertex_buffer: Option<wgpu::Buffer>,
|
||||
pub index_buffer: Option<wgpu::Buffer>,
|
||||
pub instance_buffer: Option<wgpu::Buffer>,
|
||||
}
|
||||
|
||||
impl Renderable for Mesh {
|
||||
fn initialize(&mut self, device: &Device) {
|
||||
self.vertex_buffer = Some(device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(&self.vertex_array),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
}));
|
||||
|
||||
self.index_buffer = Some(device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
contents: bytemuck::cast_slice(&self.index_array),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
}));
|
||||
self.num_indices = self.index_array.len() as u32;
|
||||
|
||||
let instance_data = self.instance_array
|
||||
.iter()
|
||||
.map(Instance::to_raw)
|
||||
.collect::<Vec<_>>();
|
||||
self.instance_buffer = Some(device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Instance Buffer"),
|
||||
contents: bytemuck::cast_slice(&instance_data),
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
}));
|
||||
}
|
||||
|
||||
fn update_instances(&mut self, queue: &Queue) {
|
||||
let instance_data = self
|
||||
.instance_array
|
||||
.iter()
|
||||
.map(Instance::to_raw)
|
||||
.collect::<Vec<_>>();
|
||||
queue.write_buffer(
|
||||
&self.instance_buffer.as_ref().unwrap(),
|
||||
0,
|
||||
bytemuck::cast_slice(&instance_data),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_bind_group(0, &self.texture_bind_group.as_ref().unwrap(), &[]);
|
||||
render_pass.set_vertex_buffer(0, self.vertex_buffer.as_ref().unwrap().slice(..));
|
||||
render_pass.set_vertex_buffer(1, self.instance_buffer.as_ref().unwrap().slice(..));
|
||||
render_pass.set_index_buffer(self.index_buffer.as_ref().unwrap().slice(..), wgpu::IndexFormat::Uint16);
|
||||
}
|
||||
|
||||
fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.draw_indexed(0..self.num_indices as _, 0, 0..self.instance_array.len() as _);
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
mod vertex;
|
||||
pub use vertex::Vertex;
|
||||
|
||||
mod camera;
|
||||
pub use camera::Camera;
|
||||
|
||||
mod texture;
|
||||
pub use texture::{Texture, TextureManager};
|
||||
|
||||
mod instance;
|
||||
pub use instance::{
|
||||
Instance, InstanceRaw
|
||||
};
|
||||
use wgpu::{Device, Queue};
|
||||
|
||||
mod mesh;
|
||||
pub use mesh::Mesh;
|
||||
|
||||
mod window;
|
||||
pub use window::Window;
|
||||
|
||||
mod pipelines;
|
||||
|
||||
pub trait Renderable {
|
||||
fn initialize(&mut self, device: &Device);
|
||||
fn update_instances(&mut self, queue: &Queue);
|
||||
fn prepare<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>);
|
||||
fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>);
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
use wgpu::{Device, Queue};
|
||||
|
||||
use super::Renderable;
|
||||
|
||||
pub trait Processable {
|
||||
fn initialize(&mut self, device: &Device, queue: &Queue, renderable_entities: Vec<Box<dyn Renderable>>);
|
||||
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>, renderable_entities: Vec<Box<dyn Renderable>>);
|
||||
fn render(&mut self, renderable_entities: Vec<Box<dyn Renderable>>) -> Result<(), wgpu::SurfaceError>;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
mod texture;
|
||||
pub use texture::Texture;
|
||||
use wgpu::{BindGroup, Device, Queue};
|
||||
|
||||
pub struct TextureManager {
|
||||
texture_bind_group_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
impl TextureManager {
|
||||
pub fn new(device: &Device) -> Self {
|
||||
let texture_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group_layout"),
|
||||
});
|
||||
Self {
|
||||
texture_bind_group_layout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_texture_from_bytes(
|
||||
&self,
|
||||
device: &Device,
|
||||
queue: &Queue,
|
||||
bytes: &[u8],
|
||||
label: &str,
|
||||
) -> BindGroup {
|
||||
let diffuse_texture = Texture::from_bytes(&device, &queue, bytes, label).unwrap();
|
||||
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &self.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
|
||||
},
|
||||
],
|
||||
label: Some(&format!("diffuse_bind_group_{}", label)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_texture_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
|
||||
&self.texture_bind_group_layout
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Vertex {
|
||||
pub position: [f32; 3],
|
||||
pub tex_coords: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
|
||||
const ATTRIBS: [wgpu::VertexAttribute; 2] =
|
||||
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2];
|
||||
|
||||
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
use std::mem;
|
||||
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
// wgpu::VertexBufferLayout {
|
||||
// array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||
// step_mode: wgpu::VertexStepMode::Vertex,
|
||||
// attributes: &[
|
||||
// wgpu::VertexAttribute {
|
||||
// offset: 0,
|
||||
// shader_location: 0,
|
||||
// format: wgpu::VertexFormat::Float32x3,
|
||||
// },
|
||||
// wgpu::VertexAttribute {
|
||||
// offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||
// shader_location: 1,
|
||||
// format: wgpu::VertexFormat::Float32x2,
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
pub struct Window {
|
||||
title: &'static str,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(title: &'static str) -> Self {
|
||||
Self { title }
|
||||
}
|
||||
|
||||
pub async fn run(self) {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(self.title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut state = crate::State::new(&window).await;
|
||||
|
||||
event_loop.run(
|
||||
move |event: Event<()>, _, control_flow: &mut ControlFlow| match event {
|
||||
Event::WindowEvent {
|
||||
ref event,
|
||||
window_id,
|
||||
} if window_id == window.id() => {
|
||||
if !state.input(&event) {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput { input, .. } => match input {
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => {}
|
||||
},
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
state.resize(*physical_size);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
// new_inner_size is &&mut so we have to dereference it twice
|
||||
state.resize(**new_inner_size);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(window_id) if window_id == window.id() => {
|
||||
state.update();
|
||||
match state.render() {
|
||||
Ok(_) => {}
|
||||
// Reconfigure the surface if lost
|
||||
Err(wgpu::SurfaceError::Lost) => state.resize(state.size),
|
||||
// The system is out of memory, we should probably quit
|
||||
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||
// All other errors (Outdated, Timeout) should be resolved by the next frame
|
||||
Err(e) => eprintln!("{:?}", e),
|
||||
}
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
// RedrawRequested will only trigger once, unless we manually
|
||||
// request it.
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
219
src/resources.rs
Normal file
219
src/resources.rs
Normal file
|
@ -0,0 +1,219 @@
|
|||
use std::io::{BufReader, Cursor};
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::{model, texture};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn format_url(file_name: &str) -> reqwest::Url {
|
||||
let window = web_sys::window().unwrap();
|
||||
let location = window.location();
|
||||
let base = reqwest::Url::parse(&format!(
|
||||
"{}/{}/",
|
||||
location.origin().unwrap(),
|
||||
option_env!("RES_PATH").unwrap_or("res"),
|
||||
)).unwrap();
|
||||
base.join(file_name).unwrap()
|
||||
}
|
||||
|
||||
pub async fn load_string(file_name: &str) -> anyhow::Result<String> {
|
||||
cfg_if! {
|
||||
if #[cfg(target_arch = "wasm32")] {
|
||||
let url = format_url(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_url(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;
|
||||
// We flip the bitangent to enable right-handed normal
|
||||
// maps with wgpu texture coordinate system
|
||||
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 })
|
||||
}
|
40
src/shader.frag
Normal file
40
src/shader.frag
Normal file
|
@ -0,0 +1,40 @@
|
|||
#version 450
|
||||
|
||||
layout(location=0) in vec2 v_tex_coords;
|
||||
layout(location=1) in vec3 v_position; // UPDATED!
|
||||
layout(location=2) in vec3 v_light_position; // NEW!
|
||||
layout(location=3) in vec3 v_view_position; // NEW!
|
||||
|
||||
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 = 0, binding = 2) uniform texture2D t_normal;
|
||||
layout(set = 0, binding = 3) uniform sampler s_normal;
|
||||
|
||||
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);
|
||||
vec4 object_normal = texture(sampler2D(t_normal, s_normal), v_tex_coords);
|
||||
|
||||
float ambient_strength = 0.1;
|
||||
vec3 ambient_color = light_color * ambient_strength;
|
||||
|
||||
vec3 normal = normalize(object_normal.rgb * 2.0 - 1.0); // UPDATED!
|
||||
vec3 light_dir = normalize(v_light_position - v_position); // UPDATED!
|
||||
|
||||
float diffuse_strength = max(dot(normal, light_dir), 0.0);
|
||||
vec3 diffuse_color = light_color * diffuse_strength;
|
||||
|
||||
vec3 view_dir = normalize(v_view_position - v_position); // UPDATED!
|
||||
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;
|
||||
f_color = vec4(result, object_color.a);
|
||||
}
|
BIN
src/shader.frag.spv
Normal file
BIN
src/shader.frag.spv
Normal file
Binary file not shown.
61
src/shader.vert
Normal file
61
src/shader.vert
Normal file
|
@ -0,0 +1,61 @@
|
|||
#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=3) in vec3 a_tangent;
|
||||
layout(location=4) in vec3 a_bitangent;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
layout(location=1) out vec3 v_position; // UPDATED!
|
||||
layout(location=2) out vec3 v_light_position; // NEW!
|
||||
layout(location=3) out vec3 v_view_position; // NEW!
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Camera {
|
||||
vec3 u_view_position;
|
||||
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;
|
||||
|
||||
// NEW!
|
||||
layout(set=2, binding=0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
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)));
|
||||
vec3 normal = normalize(normal_matrix * a_normal);
|
||||
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||
|
||||
// UDPATED!
|
||||
mat3 tangent_matrix = transpose(mat3(
|
||||
tangent,
|
||||
bitangent,
|
||||
normal
|
||||
));
|
||||
|
||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||
v_position = model_space.xyz;
|
||||
|
||||
// NEW!
|
||||
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;
|
||||
}
|
BIN
src/shader.vert.spv
Normal file
BIN
src/shader.vert.spv
Normal file
Binary file not shown.
115
src/shader.wgsl
Normal file
115
src/shader.wgsl
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Vertex shader
|
||||
|
||||
struct Camera {
|
||||
view_pos: vec4<f32>;
|
||||
view_proj: mat4x4<f32>;
|
||||
};
|
||||
[[group(1), binding(0)]]
|
||||
var<uniform> camera: Camera;
|
||||
|
||||
struct Light {
|
||||
position: vec3<f32>;
|
||||
color: vec3<f32>;
|
||||
};
|
||||
[[group(2), binding(0)]]
|
||||
var<uniform> light: Light;
|
||||
|
||||
struct VertexInput {
|
||||
[[location(0)]] position: vec3<f32>;
|
||||
[[location(1)]] tex_coords: vec2<f32>;
|
||||
[[location(2)]] normal: vec3<f32>;
|
||||
[[location(3)]] tangent: vec3<f32>;
|
||||
[[location(4)]] bitangent: vec3<f32>;
|
||||
};
|
||||
struct InstanceInput {
|
||||
[[location(5)]] model_matrix_0: vec4<f32>;
|
||||
[[location(6)]] model_matrix_1: vec4<f32>;
|
||||
[[location(7)]] model_matrix_2: vec4<f32>;
|
||||
[[location(8)]] model_matrix_3: vec4<f32>;
|
||||
[[location(9)]] normal_matrix_0: vec3<f32>;
|
||||
[[location(10)]] normal_matrix_1: vec3<f32>;
|
||||
[[location(11)]] normal_matrix_2: vec3<f32>;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] clip_position: vec4<f32>;
|
||||
[[location(0)]] tex_coords: vec2<f32>;
|
||||
[[location(1)]] tangent_position: vec3<f32>;
|
||||
[[location(2)]] tangent_light_position: vec3<f32>;
|
||||
[[location(3)]] tangent_view_position: vec3<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vs_main(
|
||||
model: VertexInput,
|
||||
instance: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
let model_matrix = mat4x4<f32>(
|
||||
instance.model_matrix_0,
|
||||
instance.model_matrix_1,
|
||||
instance.model_matrix_2,
|
||||
instance.model_matrix_3,
|
||||
);
|
||||
let normal_matrix = mat3x3<f32>(
|
||||
instance.normal_matrix_0,
|
||||
instance.normal_matrix_1,
|
||||
instance.normal_matrix_2,
|
||||
);
|
||||
|
||||
// Construct the tangent matrix
|
||||
let world_normal = normalize(normal_matrix * model.normal);
|
||||
let world_tangent = normalize(normal_matrix * model.tangent);
|
||||
let world_bitangent = normalize(normal_matrix * model.bitangent);
|
||||
let tangent_matrix = transpose(mat3x3<f32>(
|
||||
world_tangent,
|
||||
world_bitangent,
|
||||
world_normal,
|
||||
));
|
||||
|
||||
let world_position = model_matrix * vec4<f32>(model.position, 1.0);
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = camera.view_proj * world_position;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.tangent_position = tangent_matrix * world_position.xyz;
|
||||
out.tangent_view_position = tangent_matrix * camera.view_pos.xyz;
|
||||
out.tangent_light_position = tangent_matrix * light.position;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment shader
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
[[group(0), binding(1)]]
|
||||
var s_diffuse: sampler;
|
||||
[[group(0), binding(2)]]
|
||||
var t_normal: texture_2d<f32>;
|
||||
[[group(0), binding(3)]]
|
||||
var s_normal: sampler;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
let object_normal: vec4<f32> = textureSample(t_normal, s_normal, in.tex_coords);
|
||||
|
||||
// We don't need (or want) much ambient light, so 0.1 is fine
|
||||
let ambient_strength = 0.1;
|
||||
let ambient_color = light.color * ambient_strength;
|
||||
|
||||
// Create the lighting vectors
|
||||
let tangent_normal = object_normal.xyz * 2.0 - 1.0;
|
||||
let light_dir = normalize(in.tangent_light_position - in.tangent_position);
|
||||
let view_dir = normalize(in.tangent_view_position - in.tangent_position);
|
||||
let half_dir = normalize(view_dir + light_dir);
|
||||
|
||||
let diffuse_strength = max(dot(tangent_normal, light_dir), 0.0);
|
||||
let diffuse_color = light.color * diffuse_strength;
|
||||
|
||||
let specular_strength = pow(max(dot(tangent_normal, half_dir), 0.0), 32.0);
|
||||
let specular_color = specular_strength * light.color;
|
||||
|
||||
let result = (ambient_color + diffuse_color + specular_color) * object_color.xyz;
|
||||
|
||||
return vec4<f32>(result, object_color.a);
|
||||
}
|
260
src/state.rs
260
src/state.rs
|
@ -1,260 +0,0 @@
|
|||
use crate::{
|
||||
input::Controllable,
|
||||
meshs::DefaultMesh,
|
||||
render::{Renderable, TextureManager},
|
||||
};
|
||||
|
||||
use super::render::{Camera, InstanceRaw, Texture, Vertex};
|
||||
use winit::{event::WindowEvent, window::Window};
|
||||
|
||||
pub struct State {
|
||||
pub surface: wgpu::Surface,
|
||||
pub device: wgpu::Device,
|
||||
pub queue: wgpu::Queue,
|
||||
pub config: wgpu::SurfaceConfiguration,
|
||||
pub size: winit::dpi::PhysicalSize<u32>,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
camera: Camera,
|
||||
depth_texture: Texture,
|
||||
mesh: DefaultMesh,
|
||||
#[allow(dead_code)]
|
||||
texture_manager: TextureManager,
|
||||
}
|
||||
|
||||
impl State {
|
||||
// Creating some of the wgpu types requires async code
|
||||
pub async fn new(window: &Window) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
// The instance is a handle to our GPU
|
||||
// Backends::all => Vulkan + Metal + DX12 + Browser WebGPU
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||
let surface = unsafe { instance.create_surface(window) };
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// let adapter = instance
|
||||
// .enumerate_adapters(wgpu::Backends::all())
|
||||
// .filter(|adapter| {
|
||||
// // Check if this adapter supports our surface
|
||||
// surface.get_preferred_format(&adapter).is_some()
|
||||
// })
|
||||
// .next()
|
||||
// .unwrap();
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
features: wgpu::Features::empty(),
|
||||
// WebGL doesn't support all of wgpu's features, so if
|
||||
// we're building for the web we'll have to disable some.
|
||||
limits: if cfg!(target_arch = "wasm32") {
|
||||
wgpu::Limits::downlevel_webgl2_defaults()
|
||||
} else {
|
||||
wgpu::Limits::default()
|
||||
},
|
||||
label: None,
|
||||
},
|
||||
None, // Trace path
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface.get_preferred_format(&adapter).unwrap(),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let texture_manager = TextureManager::new(&device);
|
||||
|
||||
let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/res/shaders/main.wgsl"
|
||||
))
|
||||
.into(),
|
||||
),
|
||||
});
|
||||
|
||||
let mut camera = Camera::new(config.width as f32, config.height as f32, 0.2);
|
||||
camera.initialize(&device);
|
||||
|
||||
let depth_texture = Texture::create_depth_texture(&device, &config, "depth_texture");
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &[
|
||||
&texture_manager.get_texture_bind_group_layout(),
|
||||
camera.get_bind_group_layout(),
|
||||
],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Render Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[Vertex::desc(), InstanceRaw::desc()],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
// Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
// Requires Features::DEPTH_CLIP_CONTROL
|
||||
unclipped_depth: false,
|
||||
// Requires Features::CONSERVATIVE_RASTERIZATION
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: Texture::DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let diffuse_bind_group = texture_manager.create_texture_from_bytes(
|
||||
&device,
|
||||
&queue,
|
||||
include_bytes!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/res/images/happy-tree.png"
|
||||
)),
|
||||
"happy-tree.png",
|
||||
);
|
||||
|
||||
let diffuse_bind_group_pikachu = texture_manager.create_texture_from_bytes(
|
||||
&device,
|
||||
&queue,
|
||||
include_bytes!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/res/images/pikachu.png"
|
||||
)),
|
||||
"pikachu.png",
|
||||
);
|
||||
|
||||
let mut mesh = DefaultMesh::new(diffuse_bind_group, diffuse_bind_group_pikachu);
|
||||
mesh.initialize(&device);
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
config,
|
||||
size,
|
||||
render_pipeline,
|
||||
camera,
|
||||
depth_texture,
|
||||
mesh,
|
||||
texture_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.size = new_size;
|
||||
self.config.width = new_size.width;
|
||||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
self.depth_texture =
|
||||
Texture::create_depth_texture(&self.device, &self.config, "depth_texture");
|
||||
}
|
||||
|
||||
pub fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
self.mesh.process_events(&event) || self.camera.process_events(&event)
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.camera.update_instances(&self.queue);
|
||||
self.mesh.update_instances(&self.queue);
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[
|
||||
// This is what [[location(0)]] in the fragment shader targets
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_texture.view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
self.camera.prepare(&mut render_pass);
|
||||
self.mesh.prepare(&mut render_pass);
|
||||
self.camera.draw(&mut render_pass);
|
||||
self.mesh.draw(&mut render_pass);
|
||||
}
|
||||
|
||||
// submit will accept anything that implements IntoIter
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use anyhow::*;
|
||||
use image::GenericImageView;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
pub struct Texture {
|
||||
pub texture: wgpu::Texture,
|
||||
|
@ -9,14 +10,58 @@ pub struct Texture {
|
|||
|
||||
impl Texture {
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||
|
||||
pub fn create_depth_texture(
|
||||
device: &wgpu::Device,
|
||||
config: &wgpu::SurfaceConfiguration,
|
||||
label: &str,
|
||||
) -> Self {
|
||||
let size = wgpu::Extent3d {
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: Self::DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
};
|
||||
let texture = device.create_texture(&desc);
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
lod_min_clamp: -100.0,
|
||||
lod_max_clamp: 100.0,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
Self {
|
||||
texture,
|
||||
view,
|
||||
sampler,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_bytes(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bytes: &[u8],
|
||||
label: &str,
|
||||
is_normal_map: bool,
|
||||
) -> 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), is_normal_map)
|
||||
}
|
||||
|
||||
pub fn from_image(
|
||||
|
@ -24,9 +69,10 @@ impl Texture {
|
|||
queue: &wgpu::Queue,
|
||||
img: &image::DynamicImage,
|
||||
label: Option<&str>,
|
||||
is_normal_map: bool,
|
||||
) -> Result<Self> {
|
||||
let rgba = img.to_rgba8();
|
||||
let dimensions = img.dimensions();
|
||||
let rgba = img.to_rgba8();
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width: dimensions.0,
|
||||
|
@ -39,7 +85,11 @@ impl Texture {
|
|||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
format: if is_normal_map {
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
} else {
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||
},
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
});
|
||||
|
||||
|
@ -53,8 +103,8 @@ impl Texture {
|
|||
&rgba,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0),
|
||||
rows_per_image: std::num::NonZeroU32::new(dimensions.1),
|
||||
bytes_per_row: NonZeroU32::new(4 * dimensions.0),
|
||||
rows_per_image: NonZeroU32::new(dimensions.1),
|
||||
},
|
||||
size,
|
||||
);
|
||||
|
@ -76,41 +126,4 @@ impl Texture {
|
|||
sampler,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_depth_texture(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, label: &str) -> Self {
|
||||
let size = wgpu::Extent3d {
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: Self::DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
};
|
||||
let texture = device.create_texture(&desc);
|
||||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let sampler = device.create_sampler(
|
||||
&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
lod_min_clamp: -100.0,
|
||||
lod_max_clamp: 100.0,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
Self { texture, view, sampler }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue