Use hand texture as render background

This commit is contained in:
lenn
2026-06-26 16:59:58 +08:00
parent 0b658d11c5
commit b3fa3c8341
13 changed files with 1962 additions and 1375180 deletions

70
Cargo.lock generated
View File

@@ -559,6 +559,12 @@ dependencies = [
"arrayvec", "arrayvec",
] ]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.9.1" version = "0.9.1"
@@ -664,6 +670,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "byteorder-lite" name = "byteorder-lite"
version = "0.1.0" version = "0.1.0"
@@ -1303,6 +1315,7 @@ dependencies = [
"env_logger", "env_logger",
"fs_extra", "fs_extra",
"glam", "glam",
"gltf",
"image", "image",
"log", "log",
"serialport", "serialport",
@@ -1592,6 +1605,45 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "gltf"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7"
dependencies = [
"base64",
"byteorder",
"gltf-json",
"image",
"lazy_static",
"serde_json",
"urlencoding",
]
[[package]]
name = "gltf-derive"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51"
dependencies = [
"inflections",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "gltf-json"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14"
dependencies = [
"gltf-derive",
"serde",
"serde_derive",
"serde_json",
]
[[package]] [[package]]
name = "glutin" name = "glutin"
version = "0.32.3" version = "0.32.3"
@@ -1915,6 +1967,12 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "inflections"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
[[package]] [[package]]
name = "interpolate_name" name = "interpolate_name"
version = "0.2.4" version = "0.2.4"
@@ -2090,6 +2148,12 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "leb128fmt" name = "leb128fmt"
version = "0.1.0" version = "0.1.0"
@@ -4064,6 +4128,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]] [[package]]
name = "utf8_iter" name = "utf8_iter"
version = "1.0.4" version = "1.0.4"

View File

@@ -17,6 +17,7 @@ crossbeam-channel = "0.5.15"
crc = "3.4.0" crc = "3.4.0"
log = "0.4.29" log = "0.4.29"
tobj = "4.0.4" tobj = "4.0.4"
gltf = "1.4.1"
[build-dependencies] [build-dependencies]
anyhow = "1.0.102" anyhow = "1.0.102"

View File

@@ -1,14 +0,0 @@
# 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,232 +1,347 @@
# Blender 5.1.0 MTL File: 'None' # Blender 5.1.2 MTL File: '未命名.blend'
# www.blender.org # www.blender.org
newmtl mat_0 newmtl mat_0
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.439636 0.439636 0.439636 Kd 0.439636 0.439636 0.439636
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_1 newmtl mat_1
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.450786 0.545725 0.745404 Kd 0.450786 0.545725 0.745404
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_10 newmtl mat_10
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.473532 0.623934 0.545725 Kd 0.473532 0.623934 0.545725
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_11 newmtl mat_11
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.033105 0.033105 0.033105 Kd 0.033105 0.033105 0.033105
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_12 newmtl mat_12
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.022170 0.017642 0.015993 Kd 0.022170 0.017642 0.015993
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_13 newmtl mat_13
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.000000 0.000000 Kd 0.000000 0.000000 0.000000
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_14 newmtl mat_14
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.313971 0.401978 0.428669 Kd 0.313971 0.401978 0.428669
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_15 newmtl mat_15
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 1.000000 0.318547 0.000000 Kd 1.000000 0.318547 0.000000
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_16 newmtl mat_16
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.033105 0.132868 0.033105 Kd 0.033105 0.132868 0.033105
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_17 newmtl mat_17
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.623934 0.679542 0.806922 Kd 0.623934 0.679542 0.806922
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_18 newmtl mat_18
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.052861 0.038204 0.031891 Kd 0.052861 0.038204 0.031891
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_19 newmtl mat_19
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 1.000000 0.679542 0.644453 Kd 1.000000 0.679542 0.644453
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_2 newmtl mat_2
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 1.000000 0.278894 0.278894 Kd 1.000000 0.278894 0.278894
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_20 newmtl mat_20
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 1.000000 0.318547 0.033105 Kd 1.000000 0.318547 0.033105
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_21 newmtl mat_21
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 1.000000 0.396755 1.000000 Kd 1.000000 0.396755 1.000000
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_22 newmtl mat_22
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.930111 0.428669 0.230740 Kd 0.930111 0.428669 0.230740
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_3 newmtl mat_3
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.291771 0.258167 0.127438 Kd 0.291771 0.258167 0.127438
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_4 newmtl mat_4
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.318547 0.318547 0.318547 Kd 0.318547 0.318547 0.318547
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_5 newmtl mat_5
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.564712 0.391552 0.266356 Kd 0.564712 0.391552 0.266356
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_6 newmtl mat_6
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.318547 0.000000 Kd 0.000000 0.318547 0.000000
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_7 newmtl mat_7
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 1.000000 1.000000 0.000000 Kd 1.000000 1.000000 0.000000
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_8 newmtl mat_8
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.887891 0.887891 0.887891 Kd 0.887891 0.887891 0.887891
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000
newmtl mat_9 newmtl mat_9
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.527115 0.768151 0.737881 Kd 0.527115 0.768151 0.737881
Ks 0.500000 0.500000 0.500000 Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000 Ke 0.000000 0.000000 0.000000
Ni 1.500000 Ni 1.500000
d 1.000000 d 1.000000
illum 3 illum 3
Pr 1.000000
Pm 1.000000
Ps 0.000000
Pc 0.000000
Pcr 0.030000
aniso 0.000000
anisor 0.000000

1373826
res/hand.obj

File diff suppressed because it is too large Load Diff

