From 0bfb852f7a2784938f1b9724bc46d98bb5257f86 Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Tue, 6 Oct 2026 14:13:44 -0400 Subject: Add initial support for cubemaps --- crates/teleia/src/assets/meshes/cube.obj | 38 +++++++++++++++ crates/teleia/src/assets/shaders/skybox/frag.glsl | 8 ++++ crates/teleia/src/assets/shaders/skybox/vert.glsl | 7 +++ crates/teleia/src/renderer.rs | 29 +++++++++++- crates/teleia/src/state.rs | 10 ++++ crates/teleia/src/texture.rs | 58 +++++++++++++++++++---- 6 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 crates/teleia/src/assets/meshes/cube.obj create mode 100644 crates/teleia/src/assets/shaders/skybox/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/skybox/vert.glsl diff --git a/crates/teleia/src/assets/meshes/cube.obj b/crates/teleia/src/assets/meshes/cube.obj new file mode 100644 index 0000000..2f3a670 --- /dev/null +++ b/crates/teleia/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/crates/teleia/src/assets/shaders/skybox/frag.glsl b/crates/teleia/src/assets/shaders/skybox/frag.glsl new file mode 100644 index 0000000..27efd37 --- /dev/null +++ b/crates/teleia/src/assets/shaders/skybox/frag.glsl @@ -0,0 +1,8 @@ +uniform samplerCube skybox; + +in vec3 vertex_texcoord_3d; + +void main() { + vec3 v = vec3(vertex_texcoord_3d.x, -vertex_texcoord_3d.z, vertex_texcoord_3d.y); + frag_color = texture(skybox, v); +} diff --git a/crates/teleia/src/assets/shaders/skybox/vert.glsl b/crates/teleia/src/assets/shaders/skybox/vert.glsl new file mode 100644 index 0000000..3a6d881 --- /dev/null +++ b/crates/teleia/src/assets/shaders/skybox/vert.glsl @@ -0,0 +1,7 @@ +out vec3 vertex_texcoord_3d; + +void main() { + vertex_texcoord_3d = vertex; + vec4 pos = projection * view * vec4(vertex, 1.0); + gl_Position = pos.xyww; +} diff --git a/crates/teleia/src/renderer.rs b/crates/teleia/src/renderer.rs index 7384f0d..dd0ffd0 100644 --- a/crates/teleia/src/renderer.rs +++ b/crates/teleia/src/renderer.rs @@ -49,13 +49,19 @@ pub trait Assets { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ShaderMode { TwoDimension, ThreeDimension, ThreeDimensionOrth } #[derive(Debug, Clone, Copy)] -enum BoundShader { None, Uber(UberFlags, ShaderMode), Shader(A::Shader, ShaderMode) } +enum BoundShader { + None, + Uber(UberFlags, ShaderMode), + Shader(A::Shader, ShaderMode), + Skybox, +} impl PartialEq for BoundShader { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::None, Self::None) => true, (Self::Uber(sf, sm), Self::Uber(of, om)) => sf == of && sm == om, (Self::Shader(ss, sm), Self::Shader(os, om)) => ss == os && sm == om, + (Self::Skybox, Self::Skybox) => true, _ => false, } } @@ -298,12 +304,23 @@ impl Renderer { pub fn bind_shader_3d(&mut self, ctx: &context::Context, st: &mut state::State, shader: A::Shader) { self.bind_shader(ctx, st, shader, ShaderMode::ThreeDimension); } + pub fn bind_shader_skybox(&mut self, ctx: &context::Context, st: &mut state::State) { + if BoundShader::Skybox == self.shader { return } + let view = glam::Mat4::from_mat3(glam::Mat3::from_mat4(st.view())); + st.shader_skybox.bind(ctx); + st.shader_skybox.set_mat4(ctx, "projection", &st.projection); + st.shader_skybox.set_mat4(ctx, "view", &view); + self.shader = BoundShader::Skybox; + } pub fn render(&self, ctx: &context::Context, _st: &state::State, mesh: A::Mesh) { self.assets.mesh(mesh).render(ctx) } pub fn render_square(&self, ctx: &context::Context, st: &state::State) { st.mesh_square.render(ctx) } + pub fn render_cube(&self, ctx: &context::Context, st: &state::State) { + st.mesh_cube.render(ctx) + } pub fn set_position_2d_mat(&self, ctx: &context::Context, st: &state::State, pos: glam::Mat4) { if let Some((s, sm)) = self.shader() { debug_assert!(sm == ShaderMode::TwoDimension, "attempted to set_position_2d in wrong mode"); @@ -445,4 +462,14 @@ impl Renderer { offset: None, } } + + /// Common case: draw a cubemap as a skybox + pub fn skybox(&mut self, + ctx: &context::Context, st: &mut state::State, + skybox: &texture::Cubemap, + ) { + self.bind_shader_skybox(ctx, st); + skybox.bind(ctx); + self.render_cube(ctx, st); + } } diff --git a/crates/teleia/src/state.rs b/crates/teleia/src/state.rs index d4c9742..0f202ee 100644 --- a/crates/teleia/src/state.rs +++ b/crates/teleia/src/state.rs @@ -164,7 +164,9 @@ pub struct State { pub render_dims: glam::Vec2, pub postprocessing: postprocessing::Pipeline, pub shader_text_bitmap: shader::Shader, + pub shader_skybox: shader::Shader, pub mesh_square: mesh::Mesh, + pub mesh_cube: mesh::Mesh, pub font_default: font::Bitmap, pub font_small: font::Bitmap, pub audio: Option, @@ -239,7 +241,13 @@ impl State { include_str!("assets/shaders/bitmap/vert.glsl"), include_str!("assets/shaders/bitmap/frag.glsl"), ); + let shader_skybox = shader::Shader::new( + ctx, + include_str!("assets/shaders/skybox/vert.glsl"), + include_str!("assets/shaders/skybox/frag.glsl"), + ); let mesh_square = mesh::Mesh::from_obj(ctx, include_bytes!("assets/meshes/square.obj")); + let mesh_cube = mesh::Mesh::from_obj(ctx, include_bytes!("assets/meshes/cube.obj")); let nextframe = now(ctx); @@ -262,7 +270,9 @@ impl State { render_dims: glam::Vec2::new(ctx.render_width, ctx.render_height), postprocessing: postprocessing::Pipeline::new(ctx), shader_text_bitmap, + shader_skybox, mesh_square, + mesh_cube, font_default: font::Bitmap::default(ctx), font_small: font::Bitmap::small(ctx), audio: None, diff --git a/crates/teleia/src/texture.rs b/crates/teleia/src/texture.rs index e20ae05..71a39d8 100644 --- a/crates/teleia/src/texture.rs +++ b/crates/teleia/src/texture.rs @@ -8,7 +8,6 @@ pub struct Texture { pub width: i32, pub height: i32, } - impl Texture { pub fn new_empty(ctx: &context::Context) -> Self { unsafe { @@ -24,7 +23,6 @@ impl Texture { } } } - pub fn new(ctx: &context::Context, bytes: &[u8]) -> Self { let rgba = image::ImageReader::new(std::io::Cursor::new(bytes)) .with_guessed_format() @@ -52,7 +50,6 @@ impl Texture { Some(pixels), ); ctx.gl.generate_mipmap(glow::TEXTURE_2D); - Self { tex, width: rgba.width() as i32, @@ -60,7 +57,6 @@ impl Texture { } } } - pub fn upload(&mut self, ctx: &context::Context, bytes: &[u8]) { let rgba = image::ImageReader::new(std::io::Cursor::new(bytes)) .with_guessed_format() @@ -71,7 +67,6 @@ impl Texture { let pixels = rgba.as_bytes(); self.upload_rgba8(ctx, rgba.width() as i32, rgba.height() as i32, pixels); } - pub fn upload_rgba8(&mut self, ctx: &context::Context, width: i32, height: i32, data: &[u8]) { unsafe { ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); @@ -95,7 +90,6 @@ impl Texture { self.width = width; self.height = height; } - pub fn set_repeat(&self, ctx: &context::Context) { unsafe { ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); @@ -103,7 +97,6 @@ impl Texture { ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::REPEAT as i32); } } - pub fn set_anisotropic_filtering(&self, ctx: &context::Context) { unsafe { ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); @@ -114,21 +107,18 @@ impl Texture { ctx.gl.tex_parameter_f32(glow::TEXTURE_2D, glow::TEXTURE_MAX_ANISOTROPY_EXT, 4.0); } } - pub fn bind(&self, ctx: &context::Context) { unsafe { ctx.gl.active_texture(glow::TEXTURE0); ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); } } - pub fn bind_initial(ctx: &context::Context) { unsafe { ctx.gl.active_texture(glow::TEXTURE0); ctx.gl.bind_texture(glow::TEXTURE_2D, None); } } - pub fn bind_index(&self, ctx: &context::Context, idx: u32) { unsafe { ctx.gl.active_texture(glow::TEXTURE0 + idx); @@ -154,3 +144,51 @@ impl Material { self.normal.bind_index(ctx, 1); } } + +pub struct Cubemap { + pub tex: glow::Texture, +} +impl Cubemap { + // expects an iterator of side image bytes in this order: + // right, left, top, bottom, front, back + pub fn new<'a>(ctx: &context::Context, sides: impl IntoIterator) -> Self { + unsafe { + let tex = ctx.gl.create_texture().expect("failed to create texture"); + ctx.gl.bind_texture(glow::TEXTURE_CUBE_MAP, Some(tex)); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_R, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32); + for (i, bytes) in sides.into_iter().enumerate() { + let rgba = image::ImageReader::new(std::io::Cursor::new(bytes)) + .with_guessed_format() + .expect("failed to guess image format") + .decode() + .expect("failed to decode image") + .into_rgba8(); + let pixels = rgba.as_bytes(); + ctx.gl.tex_image_2d( + glow::TEXTURE_CUBE_MAP_POSITIVE_X + i as u32, + 0, + glow::RGBA as i32, + rgba.width() as i32, + rgba.height() as i32, + 0, + glow::RGBA, + glow::UNSIGNED_BYTE, + Some(pixels), + ); + } + Self { + tex, + } + } + } + pub fn bind(&self, ctx: &context::Context) { + unsafe { + ctx.gl.active_texture(glow::TEXTURE0); + ctx.gl.bind_texture(glow::TEXTURE_CUBE_MAP, Some(self.tex)); + } + } +} -- cgit v1.3.1