diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/assets/meshes/cube.obj | 38 | ||||
| -rw-r--r-- | src/assets/scenes/fox.glb | bin | 0 -> 162852 bytes | |||
| -rw-r--r-- | src/assets/shaders/scene/frag.glsl | 11 | ||||
| -rw-r--r-- | src/assets/shaders/scene/vert.glsl | 18 | ||||
| -rw-r--r-- | src/assets/textures/test.png | bin | 0 -> 125 bytes | |||
| -rw-r--r-- | src/framebuffer.rs | 1 | ||||
| -rw-r--r-- | src/lib.rs | 61 | ||||
| -rw-r--r-- | src/mesh.rs | 24 | ||||
| -rw-r--r-- | src/scene.rs | 312 | ||||
| -rw-r--r-- | src/shader.rs | 13 |
10 files changed, 468 insertions, 10 deletions
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 Binary files differnew file mode 100644 index 0000000..1ef5c0d --- /dev/null +++ b/src/assets/scenes/fox.glb 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 Binary files differnew file mode 100644 index 0000000..0752be0 --- /dev/null +++ b/src/assets/textures/test.png 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, @@ -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()); |
