summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2025-01-03 13:41:21 -0500
committerLLLL Colonq <llll@colonq>2025-01-03 13:41:21 -0500
commitf1b47ccb8a92280df51bb28b495829f8f7f8127e (patch)
treed5311a3d3135af498da0d808042b22c59fe5b2ae
parent2f30c4d25f3e01fb4fc8089134a610195f1cdb13 (diff)
Add initial GLTF support
-rw-r--r--.gitmodules3
-rw-r--r--Cargo.lock101
-rw-r--r--Cargo.toml3
m---------deps/gltf0
-rw-r--r--flake.nix1
-rw-r--r--index.html8
-rw-r--r--src/assets/meshes/cube.obj38
-rw-r--r--src/assets/scenes/fox.glbbin0 -> 162852 bytes
-rw-r--r--src/assets/shaders/scene/frag.glsl11
-rw-r--r--src/assets/shaders/scene/vert.glsl18
-rw-r--r--src/assets/textures/test.pngbin0 -> 125 bytes
-rw-r--r--src/framebuffer.rs1
-rw-r--r--src/lib.rs61
-rw-r--r--src/mesh.rs24
-rw-r--r--src/scene.rs312
-rw-r--r--src/shader.rs13
16 files changed, 571 insertions, 23 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..d8e9eee
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "deps/gltf"]
+ path = deps/gltf
+ url = https://github.com/lcolonq/gltf
diff --git a/Cargo.lock b/Cargo.lock
index 29ac2b3..99e1bf0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -200,6 +200,12 @@ dependencies = [
[[package]]
name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
@@ -284,6 +290,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
name = "bytes"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -838,12 +850,6 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "glam"
-version = "0.25.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
-
-[[package]]
-name = "glam"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
@@ -870,6 +876,39 @@ dependencies = [
]
[[package]]
+name = "gltf"
+version = "1.4.1"
+dependencies = [
+ "base64 0.13.1",
+ "byteorder",
+ "gltf-json",
+ "image 0.25.5",
+ "lazy_static",
+ "serde_json",
+ "urlencoding",
+]
+
+[[package]]
+name = "gltf-derive"
+version = "1.4.1"
+dependencies = [
+ "inflections",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "gltf-json"
+version = "1.4.1"
+dependencies = [
+ "gltf-derive",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
name = "h2"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1068,6 +1107,20 @@ dependencies = [
]
[[package]]
+name = "image"
+version = "0.25.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "num-traits",
+ "png",
+ "zune-core",
+ "zune-jpeg",
+]
+
+[[package]]
name = "indexmap"
version = "2.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1078,6 +1131,12 @@ dependencies = [
]
[[package]]
+name = "inflections"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
+
+[[package]]
name = "ipnet"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1160,7 +1219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a9f9dff5e262540b628b00d5e1a772270a962d6869f8695bb24832ff3b394"
dependencies = [
"cpal",
- "glam 0.29.2",
+ "glam",
"mint",
"paste",
"ringbuf",
@@ -1731,7 +1790,7 @@ version = "0.12.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
@@ -2284,9 +2343,10 @@ dependencies = [
"enum-map",
"env_logger",
"getrandom",
- "glam 0.25.0",
+ "glam",
"glow",
- "image",
+ "gltf",
+ "image 0.24.9",
"js-sys",
"kira",
"log",
@@ -2596,6 +2656,12 @@ dependencies = [
]
[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
+[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3290,6 +3356,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
+[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3297,3 +3369,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
+dependencies = [
+ "zune-core",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 94f4dd5..90f923a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,7 +16,8 @@ opt-level = 2
[dependencies]
glow = {version = "*", features = []} # rendering
-tobj = "*" # model loader
+tobj = "*" # loader for .obj meshes loader
+gltf = {path = "deps/gltf", features = ["extras", "names", "utils"]} # loader for .gltf scenes
# gltf = {version = "*", features = ["extras", "names"]} # model loader
image = "*" # texture loader
glam = "*" # linear algebra
diff --git a/deps/gltf b/deps/gltf
new file mode 160000
+Subproject 2aa1baec59d0a86384e78a3fbaa441f8f39ed71
diff --git a/flake.nix b/flake.nix
index 1546244..b188d0e 100644
--- a/flake.nix
+++ b/flake.nix
@@ -19,7 +19,6 @@
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
- flake-utils.follows = "flake-utils";
};
};
};
diff --git a/index.html b/index.html
index b48e220..f140615 100644
--- a/index.html
+++ b/index.html
@@ -12,9 +12,15 @@
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#404040">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<link data-trunk rel="css" href="index.css" />
- <title>OUBLIETTE OF GENERAL</title>
+ <title>teleia</title>
</head>
<body>
+ <script>
+ addEventListener("TrunkApplicationStarted", async (event) => {
+ console.log("initialized, starting...");
+ window.wasmBindings.main_js();
+ });
+ </script>
<div id="teleia-parent"></canvas>
</body>
</html>
diff --git a/src/assets/meshes/cube.obj b/src/assets/meshes/cube.obj
new file mode 100644
index 0000000..2f3a670
--- /dev/null
+++ b/src/assets/meshes/cube.obj
@@ -0,0 +1,38 @@
+# Blender 4.1.1
+# www.blender.org
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+s 0
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 4/5/2 3/4/2 7/6/2 8/7/2
+f 8/8/3 7/9/3 5/10/3 6/11/3
+f 6/12/4 2/13/4 4/5/4 8/14/4
+f 2/13/5 1/1/5 3/4/5 4/5/5
+f 6/11/6 5/10/6 1/1/6 2/13/6
diff --git a/src/assets/scenes/fox.glb b/src/assets/scenes/fox.glb
new file mode 100644
index 0000000..1ef5c0d
--- /dev/null
+++ b/src/assets/scenes/fox.glb
Binary files differ
diff --git a/src/assets/shaders/scene/frag.glsl b/src/assets/shaders/scene/frag.glsl
new file mode 100644
index 0000000..81e08b9
--- /dev/null
+++ b/src/assets/shaders/scene/frag.glsl
@@ -0,0 +1,11 @@
+uniform sampler2D texture_data;
+
+void main()
+{
+ vec4 texel = texture(texture_data, vertex_texcoord);
+ if (texel.a != 1.0) {
+ discard;
+ }
+
+ frag_color = vec4(texel.rgb, texel.a);
+}
diff --git a/src/assets/shaders/scene/vert.glsl b/src/assets/shaders/scene/vert.glsl
new file mode 100644
index 0000000..64f400c
--- /dev/null
+++ b/src/assets/shaders/scene/vert.glsl
@@ -0,0 +1,18 @@
+in vec4 joint;
+in vec4 weight;
+
+uniform mat4 joint_matrices[128];
+
+void main()
+{
+ vertex_texcoord = texcoord;
+ vertex_normal = (normal_matrix * vec4(normal, 1.0)).xyz;
+ mat4 skin
+ = weight.x * joint_matrices[int(joint.x)]
+ + weight.y * joint_matrices[int(joint.y)]
+ + weight.z * joint_matrices[int(joint.z)]
+ + weight.w * joint_matrices[int(joint.w)];
+ vec3 pos = (position * skin * vec4(vertex, 1.0)).xyz;
+ vertex_view_vector = camera_pos - pos;
+ gl_Position = projection * view * vec4(pos, 1.0);
+}
diff --git a/src/assets/textures/test.png b/src/assets/textures/test.png
new file mode 100644
index 0000000..0752be0
--- /dev/null
+++ b/src/assets/textures/test.png
Binary files differ
diff --git a/src/framebuffer.rs b/src/framebuffer.rs
index fb6ab02..6d8d1b5 100644
--- a/src/framebuffer.rs
+++ b/src/framebuffer.rs
@@ -23,7 +23,6 @@ impl Framebuffer {
let upscaleh = context::RENDER_HEIGHT * ratio;
let offsetx = (windoww - upscalew) / 2.0;
let offsety = (windowh - upscaleh) / 2.0;
- log::info!("{} {} {} {} {} {}", windoww, windowh, upscalew, upscaleh, offsetx, offsety);
Self {
tex: None,
fbo: None,
diff --git a/src/lib.rs b/src/lib.rs
index b8ebde2..36ea8bf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,11 +6,11 @@ pub mod framebuffer;
pub mod shader;
pub mod mesh;
pub mod texture;
+pub mod scene;
pub mod font;
pub mod shadow;
pub mod audio;
pub mod net;
-
#[cfg(target_arch = "wasm32")]
use winit::platform::web::EventLoopExtWebSys;
@@ -266,3 +266,62 @@ where
});
});
}
+
+#[cfg(target_arch = "wasm32")]
+use wasm_bindgen::prelude::*;
+
+#[cfg(target_arch = "wasm32")]
+struct TestGame {
+ font: font::Font,
+ cube: mesh::Mesh,
+ fox: scene::Scene,
+ tex: texture::Texture,
+ shader: shader::Shader,
+}
+impl TestGame {
+ pub async fn new(ctx: &context::Context) -> Self {
+ Self {
+ font: font::Font::new(ctx),
+ cube: mesh::Mesh::from_obj(ctx, include_bytes!("assets/meshes/cube.obj")),
+ // fox: scene::Scene::from_gltf(ctx, include_bytes!("/home/llll/src/colonq/assets/lcolonq_flat.vrm")),
+ fox: scene::Scene::from_gltf(ctx, include_bytes!("assets/scenes/fox.glb")),
+ tex: texture::Texture::new(ctx, include_bytes!("assets/textures/test.png")),
+ shader: shader::Shader::new(ctx, include_str!("assets/shaders/scene/vert.glsl"), include_str!("assets/shaders/scene/frag.glsl")),
+ }
+ }
+}
+impl state::Game for TestGame {
+ fn update(&mut self, ctx: &context::Context, st: &mut state::State) -> Option<()> {
+ st.move_camera(
+ ctx,
+ &glam::Vec3::new(0.0, 0.0, -10.0),
+ &glam::Vec3::new(0.0, 0.0, 1.0),
+ &glam::Vec3::new(0.0, 1.0, 0.0),
+ );
+ Some(())
+ }
+ fn render(&mut self, ctx: &context::Context, st: &mut state::State) -> Option<()> {
+ if let Some(n) = self.fox.nodes_by_name.get("b_Neck_04").and_then(|i| self.fox.nodes.get_mut(*i)) {
+ n.transform *= glam::Mat4::from_rotation_y(0.05);
+ }
+ st.bind_3d(ctx, &self.shader);
+ self.shader.set_position_3d(
+ ctx,
+ &glam::Mat4::from_scale_rotation_translation(
+ glam::Vec3::new(0.1, 0.1, 0.1),
+ glam::Quat::from_rotation_y(st.tick as f32 / 60.0),
+ glam::Vec3::new(0.0, -5.0, 0.0),
+ ),
+ );
+ self.tex.bind(ctx);
+ self.fox.render(ctx, &self.shader);
+ self.font.render_text(ctx, &glam::Vec2::new(0.0, 0.0), "he's all fucked up");
+ Some(())
+ }
+}
+
+#[cfg(target_arch = "wasm32")]
+#[wasm_bindgen]
+pub async fn main_js() {
+ run(TestGame::new).await;
+}
diff --git a/src/mesh.rs b/src/mesh.rs
index cfbbf91..56d735f 100644
--- a/src/mesh.rs
+++ b/src/mesh.rs
@@ -5,10 +5,15 @@ use crate::context;
pub const ATTRIB_VERTEX: u32 = 0;
pub const ATTRIB_NORMAL: u32 = 1;
pub const ATTRIB_TEXCOORD: u32 = 2;
+pub const ATTRIB_JOINT: u32 = 3;
+pub const ATTRIB_WEIGHT: u32 = 4;
pub struct Mesh {
pub vao: glow::VertexArray,
+ pub mode: u32, // glow::TRIANGLES, etc.
pub index_count: usize,
+ pub index_type: u32, // glow::BYTE, glow::FLOAT, etc.
+ pub index_offset: i32,
}
impl Mesh {
@@ -23,8 +28,8 @@ impl Mesh {
let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao");
ctx.gl.bind_vertex_array(Some(vao));
- let vertices_vbo = ctx.gl.create_buffer().expect("failed to initialize vbo");
- ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertices_vbo));
+ let buf = ctx.gl.create_buffer().expect("failed to create buffer object");
+ ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(buf));
ctx.gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER,
std::slice::from_raw_parts(
@@ -36,7 +41,7 @@ impl Mesh {
ctx.gl.vertex_attrib_pointer_f32(ATTRIB_VERTEX, 3, glow::FLOAT, false, 0, 0);
ctx.gl.enable_vertex_attrib_array(ATTRIB_VERTEX);
- let indices_vbo = ctx.gl.create_buffer().expect("failed to initialize vbo");
+ let indices_vbo = ctx.gl.create_buffer().expect("failed to create buffer object");
ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(indices_vbo));
ctx.gl.buffer_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
@@ -48,7 +53,7 @@ impl Mesh {
);
if let Some(normals) = snormals {
- let normals_vbo = ctx.gl.create_buffer().expect("failed to initialize vbo");
+ let normals_vbo = ctx.gl.create_buffer().expect("failed to create buffer object");
ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(normals_vbo));
ctx.gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER,
@@ -63,7 +68,7 @@ impl Mesh {
}
if let Some(texcoords) = stexcoords {
- let texcoords_vbo = ctx.gl.create_buffer().expect("failed to initialize vbo");
+ let texcoords_vbo = ctx.gl.create_buffer().expect("failed to create buffer object");
ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_vbo));
ctx.gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER,
@@ -79,12 +84,15 @@ impl Mesh {
Self {
vao,
+ mode: glow::TRIANGLES,
index_count: indices.len(),
+ index_type: glow::UNSIGNED_INT,
+ index_offset: 0,
}
}
}
- pub fn new(ctx: &context::Context, mut bytes: &[u8]) -> Self {
+ pub fn from_obj(ctx: &context::Context, mut bytes: &[u8]) -> Self {
let lopts = tobj::LoadOptions {
triangulate: true,
single_index: true,
@@ -110,14 +118,14 @@ impl Mesh {
pub fn render(&self, ctx: &context::Context) {
unsafe {
ctx.gl.bind_vertex_array(Some(self.vao));
- ctx.gl.draw_elements(glow::TRIANGLES, self.index_count as _, glow::UNSIGNED_INT, 0);
+ ctx.gl.draw_elements(self.mode, self.index_count as _, self.index_type, self.index_offset);
}
}
pub fn render_instanced(&self, ctx: &context::Context, count: u64) {
unsafe {
ctx.gl.bind_vertex_array(Some(self.vao));
- ctx.gl.draw_elements_instanced(glow::TRIANGLES, self.index_count as _, glow::UNSIGNED_INT, 0, count as _);
+ ctx.gl.draw_elements_instanced(self.mode, self.index_count as _, self.index_type, self.index_offset, count as _);
}
}
}
diff --git a/src/scene.rs b/src/scene.rs
new file mode 100644
index 0000000..8c71a12
--- /dev/null
+++ b/src/scene.rs
@@ -0,0 +1,312 @@
+use std::{collections::{HashMap, VecDeque}, mem::offset_of};
+
+use glow::HasContext;
+use image::EncodableLayout;
+
+use crate::{context, mesh, shader, texture};
+
+pub type Index = usize;
+
+pub struct Primitive {
+ pub mesh: mesh::Mesh,
+ pub material: Index,
+}
+
+pub struct Object {
+ pub primitives: Vec<Primitive>,
+}
+
+pub struct Material {
+ pub base_color_factor: glam::Vec4,
+ pub base_color_texture: Option<Index>,
+
+ pub metallic_factor: f32,
+ pub roughness_factor: f32,
+ pub metallic_roughness_texture: Option<Index>,
+
+ pub normal_texture: Option<Index>,
+
+ pub occlusion_texture: Option<Index>,
+
+ pub emissive_factor: glam::Vec3,
+ pub emissive_texture: Option<Index>,
+}
+
+pub struct Skin {
+ pub inverse_bind_matrices: Vec<glam::Mat4>,
+ pub joints: Vec<Index>,
+}
+
+pub struct Node {
+ pub children: Vec<Index>,
+ pub object: Option<Index>,
+ pub skin: Option<Index>,
+ pub transform: glam::Mat4,
+}
+
+pub struct Scene {
+ pub objects: Vec<Object>,
+ pub textures: Vec<texture::Texture>,
+ pub materials: Vec<Material>,
+ pub skins: Vec<Skin>,
+ pub nodes: Vec<Node>,
+ pub nodes_by_name: HashMap<String, Index>,
+ pub scene_nodes: Vec<Index>,
+}
+
+impl Scene {
+ pub fn from_gltf(ctx: &context::Context, bytes: &[u8]) -> Self {
+ let (gltf, buffers, images) = gltf::import_slice(bytes).expect("failed to parse GLTF");
+ let get_buffer_data = |b: gltf::Buffer| {
+ buffers.get(b.index()).map(|gltf::buffer::Data(bytes)| bytes.as_slice())
+ };
+ let objects = gltf.meshes().map(|m| {
+ let primitives = m.primitives().filter_map(|p| {
+ let mode = match p.mode() {
+ gltf::mesh::Mode::Points => glow::POINTS,
+ gltf::mesh::Mode::Lines => glow::LINES,
+ gltf::mesh::Mode::LineLoop => glow::LINE_LOOP,
+ gltf::mesh::Mode::LineStrip => glow::LINE_STRIP,
+ gltf::mesh::Mode::Triangles => glow::TRIANGLES,
+ gltf::mesh::Mode::TriangleStrip => glow::TRIANGLE_STRIP,
+ gltf::mesh::Mode::TriangleFan => glow::TRIANGLE_FAN,
+ };
+ unsafe {
+ let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao");
+ ctx.gl.bind_vertex_array(Some(vao));
+
+ // in the past, I've been lazy and just uploaded whole buffers to the GPU.
+ // this is certainly not the right thing to do in general.
+ // perhaps I am misunderstanding, but it feels like GLTF makes it pretty difficult to do
+ // that in general, on account of things like sparse accessors.
+ // instead, we'll use the gltf crate's handy "reader" abstraction to iterate over all of the
+ // data in the buffers, assemble it ourselves, and then upload that.
+ let reader = p.reader(get_buffer_data);
+
+ // on to the actual vertex data.
+ // this is the layout of a single vertex in the buffer we send to the GPU.
+ struct Vertex {
+ pos: glam::Vec3,
+ normal: glam::Vec3,
+ texcoord: glam::Vec2,
+ joints: glam::Vec4,
+ weights: glam::Vec4,
+ }
+
+ // vertices always have positions
+ let mut vertices = Vec::new();
+ for pos in reader.read_positions().expect("primitive has no positions") {
+ vertices.push(Vertex {
+ pos: glam::Vec3::from_array(pos),
+ normal: glam::Vec3::default(),
+ texcoord: glam::Vec2::default(),
+ joints: glam::Vec4::default(),
+ weights: glam::Vec4::default(),
+ });
+ }
+
+ // if we find indices, use those. otherwise generate indices
+ let indices: Vec<u32> = if let Some(ri) = reader.read_indices() {
+ ri.into_u32().collect()
+ } else {
+ vertices.iter().enumerate().map(|(i, _)| i as u32).collect()
+ };
+ let indices_bytes: Vec<u8> = indices.iter().flat_map(|x| x.to_ne_bytes()).collect();
+ let indices_buf = ctx.gl.create_buffer().expect("failed to create index buffer object");
+ ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(indices_buf));
+ ctx.gl.buffer_data_u8_slice(
+ glow::ELEMENT_ARRAY_BUFFER,
+ &indices_bytes,
+ glow::STATIC_DRAW,
+ );
+
+ // optionally, we might have some other vertex attributes too
+ if let Some(iter) = reader.read_normals() {
+ for (i, n) in iter.enumerate() {
+ vertices[i].normal = glam::Vec3::from_array(n)
+ }
+ }
+ if let Some(iter) = reader.read_tex_coords(0) {
+ for (i, uv) in iter.into_f32().enumerate() {
+ vertices[i].texcoord = glam::Vec2::from_array(uv)
+ }
+ }
+ if let Some(iter) = reader.read_joints(0) {
+ for (i, j) in iter.into_u16().enumerate() {
+ vertices[i].joints = glam::Vec4::from_slice(&j.into_iter().map(|x| x as f32).collect::<Vec<f32>>())
+ }
+ }
+ if let Some(iter) = reader.read_weights(0) {
+ for (i, w) in iter.into_f32().enumerate() {
+ vertices[i].weights = glam::Vec4::from_array(w)
+ }
+ }
+
+ let vertex_size = std::mem::size_of::<Vertex>() as i32;
+ let vertices_buf = ctx.gl.create_buffer().expect("failed to create buffer object");
+ ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertices_buf));
+ ctx.gl.buffer_data_u8_slice(
+ glow::ARRAY_BUFFER,
+ std::slice::from_raw_parts(
+ vertices.as_ptr() as _,
+ vertices.len() * (vertex_size as usize),
+ ),
+ glow::STATIC_DRAW,
+ );
+ ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX);
+ ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 3, glow::FLOAT, false, vertex_size, offset_of!(Vertex, pos) as _);
+ ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_NORMAL);
+ ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_NORMAL, 3, glow::FLOAT, false, vertex_size, offset_of!(Vertex, normal) as _);
+ ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD);
+ ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, vertex_size, offset_of!(Vertex, texcoord) as _);
+ ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_JOINT);
+ ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_JOINT, 4, glow::FLOAT, false, vertex_size, offset_of!(Vertex, joints) as _);
+ ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_WEIGHT);
+ ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_WEIGHT, 4, glow::FLOAT, false, vertex_size, offset_of!(Vertex, weights) as _);
+
+
+ Some(Primitive {
+ mesh: mesh::Mesh {
+ vao,
+ mode,
+ index_count: indices.len(),
+ index_type: glow::UNSIGNED_INT,
+ index_offset: 0,
+ },
+ material: p.material().index().unwrap(),
+ })
+ }
+ }).collect();
+ Object {
+ primitives,
+ }
+ }).collect();
+ let textures: Vec<texture::Texture> = images.into_iter().map(|bi| {
+ unsafe {
+ let i = bi.image.into_rgba8();
+ let tex = ctx.gl.create_texture().expect("failed to create texture");
+ ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex));
+ ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32);
+ ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32);
+ ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32);
+ ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32);
+ ctx.gl.tex_image_2d(
+ glow::TEXTURE_2D,
+ 0,
+ glow::RGBA as i32,
+ i.width() as i32,
+ i.height() as i32,
+ 0,
+ glow::RGBA,
+ glow::UNSIGNED_BYTE,
+ Some(&i.as_bytes()),
+ );
+ ctx.gl.generate_mipmap(glow::TEXTURE_2D);
+ texture::Texture { tex }
+ }
+ }).collect();
+ let materials: Vec<Material> = gltf.materials().map(|m| {
+ let pbr = m.pbr_metallic_roughness();
+ let [bcr, bcg, bcb, bca] = pbr.base_color_factor();
+ let [emx, emy, emz] = m.emissive_factor();
+ Material {
+ base_color_factor: glam::Vec4::new(bcr, bcg, bcb, bca),
+ base_color_texture: pbr.base_color_texture().map(|tex| tex.texture().source().index()),
+ metallic_factor: pbr.metallic_factor(),
+ roughness_factor: pbr.roughness_factor(),
+ metallic_roughness_texture: pbr.metallic_roughness_texture().map(|tex| tex.texture().source().index()),
+ normal_texture: m.normal_texture().map(|tex| tex.texture().source().index()),
+ occlusion_texture: m.occlusion_texture().map(|tex| tex.texture().source().index()),
+ emissive_factor: glam::Vec3::new(emx, emy, emz),
+ emissive_texture: m.emissive_texture().map(|tex| tex.texture().source().index()),
+ }
+ }).collect();
+
+ let skins = gltf.skins().map(|s| {
+ let ibm = s.reader(get_buffer_data).read_inverse_bind_matrices()
+ .expect("missing read inverse bind matrices")
+ .map(|m| glam::Mat4::from_cols_array_2d(&m)).collect();
+ Skin {
+ inverse_bind_matrices: ibm,
+ joints: s.joints().map(|j| j.index()).collect(),
+ }
+ }).collect();
+
+ let nodes = gltf.nodes().map(|n| {
+ Node {
+ children: n.children().map(|c| c.index()).collect(),
+ object: n.mesh().map(|m| m.index()),
+ skin: n.skin().map(|s| s.index()),
+ transform: glam::Mat4::from_cols_array_2d(&n.transform().matrix()),
+ }
+ }).collect();
+
+ let mut nodes_by_name = HashMap::new();
+ for n in gltf.nodes() {
+ if let Some(nm) = n.name() {
+ nodes_by_name.insert(nm.to_owned(), n.index());
+ }
+ }
+
+ let scene_nodes = gltf.default_scene().unwrap().nodes().map(|n| n.index()).collect();
+
+ Self {
+ objects,
+ textures,
+ materials,
+ skins,
+ nodes,
+ nodes_by_name,
+ scene_nodes,
+ }
+ }
+
+ pub fn compute_joint_matrices(&self, skin: &Skin) -> Vec<glam::Mat4> {
+ let mut q: VecDeque<(Index, glam::Mat4)> = VecDeque::new();
+ q.push_back((skin.joints[0], glam::Mat4::IDENTITY));
+ let mut transforms = vec![glam::Mat4::IDENTITY; self.nodes.len()];
+ while let Some((ni, m)) = q.pop_front() {
+ let n = &self.nodes[ni];
+ transforms[ni] = m.mul_mat4(&n.transform);
+ for ci in &n.children {
+ q.push_back((*ci, transforms[ni]));
+ }
+ }
+ let mut ret = vec![glam::Mat4::IDENTITY; skin.joints.len()];
+ for (idx, ni) in skin.joints.iter().enumerate() {
+ ret[idx] = transforms[*ni].mul_mat4(&skin.inverse_bind_matrices[idx]);
+ }
+ ret
+ }
+
+ fn render_node(&self, ctx: &context::Context, shader: &shader::Shader, n: &Node) {
+ if let Some(o) = n.object.and_then(|i| self.objects.get(i)) {
+ if let Some(s) = n.skin.and_then(|i| self.skins.get(i)) {
+ let jms = self.compute_joint_matrices(s);
+ shader.set_mat4_array(ctx, "joint_matrices[0]", &jms);
+ }
+ for p in &o.primitives {
+ if let Some(tex) = self.materials.get(p.material)
+ .and_then(|m| m.base_color_texture)
+ .and_then(|t| self.textures.get(t)) {
+ tex.bind(ctx);
+ }
+ p.mesh.render(ctx);
+ }
+ }
+ }
+
+ pub fn render(&self, ctx: &context::Context, shader: &shader::Shader) {
+ let mut q: VecDeque<Index> = VecDeque::new();
+ for sn in &self.scene_nodes {
+ q.push_back(*sn);
+ }
+ while let Some(ni) = q.pop_front() {
+ let n = &self.nodes[ni];
+ self.render_node(ctx, shader, n);
+ for ci in &n.children {
+ q.push_back(*ci);
+ }
+ }
+ }
+}
diff --git a/src/shader.rs b/src/shader.rs
index 71f2a7c..46deb82 100644
--- a/src/shader.rs
+++ b/src/shader.rs
@@ -183,6 +183,19 @@ impl Shader {
}
}
+ pub fn set_mat4_array(&self, ctx: &context::Context, name: &str, val: &[glam::Mat4]) {
+ if let Some(loc) = self.uniforms.get(name) {
+ let vs: Vec<f32> = val.iter().flat_map(|m| m.to_cols_array()).collect();
+ unsafe {
+ ctx.gl.uniform_matrix_4_f32_slice(
+ Some(loc),
+ false,
+ &vs,
+ );
+ }
+ }
+ }
+
pub fn set_position_3d(&self, ctx: &context::Context, position: &glam::Mat4) {
self.set_mat4(&ctx, "position", &position);
self.set_mat4(&ctx, "normal_matrix", &position.inverse().transpose());