BIN
res/hand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -10,8 +10,8 @@ use crate::{
}, },
ui::{ ui::{
ConfigPanelState, ConnectPanelState, FloatingPanelState, MatrixConfigState, ConfigPanelState, ConnectPanelState, FloatingPanelState, MatrixConfigState,
draw_config_panel, draw_connect_panel, draw_export_panel, draw_matrix_config_panel, draw_config_panel, draw_export_panel, draw_matrix_config_panel, draw_stats_panel,
draw_stats_panel, panel_restore_item, panel_restore_item,
}, },
}; };
use eframe::{egui, egui_wgpu}; use eframe::{egui, egui_wgpu};
@@ -257,14 +257,6 @@ impl EskinDesktopApp {
} }
fn draw_floating_panels(&mut self, ctx: &egui::Context) { fn draw_floating_panels(&mut self, ctx: &egui::Context) {
draw_connect_panel(
ctx,
&mut self.connect_panel,
&mut self.connect_state,
&self.connection,
&self.recorder,
&mut self.export_path,
);
// draw_scene_panel(ctx, &mut self.scene_panel); // draw_scene_panel(ctx, &mut self.scene_panel);
if let Some(next_mode) = if let Some(next_mode) =
draw_config_panel(ctx, &mut self.config_panel, &mut self.config_state) draw_config_panel(ctx, &mut self.config_panel, &mut self.config_state)
@@ -292,7 +284,6 @@ impl EskinDesktopApp {
ui.label("显示面板"); ui.label("显示面板");
ui.separator(); ui.separator();
panel_restore_item(ui, "连接", &mut self.connect_panel);
panel_restore_item(ui, "配置", &mut self.config_panel); panel_restore_item(ui, "配置", &mut self.config_panel);
panel_restore_item(ui, "录制", &mut self.export_panel); panel_restore_item(ui, "录制", &mut self.export_panel);
panel_restore_item(ui, "矩阵", &mut self.matrix_config_panel); panel_restore_item(ui, "矩阵", &mut self.matrix_config_panel);
@@ -301,7 +292,6 @@ impl EskinDesktopApp {
ui.separator(); ui.separator();
if ui.button("全部显示").clicked() { if ui.button("全部显示").clicked() {
self.connect_panel.visible = true;
self.config_panel.visible = true; self.config_panel.visible = true;
self.export_panel.visible = true; self.export_panel.visible = true;
self.matrix_config_panel.visible = true; self.matrix_config_panel.visible = true;

View File

@@ -2,6 +2,7 @@ use eframe::egui_wgpu::wgpu;
use std::ops::Range; use std::ops::Range;
use crate::texture; use crate::texture;
pub trait Vertex { pub trait Vertex {
fn desc<'a>() -> wgpu::VertexBufferLayout<'a>; fn desc<'a>() -> wgpu::VertexBufferLayout<'a>;
} }
@@ -12,6 +13,8 @@ pub struct ModelVertex {
pub position: [f32; 3], pub position: [f32; 3],
pub tex_coords: [f32; 2], pub tex_coords: [f32; 2],
pub normal: [f32; 3], pub normal: [f32; 3],
pub color: [f32; 4],
pub tangent: [f32; 4],
} }
impl Vertex for ModelVertex { impl Vertex for ModelVertex {
@@ -36,6 +39,16 @@ impl Vertex for ModelVertex {
shader_location: 2, shader_location: 2,
format: wgpu::VertexFormat::Float32x3, format: wgpu::VertexFormat::Float32x3,
}, },
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
},
], ],
} }
} }
@@ -54,7 +67,8 @@ impl Instance {
pub fn to_raw(&self) -> InstanceRaw { pub fn to_raw(&self) -> InstanceRaw {
InstanceRaw { InstanceRaw {
model: (glam::Mat4::from_translation(self.position) model: (glam::Mat4::from_translation(self.position)
* glam::Mat4::from_quat(self.rotation)) * glam::Mat4::from_quat(self.rotation)
* glam::Mat4::from_scale(glam::Vec3::splat(128.0)))
.to_cols_array_2d(), .to_cols_array_2d(),
} }
} }
@@ -111,9 +125,43 @@ pub struct Model {
pub materials: Vec<Material>, pub materials: Vec<Material>,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AlphaMode {
Opaque,
Mask,
Blend,
}
impl AlphaMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Opaque => "OPAQUE",
Self::Mask => "MASK",
Self::Blend => "BLEND",
}
}
pub fn shader_value(self) -> f32 {
match self {
Self::Opaque => 0.0,
Self::Mask => 1.0,
Self::Blend => 2.0,
}
}
}
pub struct Material { pub struct Material {
pub name: String, pub name: String,
pub diffuse_texture: texture::Texture, pub alpha_mode: AlphaMode,
pub alpha_cutoff: f32,
pub base_color_factor: [f32; 4],
pub double_sided: bool,
pub base_color_texture: texture::Texture,
pub metallic_roughness_texture: texture::Texture,
pub normal_texture: texture::Texture,
pub occlusion_texture: texture::Texture,
pub emissive_texture: texture::Texture,
pub params_buffer: wgpu::Buffer,
pub bind_group: wgpu::BindGroup, pub bind_group: wgpu::BindGroup,
} }
@@ -123,6 +171,7 @@ pub struct Mesh {
pub index_buffer: wgpu::Buffer, pub index_buffer: wgpu::Buffer,
pub num_elements: u32, pub num_elements: u32,
pub material: usize, pub material: usize,
pub center: [f32; 3],
} }
pub trait DrawModel<'a> { pub trait DrawModel<'a> {

View File

@@ -1,7 +1,7 @@
use crate::{ use crate::{
matrix::{MatrixLayout, build_view_projection, glyph_world_position}, matrix::{MatrixLayout, build_view_projection, glyph_world_position},
model::{Instance, InstanceRaw, Model, ModelVertex, Vertex}, model::{AlphaMode, InstanceRaw, ModelVertex, Vertex},
resources, resources, texture,
}; };
use eframe::{ use eframe::{
egui, egui,
@@ -69,20 +69,86 @@ pub struct BackgroundRenderResources {
layout: MatrixLayout, layout: MatrixLayout,
rows: u32, rows: u32,
cols: u32, cols: u32,
surface_is_srgb: bool,
uniform: MatrixUniform, uniform: MatrixUniform,
uniform_buffer: wgpu::Buffer, uniform_buffer: wgpu::Buffer,
uniform_bind_group: wgpu::BindGroup, uniform_bind_group: wgpu::BindGroup,
background_pipeline: wgpu::RenderPipeline, background_pipeline: wgpu::RenderPipeline,
hand_image_pipeline: wgpu::RenderPipeline,
glyph_pipeline: wgpu::RenderPipeline, glyph_pipeline: wgpu::RenderPipeline,
dot_pipeline: wgpu::RenderPipeline, dot_pipeline: wgpu::RenderPipeline,
model_pipeline: wgpu::RenderPipeline, hand_image_bind_group: wgpu::BindGroup,
hand_image_texture: texture::Texture,
model: Option<Model>,
model_instance_buffer: wgpu::Buffer,
glyph_vertex_buffer: wgpu::Buffer, glyph_vertex_buffer: wgpu::Buffer,
glyph_instance_buffer: wgpu::Buffer, glyph_instance_buffer: wgpu::Buffer,
glyph_instances: Vec<GlyphInstance>, glyph_instances: Vec<GlyphInstance>,
render_options: RenderOptions,
}
struct ModelPipelines {
opaque: ModelCullPipelines,
mask: ModelCullPipelines,
blend: ModelCullPipelines,
}
struct ModelCullPipelines {
single_sided: wgpu::RenderPipeline,
double_sided: wgpu::RenderPipeline,
}
#[derive(Copy, Clone, Debug)]
struct RenderOptions {
debug_mode: u32,
ambient_enabled: bool,
tone_mapping_enabled: bool,
exposure: f32,
}
impl RenderOptions {
fn from_env() -> Self {
let debug_mode = std::env::var("ESKIN_MATERIAL_DEBUG")
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(0);
let ambient_enabled = env_bool("ESKIN_AMBIENT", true);
let tone_mapping_enabled = env_bool("ESKIN_TONEMAP", true);
let exposure = std::env::var("ESKIN_EXPOSURE")
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(1.0);
log::warn!(
"render options material_debug={debug_mode} ambient_enabled={ambient_enabled} tone_mapping_enabled={tone_mapping_enabled} exposure={exposure:.3}"
);
Self {
debug_mode,
ambient_enabled,
tone_mapping_enabled,
exposure,
}
}
fn as_uniform(self) -> [f32; 4] {
[
self.debug_mode as f32,
if self.ambient_enabled { 1.0 } else { 0.0 },
if self.tone_mapping_enabled { 1.0 } else { 0.0 },
self.exposure,
]
}
}
fn env_bool(name: &str, default: bool) -> bool {
std::env::var(name)
.ok()
.map(|value| {
let value = value.to_ascii_lowercase();
matches!(value.as_str(), "1" | "true" | "yes" | "on")
})
.unwrap_or(default)
} }
impl BackgroundRenderResources { impl BackgroundRenderResources {
@@ -94,7 +160,40 @@ impl BackgroundRenderResources {
cols: u32, cols: u32,
) -> Self { ) -> Self {
let layout = MatrixLayout::new(rows, cols); let layout = MatrixLayout::new(rows, cols);
let uniform = MatrixUniform::new(1.0, 1.0, build_view_projection(1.0, &layout)); let surface_is_srgb = target_format.is_srgb();
let render_options = RenderOptions::from_env();
log::warn!("wgpu target surface format: {target_format:?}, srgb={surface_is_srgb}");
let hand_image_texture = match resources::load_texture("hand.png", device, _queue) {
Ok(texture) => texture,
Err(err) => {
log::warn!("failed to load hand.png background: {err:#}");
texture::Texture::from_rgba8(
device,
_queue,
&[0, 0, 0, 255],
1,
1,
"Fallback Hand Background Texture",
)
.expect("fallback hand texture should be valid")
}
};
log::warn!(
"using hand.png as render background: {}x{}",
hand_image_texture.width,
hand_image_texture.height
);
let uniform = MatrixUniform::new(
1.0,
1.0,
build_view_projection(1.0, &layout),
surface_is_srgb,
render_options,
[
hand_image_texture.width as f32,
hand_image_texture.height as f32,
],
);
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Pressure Matrix Uniform Buffer"), label: Some("Pressure Matrix Uniform Buffer"),
contents: bytemuck::cast_slice(&[uniform]), contents: bytemuck::cast_slice(&[uniform]),
@@ -140,9 +239,9 @@ impl BackgroundRenderResources {
// source: wgpu::ShaderSource::Wgsl(include_str!("../static/wgsl/shader.wgsl").into()), // source: wgpu::ShaderSource::Wgsl(include_str!("../static/wgsl/shader.wgsl").into()),
// }); // });
let texture_bind_group_layout = let hand_image_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("model_texture_bind_group_layout"), label: Some("hand_image_bind_group_layout"),
entries: &[ entries: &[
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 0, binding: 0,
@@ -163,11 +262,132 @@ impl BackgroundRenderResources {
], ],
}); });
let hand_image_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("hand_image_bind_group"),
layout: &hand_image_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&hand_image_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&hand_image_texture.sampler),
},
],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("model_texture_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 6,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 7,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 8,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 9,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 10,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Pressure Matrix Pipeline Layout"), label: Some("Pressure Matrix Pipeline Layout"),
bind_group_layouts: &[Some(&uniform_bind_group_layout)], bind_group_layouts: &[Some(&uniform_bind_group_layout)],
immediate_size: 0, immediate_size: 0,
}); });
let hand_image_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Hand Image Pipeline Layout"),
bind_group_layouts: &[
Some(&uniform_bind_group_layout),
Some(&hand_image_bind_group_layout),
],
immediate_size: 0,
});
let model_pipeline_layout = let model_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Hand Model Pipeline Layout"), label: Some("Hand Model Pipeline Layout"),
@@ -180,32 +400,14 @@ impl BackgroundRenderResources {
let background_pipeline = let background_pipeline =
create_background_pipeline(device, target_format, &shader, &pipeline_layout); create_background_pipeline(device, target_format, &shader, &pipeline_layout);
let hand_image_pipeline =
create_hand_image_pipeline(device, target_format, &shader, &hand_image_pipeline_layout);
let glyph_pipeline = let glyph_pipeline =
create_glyph_pipeline(device, target_format, &shader, &pipeline_layout); create_glyph_pipeline(device, target_format, &shader, &pipeline_layout);
let dot_pipeline = create_dot_pipeline(device, target_format, &shader, &pipeline_layout); let dot_pipeline = create_dot_pipeline(device, target_format, &shader, &pipeline_layout);
let model_pipeline = let _model_pipelines =
create_model_pipeline(device, target_format, &shader, &model_pipeline_layout); create_model_pipelines(device, target_format, &shader, &model_pipeline_layout);
let model =
match resources::load_model("hand.obj", device, _queue, &texture_bind_group_layout) {
Ok(model) => Some(model),
Err(err) => {
log::warn!("failed to load hand.obj: {err:#}");
None
}
};
let model_instance = Instance::new(
glam::Vec3::new(0.0, -2.0, 12.0),
glam::Quat::from_rotation_y(0.65) * glam::Quat::from_rotation_x(-0.35),
)
.to_raw();
let model_instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Hand Model Instance Buffer"),
contents: bytemuck::cast_slice(&[model_instance]),
usage: wgpu::BufferUsages::VERTEX,
});
let glyph_vertices = [ let glyph_vertices = [
GlyphVertex { GlyphVertex {
@@ -235,25 +437,36 @@ impl BackgroundRenderResources {
layout, layout,
rows, rows,
cols, cols,
surface_is_srgb,
uniform, uniform,
uniform_buffer, uniform_buffer,
uniform_bind_group, uniform_bind_group,
background_pipeline, background_pipeline,
hand_image_pipeline,
glyph_pipeline, glyph_pipeline,
dot_pipeline, dot_pipeline,
model_pipeline, hand_image_bind_group,
model, hand_image_texture,
model_instance_buffer,
glyph_vertex_buffer, glyph_vertex_buffer,
glyph_instance_buffer, glyph_instance_buffer,
glyph_instances, glyph_instances,
render_options,
} }
} }
fn prepare(&mut self, queue: &wgpu::Queue, width: f32, height: f32, pressure: &PressureFrame) { fn prepare(&mut self, queue: &wgpu::Queue, width: f32, height: f32, pressure: &PressureFrame) {
let aspect = width / height.max(1.0); let aspect = width / height.max(1.0);
self.uniform = self.uniform = MatrixUniform::new(
MatrixUniform::new(width, height, build_view_projection(aspect, &self.layout)); width,
height,
build_view_projection(aspect, &self.layout),
self.surface_is_srgb,
self.render_options,
[
self.hand_image_texture.width as f32,
self.hand_image_texture.height as f32,
],
);
queue.write_buffer( queue.write_buffer(
&self.uniform_buffer, &self.uniform_buffer,
0, 0,
@@ -280,6 +493,10 @@ impl BackgroundRenderResources {
render_pass.set_pipeline(&self.background_pipeline); render_pass.set_pipeline(&self.background_pipeline);
render_pass.draw(0..3, 0..1); render_pass.draw(0..3, 0..1);
render_pass.set_pipeline(&self.hand_image_pipeline);
render_pass.set_bind_group(1, &self.hand_image_bind_group, &[]);
render_pass.draw(0..6, 0..1);
match active_mode { match active_mode {
ActiveMode::Finger(mode) => self.paint_finger(render_pass, mode), ActiveMode::Finger(mode) => self.paint_finger(render_pass, mode),
ActiveMode::Hand(mode) => self.paint_hand(render_pass, mode), ActiveMode::Hand(mode) => self.paint_hand(render_pass, mode),
@@ -303,20 +520,7 @@ impl BackgroundRenderResources {
fn paint_hand(&self, render_pass: &mut wgpu::RenderPass<'_>, mode: &HandGatewayMode) { fn paint_hand(&self, render_pass: &mut wgpu::RenderPass<'_>, mode: &HandGatewayMode) {
let _range = mode.range.clone(); let _range = mode.range.clone();
let _ = render_pass;
if let Some(model) = &self.model {
render_pass.set_pipeline(&self.model_pipeline);
render_pass.set_vertex_buffer(1, self.model_instance_buffer.slice(..));
for mesh in &model.meshes {
if let Some(material) = model.materials.get(mesh.material) {
render_pass.set_bind_group(1, &material.bind_group, &[]);
}
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
render_pass
.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..mesh.num_elements, 0, 0..1);
}
}
} }
fn visible_instance_count(&self, rows: u32, cols: u32) -> u32 { fn visible_instance_count(&self, rows: u32, cols: u32) -> u32 {
@@ -358,6 +562,39 @@ fn create_background_pipeline(
}) })
} }
fn create_hand_image_pipeline(
device: &wgpu::Device,
target_format: &wgpu::TextureFormat,
shader: &wgpu::ShaderModule,
layout: &wgpu::PipelineLayout,
) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Hand Image Background Pipeline"),
layout: Some(layout),
vertex: wgpu::VertexState {
module: shader,
entry_point: Some("vs_hand_image"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: shader,
entry_point: Some("fs_hand_image"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: *target_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
})
}
fn create_glyph_pipeline( fn create_glyph_pipeline(
device: &wgpu::Device, device: &wgpu::Device,
target_format: &wgpu::TextureFormat, target_format: &wgpu::TextureFormat,
@@ -391,14 +628,88 @@ fn create_glyph_pipeline(
}) })
} }
fn create_model_pipelines(
device: &wgpu::Device,
target_format: &wgpu::TextureFormat,
shader: &wgpu::ShaderModule,
layout: &wgpu::PipelineLayout,
) -> ModelPipelines {
ModelPipelines {
opaque: ModelCullPipelines {
single_sided: create_model_pipeline(
device,
target_format,
shader,
layout,
AlphaMode::Opaque,
false,
),
double_sided: create_model_pipeline(
device,
target_format,
shader,
layout,
AlphaMode::Opaque,
true,
),
},
mask: ModelCullPipelines {
single_sided: create_model_pipeline(
device,
target_format,
shader,
layout,
AlphaMode::Mask,
false,
),
double_sided: create_model_pipeline(
device,
target_format,
shader,
layout,
AlphaMode::Mask,
true,
),
},
blend: ModelCullPipelines {
single_sided: create_model_pipeline(
device,
target_format,
shader,
layout,
AlphaMode::Blend,
false,
),
double_sided: create_model_pipeline(
device,
target_format,
shader,
layout,
AlphaMode::Blend,
true,
),
},
}
}
fn create_model_pipeline( fn create_model_pipeline(
device: &wgpu::Device, device: &wgpu::Device,
target_format: &wgpu::TextureFormat, target_format: &wgpu::TextureFormat,
shader: &wgpu::ShaderModule, shader: &wgpu::ShaderModule,
layout: &wgpu::PipelineLayout, layout: &wgpu::PipelineLayout,
alpha_mode: AlphaMode,
double_sided: bool,
) -> wgpu::RenderPipeline { ) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Hand Model Pipeline"), label: Some(&format!(
"Hand Model {} {} Pipeline",
alpha_mode.as_str(),
if double_sided {
"Double Sided"
} else {
"Single Sided"
}
)),
layout: Some(layout), layout: Some(layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: shader, module: shader,
@@ -412,10 +723,11 @@ fn create_model_pipeline(
compilation_options: Default::default(), compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format: *target_format, format: *target_format,
blend: Some(wgpu::BlendState { blend: if alpha_mode == AlphaMode::Blend {
color: wgpu::BlendComponent::REPLACE, Some(wgpu::BlendState::ALPHA_BLENDING)
alpha: wgpu::BlendComponent::REPLACE, } else {
}), None
},
write_mask: wgpu::ColorWrites::ALL, write_mask: wgpu::ColorWrites::ALL,
})], })],
}), }),
@@ -423,7 +735,11 @@ fn create_model_pipeline(
topology: wgpu::PrimitiveTopology::TriangleList, topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None, strip_index_format: None,
front_face: wgpu::FrontFace::Ccw, front_face: wgpu::FrontFace::Ccw,
cull_mode: None, cull_mode: if double_sided {
None
} else {
Some(wgpu::Face::Back)
},
polygon_mode: wgpu::PolygonMode::Fill, polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false, unclipped_depth: false,
conservative: false, conservative: false,
@@ -524,15 +840,26 @@ struct MatrixUniform {
viewport: [f32; 4], viewport: [f32; 4],
glyph: [f32; 4], glyph: [f32; 4],
color: [f32; 4], color: [f32; 4],
render_options: [f32; 4],
image: [f32; 4],
} }
impl MatrixUniform { impl MatrixUniform {
fn new(width: f32, height: f32, view_proj: [[f32; 4]; 4]) -> Self { fn new(
width: f32,
height: f32,
view_proj: [[f32; 4]; 4],
surface_is_srgb: bool,
render_options: RenderOptions,
image_size: [f32; 2],
) -> Self {
Self { Self {
view_proj, view_proj,
viewport: [width.max(1.0), height.max(1.0), 0.0, 0.0], viewport: [width.max(1.0), height.max(1.0), 0.0, 0.0],
glyph: [16.0, 0.0, 0.0, 0.0], glyph: [16.0, 0.0, 0.0, 0.0],
color: [0.05, 0.92, 0.32, 1.0], color: [0.05, 0.92, 0.32, if surface_is_srgb { 1.0 } else { 0.0 }],
render_options: render_options.as_uniform(),
image: [image_size[0].max(1.0), image_size[1].max(1.0), 0.0, 0.0],
} }
} }
} }

