Use hand texture as render background
This commit is contained in:
70
Cargo.lock
generated
70
Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
14
res/cube.mtl
14
res/cube.mtl
@@ -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
|
|
||||||
1143
res/cube.obj
1143
res/cube.obj
File diff suppressed because it is too large
Load Diff
209
res/hand.mtl
209
res/hand.mtl
@@ -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
1373826
res/hand.obj
File diff suppressed because it is too large
Load Diff
BIN
res/hand.png
Normal file
BIN
res/hand.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
14
src/app.rs
14
src/app.rs
@@ -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;
|
||||||
|
|||||||
53
src/model.rs
53
src/model.rs
@@ -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> {
|
||||||
|
|||||||
441
src/render.rs
441
src/render.rs
@@ -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],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
933
src/resources.rs
933
src/resources.rs
@@ -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,
|
||||||
|
¶ms_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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
|
||||||
color = mix(color, sample_range_color(t) * shade, 0.96);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
color = max(color, vec3f(0.34, 0.41, 0.46) * border);
|
|
||||||
|
|
||||||
let tick_area = step(abs(local.x), half_size.x) * step(abs(local.y), half_size.y + 8.0);
|
// hand image background
|
||||||
if (tick_area > 0.5) {
|
struct HandImageVertexOutput {
|
||||||
let ticks = array<f32, 4>(0.0, 0.33, 0.66, 1.0);
|
@builtin(position) clip_position: vec4f,
|
||||||
for (var index = 0u; index < 4u; index = index + 1u) {
|
@location(0) screen_uv: vec2f,
|
||||||
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);
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user