From f1b47ccb8a92280df51bb28b495829f8f7f8127e Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Fri, 3 Jan 2025 13:41:21 -0500 Subject: Add initial GLTF support --- .gitmodules | 3 + Cargo.lock | 101 ++++++++++-- Cargo.toml | 3 +- deps/gltf | 1 + flake.nix | 1 - index.html | 8 +- src/assets/meshes/cube.obj | 38 +++++ src/assets/scenes/fox.glb | Bin 0 -> 162852 bytes src/assets/shaders/scene/frag.glsl | 11 ++ src/assets/shaders/scene/vert.glsl | 18 +++ src/assets/textures/test.png | Bin 0 -> 125 bytes src/framebuffer.rs | 1 - src/lib.rs | 61 +++++++- src/mesh.rs | 24 ++- src/scene.rs | 312 +++++++++++++++++++++++++++++++++++++ src/shader.rs | 13 ++ 16 files changed, 572 insertions(+), 23 deletions(-) create mode 100644 .gitmodules create mode 160000 deps/gltf create mode 100644 src/assets/meshes/cube.obj create mode 100644 src/assets/scenes/fox.glb create mode 100644 src/assets/shaders/scene/frag.glsl create mode 100644 src/assets/shaders/scene/vert.glsl create mode 100644 src/assets/textures/test.png create mode 100644 src/scene.rs 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 @@ -198,6 +198,12 @@ dependencies = [ "rustc-demangle", ] +[[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" @@ -283,6 +289,12 @@ version = "1.5.0" 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" @@ -836,12 +848,6 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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" @@ -869,6 +875,39 @@ dependencies = [ "web-sys", ] +[[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" @@ -1067,6 +1106,20 @@ dependencies = [ "tiff", ] +[[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" @@ -1077,6 +1130,12 @@ dependencies = [ "hashbrown", ] +[[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" @@ -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", @@ -2595,6 +2655,12 @@ dependencies = [ "percent-encoding", ] +[[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" @@ -3289,6 +3355,12 @@ version = "1.8.1" 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" @@ -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 index 0000000..2aa1bae --- /dev/null +++ b/deps/gltf @@ -0,0 +1 @@ +Subproject commit 2aa1baec59d0a86384e78a3fbaa441f8f39ed710 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 @@ - OUBLIETTE OF GENERAL + teleia +
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 Binary files /dev/null and b/src/assets/scenes/fox.glb 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 Binary files /dev/null and b/src/assets/textures/test.png 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, +} + +pub struct Material { + pub base_color_factor: glam::Vec4, + pub base_color_texture: Option, + + pub metallic_factor: f32, + pub roughness_factor: f32, + pub metallic_roughness_texture: Option, + + pub normal_texture: Option, + + pub occlusion_texture: Option, + + pub emissive_factor: glam::Vec3, + pub emissive_texture: Option, +} + +pub struct Skin { + pub inverse_bind_matrices: Vec, + pub joints: Vec, +} + +pub struct Node { + pub children: Vec, + pub object: Option, + pub skin: Option, + pub transform: glam::Mat4, +} + +pub struct Scene { + pub objects: Vec, + pub textures: Vec, + pub materials: Vec, + pub skins: Vec, + pub nodes: Vec, + pub nodes_by_name: HashMap, + pub scene_nodes: Vec, +} + +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 = 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 = 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::>()) + } + } + 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::() 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 = 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 = 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 { + 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 = 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 = 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()); -- cgit v1.2.3