renders with depth buffer

This commit is contained in:
Joe Ardent 2025-09-10 14:06:55 -07:00
parent 7a4b11e2da
commit 5c1ae137bd
5 changed files with 240 additions and 19 deletions

View file

@ -12,6 +12,8 @@ mod model;
mod triangle;
use triangle::*;
use crate::model::Model;
const BLACK: TGAColor = TGAColor { bgra: [0u8; 4] };
const WHITE: TGAColor = TGAColor {
@ -35,18 +37,13 @@ const YELLOW: TGAColor = TGAColor {
};
fn main() {
let w = 640;
let h = 640;
let w = 800;
let h = 800;
let mut fb = TGAImage::new(w, h, TGAFormat::RGB);
let model = Model::from_obj("diablo3_pose.obj");
model.render_triangles(&mut fb);
let a = Point2i::new(170, 40).with_color(BLUE);
let b = Point2i::new(550, 390).with_color(GREEN);
let c = Point2i::new(230, 590).with_color(RED);
let t = Triangle2i::new(a, b, c);
t.render_lines(20, BLACK, &mut fb);
fb.write_file("triangle.tga", true).unwrap();
//fb.write_file("triangle.tga", true).unwrap();
}
fn line(mut a: Point2i, mut b: Point2i, color: TGAColor, mut fb: &mut TGAImage) {

View file

@ -4,8 +4,9 @@ use rand::Rng;
use crate::{
RED, Triangle2i, line,
point::{Point2i, Point3f},
point::{Point2i, Point3f, Point3i},
tga::TGAImage,
triangle::Triangle3i,
};
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
@ -98,12 +99,18 @@ impl Model {
let width = framebuffer.width;
let height = framebuffer.height;
let mut db = TGAImage::new(
framebuffer.width,
framebuffer.height,
crate::tga::TGAFormat::Grayscale,
);
let mut rng = rand::rng();
for face in self.faces.iter() {
let a = world2view(self.verts[face[0]], width, height);
let b = world2view(self.verts[face[1]], width, height);
let c = world2view(self.verts[face[2]], width, height);
let triangle = Triangle2i::new(a, b, c);
let a = world2iso(self.verts[face[0]], width, height);
let b = world2iso(self.verts[face[1]], width, height);
let c = world2iso(self.verts[face[2]], width, height);
let triangle = Triangle3i::new(a, b, c);
let color = [
rng.random_range(0..=255u8),
rng.random_range(0..=255u8),
@ -111,8 +118,9 @@ impl Model {
255,
]
.into();
triangle.render_filled(color, framebuffer);
triangle.render_filled(color, framebuffer, &mut db);
}
//db.write_file("zbuffer.tga", true);
}
}
@ -122,3 +130,11 @@ fn world2view(point: Point3f, width: u32, height: u32) -> Point2i {
let y = (point.y() * height as f32).round_ties_even() as i32;
Point2i::new(x, y)
}
fn world2iso(point: Point3f, width: u32, height: u32) -> Point3i {
let point = (point + 1.0) * 0.5;
let x = (point.x() * width as f32).round_ties_even() as i32;
let y = (point.y() * height as f32).round_ties_even() as i32;
let z = (point.z() * 255.0).round_ties_even() as i32;
Point3i::new(x, y, z)
}

View file

@ -184,3 +184,112 @@ impl From<Point3f> for Point2i {
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Point3i {
pub x: i32,
pub y: i32,
pub z: i32,
pub color: Option<TGAColor>,
}
impl Point3i {
pub fn new(x: i32, y: i32, z: i32) -> Self {
Self {
x,
y,
z,
color: None,
}
}
pub fn with_color(self, color: TGAColor) -> Self {
Self {
color: Some(color),
..self
}
}
}
impl From<Point3f> for Point3i {
fn from(p: Point3f) -> Self {
Self {
x: p.x().round_ties_even() as i32,
y: p.y().round_ties_even() as i32,
z: p.z().round_ties_even() as i32,
color: None,
}
}
}
impl Add for Point3i {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
color: if self.color.is_none() {
rhs.color
} else {
self.color
.map(|c| if let Some(oc) = rhs.color { c + oc } else { c })
},
}
}
}
impl Sub for Point3i {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
color: if self.color.is_none() {
rhs.color
} else {
self.color
.map(|c| if let Some(oc) = rhs.color { c - oc } else { c })
},
}
}
}
impl Mul for Point3i {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
x: self.x * rhs.x,
y: self.y * rhs.y,
z: self.z * rhs.z,
color: None,
}
}
}
impl Mul<f32> for Point3i {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self {
x: (self.x as f32 * rhs).round_ties_even() as i32,
y: (self.y as f32 * rhs).round_ties_even() as i32,
z: (self.z as f32 * rhs).round_ties_even() as i32,
color: self.color,
}
}
}
impl From<Point3i> for Point2i {
fn from(p: Point3i) -> Self {
Self {
x: p.x,
y: p.y,
color: p.color,
}
}
}