View File

@@ -1,8 +1,26 @@
use crate::{model, texture}; use crate::{
model::{self, AlphaMode},
texture::{self, TextureColorSpace, TextureSamplerDescriptor},
};
use anyhow::{Context, bail};
use eframe::wgpu; use eframe::wgpu;
use eframe::wgpu::util::DeviceExt; use eframe::wgpu::util::DeviceExt;
use glam::{Mat3, Mat4, Vec2, Vec3};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
const WHITE_TEXTURE: &[u8; 4] = &[255, 255, 255, 255];
const BLACK_TEXTURE: &[u8; 4] = &[0, 0, 0, 255];
const FLAT_NORMAL_TEXTURE: &[u8; 4] = &[128, 128, 255, 255];
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct MaterialParams {
base_color: [f32; 4],
metallic_roughness: [f32; 4],
emissive_alpha: [f32; 4],
flags: [f32; 4],
}
fn resource_path(file_name: &str) -> PathBuf { fn resource_path(file_name: &str) -> PathBuf {
Path::new(env!("RESOURCE_DIR")).join(file_name) Path::new(env!("RESOURCE_DIR")).join(file_name)
} }
@@ -29,6 +47,26 @@ pub fn load_model(
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
texture_bind_group_layout: &wgpu::BindGroupLayout, texture_bind_group_layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Model> {
match Path::new(file_name)
.extension()
.and_then(|extension| extension.to_str())
.map(str::to_ascii_lowercase)
.as_deref()
{
Some("obj") => load_obj_model(file_name, device, queue, texture_bind_group_layout),
Some("gltf") | Some("glb") => {
load_gltf_model(file_name, device, queue, texture_bind_group_layout)
}
_ => bail!("unsupported model format: {file_name}"),
}
}
fn load_obj_model(
file_name: &str,
device: &wgpu::Device,
queue: &wgpu::Queue,
texture_bind_group_layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Model> { ) -> anyhow::Result<model::Model> {
let path = resource_path(file_name); let path = resource_path(file_name);
let (models, obj_materials) = tobj::load_obj( let (models, obj_materials) = tobj::load_obj(
@@ -42,44 +80,93 @@ pub fn load_model(
let mut materials = Vec::new(); let mut materials = Vec::new();
for material in obj_materials? { for material in obj_materials? {
let diffuse_texture = match material.diffuse_texture.as_deref() { let base_color = material
.diffuse
.map(|diffuse| [diffuse[0], diffuse[1], diffuse[2], 1.0])
.unwrap_or([1.0, 1.0, 1.0, 1.0]);
let base_color_texture = match material.diffuse_texture.as_deref() {
Some(texture_path) => load_texture(texture_path, device, queue)?, Some(texture_path) => load_texture(texture_path, device, queue)?,
None => texture::Texture::from_rgba8( None => default_texture(
device, device,
queue, queue,
&[255, 255, 255, 255],
1,
1,
&material.name, &material.name,
TextureColorSpace::Srgb,
WHITE_TEXTURE,
)?, )?,
}; };
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { let material_textures = MaterialTextures {
label: Some(&format!("{} Texture Bind Group", material.name)), base_color_texture,
layout: texture_bind_group_layout, metallic_roughness_texture: default_texture(
entries: &[ device,
wgpu::BindGroupEntry { queue,
binding: 0, &format!("{} metallic-roughness", material.name),
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), TextureColorSpace::Linear,
}, WHITE_TEXTURE,
wgpu::BindGroupEntry { )?,
binding: 1, normal_texture: default_texture(
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), device,
}, queue,
], &format!("{} normal", material.name),
}); TextureColorSpace::Linear,
FLAT_NORMAL_TEXTURE,
)?,
occlusion_texture: default_texture(
device,
queue,
&format!("{} occlusion", material.name),
TextureColorSpace::Linear,
WHITE_TEXTURE,
)?,
emissive_texture: default_texture(
device,
queue,
&format!("{} emissive", material.name),
TextureColorSpace::Srgb,
BLACK_TEXTURE,
)?,
};
let params = MaterialParams {
base_color,
metallic_roughness: [0.0, 0.5, 1.0, 1.0],
emissive_alpha: [0.0, 0.0, 0.0, 0.0],
flags: [0.5, 0.0, 0.0, 0.0],
};
log_material_debug(
materials.len(),
&material.name,
AlphaMode::Opaque,
0.5,
base_color[3],
false,
);
materials.push(create_material(
device,
texture_bind_group_layout,
material.name,
material_textures,
params,
AlphaMode::Opaque,
0.5,
base_color,
false,
));
}
materials.push(model::Material { if materials.is_empty() {
name: material.name, materials.push(create_color_material(
diffuse_texture, "default",
bind_group, [1.0, 1.0, 1.0, 1.0],
}); device,
queue,
texture_bind_group_layout,
)?);
} }
let meshes = models let meshes = models
.into_iter() .into_iter()
.map(|m| { .map(|m| {
let vertices = (0..m.mesh.positions.len() / 3) let mut vertices = (0..m.mesh.positions.len() / 3)
.map(|i| model::ModelVertex { .map(|i| model::ModelVertex {
position: [ position: [
m.mesh.positions[i * 3], m.mesh.positions[i * 3],
@@ -87,7 +174,7 @@ pub fn load_model(
m.mesh.positions[i * 3 + 2], m.mesh.positions[i * 3 + 2],
], ],
tex_coords: if m.mesh.texcoords.len() >= i * 2 + 2 { tex_coords: if m.mesh.texcoords.len() >= i * 2 + 2 {
[m.mesh.texcoords[i * 2], m.mesh.texcoords[i * 2 + 1]] [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]]
} else { } else {
[0.0, 0.0] [0.0, 0.0]
}, },
@@ -100,8 +187,11 @@ pub fn load_model(
} else { } else {
[0.0, 1.0, 0.0] [0.0, 1.0, 0.0]
}, },
color: [1.0, 1.0, 1.0, 1.0],
tangent: [1.0, 0.0, 0.0, 1.0],
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
generate_tangents(&mut vertices, &m.mesh.indices);
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{} Vertex Buffer", m.name)), label: Some(&format!("{} Vertex Buffer", m.name)),
@@ -120,9 +210,798 @@ pub fn load_model(
index_buffer, index_buffer,
num_elements: m.mesh.indices.len() as u32, num_elements: m.mesh.indices.len() as u32,
material: m.mesh.material_id.unwrap_or(0), material: m.mesh.material_id.unwrap_or(0),
center: mesh_center(&vertices),
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(model::Model { meshes, materials }) Ok(model::Model { meshes, materials })
} }
fn load_gltf_model(
file_name: &str,
device: &wgpu::Device,
queue: &wgpu::Queue,
texture_bind_group_layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Model> {
let path = resource_path(file_name);
let (document, buffers, images) =
gltf::import(&path).with_context(|| format!("failed to import {file_name}"))?;
let mut materials = Vec::new();
for material in document.materials() {
materials.push(create_gltf_material(
&material,
&images,
device,
queue,
texture_bind_group_layout,
)?);
}
let default_material = materials.len();
materials.push(create_color_material(
"default",
[1.0, 1.0, 1.0, 1.0],
device,
queue,
texture_bind_group_layout,
)?);
let mut meshes = Vec::new();
let scene = document
.default_scene()
.or_else(|| document.scenes().next())
.context("glTF has no scene")?;
for node in scene.nodes() {
append_gltf_node_meshes(
&node,
Mat4::IDENTITY,
&buffers,
device,
&mut meshes,
default_material,
)?;
}
Ok(model::Model { meshes, materials })
}
struct MaterialTextures {
base_color_texture: texture::Texture,
metallic_roughness_texture: texture::Texture,
normal_texture: texture::Texture,
occlusion_texture: texture::Texture,
emissive_texture: texture::Texture,
}
fn create_gltf_material(
material: &gltf::Material<'_>,
images: &[gltf::image::Data],
device: &wgpu::Device,
queue: &wgpu::Queue,
texture_bind_group_layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Material> {
let pbr = material.pbr_metallic_roughness();
let name = material.name().unwrap_or("gltf_material");
let normal_info = material.normal_texture();
let occlusion_info = material.occlusion_texture();
let material_textures = MaterialTextures {
base_color_texture: texture_from_gltf_texture(
pbr.base_color_texture().map(|info| info.texture()),
images,
device,
queue,
&format!("{name} baseColor"),
TextureColorSpace::Srgb,
WHITE_TEXTURE,
)?,
metallic_roughness_texture: texture_from_gltf_texture(
pbr.metallic_roughness_texture().map(|info| info.texture()),
images,
device,
queue,
&format!("{name} metallicRoughness"),
TextureColorSpace::Linear,
WHITE_TEXTURE,
)?,
normal_texture: texture_from_gltf_texture(
normal_info.as_ref().map(|info| info.texture()),
images,
device,
queue,
&format!("{name} normal"),
TextureColorSpace::Linear,
FLAT_NORMAL_TEXTURE,
)?,
occlusion_texture: texture_from_gltf_texture(
occlusion_info.as_ref().map(|info| info.texture()),
images,
device,
queue,
&format!("{name} occlusion"),
TextureColorSpace::Linear,
WHITE_TEXTURE,
)?,
emissive_texture: texture_from_gltf_texture(
material.emissive_texture().map(|info| info.texture()),
images,
device,
queue,
&format!("{name} emissive"),
TextureColorSpace::Srgb,
BLACK_TEXTURE,
)?,
};
let alpha_mode = match material.alpha_mode() {
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => AlphaMode::Mask,
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
};
let normal_scale = normal_info.as_ref().map(|info| info.scale()).unwrap_or(1.0);
let occlusion_strength = occlusion_info
.as_ref()
.map(|info| info.strength())
.unwrap_or(1.0);
let emissive_factor = material.emissive_factor();
let base_color_factor = pbr.base_color_factor();
let alpha_cutoff = material.alpha_cutoff().unwrap_or(0.5);
let double_sided = material.double_sided();
let params = MaterialParams {
base_color: base_color_factor,
metallic_roughness: [
pbr.metallic_factor(),
pbr.roughness_factor(),
normal_scale,
occlusion_strength,
],
emissive_alpha: [
emissive_factor[0],
emissive_factor[1],
emissive_factor[2],
alpha_mode.shader_value(),
],
flags: [
alpha_cutoff,
0.0,
if normal_info.is_some() { 1.0 } else { 0.0 },
if double_sided { 1.0 } else { 0.0 },
],
};
log_material_debug(
material.index().unwrap_or(usize::MAX),
name,
alpha_mode,
alpha_cutoff,
base_color_factor[3],
double_sided,
);
Ok(create_material(
device,
texture_bind_group_layout,
name.to_owned(),
material_textures,
params,
alpha_mode,
alpha_cutoff,
base_color_factor,
double_sided,
))
}
fn create_color_material(
name: &str,
color: [f32; 4],
device: &wgpu::Device,
queue: &wgpu::Queue,
texture_bind_group_layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Material> {
let material_textures = MaterialTextures {
base_color_texture: default_texture(
device,
queue,
name,
TextureColorSpace::Srgb,
WHITE_TEXTURE,
)?,
metallic_roughness_texture: default_texture(
device,
queue,
&format!("{name} metallic-roughness"),
TextureColorSpace::Linear,
WHITE_TEXTURE,
)?,
normal_texture: default_texture(
device,
queue,
&format!("{name} normal"),
TextureColorSpace::Linear,
FLAT_NORMAL_TEXTURE,
)?,
occlusion_texture: default_texture(
device,
queue,
&format!("{name} occlusion"),
TextureColorSpace::Linear,
WHITE_TEXTURE,
)?,
emissive_texture: default_texture(
device,
queue,
&format!("{name} emissive"),
TextureColorSpace::Srgb,
BLACK_TEXTURE,
)?,
};
let params = MaterialParams {
base_color: color,
metallic_roughness: [1.0, 1.0, 1.0, 1.0],
emissive_alpha: [0.0, 0.0, 0.0, 0.0],
flags: [0.5, 0.0, 0.0, 0.0],
};
log_material_debug(usize::MAX, name, AlphaMode::Opaque, 0.5, color[3], false);
Ok(create_material(
device,
texture_bind_group_layout,
name.to_owned(),
material_textures,
params,
AlphaMode::Opaque,
0.5,
color,
false,
))
}
fn create_material(
device: &wgpu::Device,
texture_bind_group_layout: &wgpu::BindGroupLayout,
name: String,
textures: MaterialTextures,
params: MaterialParams,
alpha_mode: AlphaMode,
alpha_cutoff: f32,
base_color_factor: [f32; 4],
double_sided: bool,
) -> model::Material {
let params_buffer = create_material_params_buffer(device, &name, params);
let bind_group = create_texture_bind_group(
device,
texture_bind_group_layout,
&name,
&textures,
&params_buffer,
);
model::Material {
name,
alpha_mode,
alpha_cutoff,
base_color_factor,
double_sided,
base_color_texture: textures.base_color_texture,
metallic_roughness_texture: textures.metallic_roughness_texture,
normal_texture: textures.normal_texture,
occlusion_texture: textures.occlusion_texture,
emissive_texture: textures.emissive_texture,
params_buffer,
bind_group,
}
}
fn default_texture(
device: &wgpu::Device,
queue: &wgpu::Queue,
label: &str,
color_space: TextureColorSpace,
rgba: &[u8; 4],
) -> anyhow::Result<texture::Texture> {
texture::Texture::from_rgba8_with_sampler(
device,
queue,
rgba,
1,
1,
label,
color_space,
TextureSamplerDescriptor::default(),
)
}
fn create_material_params_buffer(
device: &wgpu::Device,
name: &str,
params: MaterialParams,
) -> wgpu::Buffer {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{name} Material Params Buffer")),
contents: bytemuck::cast_slice(&[params]),
usage: wgpu::BufferUsages::UNIFORM,
})
}
fn create_texture_bind_group(
device: &wgpu::Device,
texture_bind_group_layout: &wgpu::BindGroupLayout,
name: &str,
textures: &MaterialTextures,
params_buffer: &wgpu::Buffer,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("{name} Texture Bind Group")),
layout: texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&textures.base_color_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&textures.base_color_texture.sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: params_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(
&textures.metallic_roughness_texture.view,
),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::Sampler(
&textures.metallic_roughness_texture.sampler,
),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(&textures.normal_texture.view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::Sampler(&textures.normal_texture.sampler),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(&textures.occlusion_texture.view),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::Sampler(&textures.occlusion_texture.sampler),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::TextureView(&textures.emissive_texture.view),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::Sampler(&textures.emissive_texture.sampler),
},
],
})
}
fn texture_from_gltf_texture(
gltf_texture: Option<gltf::Texture<'_>>,
images: &[gltf::image::Data],
device: &wgpu::Device,
queue: &wgpu::Queue,
label: &str,
color_space: TextureColorSpace,
fallback_rgba: &[u8; 4],
) -> anyhow::Result<texture::Texture> {
let Some(gltf_texture) = gltf_texture else {
return default_texture(device, queue, label, color_space, fallback_rgba);
};
let image_index = gltf_texture.source().index();
let image = images
.get(image_index)
.with_context(|| format!("glTF texture references missing image {image_index}"))?;
let rgba = gltf_image_to_rgba8(image)?;
let sampler = gltf_sampler_descriptor(&gltf_texture.sampler());
texture::Texture::from_rgba8_with_sampler(
device,
queue,
&rgba,
image.width,
image.height,
&format!("{label} image {image_index} {color_space:?}"),
color_space,
sampler,
)
}
fn gltf_sampler_descriptor(sampler: &gltf::texture::Sampler<'_>) -> TextureSamplerDescriptor {
use gltf::texture::{MagFilter, MinFilter};
let mag_filter = match sampler.mag_filter().unwrap_or(MagFilter::Linear) {
MagFilter::Nearest => wgpu::FilterMode::Nearest,
MagFilter::Linear => wgpu::FilterMode::Linear,
};
let (min_filter, mipmap_filter) = match sampler.min_filter().unwrap_or(MinFilter::Linear) {
MinFilter::Nearest | MinFilter::NearestMipmapNearest | MinFilter::NearestMipmapLinear => {
(wgpu::FilterMode::Nearest, wgpu::MipmapFilterMode::Nearest)
}
MinFilter::Linear | MinFilter::LinearMipmapNearest => {
(wgpu::FilterMode::Linear, wgpu::MipmapFilterMode::Nearest)
}
MinFilter::LinearMipmapLinear => (wgpu::FilterMode::Linear, wgpu::MipmapFilterMode::Linear),
};
TextureSamplerDescriptor {
address_mode_u: gltf_wrap_mode(sampler.wrap_s()),
address_mode_v: gltf_wrap_mode(sampler.wrap_t()),
mag_filter,
min_filter,
mipmap_filter,
}
}
fn gltf_wrap_mode(mode: gltf::texture::WrappingMode) -> wgpu::AddressMode {
use gltf::texture::WrappingMode;
match mode {
WrappingMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
WrappingMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
WrappingMode::Repeat => wgpu::AddressMode::Repeat,
}
}
fn append_gltf_node_meshes(
node: &gltf::Node<'_>,
parent_transform: Mat4,
buffers: &[gltf::buffer::Data],
device: &wgpu::Device,
meshes: &mut Vec<model::Mesh>,
default_material: usize,
) -> anyhow::Result<()> {
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
let world_transform = parent_transform * local_transform;
if let Some(gltf_mesh) = node.mesh() {
let node_name = node.name().unwrap_or("gltf_node");
let mesh_index = gltf_mesh.index();
let mesh_name = node
.name()
.or_else(|| gltf_mesh.name())
.unwrap_or("gltf_mesh");
for (primitive_index, primitive) in gltf_mesh.primitives().enumerate() {
append_gltf_primitive_mesh(
mesh_name,
primitive_index,
&primitive,
world_transform,
buffers,
device,
meshes,
default_material,
node_name,
mesh_index,
)?;
}
}
for child in node.children() {
append_gltf_node_meshes(
&child,
world_transform,
buffers,
device,
meshes,
default_material,
)?;
}
Ok(())
}
fn append_gltf_primitive_mesh(
mesh_name: &str,
primitive_index: usize,
primitive: &gltf::Primitive<'_>,
world_transform: Mat4,
buffers: &[gltf::buffer::Data],
device: &wgpu::Device,
meshes: &mut Vec<model::Mesh>,
default_material: usize,
node_name: &str,
mesh_index: usize,
) -> anyhow::Result<()> {
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
let positions = reader
.read_positions()
.with_context(|| format!("{mesh_name} primitive {primitive_index} has no positions"))?
.collect::<Vec<_>>();
let normals = reader
.read_normals()
.map(|iter| iter.collect::<Vec<_>>())
.unwrap_or_else(|| vec![[0.0, 1.0, 0.0]; positions.len()]);
let tex_coords = reader
.read_tex_coords(0)
.map(|coords| coords.into_f32().collect::<Vec<_>>())
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
let colors = reader
.read_colors(0)
.map(|colors| colors.into_rgba_f32().collect::<Vec<_>>())
.unwrap_or_else(|| vec![[1.0, 1.0, 1.0, 1.0]; positions.len()]);
let tangents = reader.read_tangents().map(|iter| iter.collect::<Vec<_>>());
let has_tangents = tangents.is_some();
let tangents = tangents.unwrap_or_else(|| vec![[1.0, 0.0, 0.0, 1.0]; positions.len()]);
let normal_transform = Mat3::from_mat4(world_transform.inverse().transpose());
let mut vertices = positions
.iter()
.enumerate()
.map(|(index, position)| {
let world_position = world_transform
.transform_point3(Vec3::from_array(*position))
.to_array();
let world_normal = normal_transform
.mul_vec3(Vec3::from_array(
normals.get(index).copied().unwrap_or([0.0, 1.0, 0.0]),
))
.normalize_or_zero()
.to_array();
let tangent = tangents.get(index).copied().unwrap_or([1.0, 0.0, 0.0, 1.0]);
let world_tangent = normal_transform
.mul_vec3(Vec3::new(tangent[0], tangent[1], tangent[2]))
.normalize_or_zero();
model::ModelVertex {
position: world_position,
tex_coords: tex_coords.get(index).copied().unwrap_or([0.0, 0.0]),
normal: world_normal,
color: colors.get(index).copied().unwrap_or([1.0, 1.0, 1.0, 1.0]),
tangent: [
world_tangent.x,
world_tangent.y,
world_tangent.z,
tangent[3],
],
}
})
.collect::<Vec<_>>();
let indices = reader
.read_indices()
.map(|indices| indices.into_u32().collect::<Vec<_>>())
.unwrap_or_else(|| (0..vertices.len() as u32).collect());
let center = mesh_center(&vertices);
if !has_tangents {
generate_tangents(&mut vertices, &indices);
}
log_primitive_material_debug(
node_name,
mesh_index,
primitive_index,
&primitive.material(),
default_material,
);
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!(
"{mesh_name} Primitive {primitive_index} Vertex Buffer"
)),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!(
"{mesh_name} Primitive {primitive_index} Index Buffer"
)),
contents: bytemuck::cast_slice(&indices),
usage: wgpu::BufferUsages::INDEX,
});
meshes.push(model::Mesh {
name: format!("{mesh_name}_{primitive_index}"),
vertex_buffer,
index_buffer,
num_elements: indices.len() as u32,
material: primitive.material().index().unwrap_or(default_material),
center,
});
Ok(())
}
fn mesh_center(vertices: &[model::ModelVertex]) -> [f32; 3] {
if vertices.is_empty() {
return [0.0, 0.0, 0.0];
}
let sum = vertices.iter().fold(Vec3::ZERO, |sum, vertex| {
sum + Vec3::from_array(vertex.position)
});
(sum / vertices.len() as f32).to_array()
}
fn log_material_debug(
index: usize,
name: &str,
alpha_mode: AlphaMode,
alpha_cutoff: f32,
base_color_factor_alpha: f32,
double_sided: bool,
) {
log::warn!(
"material index={index} name=\"{name}\" alpha_mode={} alpha_cutoff={alpha_cutoff:.3} base_color_factor_alpha={base_color_factor_alpha:.3} double_sided={double_sided} selected_pipeline={}",
alpha_mode.as_str(),
selected_pipeline_label(alpha_mode, double_sided),
);
}
fn log_primitive_material_debug(
node_name: &str,
mesh_index: usize,
primitive_index: usize,
material: &gltf::Material<'_>,
default_material: usize,
) {
let pbr = material.pbr_metallic_roughness();
let material_index = material.index().unwrap_or(default_material);
let material_name = material.name().unwrap_or("default");
log::warn!(
"primitive node=\"{node_name}\" mesh_index={mesh_index} primitive_index={primitive_index} material_index={material_index} material_name=\"{material_name}\" baseColorFactor={:?} metallicFactor={:.3} roughnessFactor={:.3} alphaMode={:?}",
pbr.base_color_factor(),
pbr.metallic_factor(),
pbr.roughness_factor(),
material.alpha_mode(),
);
}
fn selected_pipeline_label(alpha_mode: AlphaMode, double_sided: bool) -> String {
format!(
"{}_{}",
alpha_mode.as_str(),
if double_sided {
"DOUBLE_SIDED"
} else {
"SINGLE_SIDED"
}
)
}
fn generate_tangents(vertices: &mut [model::ModelVertex], indices: &[u32]) {
let mut accumulated = vec![Vec3::ZERO; vertices.len()];
for triangle in indices.chunks_exact(3) {
let [i0, i1, i2] = [
triangle[0] as usize,
triangle[1] as usize,
triangle[2] as usize,
];
if i0 >= vertices.len() || i1 >= vertices.len() || i2 >= vertices.len() {
continue;
}
let p0 = Vec3::from_array(vertices[i0].position);
let p1 = Vec3::from_array(vertices[i1].position);
let p2 = Vec3::from_array(vertices[i2].position);
let uv0 = Vec2::from_array(vertices[i0].tex_coords);
let uv1 = Vec2::from_array(vertices[i1].tex_coords);
let uv2 = Vec2::from_array(vertices[i2].tex_coords);
let delta_pos1 = p1 - p0;
let delta_pos2 = p2 - p0;
let delta_uv1 = uv1 - uv0;
let delta_uv2 = uv2 - uv0;
let determinant = delta_uv1.x * delta_uv2.y - delta_uv1.y * delta_uv2.x;
if determinant.abs() < 1e-6 {
continue;
}
let tangent = (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) / determinant;
accumulated[i0] += tangent;
accumulated[i1] += tangent;
accumulated[i2] += tangent;
}
for (vertex, tangent) in vertices.iter_mut().zip(accumulated) {
let normal = Vec3::from_array(vertex.normal).normalize_or_zero();
let tangent = (tangent - normal * normal.dot(tangent)).normalize_or_zero();
let tangent = if tangent.length_squared() > 0.0 {
tangent
} else {
fallback_tangent(normal)
};
vertex.tangent = [tangent.x, tangent.y, tangent.z, 1.0];
}
}
fn fallback_tangent(normal: Vec3) -> Vec3 {
let axis = if normal.y.abs() < 0.9 {
Vec3::Y
} else {
Vec3::X
};
normal.cross(axis).normalize_or_zero()
}
fn gltf_image_to_rgba8(image: &gltf::image::Data) -> anyhow::Result<Vec<u8>> {
use gltf::image::Format;
match image.format {
Format::R8G8B8A8 => Ok(image.pixels.clone()),
Format::R8G8B8 => {
let mut rgba = Vec::with_capacity((image.width * image.height * 4) as usize);
for rgb in image.pixels.chunks_exact(3) {
rgba.extend_from_slice(&[rgb[0], rgb[1], rgb[2], 255]);
}
Ok(rgba)
}
Format::R8 => {
let mut rgba = Vec::with_capacity((image.width * image.height * 4) as usize);
for value in &image.pixels {
rgba.extend_from_slice(&[*value, *value, *value, 255]);
}
Ok(rgba)
}
Format::R8G8 => {
let mut rgba = Vec::with_capacity((image.width * image.height * 4) as usize);
for rg in image.pixels.chunks_exact(2) {
rgba.extend_from_slice(&[rg[0], rg[1], 0, 255]);
}
Ok(rgba)
}
Format::R16 => rgba_from_chunks(&image.pixels, 1, 2, read_u16_component),
Format::R16G16 => rgba_from_chunks(&image.pixels, 2, 2, read_u16_component),
Format::R16G16B16 => rgba_from_chunks(&image.pixels, 3, 2, read_u16_component),
Format::R16G16B16A16 => rgba_from_chunks(&image.pixels, 4, 2, read_u16_component),
Format::R32G32B32FLOAT => rgba_from_chunks(&image.pixels, 3, 4, read_f32_component),
Format::R32G32B32A32FLOAT => rgba_from_chunks(&image.pixels, 4, 4, read_f32_component),
}
}
fn rgba_from_chunks(
pixels: &[u8],
channels: usize,
component_width: usize,
read_component: fn(&[u8]) -> u8,
) -> anyhow::Result<Vec<u8>> {
let pixel_width = channels * component_width;
if pixel_width == 0 || pixels.len() % pixel_width != 0 {
bail!("invalid glTF image byte length for {channels} channels");
}
let mut rgba = Vec::with_capacity((pixels.len() / pixel_width) * 4);
for pixel in pixels.chunks_exact(pixel_width) {
let r = read_component(&pixel[0..component_width]);
let g = if channels > 1 {
read_component(&pixel[component_width..component_width * 2])
} else {
r
};
let b = if channels > 2 {
read_component(&pixel[component_width * 2..component_width * 3])
} else {
r
};
let a = if channels > 3 {
read_component(&pixel[component_width * 3..component_width * 4])
} else {
255
};
rgba.extend_from_slice(&[r, g, b, a]);
}
Ok(rgba)
}
fn read_u16_component(bytes: &[u8]) -> u8 {
let value = u16::from_ne_bytes([bytes[0], bytes[1]]);
(value / 257) as u8
}
fn read_f32_component(bytes: &[u8]) -> u8 {
let value = f32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
(value.clamp(0.0, 1.0) * 255.0).round() as u8
}

View File

@@ -1,16 +1,51 @@
#![allow(dead_code)] #![allow(dead_code)]
use anyhow::*; use anyhow::*;
use eframe::{ use eframe::wgpu;
egui,
egui_wgpu::{self, wgpu},
wgpu::util::DeviceExt,
};
use image::GenericImageView; use image::GenericImageView;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum TextureColorSpace {
Srgb,
Linear,
}
impl TextureColorSpace {
pub fn format(self) -> wgpu::TextureFormat {
match self {
Self::Srgb => wgpu::TextureFormat::Rgba8UnormSrgb,
Self::Linear => wgpu::TextureFormat::Rgba8Unorm,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct TextureSamplerDescriptor {
pub address_mode_u: wgpu::AddressMode,
pub address_mode_v: wgpu::AddressMode,
pub mag_filter: wgpu::FilterMode,
pub min_filter: wgpu::FilterMode,
pub mipmap_filter: wgpu::MipmapFilterMode,
}
impl Default for TextureSamplerDescriptor {
fn default() -> Self {
Self {
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
}
}
}
pub struct Texture { pub struct Texture {
pub texture: wgpu::Texture, pub texture: wgpu::Texture,
pub view: wgpu::TextureView, pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler, pub sampler: wgpu::Sampler,
pub width: u32,
pub height: u32,
} }
impl Texture { impl Texture {
@@ -55,6 +90,8 @@ impl Texture {
texture, texture,
view, view,
sampler, sampler,
width: config.width,
height: config.height,
} }
} }
@@ -89,7 +126,7 @@ impl Texture {
mip_level_count: 1, mip_level_count: 1,
sample_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb, format: TextureColorSpace::Srgb.format(),
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[], view_formats: &[],
}); });
@@ -125,6 +162,8 @@ impl Texture {
texture, texture,
view, view,
sampler, sampler,
width: dimensions.0,
height: dimensions.1,
}) })
} }
@@ -135,6 +174,28 @@ impl Texture {
width: u32, width: u32,
height: u32, height: u32,
label: &str, label: &str,
) -> Result<Self> {
Self::from_rgba8_with_sampler(
device,
queue,
rgba,
width,
height,
label,
TextureColorSpace::Srgb,
TextureSamplerDescriptor::default(),
)
}
pub fn from_rgba8_with_sampler(
device: &wgpu::Device,
queue: &wgpu::Queue,
rgba: &[u8],
width: u32,
height: u32,
label: &str,
color_space: TextureColorSpace,
sampler_desc: TextureSamplerDescriptor,
) -> Result<Self> { ) -> Result<Self> {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width, width,
@@ -147,7 +208,7 @@ impl Texture {
mip_level_count: 1, mip_level_count: 1,
sample_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb, format: color_space.format(),
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[], view_formats: &[],
}); });
@@ -170,12 +231,12 @@ impl Texture {
let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_u: sampler_desc.address_mode_u,
address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_v: sampler_desc.address_mode_v,
address_mode_w: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear, mag_filter: sampler_desc.mag_filter,
min_filter: wgpu::FilterMode::Nearest, min_filter: sampler_desc.min_filter,
mipmap_filter: wgpu::MipmapFilterMode::Nearest, mipmap_filter: sampler_desc.mipmap_filter,
..Default::default() ..Default::default()
}); });
@@ -183,6 +244,8 @@ impl Texture {
texture, texture,
view, view,
sampler, sampler,
width,
height,
}) })
} }
} }

