diff --git a/crates/comet_renderer/Cargo.toml b/crates/comet_renderer/Cargo.toml index ed5463e..69a46be 100644 --- a/crates/comet_renderer/Cargo.toml +++ b/crates/comet_renderer/Cargo.toml @@ -16,4 +16,4 @@ wgpu = { version = "22.0", features = ["glsl", "wgsl", "naga-ir"]} winit = { version = "0.29", features = ["rwh_05"] } chrono = "0.4.40" pollster = "0.3" -image = "0.24" \ No newline at end of file +image = "0.24" diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs index f498023..3cc06d0 100644 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -1,12 +1,12 @@ -use crate::renderer::Renderer; use crate::{ camera::CameraManager, render_context::RenderContext, render_pass::{universal_clear_execute, universal_load_execute, RenderPass}, + renderer::Renderer, }; use comet_colors::Color; use comet_ecs::{Component, Render, Render2D, Transform2D}; -use comet_log::{debug, error, info}; +use comet_log::*; use comet_resources::{ font::Font, graphic_resource_manager::GraphicResourceManager, texture_atlas::*, Texture, Vertex, }; @@ -541,13 +541,28 @@ impl<'a> Renderer2D<'a> { } fn get_glyph_region(&self, glyph: char, font: String) -> &TextureRegion { - let font_atlas = self - .resource_manager - .fonts() - .iter() - .find(|f| f.name() == font) - .unwrap(); - font_atlas.get_glyph(glyph).unwrap() + let key = format!("{}::{}", font, glyph); + + match self.resource_manager.font_atlas().textures().get(&key) { + Some(region) => region, + None => { + warn!( + "Missing glyph for character '{}' in font '{}', using fallback.", + glyph, font + ); + let fallback_key = format!("{}:: ", font); + self.resource_manager + .font_atlas() + .textures() + .get(&fallback_key) + .unwrap_or_else(|| { + panic!( + "No fallback glyph available (space also missing) for font '{}'", + font + ) + }) + } + } } pub fn add_text_to_buffers( diff --git a/crates/comet_resources/Cargo.toml b/crates/comet_resources/Cargo.toml index 0980ad6..b2ec333 100644 --- a/crates/comet_resources/Cargo.toml +++ b/crates/comet_resources/Cargo.toml @@ -5,11 +5,12 @@ edition = "2021" [dependencies] comet_log = { path = "../comet_log" } -wgpu = { version = "22.0", features = ["spirv"] } +wgpu = { version = "22.0", features = ["glsl", "wgsl", "naga-ir"] } anyhow = "1.0" bytemuck = { version = "1.16", features = [ "derive" ] } ab_glyph = "0.2.29" chrono = "0.4.40" +rect_packer = "0.2.1" [dependencies.image] version = "0.24" diff --git a/crates/comet_resources/src/graphic_resource_manager.rs b/crates/comet_resources/src/graphic_resource_manager.rs index f092e0c..a82c0bb 100644 --- a/crates/comet_resources/src/graphic_resource_manager.rs +++ b/crates/comet_resources/src/graphic_resource_manager.rs @@ -6,10 +6,7 @@ use crate::{ Texture, }; use comet_log::info; -use wgpu::{ - naga::{self, ShaderStage}, - Device, Queue, ShaderModule, -}; +use wgpu::{naga::ShaderStage, Device, Queue, ShaderModule}; pub struct GraphicResourceManager { texture_atlas: TextureAtlas, @@ -127,7 +124,7 @@ impl GraphicResourceManager { source: wgpu::ShaderSource::Glsl { shader: shader_source.into(), stage, - defines: naga::FastHashMap::default(), + defines: Default::default(), }, }) } else { diff --git a/crates/comet_resources/src/texture_atlas.rs b/crates/comet_resources/src/texture_atlas.rs index 0e558fc..95bfac7 100644 --- a/crates/comet_resources/src/texture_atlas.rs +++ b/crates/comet_resources/src/texture_atlas.rs @@ -1,6 +1,7 @@ use crate::font::*; use comet_log::*; -use image::{DynamicImage, GenericImage, GenericImageView}; +use image::{DynamicImage, GenericImage, GenericImageView, RgbaImage}; +use rect_packer::{Config, Packer, Rect}; use std::collections::HashMap; use std::path::Path; @@ -81,335 +82,332 @@ pub struct TextureAtlas { impl TextureAtlas { pub fn empty() -> Self { Self { - atlas: DynamicImage::new(1, 1, image::ColorType::Rgb8), + atlas: DynamicImage::new_rgba8(1, 1), textures: HashMap::new(), } } pub fn texture_paths(&self) -> Vec { - self.textures.keys().map(|k| k.to_string()).collect() + self.textures.keys().cloned().collect() } - fn calculate_atlas_width(textures: &Vec) -> u32 { - let mut last_height: u32 = textures.get(0).unwrap().height(); - let mut widths: Vec = Vec::new(); - let mut current_width: u32 = 0; - - for texture in textures { - if last_height != texture.height() { - widths.push(current_width); - current_width = 0; - last_height = texture.height(); - } - current_width += texture.width(); + #[inline(always)] + fn next_power_of_two(mut x: u32) -> u32 { + if x == 0 { + return 1; } - - widths.push(current_width); - - *widths.iter().max().unwrap() + x -= 1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x + 1 } - fn calculate_atlas_height(textures: &Vec) -> u32 { - let last_height: u32 = textures.get(0).unwrap().height(); - let mut height: u32 = 0; - height += last_height; + fn pack_textures( + textures: &[(&String, &DynamicImage)], + padding: u32, + ) -> (u32, u32, HashMap) { + let mut atlas_size = 512; + let max_size = 8192; - for texture in textures { - if last_height == texture.height() { - continue; - } + let valid_textures: Vec<(String, DynamicImage)> = textures + .iter() + .map(|(name, tex)| { + let (w, h) = (tex.width(), tex.height()); + if w == 0 || h == 0 { + warn!( + "Texture '{}' has invalid size {}x{}, replacing with 1x1 transparent dummy.", + name, w, h + ); + let mut img = RgbaImage::new(1, 1); + img.put_pixel(0, 0, image::Rgba([0, 0, 0, 0])); + (name.to_string(), DynamicImage::ImageRgba8(img)) + } else { + ((*name).clone(), (*tex).clone()) + } + }) + .collect(); - height += texture.height(); + if valid_textures.is_empty() { + error!("No valid textures to pack!"); + return (0, 0, HashMap::new()); } - height + loop { + let config = Config { + width: atlas_size as i32, + height: atlas_size as i32, + border_padding: padding as i32, + rectangle_padding: padding as i32, + }; + + let mut packer = Packer::new(config); + let mut placements = HashMap::new(); + let mut max_x = 0i32; + let mut max_y = 0i32; + let mut failed = false; + + for (name, tex) in &valid_textures { + let width = tex.width() as i32; + let height = tex.height() as i32; + + if width > atlas_size as i32 || height > atlas_size as i32 { + error!( + "Texture '{}' is too large ({width}x{height}) for current atlas size {atlas_size}x{atlas_size}", + name + ); + failed = true; + break; + } + + if let Some(rect) = packer.pack(width, height, false) { + max_x = max_x.max(rect.x + rect.width); + max_y = max_y.max(rect.y + rect.height); + placements.insert(name.clone(), rect); + } else { + failed = true; + break; + } + } + + if failed { + if atlas_size >= max_size { + error!( + "Failed to pack all textures even at max atlas size ({}x{}).", + max_size, max_size + ); + return (max_x as u32, max_y as u32, placements); + } + + info!( + "Atlas size {}x{} too small, doubling to {}x{}...", + atlas_size, + atlas_size, + atlas_size * 2, + atlas_size * 2 + ); + atlas_size *= 2; + } else { + info!( + "Created texture atlas ({}x{}) with {} textures.", + atlas_size, + atlas_size, + placements.len() + ); + return (max_x as u32, max_y as u32, placements); + } + } } - fn insert_texture_at(base: &mut DynamicImage, texture: &DynamicImage, x_pos: u32, y_pos: u32) { - for y in 0..texture.height() { - for x in 0..texture.width() { - let pixel = texture.get_pixel(x, y); - base.put_pixel(x + x_pos, y + y_pos, pixel); + fn build_atlas( + textures: &[(&String, &DynamicImage)], + placements: &HashMap, + atlas_width: u32, + atlas_height: u32, + ) -> (RgbaImage, HashMap) { + let mut base = RgbaImage::new(atlas_width, atlas_height); + let mut regions = HashMap::new(); + + for (name, tex) in textures { + if let Some(rect) = placements.get(*name) { + base.copy_from(&tex.to_rgba8(), rect.x as u32, rect.y as u32) + .unwrap_or_else(|_| { + panic!( + "Failed to blit texture '{}' into atlas at ({}, {})", + name, rect.x, rect.y + ) + }); + + let u0 = rect.x as f32 / atlas_width as f32; + let v0 = rect.y as f32 / atlas_height as f32; + let u1 = (rect.x + rect.width) as f32 / atlas_width as f32; + let v1 = (rect.y + rect.height) as f32 / atlas_height as f32; + + regions.insert( + (*name).clone(), + TextureRegion::new( + u0, + v0, + u1, + v1, + (rect.width as u32, rect.height as u32), + 0.0, + 0.0, + 0.0, + ), + ); } } + + (base, regions) } pub fn from_texture_paths(paths: Vec) -> Self { - let mut textures: Vec = Vec::new(); - let mut regions: HashMap = HashMap::new(); + let mut textures = Vec::new(); info!("Loading textures..."); - for path in &paths { - textures.push(image::open(&Path::new(path.as_str())).expect("Failed to load texture")); + let img = image::open(Path::new(path)).expect("Failed to load texture"); + textures.push((path, img)); } - info!("Textures loaded!"); - info!("Sorting textures by height..."); + info!("Packing textures..."); + let tex_refs: Vec<(&String, &DynamicImage)> = + textures.iter().map(|(p, i)| (*p, i)).collect(); - let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = - textures.iter().zip(paths.iter()).collect(); - texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); - let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = - texture_path_pairs.into_iter().unzip(); - let sorted_textures: Vec = - sorted_textures.into_iter().map(|t| t.clone()).collect(); - let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); + let (atlas_w, atlas_h, placements) = Self::pack_textures(&tex_refs, 2); - let (height, width) = ( - Self::calculate_atlas_height(&sorted_textures), - Self::calculate_atlas_width(&sorted_textures), + let atlas_w = Self::next_power_of_two(atlas_w); + let atlas_h = Self::next_power_of_two(atlas_h); + + let (base, regions) = Self::build_atlas(&tex_refs, &placements, atlas_w, atlas_h); + + info!( + "Created texture atlas ({}x{}) with {} textures.", + atlas_w, + atlas_h, + regions.len() ); - let mut base = DynamicImage::new_rgba8(width, height); - - let mut previous = sorted_textures.get(0).unwrap().height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; - - info!("Creating texture atlas..."); - - for (texture, path) in sorted_textures.iter().zip(sorted_paths.iter()) { - if texture.height() != previous { - y_offset += previous; - x_offset = 0; - previous = texture.height(); - } - - Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); - let texel_w = 0.5 / width as f32; - let texel_h = 0.5 / height as f32; - - let u0 = (x_offset as f32 + texel_w) / width as f32; - let v0 = (y_offset as f32 + texel_h) / height as f32; - let u1 = ((x_offset + texture.width()) as f32 - texel_w) / width as f32; - let v1 = ((y_offset + texture.height()) as f32 - texel_h) / height as f32; - - regions.insert( - path.to_string(), - TextureRegion::new(u0, v0, u1, v1, texture.dimensions(), 0.0, 0.0, 0.0), - ); - x_offset += texture.width(); - } - - info!("Texture atlas created!"); TextureAtlas { - atlas: base, + atlas: DynamicImage::ImageRgba8(base), textures: regions, } } pub fn from_textures(names: Vec, textures: Vec) -> Self { - let mut regions: HashMap = HashMap::new(); - - info!("Sorting textures by height..."); - - let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = - textures.iter().zip(names.iter()).collect(); - texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); - let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = - texture_path_pairs.into_iter().unzip(); - let sorted_textures: Vec = - sorted_textures.into_iter().map(|t| t.clone()).collect(); - let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); - - let (height, width) = ( - Self::calculate_atlas_height(&sorted_textures), - Self::calculate_atlas_width(&sorted_textures), + assert_eq!( + names.len(), + textures.len(), + "Names and textures must have the same length." ); - let mut base = DynamicImage::new_rgba8(width, height); - let mut previous = sorted_textures.get(0).unwrap().height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; + let tex_refs: Vec<(&String, &DynamicImage)> = names.iter().zip(textures.iter()).collect(); - info!("Creating texture atlas..."); + let (atlas_w, atlas_h, placements) = Self::pack_textures(&tex_refs, 2); + let atlas_w = Self::next_power_of_two(atlas_w); + let atlas_h = Self::next_power_of_two(atlas_h); - for (texture, name) in sorted_textures.iter().zip(sorted_paths.iter()) { - if texture.height() != previous { - y_offset += previous; - x_offset = 0; - previous = texture.height(); - } - - Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); - regions.insert( - name.to_string(), - TextureRegion::new( - x_offset as f32 / width as f32, - y_offset as f32 / height as f32, - (x_offset + texture.width()) as f32 / width as f32, - (y_offset + texture.height()) as f32 / height as f32, - texture.dimensions(), - 0.0, - 0.0, - 0.0, - ), - ); - x_offset += texture.width(); - } - - info!("Texture atlas created!"); + let (base, regions) = Self::build_atlas(&tex_refs, &placements, atlas_w, atlas_h); TextureAtlas { - atlas: base, + atlas: DynamicImage::ImageRgba8(base), textures: regions, } } - pub fn from_glyphs(mut glyphs: Vec) -> Self { - glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height())); + pub fn from_glyphs(glyphs: Vec) -> Self { + let textures: Vec<(String, DynamicImage)> = glyphs + .iter() + .map(|g| (g.name.clone(), g.render.clone())) + .collect(); - let height = Self::calculate_atlas_height( - &glyphs.iter().map(|g| g.render.clone()).collect::>(), - ); - let width = Self::calculate_atlas_width( - &glyphs.iter().map(|g| g.render.clone()).collect::>(), - ); + let tex_refs: Vec<(&String, &DynamicImage)> = + textures.iter().map(|(n, i)| (n, i)).collect(); - let padding = (glyphs.len() * 3) as u32; + let (atlas_w, atlas_h, placements) = Self::pack_textures(&tex_refs, 2); + let atlas_w = Self::next_power_of_two(atlas_w); + let atlas_h = Self::next_power_of_two(atlas_h); - let mut base = DynamicImage::new_rgba8(width + padding, height); + let mut base = RgbaImage::new(atlas_w, atlas_h); let mut regions = HashMap::new(); - let mut current_row_height = glyphs[0].render.height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; for g in glyphs.iter() { - let glyph_w = g.render.width(); - let glyph_h = g.render.height(); + if let Some(rect) = placements.get(&g.name) { + base.copy_from(&g.render.to_rgba8(), rect.x as u32, rect.y as u32) + .unwrap(); - if glyph_h != current_row_height { - y_offset += current_row_height + 3; - x_offset = 0; - current_row_height = glyph_h; + let u0 = rect.x as f32 / atlas_w as f32; + let v0 = rect.y as f32 / atlas_h as f32; + let u1 = (rect.x + rect.width) as f32 / atlas_w as f32; + let v1 = (rect.y + rect.height) as f32 / atlas_h as f32; + + let region = TextureRegion::new( + u0, + v0, + u1, + v1, + (rect.width as u32, rect.height as u32), + g.advance, + g.offset_x, + g.offset_y, + ); + + regions.insert(g.name.clone(), region); } - - Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset); - - let u0 = x_offset as f32 / (width + padding) as f32; - let v0 = y_offset as f32 / height as f32; - let u1 = (x_offset + glyph_w) as f32 / (width + padding) as f32; - let v1 = (y_offset + glyph_h) as f32 / height as f32; - - let region = TextureRegion::new( - u0, - v0, - u1, - v1, - (glyph_w, glyph_h), - g.advance, - g.offset_x, - g.offset_y, - ); - - regions.insert(g.name.clone(), region); - - x_offset += glyph_w + 3; } TextureAtlas { - atlas: base, + atlas: DynamicImage::ImageRgba8(base), textures: regions, } } - pub fn from_fonts(fonts: &Vec) -> Self { + pub fn from_fonts(fonts: &[Font]) -> Self { if fonts.is_empty() { return Self::empty(); } - let mut all_glyphs: Vec<(String, DynamicImage, TextureRegion)> = Vec::new(); + let mut all_glyphs = Vec::new(); - let mut font_indices: Vec = (0..fonts.len()).collect(); - font_indices.sort_by(|&a, &b| fonts[a].name().cmp(&fonts[b].name())); - - for fi in font_indices { - let font = &fonts[fi]; + for font in fonts { let font_name = font.name(); + let src_atlas = font.glyphs().atlas(); + let atlas_width = src_atlas.width(); + let atlas_height = src_atlas.height(); - let mut glyph_names: Vec = font.glyphs().textures().keys().cloned().collect(); - glyph_names.sort(); + for (glyph_name, region) in font.glyphs().textures() { + let src_x = (region.u0() * atlas_width as f32) as u32; + let src_y = (region.v0() * atlas_height as f32) as u32; + let width = region.dimensions().0; + let height = region.dimensions().1; - for glyph_name in glyph_names { - let region = font.glyphs().textures().get(&glyph_name).unwrap(); - - let (u0, v0) = (region.u0(), region.v0()); - let (width, height) = region.dimensions(); - - let src_x = (u0 * font.glyphs().atlas().width() as f32) as u32; - let src_y = (v0 * font.glyphs().atlas().height() as f32) as u32; - - let glyph_img = DynamicImage::ImageRgba8( - font.glyphs() - .atlas() - .view(src_x, src_y, width, height) - .to_image(), - ); + let glyph_img = src_atlas.view(src_x, src_y, width, height).to_image(); let key = format!("{}::{}", font_name, glyph_name); - - all_glyphs.push((key, glyph_img, region.clone())); + all_glyphs.push((key, DynamicImage::ImageRgba8(glyph_img), region.clone())); } } - all_glyphs.sort_by(|a, b| { - let ha = a.1.height(); - let hb = b.1.height(); - match hb.cmp(&ha) { - std::cmp::Ordering::Equal => a.0.cmp(&b.0), - other => other, - } - }); + let tex_refs: Vec<(&String, &DynamicImage)> = + all_glyphs.iter().map(|(n, i, _)| (n, i)).collect(); + let (atlas_w, atlas_h, placements) = Self::pack_textures(&tex_refs, 2); + let atlas_w = Self::next_power_of_two(atlas_w); + let atlas_h = Self::next_power_of_two(atlas_h); - let textures: Vec = - all_glyphs.iter().map(|(_, img, _)| img.clone()).collect(); - let atlas_height = Self::calculate_atlas_height(&textures); - let atlas_width = Self::calculate_atlas_width(&textures); - - let padding = (all_glyphs.len() * 3) as u32; - let mut base = DynamicImage::new_rgba8(atlas_width + padding, atlas_height); + let mut base = RgbaImage::new(atlas_w, atlas_h); let mut regions = HashMap::new(); - let mut current_row_height = textures[0].height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; - for (key, img, original_region) in all_glyphs { - let w = img.width(); - let h = img.height(); + if let Some(rect) = placements.get(&key) { + base.copy_from(&img.to_rgba8(), rect.x as u32, rect.y as u32) + .unwrap(); - if h != current_row_height { - y_offset += current_row_height + 3; - x_offset = 0; - current_row_height = h; + let u0 = rect.x as f32 / atlas_w as f32; + let v0 = rect.y as f32 / atlas_h as f32; + let u1 = (rect.x + rect.width) as f32 / atlas_w as f32; + let v1 = (rect.y + rect.height) as f32 / atlas_h as f32; + + regions.insert( + key, + TextureRegion::new( + u0, + v0, + u1, + v1, + (rect.width as u32, rect.height as u32), + original_region.advance(), + original_region.offset_x(), + original_region.offset_y(), + ), + ); } - - Self::insert_texture_at(&mut base, &img, x_offset, y_offset); - - let u0 = x_offset as f32 / (atlas_width + padding) as f32; - let v0 = y_offset as f32 / atlas_height as f32; - let u1 = (x_offset + w) as f32 / (atlas_width + padding) as f32; - let v1 = (y_offset + h) as f32 / atlas_height as f32; - - let region = TextureRegion::new( - u0, - v0, - u1, - v1, - (w, h), - original_region.advance(), - original_region.offset_x(), - original_region.offset_y(), - ); - - regions.insert(key, region); - - x_offset += w + 3; } TextureAtlas { - atlas: base, + atlas: DynamicImage::ImageRgba8(base), textures: regions, } } diff --git a/res/shaders/base2d.wgsl b/res/shaders/base2d.wgsl deleted file mode 100644 index 79a3aa0..0000000 --- a/res/shaders/base2d.wgsl +++ /dev/null @@ -1,42 +0,0 @@ -// Vertex shader -struct CameraUniform { - view_proj: mat4x4, -}; -@group(1) @binding(0) // 1. -var camera: CameraUniform; - -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, - @location(2) color: vec4, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) color: vec4, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.color = model.color; - out.clip_position = camera.view_proj * vec4(model.position, 1.0); - return out; -} - -// Fragment shader - -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0) @binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let sample_color = textureSample(t_diffuse, s_diffuse, in.tex_coords); - return sample_color * in.color; -} \ No newline at end of file diff --git a/res/shaders/blacknwhite.wgsl b/res/shaders/blacknwhite.wgsl deleted file mode 100644 index dac0caf..0000000 --- a/res/shaders/blacknwhite.wgsl +++ /dev/null @@ -1,39 +0,0 @@ -struct CameraUniform { - view_proj: mat4x4, -}; -@group(1) @binding(0) -var camera: CameraUniform; - -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, - @location(2) color: vec4, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) color: vec4, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.color = model.color; - out.clip_position = camera.view_proj * vec4(model.position, 1.0); - return out; -} - -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0) @binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let color = textureSample(t_diffuse, s_diffuse, in.tex_coords); - return vec4((color.r + color.g + color.b) / 3.0, (color.r + color.g + color.b) / 3.0, (color.r + color.g + color.b) / 3.0, color.a); -} \ No newline at end of file diff --git a/res/shaders/crt.wgsl b/res/shaders/crt.wgsl deleted file mode 100644 index cd20a11..0000000 --- a/res/shaders/crt.wgsl +++ /dev/null @@ -1,68 +0,0 @@ -// Vertex shader -struct CameraUniform { - view_proj: mat4x4, -}; -@group(1) @binding(0) // 1. -var camera: CameraUniform; - -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, - @location(2) color: vec4, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) color: vec4, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.color = model.color; - out.clip_position = camera.view_proj * vec4(model.position, 1.0); - return out; -} - -@group(0) @binding(0) -var t_diffuse: texture_2d; // Diffuse texture -@group(0) @binding(1) -var s_diffuse: sampler; // Sampler for the texture - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - // Sample the texture using the input texture coordinates - var texColor: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); - - // Apply CRT curvature effect (distortion) - let center = vec2(0.5, 0.5); // center of the screen - let dist = distance(in.tex_coords, center); // Distance from the center - let curvature = 0.1; // Adjust for more or less curvature - var distorted_uv = in.tex_coords + (in.tex_coords - center) * curvature * dist; - - // Apply chromatic aberration by slightly offsetting the UV coordinates - let redOffset = 0.005; - let greenOffset = 0.0; - let blueOffset = -0.005; - - let redColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2(redOffset, 0.0)); - let greenColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2(greenOffset, 0.0)); - let blueColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2(blueOffset, 0.0)); - - // Combine chromatic aberration with the original color - texColor.r = redColor.r; - texColor.g = greenColor.g; - texColor.b = blueColor.b; - - // Apply scanline effect (darken even rows for scanlines) - let scanlineEffect = 0.1 * (sin(in.clip_position.y * 0.3) + 1.0); // Horizontal scanlines - texColor.r *= scanlineEffect; // Apply scanline effect to red channel - texColor.g *= scanlineEffect; // Apply scanline effect to green channel - texColor.b *= scanlineEffect; // Apply scanline effect to blue channel - - return texColor; -} diff --git a/res/shaders/glitch.wgsl b/res/shaders/glitch.wgsl deleted file mode 100644 index d4c2534..0000000 --- a/res/shaders/glitch.wgsl +++ /dev/null @@ -1,89 +0,0 @@ -// Vertex shader -struct CameraUniform { - view_proj: mat4x4, -}; -@group(1) @binding(0) // 1. -var camera: CameraUniform; - -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, - @location(2) color: vec4, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) color: vec4, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.color = model.color; - out.clip_position = camera.view_proj * vec4(model.position, 1.0); - return out; -} - -@group(0) @binding(0) -var t_diffuse: texture_2d; // Diffuse texture -@group(0) @binding(1) -var s_diffuse: sampler; // Sampler for the texture - -// A simple pseudo-random number generator (using fragment coordinates) -fn rand2D(p: vec2) -> f32 { - let s = sin(dot(p, vec2(12.9898, 78.233))); // Pseudorandom calculation - return fract(s * 43758.5453); // Return value between 0 and 1 -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - // Sample the texture using the input texture coordinates - var texColor: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); - - // Random horizontal displacement for glitching effect - let glitchStrength = 0.03; // How far the texture will "glitch" - let glitchAmount = rand2D(in.tex_coords * 100.0); // Use a different scale for more variation - var displacedUV = in.tex_coords + vec2(glitchAmount * glitchStrength, 0.0); - - // Sample the texture at the displaced position - texColor = textureSample(t_diffuse, s_diffuse, displacedUV); - - // Apply random color flickering - let colorFlickerAmount = rand2D(in.tex_coords * 50.0); // More frequency for faster flickering - texColor.r *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust red channel brightness - texColor.g *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust green channel brightness - texColor.b *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust blue channel brightness - - // Occasionally "flicker" the texture to simulate complete signal loss - let flickerChance = rand2D(in.tex_coords * 200.0); // Different scale for randomness - if (flickerChance < 0.05) { // 5% chance to completely "flicker" out - texColor.r = 0.0; // Turn red channel to black - texColor.g = 0.0; // Turn green channel to black - texColor.b = 0.0; // Turn blue channel to black - } - - // Apply random horizontal offset to simulate screen tearing - let tearEffect = rand2D(in.tex_coords * 25.0); // Vary this value for different effects - if (tearEffect < 0.15) { - texColor.r = 1.0; // Simulate red "tear" - texColor.g = 0.0; // No green in the tear - texColor.b = 0.0; // No blue in the tear - } else if (tearEffect < 0.3) { - texColor.r = 0.0; // No red in the tear - texColor.g = 1.0; // Simulate green "tear" - texColor.b = 0.0; // No blue in the tear - } - - // Optionally, you can add a "flickering noise" layer for additional effect - let noiseAmount = rand2D(in.tex_coords * 500.0); // Highly random noise for flickering - texColor.r += noiseAmount * 0.1; // Add small amount of random noise to red channel - texColor.g += noiseAmount * 0.1; // Add small amount of random noise to green channel - texColor.b += noiseAmount * 0.1; // Add small amount of random noise to blue channel - - // Return the altered texture color - return texColor; -}