View file

@ -73,6 +73,12 @@ impl From<ColorBuf> for TGAColor {
}
}
impl From<u8> for TGAColor {
fn from(value: u8) -> Self {
[value; 4].into()
}
}
impl Add for TGAColor {
type Output = Self;
@ -145,7 +151,7 @@ pub struct TGAImage {
pub format: TGAFormat,
pub width: u32,
pub height: u32,
data: Vec<u8>,
pub data: Vec<u8>,
}
pub type IoResult = std::result::Result<(), TGAError>;

View file

@ -4,7 +4,7 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::{
AABB, BLACK,
point::Point2i,
point::{Point2i, Point3i},
tga::{TGAColor, TGAImage},
};
@ -38,7 +38,7 @@ impl Triangle2i {
}
}
pub fn render_filled(&self, color: TGAColor, fb: &mut TGAImage) {
pub fn render_filled(&self, color: TGAColor, fb: &mut TGAImage, depth_buffer: &mut TGAImage) {
let fb = Arc::new(Mutex::new(fb));
let bb = self.bb();
let total_area = self.signed_area();
@ -139,3 +139,96 @@ impl Triangle2i {
Point2i { x, y, color }
}
}
pub struct Triangle3i {
a: Point3i,
b: Point3i,
c: Point3i,
}
struct Wrapper<T>(T);
unsafe impl<T> Send for Wrapper<T> {}
impl Triangle3i {
pub fn new(a: Point3i, b: Point3i, c: Point3i) -> Self {
Self { a, b, c }
}
pub fn signed_area(&self) -> f32 {
0.5 * ((self.b.y - self.a.y) * (self.a.x + self.b.x)
+ (self.c.y - self.b.y) * (self.c.x + self.b.x)
+ (self.a.y - self.c.y) * (self.a.x + self.c.x)) as f32
}
pub fn bb(&self) -> AABB {
let xmax = self.a.x.max(self.b.x.max(self.c.x));
let xmin = self.a.x.min(self.b.x.min(self.c.x));
let ymax = self.a.y.max(self.b.y.max(self.c.y));
let ymin = self.a.y.min(self.b.y.min(self.c.y));
AABB {
lower_left: Point2i::new(xmin, ymin),
upper_right: Point2i::new(xmax, ymax),
}
}
pub fn render_filled(&self, color: TGAColor, fb: &mut TGAImage, depth_buffer: &mut TGAImage) {
let fb = Arc::new(Mutex::new(fb));
let zb = Arc::new(Mutex::new(depth_buffer));
let bb = self.bb();
let total_area = self.signed_area();
if total_area < 1.0 {
return;
}
let it = 1.0 / total_area;
(bb.xmin()..=bb.xmax()).into_par_iter().for_each(|x| {
(bb.ymin()..=bb.ymax()).into_par_iter().for_each(|y| {
let p = Point2i::new(x, y);
let alpha = Triangle2i::new(p, self.b.into(), self.c.into()).signed_area() * it;
let beta = Triangle2i::new(p, self.c.into(), self.a.into()).signed_area() * it;
let gamma = Triangle2i::new(p, self.a.into(), self.b.into()).signed_area() * it;
if alpha.is_sign_positive() && beta.is_sign_positive() && gamma.is_sign_positive() {
let color = if let Some(ca) = self.a.color
&& let Some(cb) = self.b.color
&& let Some(cc) = self.c.color
{
color + (ca * alpha) + (cb * beta) + (cc * gamma)
} else {
color
};
let z = (alpha * self.a.z as f32
+ beta * self.b.z as f32
+ gamma * self.c.z as f32)
.round_ties_even() as u8;
if let Ok(mut zb) = zb.lock() {
let oz = zb.get(x as u32, y as u32).unwrap_or_default().b();
if oz <= z {
if let Ok(mut fb) = fb.lock() {
zb.set(x as u32, y as u32, z.into());
fb.set(x as u32, y as u32, color);
}
}
}
};
});
});
}
pub fn centroid(&self) -> Point3i {
let x = self.a.x / 3 + self.b.x / 3 + self.c.x / 3;
let y = self.a.y / 3 + self.b.y / 3 + self.c.y / 3;
let z = self.a.z / 3 + self.b.z / 3 + self.c.z / 3;
let has_color = self.a.color.is_some() || self.b.color.is_some() || self.c.color.is_some();
let denom = 1.0 / 3.0;
let color = if has_color {
let c = self.a.color.unwrap_or(BLACK) * denom
+ self.b.color.unwrap_or(BLACK) * denom
+ self.c.color.unwrap_or(BLACK) * denom;
Some(c)
} else {
None
};
Point3i { x, y, z, color }
}
}