From d0ca80f53f3186cf733753c12805271a9ffd719f Mon Sep 17 00:00:00 2001 From: Caspar Krieger Date: Mon, 8 Apr 2024 09:01:12 +0800 Subject: [PATCH] Add support for transparency --- benches/benches.rs | 8 +++---- src/canvas.rs | 25 ++++++++------------ src/canvas_legacy.rs | 54 ++++++++++++++++++++++---------------------- src/colors.rs | 24 +++++++++++--------- src/model.rs | 8 +++---- src/shaders.rs | 16 ++++++------- src/ui.rs | 12 +++++----- 7 files changed, 72 insertions(+), 75 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index 3ed827e..82fc2f7 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -2,7 +2,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use crab_tv::{Canvas, WHITE}; use glam::IVec2; -use rgb::RGB8; +use rgb::RGBA8; fn line_drawing(c: &mut Criterion) { let mut group = c.benchmark_group("line-drawing"); @@ -10,7 +10,7 @@ fn line_drawing(c: &mut Criterion) { group.bench_function("v1-slow", |b| { let mut image = Canvas::new(100, 100); b.iter(|| { - image.line_slow(0, 0, 99, 99, RGB8::new(255, 0, 0)); + image.line_slow(0, 0, 99, 99, RGBA8::new(255, 0, 0)); }); black_box(image); }); @@ -18,7 +18,7 @@ fn line_drawing(c: &mut Criterion) { group.bench_function("v2-faster", |b| { let mut image = Canvas::new(100, 100); b.iter(|| { - image.line_faster(0, 0, 99, 99, RGB8::new(255, 0, 0)); + image.line_faster(0, 0, 99, 99, RGBA8::new(255, 0, 0)); }); black_box(image); }); @@ -26,7 +26,7 @@ fn line_drawing(c: &mut Criterion) { group.bench_function("v3-integer maths", |b| { let mut image = Canvas::new(100, 100); b.iter(|| { - image.line_fastest(0, 0, 99, 99, RGB8::new(255, 0, 0)); + image.line_fastest(0, 0, 99, 99, RGBA8::new(255, 0, 0)); }); black_box(image); }); diff --git a/src/canvas.rs b/src/canvas.rs index f817f2e..ff56602 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -1,11 +1,10 @@ use std::f32::consts::PI; use glam::{Mat3, Vec2, Vec3}; -use rgb::{ComponentMap, RGB8}; +use rgb::{ComponentMap, RGBA8}; use crate::{ - maths::{self, yolo_max, yolo_min}, - Model, DEPTH_MAX, + maths::{self, yolo_max, yolo_min}, Model, CLEAR, DEPTH_MAX }; #[derive(Copy, Clone, Debug, PartialEq, Default)] @@ -17,14 +16,14 @@ pub struct Vertex { pub trait Shader { fn vertex(&self, triangle: [Vertex; 3]) -> (Mat3, S); - fn fragment(&self, barycentric_coords: Vec3, state: &S) -> Option; + fn fragment(&self, barycentric_coords: Vec3, state: &S) -> Option; } #[derive(Clone, Debug)] pub struct Canvas { width: usize, height: usize, - pixels: Vec, + pixels: Vec, z_buffer: Vec, } @@ -33,7 +32,7 @@ impl Canvas { Self { width, height, - pixels: vec![RGB8::default(); width * height], + pixels: vec![RGBA8::default(); width * height], z_buffer: vec![f32::NEG_INFINITY; width * height], } } @@ -48,20 +47,16 @@ impl Canvas { self.height } - pub fn pixels(&self) -> &[RGB8] { + pub fn pixels(&self) -> &[RGBA8] { &self.pixels } - pub fn pixels_mut(&mut self) -> &mut [RGB8] { + pub fn pixels_mut(&mut self) -> &mut [RGBA8] { &mut self.pixels } - pub fn into_pixels(self) -> Vec { - self.pixels - } - #[inline] - pub fn pixel(&self, x: i32, y: i32) -> RGB8 { + pub fn pixel(&self, x: i32, y: i32) -> RGBA8 { debug_assert!( x >= 0 && x < self.width as i32, "x coordinate of '{}' is out of bounds 0 to {}", @@ -78,7 +73,7 @@ impl Canvas { } #[inline] - pub fn pixel_mut(&mut self, x: i32, y: i32) -> &mut RGB8 { + pub fn pixel_mut(&mut self, x: i32, y: i32) -> &mut RGBA8 { debug_assert!( x >= 0 && x < self.width as i32, "x coordinate of '{}' is out of bounds 0 to {}", @@ -133,7 +128,7 @@ impl Canvas { .z_buffer .iter() .map(|d| (*d * 255.0 / DEPTH_MAX) as u8) - .map(|c| RGB8::new(c, c, c)) + .map(|c| RGBA8::new(c, c, c, 255)) .collect(); } diff --git a/src/canvas_legacy.rs b/src/canvas_legacy.rs index b793378..e2be2d2 100644 --- a/src/canvas_legacy.rs +++ b/src/canvas_legacy.rs @@ -1,6 +1,6 @@ /// Legacy canvas API, where only certain fixed functions are supported (no shaders). use glam::{IVec2, Mat4, Vec2, Vec3, Vec4}; -use rgb::{ComponentMap, RGB8}; +use rgb::{ComponentMap, RGBA8}; use crate::{ maths::{self, yolo_max, yolo_min}, @@ -19,7 +19,7 @@ pub enum ModelShading { impl Canvas { // incorrect because it depends on choosing the correct "increment", which will vary based on // how many pixels need to be drawn - pub fn line_naive1(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: RGB8) { + pub fn line_naive1(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: RGBA8) { let increment = 0.1; for i in 0..((1.0 / increment) as i32) { let i = f64::from(i) * increment; @@ -30,16 +30,16 @@ impl Canvas { } // incorrect because it doesn't handle the case where the line is near vertical or x1 < x0 - pub fn line_naive2(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: RGB8) { + pub fn line_naive2(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: RGBA8) { for x in x0..x1 { let t = (x - x0) as f64 / (x1 - x0) as f64; - let y = y0 as f64 * (1.0 - t) as f64 + y1 as f64 * t as f64; - *self.pixel_mut(x as i32, y as i32) = color; + let y = y0 as f64 * (1.0 - t) + y1 as f64 * t; + *self.pixel_mut(x, y as i32) = color; } } // Bresenham's algorithm 1 - correct but slow due to needing floating point maths - pub fn line_slow(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: RGB8) { + pub fn line_slow(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: RGBA8) { let steep = if (x0 - x1).abs() < (y0 - y1).abs() { std::mem::swap(&mut x0, &mut y0); std::mem::swap(&mut x1, &mut y1); @@ -56,17 +56,17 @@ impl Canvas { let divisor = x1 - x0; for x in x0..x1 { let t = (x - x0) as f64 / divisor as f64; - let y = y0 as f64 * (1.0 - t) as f64 + y1 as f64 * t as f64; + let y = y0 as f64 * (1.0 - t) + y1 as f64 * t; if steep { - *self.pixel_mut(y as i32, x as i32) = color; + *self.pixel_mut(y as i32, x) = color; } else { - *self.pixel_mut(x as i32, y as i32) = color; + *self.pixel_mut(x, y as i32) = color; } } } // Bresenham's algorithm 2 - still using floating point maths but avoiding some division - pub fn line_faster(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: RGB8) { + pub fn line_faster(&mut self, mut x0: i32, mut y0: i32, mut x1: i32, mut y1: i32, color: RGBA8) { let steep = if (x0 - x1).abs() < (y0 - y1).abs() { std::mem::swap(&mut x0, &mut y0); std::mem::swap(&mut x1, &mut y1); @@ -106,7 +106,7 @@ impl Canvas { mut y0: i32, mut x1: i32, mut y1: i32, - color: RGB8, + color: RGBA8, ) { let steep = if (x0 - x1).abs() < (y0 - y1).abs() { std::mem::swap(&mut x0, &mut y0); @@ -128,9 +128,9 @@ impl Canvas { let mut y = y0; for x in x0..=x1 { if steep { - *self.pixel_mut(y as i32, x as i32) = color; + *self.pixel_mut(y, x) = color; } else { - *self.pixel_mut(x as i32, y as i32) = color; + *self.pixel_mut(x, y) = color; } error2 += derror2; if error2 > dx { @@ -140,13 +140,13 @@ impl Canvas { } } - pub fn line(&mut self, p1: IVec2, p2: IVec2, color: RGB8) { + pub fn line(&mut self, p1: IVec2, p2: IVec2, color: RGBA8) { let (x0, y0) = (p1.x, p1.y); let (x1, y1) = (p2.x, p2.y); self.line_fastest(x0, y0, x1, y1, color); } - pub fn model_wireframe(&mut self, model: &Model, color: RGB8) { + pub fn model_wireframe(&mut self, model: &Model, color: RGBA8) { for face in model.faces.iter() { for j in 0..3 { let v0 = model.vertices[face.points[j].vertices_index]; @@ -301,10 +301,10 @@ impl Canvas { let w = (avg_intensity * 255.0) as u8; match shading { ModelShading::FlatOnly => { - self.triangle_barycentric(&screen_coords_2d, RGB8::new(w, w, w)) + self.triangle_barycentric(&screen_coords_2d, RGBA8::new(w, w, w, 255)) } ModelShading::DepthTested => self - .triangle_barycentric_depth_tested(&screen_coords_3d, RGB8::new(w, w, w)), + .triangle_barycentric_depth_tested(&screen_coords_3d, RGBA8::new(w, w, w, 255)), ModelShading::Textured => self.triangle_barycentric_texture( &screen_coords_3d, &model.diffuse_texture, @@ -323,7 +323,7 @@ impl Canvas { } /// Output a wireframe (unfilled) triangle by using line drawing - pub fn triangle_wireframe(&mut self, t0: IVec2, t1: IVec2, t2: IVec2, color: RGB8) { + pub fn triangle_wireframe(&mut self, t0: IVec2, t1: IVec2, t2: IVec2, color: RGBA8) { self.line(t0, t1, color); self.line(t1, t2, color); self.line(t2, t0, color); @@ -340,13 +340,13 @@ impl Canvas { (vertices[0], vertices[1], vertices[2]) }; - self.line(t2, t0, RGB8::new(255, 0, 0)); - self.line(t0, t1, RGB8::new(0, 255, 0)); - self.line(t1, t2, RGB8::new(0, 0, 255)); + self.line(t2, t0, RGBA8::new(255, 0, 0, 255)); + self.line(t0, t1, RGBA8::new(0, 255, 0, 255)); + self.line(t1, t2, RGBA8::new(0, 0, 255, 255)); } // Draw a filled triangle using line sweeping. - pub fn triangle_linesweep_verbose(&mut self, pts: &[IVec2], color: RGB8) { + pub fn triangle_linesweep_verbose(&mut self, pts: &[IVec2], color: RGBA8) { let (t0, t1, t2) = (pts[0], pts[1], pts[2]); if t0.y == t1.y && t0.y == t2.y { @@ -416,7 +416,7 @@ impl Canvas { } // Draw a filled triangle using line sweeping, approach 2 - pub fn triangle_linesweep_compact(&mut self, pts: &[IVec2], color: RGB8) { + pub fn triangle_linesweep_compact(&mut self, pts: &[IVec2], color: RGBA8) { let (t0, t1, t2) = (pts[0], pts[1], pts[2]); if t0.y == t1.y && t0.y == t2.y { @@ -457,7 +457,7 @@ impl Canvas { } } - pub fn triangle_barycentric(&mut self, pts: &[IVec2], color: RGB8) { + pub fn triangle_barycentric(&mut self, pts: &[IVec2], color: RGBA8) { let mut bboxmin = IVec2::new((self.width() - 1) as i32, (self.height() - 1) as i32); let mut bboxmax = IVec2::new(0, 0); let clamp = IVec2::new((self.width() - 1) as i32, (self.height() - 1) as i32); @@ -481,7 +481,7 @@ impl Canvas { } } - pub fn triangle_barycentric_depth_tested(&mut self, pts: &[Vec3], color: RGB8) { + pub fn triangle_barycentric_depth_tested(&mut self, pts: &[Vec3], color: RGBA8) { let mut bboxmin = Vec2::new((self.width() - 1) as f32, (self.height() - 1) as f32); let mut bboxmax = Vec2::new(0.0, 0.0); let clamp = Vec2::new((self.width() - 1) as f32, (self.height() - 1) as f32); @@ -553,7 +553,7 @@ impl Canvas { let color = tex.data[(tex.height - uv.y as usize) * tex.width + uv.x as usize] .map(|comp| (comp as f32 * light_intensity) as u8); - *self.pixel_mut(i, j) = color; + *self.pixel_mut(i, j) = color.into(); } } } @@ -604,7 +604,7 @@ impl Canvas { let color = tex.data[(tex.height - uv.y as usize) * tex.width + uv.x as usize] .map(|comp| (comp as f32 * weighted_light_intensity) as u8); - *self.pixel_mut(i, j) = color; + *self.pixel_mut(i, j) = color.into(); } } } diff --git a/src/colors.rs b/src/colors.rs index 4388449..5b4b6d5 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,20 +1,22 @@ -use rgb::RGB8; +use rgb::RGBA8; -pub const WHITE: RGB8 = RGB8::new(255, 255, 255); -pub const BLACK: RGB8 = RGB8::new(0, 0, 0); +pub const WHITE: RGBA8 = RGBA8::new(255, 255, 255, 255); +pub const BLACK: RGBA8 = RGBA8::new(0, 0, 0, 255); +pub const CLEAR: RGBA8 = RGBA8::new(100, 100, 100, 0); -pub const RED: RGB8 = RGB8::new(255, 0, 0); -pub const GREEN: RGB8 = RGB8::new(0, 255, 0); -pub const BLUE: RGB8 = RGB8::new(0, 0, 255); +pub const RED: RGBA8 = RGBA8::new(255, 0, 0, 255); +pub const GREEN: RGBA8 = RGBA8::new(0, 255, 0, 255); +pub const BLUE: RGBA8 = RGBA8::new(0, 0, 255, 255); -pub const YELLOW: RGB8 = RGB8::new(255, 255, 0); -pub const CYAN: RGB8 = RGB8::new(0, 255, 255); -pub const MAGENTA: RGB8 = RGB8::new(255, 0, 255); +pub const YELLOW: RGBA8 = RGBA8::new(255, 255, 0, 255); +pub const CYAN: RGBA8 = RGBA8::new(0, 255, 255, 255); +pub const MAGENTA: RGBA8 = RGBA8::new(255, 0, 255, 255); -pub fn random_color() -> RGB8 { - RGB8::new( +pub fn random_color() -> RGBA8 { + RGBA8::new( rand::random::() % 255, rand::random::() % 255, rand::random::() % 255, + 255, ) } diff --git a/src/model.rs b/src/model.rs index 34b86fc..7a2eda0 100644 --- a/src/model.rs +++ b/src/model.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use anyhow::{anyhow, bail, Context, Result}; use derive_more::Constructor; use glam::{Vec2, Vec3}; -use rgb::{ComponentMap, RGB8}; +use rgb::{ComponentMap, RGB8, RGBA8}; #[derive(Clone, Copy, Debug, PartialEq, Constructor)] pub struct Vertex { @@ -50,13 +50,13 @@ impl Texture { )) } - pub fn get_pixel(&self, uv: Vec2) -> RGB8 { + pub fn get_pixel(&self, uv: Vec2) -> RGBA8 { let x = uv.x as usize; let y = uv.y as usize; debug_assert!(x < self.width); debug_assert!(y < self.height); - self.data[(self.height - y as usize) * self.width + x as usize] + self.data[(self.height - y) * self.width + x].into() } pub fn get_normal(&self, uv: Vec2) -> Vec3 { @@ -254,7 +254,7 @@ impl Model { let specular_texture = Texture::load_from_file(&input.specular_texture) .context("Loading specular texture failed")?; let glow_texture = input.glow_texture.as_ref().and_then(|texture| { - Texture::load_from_file(&texture) + Texture::load_from_file(texture) .context("Loading glow texture failed") .ok() }); diff --git a/src/shaders.rs b/src/shaders.rs index af68ada..5489e41 100644 --- a/src/shaders.rs +++ b/src/shaders.rs @@ -1,7 +1,7 @@ use glam::{Mat3, Mat4, Vec2, Vec3, Vec4}; use crab_tv::{Canvas, Shader, Texture, Vertex}; -use rgb::{ComponentMap, RGB8}; +use rgb::{ComponentMap, RGBA8}; pub struct GouraudShaderState { varying_uv: [Vec2; 3], @@ -72,7 +72,7 @@ impl Shader for GouraudShader<'_> { ) } - fn fragment(&self, barycentric_coords: Vec3, state: &GouraudShaderState) -> Option { + fn fragment(&self, barycentric_coords: Vec3, state: &GouraudShaderState) -> Option { let GouraudShaderState { varying_uv, varying_light_intensity: light_intensity, @@ -170,7 +170,7 @@ impl Shader for NormalShader<'_> { (varying_tri, varying_uv) } - fn fragment(&self, barycentric_coords: Vec3, varying_uv: &VertexUVs) -> Option { + fn fragment(&self, barycentric_coords: Vec3, varying_uv: &VertexUVs) -> Option { let uv = varying_uv[0] * barycentric_coords[0] + varying_uv[1] * barycentric_coords[1] + varying_uv[2] * barycentric_coords[2]; @@ -306,7 +306,7 @@ impl Shader for PhongShader<'_> { ) } - fn fragment(&self, barycentric_coords: Vec3, state: &PhongShaderState) -> Option { + fn fragment(&self, barycentric_coords: Vec3, state: &PhongShaderState) -> Option { let PhongShaderState { varying_tri, varying_uv, @@ -427,7 +427,7 @@ impl Shader for PhongShader<'_> { pub struct UnlitShaderState { varying_uv: [Vec2; 3], - triangle_color: RGB8, + triangle_color: RGBA8, } /// A shader that renders a texture but doesn't do any lighting @@ -484,7 +484,7 @@ impl Shader for UnlitShader<'_> { ) } - fn fragment(&self, barycentric_coords: Vec3, state: &UnlitShaderState) -> Option { + fn fragment(&self, barycentric_coords: Vec3, state: &UnlitShaderState) -> Option { let UnlitShaderState { varying_uv, triangle_color, @@ -533,7 +533,7 @@ impl Shader for DepthShader { (varying_tri, varying_tri) } - fn fragment(&self, barycentric_coords: Vec3, varying_tri: &DepthVaryingTri) -> Option { + fn fragment(&self, barycentric_coords: Vec3, varying_tri: &DepthVaryingTri) -> Option { let p = (*varying_tri) * barycentric_coords; let depth_scaled = p.z / crab_tv::DEPTH_MAX; Some(crab_tv::WHITE.map(|c| (c as f32 * depth_scaled) as u8)) @@ -568,7 +568,7 @@ impl Shader<()> for PureColorShader { (varying_tri, ()) } - fn fragment(&self, _barycentric_coords: Vec3, _: &()) -> Option { + fn fragment(&self, _barycentric_coords: Vec3, _: &()) -> Option { Some(crab_tv::WHITE) } } diff --git a/src/ui.rs b/src/ui.rs index 280296c..3934edd 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -6,7 +6,7 @@ use eframe::{ epi, }; use glam::Vec3; -use rgb::RGB8; +use rgb::RGBA8; use strum::IntoEnumIterator; use crate::{RenderConfig, RenderInput, RenderScene}; @@ -15,7 +15,7 @@ use crate::{RenderConfig, RenderInput, RenderScene}; struct UiData { last_render_width: usize, last_render_height: usize, - last_render_pixels: Vec, + last_render_pixels: Vec, last_render_tex: Option, } @@ -24,7 +24,7 @@ impl UiData { Self { last_render_width: width, last_render_height: height, - last_render_pixels: vec![RGB8 { r: 0, g: 0, b: 0 }; width * height], + last_render_pixels: vec![RGBA8 { r: 0, g: 0, b: 0, a: 255 }; width * height], ..Default::default() } } @@ -38,7 +38,7 @@ impl UiData { fn store_image( &mut self, - pixels: &[RGB8], + pixels: &[RGBA8], tex_allocator: &mut dyn eframe::epi::TextureAllocator, ) { assert_eq!( @@ -46,7 +46,7 @@ impl UiData { self.last_render_width * self.last_render_height ); - self.last_render_pixels = pixels.iter().copied().collect(); + self.last_render_pixels = pixels.to_vec(); if let Some(existing_tex) = self.last_render_tex { tex_allocator.free(existing_tex); @@ -167,7 +167,7 @@ impl RendererApp { .as_mut() .expect("ui data must be present for storing pixels"); - data.store_image(image.into_pixels().as_slice(), tex_allocator); + data.store_image(image.pixels(), tex_allocator); } }