Skip to main content

ling_graphics/
renderer.rs

1use glam::{Vec3, Vec4, Mat4};
2use crate::camera::Camera3D;
3use crate::color::Color;
4use crate::geometry::{Mesh, Vertex};
5use crate::material::{Material, AlphaMode};
6use crate::scene::Transform;
7use crate::font::FontAtlas;
8
9// ── Frame buffer ──────────────────────────────────────────────────────────────
10
11#[derive(Clone)]
12pub struct FrameBuffer {
13    pub width: u32,
14    pub height: u32,
15    /// RGBA bytes, row-major, top-left origin.
16    pub pixels: Vec<u8>,
17    pub depth: Vec<f32>,
18}
19
20impl FrameBuffer {
21    pub fn new(width: u32, height: u32) -> Self {
22        let n = (width * height) as usize;
23        Self { width, height, pixels: vec![0u8; n * 4], depth: vec![f32::INFINITY; n] }
24    }
25
26    pub fn clear(&mut self, color: Color) {
27        let b = color.to_rgba_bytes();
28        for chunk in self.pixels.chunks_exact_mut(4) { chunk.copy_from_slice(&b); }
29        self.depth.fill(f32::INFINITY);
30    }
31
32    pub fn set_pixel(&mut self, x: u32, y: u32, color: Color, depth: f32) {
33        if x >= self.width || y >= self.height { return; }
34        let idx = (y * self.width + x) as usize;
35        if depth >= self.depth[idx] { return; }
36        self.depth[idx] = depth;
37        let base = idx * 4;
38        let b = color.to_rgba_bytes();
39        self.pixels[base..base + 4].copy_from_slice(&b);
40    }
41
42    pub fn blend_pixel(&mut self, x: u32, y: u32, color: Color, depth: f32) {
43        if x >= self.width || y >= self.height { return; }
44        let idx = (y * self.width + x) as usize;
45        if depth >= self.depth[idx] { return; }
46        let base = idx * 4;
47        let dst = Color::new(
48            self.pixels[base]     as f32 / 255.0,
49            self.pixels[base + 1] as f32 / 255.0,
50            self.pixels[base + 2] as f32 / 255.0,
51            self.pixels[base + 3] as f32 / 255.0,
52        );
53        let blended = crate::color::BlendMode::Normal.blend(color, dst);
54        let b = blended.to_rgba_bytes();
55        self.pixels[base..base + 4].copy_from_slice(&b);
56        if color.a >= 1.0 { self.depth[idx] = depth; }
57    }
58}
59
60// ── Renderer trait ────────────────────────────────────────────────────────────
61
62pub trait Renderer {
63    fn begin_frame(&mut self, width: u32, height: u32, clear_color: Color);
64    fn draw_mesh(&mut self, mesh: &Mesh, transform: &Transform, material: &Material, camera: &Camera3D);
65    fn draw_text(&mut self, text: &str, world_pos: Vec3, font: &mut FontAtlas, px: f32, color: Color, camera: &Camera3D);
66    fn end_frame(&mut self) -> &FrameBuffer;
67}
68
69// ── Software rasterizer ───────────────────────────────────────────────────────
70
71struct ScreenVert {
72    x: f32, y: f32,
73    z: f32,       // NDC depth in [−1, 1]
74    inv_w: f32,   // 1/clip.w for perspective-correct interpolation
75    u: f32, v: f32,
76    color: Color,
77}
78
79pub struct SoftwareRenderer {
80    fb: FrameBuffer,
81    light_dir: Vec3,
82    ambient: f32,
83}
84
85impl SoftwareRenderer {
86    pub fn new() -> Self {
87        Self {
88            fb: FrameBuffer::new(1, 1),
89            light_dir: Vec3::new(0.5, -1.0, -0.5).normalize(),
90            ambient: 0.2,
91        }
92    }
93
94    pub fn set_light(&mut self, dir: Vec3) { self.light_dir = dir.normalize(); }
95
96    fn project(&self, mvp: Mat4, v: &Vertex) -> Option<ScreenVert> {
97        let clip = mvp * Vec4::new(v.position.x, v.position.y, v.position.z, 1.0);
98        if clip.w.abs() < 1e-6 { return None; }
99        let inv_w = 1.0 / clip.w;
100        let ndc = clip.truncate() * inv_w;
101        if ndc.z < -1.0 || ndc.z > 1.0 { return None; }
102        let x = (ndc.x + 1.0) * 0.5 * self.fb.width  as f32;
103        let y = (1.0 - (ndc.y + 1.0) * 0.5) * self.fb.height as f32;
104        Some(ScreenVert { x, y, z: ndc.z, inv_w, u: v.uv.x * inv_w, v: v.uv.y * inv_w, color: v.color })
105    }
106
107    fn rasterize(&mut self, sv: [&ScreenVert; 3], material: &Material, model_mat: Mat4, normals: [Vec3; 3]) {
108        let w = self.fb.width as i32;
109        let h = self.fb.height as i32;
110
111        let min_x = sv.iter().map(|v| v.x as i32).min().unwrap().max(0);
112        let max_x = sv.iter().map(|v| v.x as i32).max().unwrap().min(w - 1);
113        let min_y = sv.iter().map(|v| v.y as i32).min().unwrap().max(0);
114        let max_y = sv.iter().map(|v| v.y as i32).max().unwrap().min(h - 1);
115        if min_x > max_x || min_y > max_y { return; }
116
117        let edge = |a: &ScreenVert, b: &ScreenVert, px: f32, py: f32| -> f32 {
118            (b.x - a.x) * (py - a.y) - (b.y - a.y) * (px - a.x)
119        };
120
121        let area = edge(sv[0], sv[1], sv[2].x, sv[2].y);
122        if area.abs() < 1.0 { return; }
123
124        for py in min_y..=max_y {
125            for px in min_x..=max_x {
126                let fx = px as f32 + 0.5;
127                let fy = py as f32 + 0.5;
128                let w0 = edge(sv[1], sv[2], fx, fy);
129                let w1 = edge(sv[2], sv[0], fx, fy);
130                let w2 = edge(sv[0], sv[1], fx, fy);
131
132                if (area > 0.0 && w0 >= 0.0 && w1 >= 0.0 && w2 >= 0.0)
133                    || (area < 0.0 && w0 <= 0.0 && w1 <= 0.0 && w2 <= 0.0)
134                {
135                    let b0 = w0 / area;
136                    let b1 = w1 / area;
137                    let b2 = w2 / area;
138
139                    let depth = b0 * sv[0].z + b1 * sv[1].z + b2 * sv[2].z;
140
141                    // Perspective-correct UV
142                    let inv_w = b0 * sv[0].inv_w + b1 * sv[1].inv_w + b2 * sv[2].inv_w;
143                    let u = (b0 * sv[0].u + b1 * sv[1].u + b2 * sv[2].u) / inv_w;
144                    let v = (b0 * sv[0].v + b1 * sv[1].v + b2 * sv[2].v) / inv_w;
145
146                    // Interpolate and transform world normal
147                    let n_local = normals[0] * b0 + normals[1] * b1 + normals[2] * b2;
148                    let n_world = (model_mat * Vec4::new(n_local.x, n_local.y, n_local.z, 0.0))
149                        .truncate().normalize_or_zero();
150
151                    // Diffuse lighting
152                    let ndotl = (-self.light_dir).dot(n_world).max(0.0);
153                    let light = (self.ambient + (1.0 - self.ambient) * ndotl).min(1.0);
154
155                    let albedo = material.sample_albedo(u, v);
156                    let lit = Color::new(albedo.r * light, albedo.g * light, albedo.b * light, albedo.a);
157                    let lit = (lit + material.emissive).clamp();
158
159                    match material.alpha_mode {
160                        AlphaMode::Mask { cutoff } => {
161                            if lit.a < cutoff { continue; }
162                            self.fb.set_pixel(px as u32, py as u32, lit, depth);
163                        }
164                        AlphaMode::Blend | AlphaMode::Premultiplied => {
165                            self.fb.blend_pixel(px as u32, py as u32, lit, depth);
166                        }
167                        AlphaMode::Opaque => {
168                            self.fb.set_pixel(px as u32, py as u32, lit, depth);
169                        }
170                    }
171                }
172            }
173        }
174    }
175}
176
177impl Default for SoftwareRenderer { fn default() -> Self { Self::new() } }
178
179impl Renderer for SoftwareRenderer {
180    fn begin_frame(&mut self, width: u32, height: u32, clear_color: Color) {
181        if self.fb.width != width || self.fb.height != height {
182            self.fb = FrameBuffer::new(width, height);
183        }
184        self.fb.clear(clear_color);
185    }
186
187    fn draw_mesh(&mut self, mesh: &Mesh, transform: &Transform, material: &Material, camera: &Camera3D) {
188        let model = transform.matrix();
189        let mvp   = camera.view_proj() * model;
190
191        let sv: Vec<Option<ScreenVert>> = mesh.vertices.iter().map(|v| self.project(mvp, v)).collect();
192
193        for tri in mesh.indices.chunks(3) {
194            let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
195            if let (Some(a), Some(b), Some(c)) = (&sv[i0], &sv[i1], &sv[i2]) {
196                let normals = [
197                    mesh.vertices[i0].normal,
198                    mesh.vertices[i1].normal,
199                    mesh.vertices[i2].normal,
200                ];
201                self.rasterize([a, b, c], material, model, normals);
202            }
203        }
204    }
205
206    fn draw_text(&mut self, text: &str, world_pos: Vec3, font: &mut FontAtlas, px: f32, color: Color, camera: &Camera3D) {
207        let text_mesh = crate::font::generate_text_mesh(font, text, px, color);
208        let mut mat = Material::new(color);
209        mat.albedo_texture = Some(font.texture.clone());
210        let t = Transform::from_translation(world_pos);
211        self.draw_mesh(&text_mesh, &t, &mat, camera);
212    }
213
214    fn end_frame(&mut self) -> &FrameBuffer { &self.fb }
215}