View File

@@ -3,21 +3,74 @@ struct MatrixUniform {
viewport: vec4f, viewport: vec4f,
glyph: vec4f, glyph: vec4f,
color: vec4f, color: vec4f,
render_options: vec4f,
image: vec4f,
} }
@group(0) @binding(0) @group(0) @binding(0)
var<uniform> u: MatrixUniform; var<uniform> u: MatrixUniform;
@group(1) @binding(0) @group(1) @binding(0)
var model_texture: texture_2d<f32>; var base_color_texture: texture_2d<f32>;
@group(1) @binding(1) @group(1) @binding(1)
var model_sampler: sampler; var base_color_sampler: sampler;
struct MaterialParams {
base_color: vec4f,
metallic_roughness: vec4f,
emissive_alpha: vec4f,
flags: vec4f,
}
@group(1) @binding(2)
var<uniform> material: MaterialParams;
@group(1) @binding(3)
var metallic_roughness_texture: texture_2d<f32>;
@group(1) @binding(4)
var metallic_roughness_sampler: sampler;
@group(1) @binding(5)
var normal_texture: texture_2d<f32>;
@group(1) @binding(6)
var normal_sampler: sampler;
@group(1) @binding(7)
var occlusion_texture: texture_2d<f32>;
@group(1) @binding(8)
var occlusion_sampler: sampler;
@group(1) @binding(9)
var emissive_texture: texture_2d<f32>;
@group(1) @binding(10)
var emissive_sampler: sampler;
fn saturate(value: f32) -> f32 { fn saturate(value: f32) -> f32 {
return clamp(value, 0.0, 1.0); return clamp(value, 0.0, 1.0);
} }
fn linear_to_srgb(linear: vec3f) -> vec3f {
let x = max(linear, vec3f(0.0));
let low = x * 12.92;
let high = 1.055 * pow(x, vec3f(1.0 / 2.4)) - vec3f(0.055);
return select(high, low, x <= vec3f(0.0031308));
}
fn output_color(linear_rgb: vec3f, alpha: f32) -> vec4f {
let clamped = clamp(linear_rgb, vec3f(0.0), vec3f(1.0));
if (u.color.w > 0.5) {
return vec4f(clamped, alpha);
}
let encoded = linear_to_srgb(clamped);
return vec4f(encoded, alpha);
}
fn range_stop_color(index: u32) -> vec3f { fn range_stop_color(index: u32) -> vec3f {
switch index { switch index {
case 0u: { case 0u: {
@@ -76,38 +129,66 @@ fn fs_background(@builtin(position) frag_coord: vec4f) -> @location(0) vec4f {
let pixel = frag_coord.xy; let pixel = frag_coord.xy;
let viewport = u.viewport.xy; let viewport = u.viewport.xy;
let uv = pixel / max(viewport, vec2f(1.0, 1.0)); let uv = pixel / max(viewport, vec2f(1.0, 1.0));
var color = mix(vec3f(0.112, 0.126, 0.160), vec3f(0.145, 0.160, 0.198), 1.0 - uv.y); var color = mix(vec3f(0.018, 0.019, 0.022), vec3f(0.038, 0.040, 0.046), 1.0 - uv.y);
let vignette = smoothstep(0.18, 0.92, length((uv - vec2f(0.52, 0.48)) * vec2f(viewport.x / viewport.y, 1.0))); let vignette = smoothstep(0.18, 0.92, length((uv - vec2f(0.52, 0.48)) * vec2f(viewport.x / viewport.y, 1.0)));
color *= 1.0 - vignette * 0.30; color *= 1.0 - vignette * 0.22;
color += vec3f(0.035, 0.070, 0.090) * (1.0 - smoothstep(0.0, 0.9, abs(uv.y - 0.52))); color += vec3f(0.010, 0.010, 0.012) * (1.0 - smoothstep(0.0, 0.85, abs(uv.y - 0.50)));
let track_width = clamp(viewport.x * 0.42, 260.0, 560.0); return output_color(color, 1.0);
let track_height = 12.0; }
let track_center = vec2f(viewport.x * 0.5, viewport.y - 38.0);
let local = pixel - track_center;
let half_size = vec2f(track_width * 0.5, track_height * 0.5);
let inside_track = step(abs(local.x), half_size.x) * step(abs(local.y), half_size.y);
let border = step(abs(local.x), half_size.x + 1.0) * step(abs(local.y), half_size.y + 1.0) - inside_track;
let t = saturate((local.x + half_size.x) / track_width);
if (inside_track > 0.5) {
let shade = mix(0.72, 1.0, 1.0 - saturate((local.y + half_size.y) / track_height)); // hand image background
color = mix(color, sample_range_color(t) * shade, 0.96); struct HandImageVertexOutput {
@builtin(position) clip_position: vec4f,
@location(0) screen_uv: vec2f,
}
@vertex
fn vs_hand_image(@builtin(vertex_index) vertex_index: u32) -> HandImageVertexOutput {
let positions = array<vec2f, 6>(
vec2f(-1.0, -1.0),
vec2f(1.0, -1.0),
vec2f(-1.0, 1.0),
vec2f(-1.0, 1.0),
vec2f(1.0, -1.0),
vec2f(1.0, 1.0),
);
let uvs = array<vec2f, 6>(
vec2f(0.0, 1.0),
vec2f(1.0, 1.0),
vec2f(0.0, 0.0),
vec2f(0.0, 0.0),
vec2f(1.0, 1.0),
vec2f(1.0, 0.0),
);
var out: HandImageVertexOutput;
out.clip_position = vec4f(positions[vertex_index], 0.0, 1.0);
out.screen_uv = uvs[vertex_index];
return out;
}
@fragment
fn fs_hand_image(in: HandImageVertexOutput) -> @location(0) vec4f {
let viewport_aspect = u.viewport.x / max(u.viewport.y, 1.0);
let image_aspect = u.image.x / max(u.image.y, 1.0);
var uv = in.screen_uv;
if (viewport_aspect > image_aspect) {
let image_width = image_aspect / viewport_aspect;
uv.x = (uv.x - (1.0 - image_width) * 0.5) / image_width;
} else {
let image_height = viewport_aspect / image_aspect;
uv.y = (uv.y - (1.0 - image_height) * 0.5) / image_height;
} }
color = max(color, vec3f(0.34, 0.41, 0.46) * border); if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
discard;
let tick_area = step(abs(local.x), half_size.x) * step(abs(local.y), half_size.y + 8.0);
if (tick_area > 0.5) {
let ticks = array<f32, 4>(0.0, 0.33, 0.66, 1.0);
for (var index = 0u; index < 4u; index = index + 1u) {
let tick_x = (ticks[index] - 0.5) * track_width;
let tick = step(abs(local.x - tick_x), 1.0) * step(abs(local.y), half_size.y + 7.0);
color = max(color, vec3f(0.78, 0.84, 0.90) * tick);
}
} }
return vec4f(color, 1.0); let color = textureSample(base_color_texture, base_color_sampler, uv);
return output_color(color.rgb, color.a);
} }
@@ -252,7 +333,7 @@ fn vs_glyph(vertex: GlyphVertexInput, instance: GlyphInstanceInput) -> GlyphVert
fn fs_glyph(in: GlyphVertexOutput) -> @location(0) vec4f { fn fs_glyph(in: GlyphVertexOutput) -> @location(0) vec4f {
let alpha = number_alpha(in.local, in.display_value); let alpha = number_alpha(in.local, in.display_value);
let color = sample_range_color(in.intensity) * mix(0.82, 1.16, saturate(in.intensity)); let color = sample_range_color(in.intensity) * mix(0.82, 1.16, saturate(in.intensity));
return vec4f(color, alpha); return output_color(color, alpha);
} }
@@ -310,7 +391,7 @@ fn fs_dot(in: DotVertexOutput) -> @location(0) vec4f {
let alpha = max(core, glow); let alpha = max(core, glow);
return vec4f(color, alpha); return output_color(color, alpha);
} }
@@ -319,6 +400,8 @@ struct ModelVertexInput {
@location(0) position: vec3f, @location(0) position: vec3f,
@location(1) tex_coords: vec2f, @location(1) tex_coords: vec2f,
@location(2) normal: vec3f, @location(2) normal: vec3f,
@location(3) color: vec4f,
@location(4) tangent: vec4f,
@location(5) model_0: vec4f, @location(5) model_0: vec4f,
@location(6) model_1: vec4f, @location(6) model_1: vec4f,
@location(7) model_2: vec4f, @location(7) model_2: vec4f,
@@ -330,29 +413,217 @@ struct ModelVertexOutput {
@location(0) world_normal: vec3f, @location(0) world_normal: vec3f,
@location(1) world_position: vec3f, @location(1) world_position: vec3f,
@location(2) tex_coords: vec2f, @location(2) tex_coords: vec2f,
@location(3) color: vec4f,
@location(4) world_tangent: vec4f,
}
fn inverse_transpose3x3(m: mat3x3f) -> mat3x3f {
let c0 = m[0];
let c1 = m[1];
let c2 = m[2];
let r0 = cross(c1, c2);
let r1 = cross(c2, c0);
let r2 = cross(c0, c1);
let det = dot(c0, r0);
let safe_det = select(det, select(-0.000001, 0.000001, det >= 0.0), abs(det) < 0.000001);
let inv_det = 1.0 / safe_det;
return mat3x3f(r0 * inv_det, r1 * inv_det, r2 * inv_det);
} }
@vertex @vertex
fn vs_model(vertex: ModelVertexInput) -> ModelVertexOutput { fn vs_model(vertex: ModelVertexInput) -> ModelVertexOutput {
let model = mat4x4f(vertex.model_0, vertex.model_1, vertex.model_2, vertex.model_3); let model = mat4x4f(vertex.model_0, vertex.model_1, vertex.model_2, vertex.model_3);
let world_position = model * vec4f(vertex.position, 1.0); let world_position = model * vec4f(vertex.position, 1.0);
let normal_matrix = mat3x3f(model[0].xyz, model[1].xyz, model[2].xyz); let model3 = mat3x3f(model[0].xyz, model[1].xyz, model[2].xyz);
let normal_matrix = inverse_transpose3x3(model3);
var out: ModelVertexOutput; var out: ModelVertexOutput;
out.clip_position = u.view_proj * world_position; out.clip_position = u.view_proj * world_position;
out.world_position = world_position.xyz; out.world_position = world_position.xyz;
out.world_normal = normalize(normal_matrix * vertex.normal); out.world_normal = normalize(normal_matrix * vertex.normal);
out.tex_coords = vec2f(vertex.tex_coords.x, 1.0 - vertex.tex_coords.y); out.tex_coords = vertex.tex_coords;
out.color = vertex.color;
out.world_tangent = vec4f(normalize(normal_matrix * vertex.tangent.xyz), vertex.tangent.w);
return out; return out;
} }
@fragment fn distribution_ggx(normal: vec3f, half_dir: vec3f, roughness: f32) -> f32 {
fn fs_model(in: ModelVertexOutput) -> @location(0) vec4f { let a = roughness * roughness;
let light_dir = normalize(vec3f(-0.35, 0.85, 0.45)); let a2 = a * a;
let normal = normalize(in.world_normal); let n_dot_h = max(dot(normal, half_dir), 0.0);
let diffuse = max(dot(normal, light_dir), 0.0); let n_dot_h2 = n_dot_h * n_dot_h;
let rim = pow(1.0 - saturate(abs(normal.y)), 2.0) * 0.16; let denom = (n_dot_h2 * (a2 - 1.0) + 1.0);
let base = textureSample(model_texture, model_sampler, in.tex_coords).rgb; return a2 / max(3.14159265 * denom * denom, 0.000001);
let color = base * (0.28 + diffuse * 0.72) + vec3f(0.14, 0.24, 0.32) * rim; }
return vec4f(color, 1.0);
fn geometry_schlick_ggx(n_dot_v: f32, roughness: f32) -> f32 {
let r = roughness + 1.0;
let k = (r * r) / 8.0;
return n_dot_v / max(n_dot_v * (1.0 - k) + k, 0.000001);
}
fn geometry_smith(normal: vec3f, view_dir: vec3f, light_dir: vec3f, roughness: f32) -> f32 {
let n_dot_v = max(dot(normal, view_dir), 0.0);
let n_dot_l = max(dot(normal, light_dir), 0.0);
let ggx_v = geometry_schlick_ggx(n_dot_v, roughness);
let ggx_l = geometry_schlick_ggx(n_dot_l, roughness);
return ggx_v * ggx_l;
}
fn fresnel_schlick(cos_theta: f32, f0: vec3f) -> vec3f {
let factor = pow(clamp(1.0 - cos_theta, 0.0, 1.0), 5.0);
return f0 + (vec3f(1.0) - f0) * factor;
}
fn pbr_directional_light(
albedo: vec3f,
normal: vec3f,
view_dir: vec3f,
light_dir: vec3f,
radiance: vec3f,
metallic: f32,
roughness: f32,
) -> vec3f {
let half_dir = normalize(view_dir + light_dir);
let n_dot_l = max(dot(normal, light_dir), 0.0);
let n_dot_v = max(dot(normal, view_dir), 0.0);
let h_dot_v = max(dot(half_dir, view_dir), 0.0);
let f0 = mix(vec3f(0.04), albedo, metallic);
let f = fresnel_schlick(h_dot_v, f0);
let d = distribution_ggx(normal, half_dir, roughness);
let g = geometry_smith(normal, view_dir, light_dir, roughness);
let specular = (d * g * f) / max(4.0 * n_dot_v * n_dot_l, 0.000001);
let k_s = f;
let k_d = (vec3f(1.0) - k_s) * (1.0 - metallic);
let diffuse = k_d * albedo / 3.14159265;
return (diffuse + specular) * radiance * n_dot_l;
}
fn aces_tonemap(x: vec3f) -> vec3f {
let a = 2.51;
let b = 0.03;
let c = 2.43;
let d = 0.59;
let e = 0.14;
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), vec3f(0.0), vec3f(1.0));
}
fn material_normal(in: ModelVertexOutput, front_facing: bool) -> vec3f {
var n = normalize(in.world_normal);
if (!front_facing && material.flags.w > 0.5) {
n = -n;
}
if (material.flags.z < 0.5) {
return n;
}
let t = normalize(in.world_tangent.xyz);
let b = normalize(cross(n, t) * in.world_tangent.w);
let tangent_normal_sample = textureSample(normal_texture, normal_sampler, in.tex_coords).xyz;
var tangent_normal = tangent_normal_sample * 2.0 - vec3f(1.0);
tangent_normal.x = tangent_normal.x * material.metallic_roughness.z;
tangent_normal.y = tangent_normal.y * material.metallic_roughness.z;
return normalize(mat3x3f(t, b, n) * tangent_normal);
}
@fragment
fn fs_model(in: ModelVertexOutput, @builtin(front_facing) front_facing: bool) -> @location(0) vec4f {
let base_sample = textureSample(base_color_texture, base_color_sampler, in.tex_coords);
let base_color = base_sample * material.base_color * in.color;
let alpha_mode = material.emissive_alpha.w;
var alpha = base_color.a;
if (alpha_mode < 0.5) {
alpha = 1.0;
} else if (alpha_mode < 1.5) {
if (alpha < material.flags.x) {
discard;
}
alpha = 1.0;
}
let debug_mode = u32(u.render_options.x + 0.5);
if (debug_mode == 1u) {
return output_color(base_color.rgb, alpha);
}
let mr_sample = textureSample(
metallic_roughness_texture,
metallic_roughness_sampler,
in.tex_coords,
);
let roughness = clamp(mr_sample.g * material.metallic_roughness.y, 0.04, 1.0);
let metallic = clamp(mr_sample.b * material.metallic_roughness.x, 0.0, 1.0);
let ao_sample = textureSample(occlusion_texture, occlusion_sampler, in.tex_coords).r;
let ao = mix(1.0, ao_sample, clamp(material.metallic_roughness.w, 0.0, 1.0));
let emissive = textureSample(emissive_texture, emissive_sampler, in.tex_coords).rgb
* material.emissive_alpha.rgb;
let albedo = base_color.rgb;
let normal = material_normal(in, front_facing);
switch debug_mode {
case 2u: {
return output_color(normal * 0.5 + vec3f(0.5), 1.0);
}
case 3u: {
return output_color(vec3f(roughness), 1.0);
}
case 4u: {
return output_color(vec3f(metallic), 1.0);
}
case 5u: {
return output_color(vec3f(ao), 1.0);
}
case 6u: {
return output_color(emissive, 1.0);
}
case 7u: {
return output_color(vec3f(fract(in.tex_coords), 0.0), 1.0);
}
default: {}
}
let view_dir = normalize(vec3f(0.0, 0.72, 0.70));
let key = pbr_directional_light(
albedo,
normal,
view_dir,
normalize(vec3f(-0.35, 0.82, 0.44)),
vec3f(2.15, 2.08, 1.92),
metallic,
roughness,
);
let fill = pbr_directional_light(
albedo,
normal,
view_dir,
normalize(vec3f(0.62, 0.44, -0.36)),
vec3f(0.50, 0.50, 0.50),
metallic,
roughness,
);
let f0 = mix(vec3f(0.04), albedo, metallic);
let f_ambient = fresnel_schlick(max(dot(normal, view_dir), 0.0), f0);
let ambient_color = vec3f(1.0);
let ambient_strength = 0.08;
// Temporary neutral linear ambient term until a real IBL/HDR environment is added.
var ambient = vec3f(0.0);
if (u.render_options.y > 0.5) {
let ambient_diffuse = albedo * (1.0 - metallic) * ambient_color * ambient_strength;
let ambient_specular = f_ambient * ambient_color * ambient_strength * (1.0 - roughness * 0.55);
ambient = (ambient_diffuse + ambient_specular) * ao;
}
let exposed_color = (ambient + key + fill + emissive) * max(u.render_options.w, 0.0);
var color = exposed_color;
if (u.render_options.z > 0.5) {
color = aces_tonemap(exposed_color);
}
return output_color(color, alpha);
} }