From b2f9d492c13cbedae9229096d9ca703225cf3fe8 Mon Sep 17 00:00:00 2001 From: Georg Hopp Date: Thu, 2 Jan 2020 18:41:25 +0100 Subject: [PATCH] first very simple shading Add a very simple shader with a direct light source. The shader modifies the color of a polygon just by the angle between the face normal and the light source. This has no physical resemblance at all but let me check if the normal calculation works so far... --- fractional/notes/math.url | 1 + fractional/notes/texture-mapping.url | 2 + fractional/src/geometry.rs | 183 +++++++++++++++++++++------ fractional/src/main.rs | 47 ++++--- fractional/src/vector.rs | 8 +- 5 files changed, 184 insertions(+), 57 deletions(-) create mode 100644 fractional/notes/math.url diff --git a/fractional/notes/math.url b/fractional/notes/math.url new file mode 100644 index 0000000..b2b55d3 --- /dev/null +++ b/fractional/notes/math.url @@ -0,0 +1 @@ +https://www.themathpage.com/Alg/reciprocals.htm diff --git a/fractional/notes/texture-mapping.url b/fractional/notes/texture-mapping.url index fd55dc7..88914ef 100644 --- a/fractional/notes/texture-mapping.url +++ b/fractional/notes/texture-mapping.url @@ -11,3 +11,5 @@ http://www.lysator.liu.se/~mikaelk/doc/perspectivetexture/ # Shader... This also describes z-Buffer... :) https://people.ece.cornell.edu/land/OldStudentProjects/cs490-95to96/GUO/report.html +https://software.intel.com/en-us/articles/the-basics-of-the-art-of-lighting-part-3-lighting-and-shaders/ +https://docs.unity3d.com/Manual/Lighting.html diff --git a/fractional/src/geometry.rs b/fractional/src/geometry.rs index 7fa883c..ede5968 100644 --- a/fractional/src/geometry.rs +++ b/fractional/src/geometry.rs @@ -27,18 +27,27 @@ use crate::transform::TMatrix; use crate::trigonometry::Trig; use crate::vector::Vector; +#[derive(Debug, Clone)] +pub struct Face +where T: Add + Sub + Neg + Mul + Div + Copy + Trig { + corners :Vec, + normal :Option>, +} + #[derive(Debug)] pub struct Polyeder -where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig { - points :Vec>, - faces :Vec>, - normals :Vec>, +where T: Add + Sub + Neg + Mul + Div + Copy + Trig { + points :Vec>, + faces :Vec>, } pub trait Primitives where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { fn transform(&self, m :&TMatrix) -> Self; - fn project(&self, camera :&Camera) -> Vec; + fn project( &self + , camera :&Camera + , light :&DirectLight + , col :u32 ) -> Vec<(Polygon, u32)>; } pub struct Camera @@ -48,6 +57,11 @@ where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { project :TMatrix, } +pub struct DirectLight +where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { + direction: Vector, +} + impl Camera where T: Add + Sub + Neg + Mul + Div @@ -83,10 +97,46 @@ where T: Add + Sub + Neg } } +impl DirectLight +where T: Add + Sub + Neg + + Mul + Div + + Debug + Copy + Trig + From { + pub fn new(v :Vector) -> Self { + DirectLight{ direction: v } + } + + pub fn dir(&self) -> Vector { + self.direction + } +} + +impl Face +where T: Add + Sub + Neg + + Mul + Div + + Debug + Copy + Trig + From { + fn new(corners :Vec, ps :&[Vector]) -> Self { + let mut f = Face{ corners: corners, normal: None }; + f.update_normal(ps); + f + } + + fn update_normal(&mut self, ps :&[Vector]) { + let edge10 = ps[self.corners[1]] - ps[self.corners[0]]; + let edge12 = ps[self.corners[1]] - ps[self.corners[2]]; + self.normal = Some(edge10 * edge12); + } +} + impl Polyeder where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { + fn update_normals(&mut self) { + for f in self.faces.iter_mut() { + f.update_normal(&self.points); + } + } + // https://rechneronline.de/pi/tetrahedron.php pub fn tetrahedron(a :T) -> Polyeder { let f0 :T = 0.into(); @@ -106,49 +156,86 @@ where T: Add + Sub + Neg // half the deeps in z let _zh :T = T::sqrt(f3).unwrap() / f4 * a; - Polyeder{ points: vec!( Vector( f0, yc, f0) - , Vector(-ah, -yi, -zi) - , Vector( ah, -yi, -zi) - , Vector( f0, -yi, zc) ) - , faces: vec!( vec!(1, 2, 3) - , vec!(1, 0, 2) - , vec!(3, 0, 1) - , vec!(2, 0, 3) )} + let ps = vec!( Vector( f0, yc, f0) + , Vector(-ah, -yi, -zi) + , Vector( ah, -yi, -zi) + , Vector( f0, -yi, zc) ); + + let fs = vec!( Face::new(vec!(1, 2, 3), &ps) + , Face::new(vec!(1, 0, 2), &ps) + , Face::new(vec!(3, 0, 1), &ps) + , Face::new(vec!(2, 0, 3), &ps) ); + + Polyeder{ points: ps, faces: fs } + } + + pub fn triangle(a :T) -> Polyeder { + let f0 :T = 0.into(); + let f3 :T = 3.into(); + let f6 :T = 6.into(); + let zi :T = T::sqrt(f3).unwrap() / f6 * a; + let zc :T = T::sqrt(f3).unwrap() / f3 * a; + let ah :T = a / 2.into(); + + let ps = vec!( Vector(-ah, f0, -zi) + , Vector( f0, f0, zc) + , Vector( ah, f0, -zi) ); + + let fs = vec!(Face::new(vec!(0, 1, 2), &ps)); + + println!("== {:?}", fs); + + Polyeder{ points: ps, faces: fs } } pub fn cube(a :T) -> Polyeder { let ah :T = a / From::::from(2); - Polyeder{ points: vec!( Vector(-ah, ah, -ah) // 0 => front 1 - , Vector(-ah, -ah, -ah) // 1 => front 2 - , Vector( ah, -ah, -ah) // 2 => front 3 - , Vector( ah, ah, -ah) // 3 => front 4 - , Vector(-ah, ah, ah) // 4 => back 1 - , Vector(-ah, -ah, ah) // 5 => back 2 - , Vector( ah, -ah, ah) // 6 => back 3 - , Vector( ah, ah, ah) ) // 7 => back 4 - , faces: vec!( vec!(0, 1, 2, 3) // front - , vec!(7, 6, 5, 4) // back - , vec!(1, 5, 6, 2) // top - , vec!(0, 3, 7, 4) // bottom - , vec!(0, 4, 5, 1) // left - , vec!(2, 6, 7, 3) )} // right + let ps = vec!( Vector(-ah, ah, -ah) // 0 => front 1 + , Vector(-ah, -ah, -ah) // 1 => front 2 + , Vector( ah, -ah, -ah) // 2 => front 3 + , Vector( ah, ah, -ah) // 3 => front 4 + , Vector(-ah, ah, ah) // 4 => back 1 + , Vector(-ah, -ah, ah) // 5 => back 2 + , Vector( ah, -ah, ah) // 6 => back 3 + , Vector( ah, ah, ah) ); // 7 => back 4 + + let fs = vec!( Face::new(vec!(0, 1, 2, 3), &ps) // front + , Face::new(vec!(7, 6, 5, 4), &ps) // back + , Face::new(vec!(1, 5, 6, 2), &ps) // top + , Face::new(vec!(0, 3, 7, 4), &ps) // bottom + , Face::new(vec!(0, 4, 5, 1), &ps) // left + , Face::new(vec!(2, 6, 7, 3), &ps) ); // right + + Polyeder{ points: ps, faces: fs } } } impl Primitives for Polyeder where T: Add + Sub + Neg + Mul + Div - + Debug + Copy + Trig + From + From { + + Debug + Copy + Trig + From + PartialOrd { fn transform(&self, m :&TMatrix) -> Self { - Polyeder{ points: self.points.iter().map(|p| m.apply(p)).collect() - , faces: self.faces.to_vec() } + let Polyeder{ points: ps, faces: fs } = self; + + let mut p = Polyeder{ points: ps.iter().map(|p| m.apply(p)).collect() + , faces: fs.to_vec() }; + + // TODO alternatively we could rotate the normals too, but this cannot + // done with the original matrix… the question is, what is faster. + p.update_normals(); + p } // TODO for now we assume already prejected vertices (points) // in future we need to distinguish more clear between vertex and point // and projected_point. - fn project(&self, _camera :&Camera) -> Vec { + fn project( &self + , camera :&Camera + , light :&DirectLight + , color :u32 ) -> Vec<(Polygon, u32)> { + // Helper to create a Polygon from Coordinates… + // TODO probably there needs to be a Polygon constructor for this. fn polygon(c :I) -> Polygon where I: Iterator { Polygon(Coordinates(c.collect())) @@ -156,12 +243,34 @@ where T: Add + Sub + Neg // this one does the projection... as the projection was the last // matrix we do not need to do it here. - // let to_coord = |p :&usize| _camera.project(self.points[*p]); - let to_coord = |p :&usize| { - let v = self.points[*p]; - Coordinate(v.x().round(), v.y().round()) }; - let to_poly = |f :&Vec| polygon(f.iter().map(to_coord)); + let to_coord = |p :&usize| camera.project(self.points[*p]); + let to_poly = |f :&Face| { + let pg = polygon(f.corners.iter().map(to_coord)); + let mut r :T = (((color >> 16) & 0xFF) as i32).into(); + let mut g :T = (((color >> 8) & 0xFF) as i32).into(); + let mut b :T = (((color ) & 0xFF) as i32).into(); + let lf :T = match f.normal { + None => 1.into(), + Some(n) => n.dot(light.dir()) + / (n.mag() * light.dir().mag()), + }; + + if lf < 0.into() { + r = r * -lf; + g = g * -lf; + b = b * -lf; + + let c :u32 = (r.round() as u32) << 16 + | (g.round() as u32) << 8 + | (b.round() as u32); + + Some((pg, c)) + } else { + None + }}; - self.faces.iter().map(to_poly).collect() + let mut ps :Vec<(Polygon, u32)> = self.faces.iter().filter_map(to_poly).collect(); + ps.sort_by(|a, b| a.1.cmp(&b.1)); + ps } } diff --git a/fractional/src/main.rs b/fractional/src/main.rs index 028732e..1a24aef 100644 --- a/fractional/src/main.rs +++ b/fractional/src/main.rs @@ -39,7 +39,7 @@ use fractional::transform::{TMatrix, translate, rotate_x, rotate_y, rotate_z, ro use fractional::xcb::XcbEasel; use fractional::easel::Canvas; -use fractional::geometry::{Camera,Polyeder,Primitives}; +use fractional::geometry::{Camera,DirectLight,Polyeder,Primitives}; fn mean(v: &Vec) -> Result { let r = v.iter().fold(0, |acc, x| acc + x); @@ -162,16 +162,16 @@ fn _vector(v1 :Vector, v2 :Vector, s :T) + Mul + Div + Trig + Copy + Display { println!("{:>14} : {}", "Vector v1", v1); println!("{:>14} : {}", "Vector v2", v2); - println!("{:>14} : {}", "abs v1", v1.abs()); + println!("{:>14} : {}", "magnitude v1", v1.mag()); println!("{:>14} : {}", "-v1", -v1); println!("{:>14} : {}", "v1 + v1", v1 + v1); println!("{:>14} : {}", "v1 - v1", v1 - v1); println!("{:>14} : {}", "v2 - v1", v2 - v1); println!("{:>14} : {}", format!("v1 * {}", s), v1.mul(&s)); println!("{:>14} : {}", "norm v1", v1.norm()); - println!("{:>14} : {}", "abs norm v1", v1.norm().abs()); - println!("{:>14} : {}", "abs v1", v1.abs()); - println!("{:>14} : {}", "norm * abs", v1.norm().mul(&v1.abs())); + println!("{:>14} : {}", "magnitude norm v1", v1.norm().mag()); + println!("{:>14} : {}", "magnitude v1", v1.mag()); + println!("{:>14} : {}", "norm * magnitude", v1.norm().mul(&v1.mag())); println!("{:>14} : {}", "distance v1 v2", v1.distance(v2)); println!("{:>14} : {}", "distance v2 v1", v2.distance(v1)); println!("{:>14} : {}", "v1 dot v2", v1.dot(v2)); @@ -313,15 +313,17 @@ fn _line() { fn _democanvas( xcb :&XcbEasel , title :&'static str , tx :mpsc::Sender + , _triangle :Polyeder , tetrahedron :Polyeder - , cube :Polyeder ) + , cube :Polyeder + , light :DirectLight ) where T: 'static + Add + Sub + Neg + Mul + Div - + Debug + Copy + Trig + Send + From { + + Debug + Copy + Trig + Send + From + PartialOrd { let mut canvas = xcb.canvas(151, 151).unwrap(); - let camera = Camera::::new(&canvas, 45); // the orig. view angle - // was 50. + let camera = Camera::::new(&canvas, 45); // the orig. view angle + // was 50. canvas.set_title(title); canvas.init_events(); @@ -333,24 +335,31 @@ fn _democanvas( xcb :&XcbEasel let mut last = Instant::now(); let t :TMatrix = translate(Vector(0.into(), 0.into(), 150.into())); - let p :TMatrix = camera.get_projection(); + // We do not need this here… it is used within projection… + // let p :TMatrix = camera.get_projection(); loop { let deg = ((start.elapsed() / 25).as_millis() % 360) as i32; let rz :TMatrix = rotate_z(deg); - let rot1 = TMatrix::combine(vec!(rz, rotate_x(-deg*2), t, p)); - let rot2 = TMatrix::combine(vec!(rz, rotate_y(-deg*2), t, p)); + // I can not apply the projection in one turn, as I generate the + // normals always… and this is no longer possible after the + // projection… + // let rot1 = TMatrix::combine(vec!(rz, rotate_x(-deg*2), t, p)); + // let rot2 = TMatrix::combine(vec!(rz, rotate_y(-deg*2), t, p)); + let rot1 = TMatrix::combine(vec!(rz, rotate_x(-deg*2), t)); + let rot2 = TMatrix::combine(vec!(rz, rotate_y(-deg*2), t)); let objects = vec!( (tetrahedron.transform(&rot1), 0xFFFF00) , ( cube.transform(&rot2), 0x0000FF) ); + //let objects = vec!( (triangle.transform(&rot1), 0xFFFF00) ); canvas.clear(); for (o, color) in objects { - for pg in o.project(&camera) { - canvas.draw(&pg, Coordinate(0,0), color); + for (pg, c) in o.project(&camera, &light, color) { + canvas.draw(&pg, Coordinate(0,0), c); } } @@ -400,11 +409,17 @@ fn main() { let (tx, rx) = mpsc::channel(); _democanvas( &xcb, "Something...(f64)", tx.clone() + , Polyeder::triangle(60.0) , Polyeder::tetrahedron(60.0) - , Polyeder::cube(60.0) ); + , Polyeder::cube(60.0) + , DirectLight::new(Vector(0.0, 0.0, 1.0)) ); _democanvas( &xcb, "Something...(Fractional)", tx.clone() + , Polyeder::triangle(Fractional(60,1)) , Polyeder::tetrahedron(Fractional(60,1)) - , Polyeder::cube(Fractional(60,1)) ); + , Polyeder::cube(Fractional(60,1)) + , DirectLight::new(Vector( Fractional(0,1) + , Fractional(0,1) + , Fractional(1,1) )) ); for x in rx { match x { diff --git a/fractional/src/vector.rs b/fractional/src/vector.rs index e56afae..30d126a 100644 --- a/fractional/src/vector.rs +++ b/fractional/src/vector.rs @@ -34,7 +34,7 @@ where T: Add + Sub + Neg pub fn y(self) -> T { self.1 } pub fn z(self) -> T { self.2 } - pub fn abs(self) -> T { + pub fn mag(self) -> T { let Vector(x, y, z) = self; (x * x + y * y + z * z).sqrt().unwrap() } @@ -53,12 +53,12 @@ where T: Add + Sub + Neg pub fn norm(self) -> Self { // TODO This can result in 0 or inf Vectors… - // Maybe we need to handle zero and inf abs here… - self.mul(&self.abs()) + // Maybe we need to handle zero and inf magnitude here… + self.mul(&self.mag().recip()) } pub fn distance(self, other :Self) -> T { - (self - other).abs() + (self - other).mag() } }