# TinyRenderer笔记0：画线和三角以及面剔除

tinyrenderer教程地址：https://github.com/ssloy/tinyrenderer/wiki

• image - 这是个图片库，充当画布，控制每个像素的颜色，生成图片
• obj-rs - 解析obj文件的库，关于obj文件可以看Wavefront OBJ文件格式，里面存的是一些模型顶点
• glm - 数学库，比如坐标表示，计算向量点乘、叉乘，矩阵运算

## 准备画布

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  use image::{imageops::flip_vertical_in_place, ImageBuffer, Rgba}; const WHITE: Rgba = Rgba([255, 255, 255, 255]); const RED: Rgba = Rgba([255, 0, 0, 255]); fn main() { let (width, height) = (400, 400); let mut image = ImageBuffer::, _>::from_pixel(width, height, BLACK); for x in 0..200 { for y in 0..200 { image.put_pixel(x, y, RED); } } flip_vertical_in_place(&mut image); // 垂直反转，因为默认坐标原点在左上角，反转后在左下角 image.save("a.png").unwrap(); } 

## 画线

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30  fn line(mut a: glm::IVec2, mut b: glm::IVec2, image: &mut I, color: I::Pixel) { let mut steep = false; if (a.x - b.x).abs() < (a.y - b.y).abs() { // 如果这条线是陡峭的，就交换x，y让它躺平 swap(&mut a.x, &mut a.y); swap(&mut b.x, &mut b.y); steep = true; } if a.x > b.x { // make it left−to−right swap(&mut a, &mut b); } let dx = b.x - a.x; let dy = b.y - a.y; let derror = (dy as f64 / dx as f64).abs(); let mut error = 0.0; let mut y = a.y; for x in a.x..=b.x { if steep { image.put_pixel(y as u32, x as u32, color); } else { image.put_pixel(x as u32, y as u32, color); } error += derror; if error > 0.5 { y += if b.y > a.y { 1 } else { -1 }; error -= 1.0; } } } 

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30  fn line(mut a: glm::IVec2, mut b: glm::IVec2, image: &mut I, color: I::Pixel) { let mut steep = false; if (a.x - b.x).abs() < (a.y - b.y).abs() { // if the line is steep, we transpose the image swap(&mut a.x, &mut a.y); swap(&mut b.x, &mut b.y); steep = true; } if a.x > b.x { // make it left−to−right swap(&mut a, &mut b); } let dx = b.x - a.x; let dy = b.y - a.y; let derror = dy.abs() * 2; let mut error = 0; let mut y = a.y; for x in a.x..=b.x { if steep { image.put_pixel(y as u32, x as u32, color); } else { image.put_pixel(x as u32, y as u32, color); } error += derror; if error > dx { y += if b.y > a.y { 1 } else { -1 }; error -= dx * 2; } } } 

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25  fn main() { let (width, height) = (800, 800); let mut image = ImageBuffer::, _>::from_pixel(width, height, BLACK); let input = BufReader::new(File::open("a.obj").unwrap()); let model: obj::Obj = obj::load_obj(input).unwrap(); for arr in model.indices.chunks(3) { for i in 0..3 { let v0 = model.vertices.get(arr[i] as usize).unwrap().position; let v1 = model .vertices .get(arr[(i + 1) % 3] as usize) .unwrap() .position; let x0 = ((v0[0] + 1.0) * (width - 1) as f32 / 2.0) as i32; let y0 = ((v0[1] + 1.0) * (height - 1) as f32 / 2.0) as i32; let x1 = ((v1[0] + 1.0) * (width - 1) as f32 / 2.0) as i32; let y1 = ((v1[1] + 1.0) * (height - 1) as f32 / 2.0) as i32; draw::line(glm::ivec2(x0, y0), glm::ivec2(x1, y1), &mut image, WHITE); } } flip_vertical_in_place(&mut image); image.save("a.png").unwrap(); } 

## 填充三角形

### 扫描线

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56  pub fn triangle( mut t0: glm::Vec2, mut t1: glm::Vec2, mut t2: glm::Vec2, image: &mut I, color: I::Pixel, ) { if t0.y == t1.y && t0.y == t2.y { return; } // 按y坐标排序，从低到高 t0 t1 t2 if t0.y > t1.y { swap(&mut t0, &mut t1); } if t0.y > t2.y { swap(&mut t0, &mut t2); } if t1.y > t2.y { swap(&mut t1, &mut t2); } let total_height = t2.y - t0.y; // 下半部分 // y in t0.y -> t1.y for y in t0.y as i32..=t1.y as i32 { let segment_height = t1.y - t0.y + 1.; let alpha = (y as f32 - t0.y) as f32 / total_height as f32; let beta = (y as f32 - t0.y) as f32 / segment_height as f32; // be careful with divisions by zero let mut a = t0 + (t2 - t0) * alpha; let mut b = t0 + (t1 - t0) * beta; if a.x > b.x { swap(&mut a, &mut b); } for j in a.x as i32..=b.x as i32 { image.put_pixel(j as u32, y as u32, color); } } // 上半部分 for y in t1.y as i32..=t2.y as i32 { let segment_height = t2.y - t1.y + 1.; let alpha = (y as f32 - t0.y) / total_height; let beta = (y as f32 - t1.y) / segment_height; let mut a = t0 + (t2 - t0) * alpha; let mut b = t1 + (t2 - t1) * beta; if a.x > b.x { swap(&mut a, &mut b); } for j in a.x as i32..=b.x as i32 { image.put_pixel(j as u32, y as u32, color); } } } 

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53  pub fn triangle( mut t0: glm::Vec2, mut t1: glm::Vec2, mut t2: glm::Vec2, image: &mut I, color: I::Pixel, ) { if t0.y == t1.y && t0.y == t2.y { return; } // 按y坐标排序 if t0.y > t1.y { swap(&mut t0, &mut t1); } if t0.y > t2.y { swap(&mut t0, &mut t2); } if t1.y > t2.y { swap(&mut t1, &mut t2); } let total_height = t2.y - t0.y; // 同时画两部分 for i in 0..=total_height as i32 { let second_half = if i > (t1.y - t0.y) as i32 || t1.y == t0.y { true } else { false }; let segment_height = if second_half { t2.y - t1.y } else { t1.y - t0.y }; let alpha = i as f32 / total_height as f32; let beta = (i as f32 - if second_half { t1.y - t0.y } else { 0. }) as f32 / segment_height as f32; // be careful with divisions by zero let mut a = t0 + (t2 - t0) * alpha; let mut b = if second_half { t1 + (t2 - t1) * beta } else { t0 + (t1 - t0) * beta }; if a.x > b.x { swap(&mut a, &mut b); } for j in a.x as i32..=b.x as i32 { image.put_pixel(j as u32, (t0.y + i as f32) as u32, color); } } } 

### 重心坐标

w1+w2+w3=1 且 w1,w2,w3非负数（负数说明不在内部）

P=w1*A+w2*B+w3*C （即P表示成A,B,C的线性组合）

\begin{aligned} P &= (1-u-v)A + uB + vC \\ &= A + u(B-A) + v(C-A) \\ &= A + u\overrightarrow{AB} + v\overrightarrow{AC} \end{aligned}

$$u\overrightarrow{AB} + v\overrightarrow{AC} + \overrightarrow{PA} = 0$$

$$\begin{cases} u\overrightarrow{AB}_x + v\overrightarrow{AC}_x + \overrightarrow{PA}_x &= 0\\ u\overrightarrow{AB}_y + v\overrightarrow{AC}_y + \overrightarrow{PA}_y &= 0\\ \end{cases}$$

$$\begin{cases} \begin{bmatrix} u & v &1 \end{bmatrix} \begin{bmatrix} \overrightarrow{AB}_x \\ \overrightarrow{AC}_x \\ \overrightarrow{PA}_x \end{bmatrix} &= 0 \\ \\ \begin{bmatrix} u & v &1 \end{bmatrix} \begin{bmatrix} \overrightarrow{AB}_y \\ \overrightarrow{AC}_y \\ \overrightarrow{PA}_y \end{bmatrix} &= 0 \end{cases}$$

\begin{aligned} (u,v,1)\ &*\ (x_1,x_2,x_3) &= 0\\ (u,v,1)\ &*\ (y_1,y_2,y_3) &= 0 \end{aligned}

$$(\overrightarrow{AB}_x , \overrightarrow{AC}_x , \overrightarrow{PA}_x) \times (\overrightarrow{AB}_y , \overrightarrow{AC}_y , \overrightarrow{PA}_y)$$

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  // 求重心坐标 fn barycentric(a: glm::IVec2, b: glm::IVec2, c: glm::IVec2, p: glm::IVec2) -> glm::Vec3 { let ab = b - a; let ac = c - a; let pa = a - p; let u = glm::cross( glm::vec3(ab.x as f32, ac.x as f32, pa.x as f32), glm::vec3(ab.y as f32, ac.y as f32, pa.y as f32), ); // 因为传入坐标都是整数，所以z小于1就意味着z是0，这种情况是因为三角形三个顶点在一条直线上，不是合法三角形 // 这种情况返回一个负值 if u.z.abs() < 1. { return glm::vec3(-1., 1., 1.); } // vec(x,y,z)/z -> (u,v,1) -> (1-u-v, u, v) return glm::vec3(1. - ((u.x + u.y) / u.z) as f32, u.x / u.z, u.y / u.z); } 

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  pub fn triangle_with_barycentric( t0: glm::IVec2, t1: glm::IVec2, t2: glm::IVec2, image: &mut I, color: I::Pixel, ) { let bboxmin = glm::ivec2(t0.x.min(t1.x).min(t2.x), t0.y.min(t1.y).min(t2.y)); let bboxmax = glm::ivec2(t0.x.max(t1.x).max(t2.x), t0.y.max(t1.y).max(t2.y)); let a = t0[1]; for px in bboxmin.x..=bboxmax.x { for py in bboxmin.y..=bboxmax.y { let bc_screen = barycentric(t0, t1, t2, glm::ivec2(px, py)); if bc_screen.x < 0. || bc_screen.y < 0. || bc_screen.z < 0. { continue; } image.put_pixel(px as u32, py as u32, color); } } } 

## 渲染阴影以及面剔除

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39  for arr in model.indices.chunks(3) { let (a, b, c) = ( model.vertices.get(arr[0] as usize).unwrap().position, model.vertices.get(arr[1] as usize).unwrap().position, model.vertices.get(arr[2] as usize).unwrap().position, ); let (a, b, c) = ( glm::vec3(a[0], a[1], a[2]), glm::vec3(b[0], b[1], b[2]), glm::vec3(c[0], c[1], c[2]), ); let (sa, sb, sc) = ( glm::ivec2( (((a.x + 1.) * (width - 1) as f32) / 2.) as i32, (((a.y + 1.) * (height - 1) as f32) / 2.) as i32, ), glm::ivec2( (((b.x + 1.) * (width - 1) as f32) / 2.) as i32, (((b.y + 1.) * (height - 1) as f32) / 2.) as i32, ), glm::ivec2( (((c.x + 1.) * (width - 1) as f32) / 2.) as i32, (((c.y + 1.) * (height - 1) as f32) / 2.) as i32, ), ); draw::triangle_with_barycentric( sa, sb, sc, &mut image, Rgba([ rand::random::() % 255, rand::random::() % 255, rand::random::() % 255, 255, ]), ); } 

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  let n = glm::cross(c - a, b - a); // 计算三角形面向 let n = glm::normalize(n); let intensity = glm::dot(light_dir, n); if intensity > 0. { // 既是光照强度，也能当面剔除用，小于0说明背对我们 draw::triangle_with_barycentric( sa, sb, sc, &mut image, Rgba([ (255. * intensity) as u8, (255. * intensity) as u8, (255. * intensity) as u8, 255, ]), ); } 

note: 模型文件中三角形的坐标是有顺序的，一般顺时针或逆时针排列，从而方便我们计算面向