1use crate::camera::Camera3D;
2use crate::color::Color;
3use crate::font::FontAtlas;
4use crate::geometry::{Mesh, Vertex};
5use crate::material::{AlphaMode, Material};
6use crate::scene::Transform;
7use glam::{Mat4, Vec3, Vec4};
8
9#[derive(Clone)]
12pub struct FrameBuffer {
13 pub width: u32,
14 pub height: u32,
15 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 {
24 width,
25 height,
26 pixels: vec![0u8; n * 4],
27 depth: vec![f32::INFINITY; n],
28 }
29 }
30
31 pub fn clear(&mut self, color: Color) {
32 let b = color.to_rgba_bytes();
33 for chunk in self.pixels.chunks_exact_mut(4) {
34 chunk.copy_from_slice(&b);
35 }
36 self.depth.fill(f32::INFINITY);
37 }
38
39 pub fn set_pixel(&mut self, x: u32, y: u32, color: Color, depth: f32) {
40 if x >= self.width || y >= self.height {
41 return;
42 }
43 let idx = (y * self.width + x) as usize;
44 if depth >= self.depth[idx] {
45 return;
46 }
47 self.depth[idx] = depth;
48 let base = idx * 4;
49 let b = color.to_rgba_bytes();
50 self.pixels[base..base + 4].copy_from_slice(&b);
51 }
52
53 pub fn blend_pixel(&mut self, x: u32, y: u32, color: Color, depth: f32) {
54 if x >= self.width || y >= self.height {
55 return;
56 }
57 let idx = (y * self.width + x) as usize;
58 if depth >= self.depth[idx] {
59 return;
60 }
61 let base = idx * 4;
62 let dst = Color::new(
63 self.pixels[base] as f32 / 255.0,
64 self.pixels[base + 1] as f32 / 255.0,
65 self.pixels[base + 2] as f32 / 255.0,
66 self.pixels[base + 3] as f32 / 255.0,
67 );
68 let blended = crate::color::BlendMode::Normal.blend(color, dst);
69 let b = blended.to_rgba_bytes();
70 self.pixels[base..base + 4].copy_from_slice(&b);
71 if color.a >= 1.0 {
72 self.depth[idx] = depth;
73 }
74 }
75}
76
77pub trait Renderer {
80 fn begin_frame(&mut self, width: u32, height: u32, clear_color: Color);
81 fn draw_mesh(
82 &mut self,
83 mesh: &Mesh,
84 transform: &Transform,
85 material: &Material,
86 camera: &Camera3D,
87 );
88 fn draw_text(
89 &mut self,
90 text: &str,
91 world_pos: Vec3,
92 font: &mut FontAtlas,
93 px: f32,
94 color: Color,
95 camera: &Camera3D,
96 );
97 fn end_frame(&mut self) -> &FrameBuffer;
98}
99
100#[allow(dead_code)] struct ScreenVert {
104 x: f32,
105 y: f32,
106 z: f32, inv_w: f32, u: f32,
109 v: f32,
110 color: Color,
111}
112
113pub struct SoftwareRenderer {
114 fb: FrameBuffer,
115 light_dir: Vec3,
116 ambient: f32,
117}
118
119impl SoftwareRenderer {
120 pub fn new() -> Self {
121 Self {
122 fb: FrameBuffer::new(1, 1),
123 light_dir: Vec3::new(0.5, -1.0, -0.5).normalize(),
124 ambient: 0.2,
125 }
126 }
127
128 pub fn set_light(&mut self, dir: Vec3) {
129 self.light_dir = dir.normalize();
130 }
131
132 fn project(&self, mvp: Mat4, v: &Vertex) -> Option<ScreenVert> {
133 let clip = mvp * Vec4::new(v.position.x, v.position.y, v.position.z, 1.0);
134 if clip.w.abs() < 1e-6 {
135 return None;
136 }
137 let inv_w = 1.0 / clip.w;
138 let ndc = clip.truncate() * inv_w;
139 if ndc.z < -1.0 || ndc.z > 1.0 {
140 return None;
141 }
142 let x = (ndc.x + 1.0) * 0.5 * self.fb.width as f32;
143 let y = (1.0 - (ndc.y + 1.0) * 0.5) * self.fb.height as f32;
144 Some(ScreenVert {
145 x,
146 y,
147 z: ndc.z,
148 inv_w,
149 u: v.uv.x * inv_w,
150 v: v.uv.y * inv_w,
151 color: v.color,
152 })
153 }
154
155 fn rasterize(
156 &mut self,
157 sv: [&ScreenVert; 3],
158 material: &Material,
159 model_mat: Mat4,
160 normals: [Vec3; 3],
161 ) {
162 let w = self.fb.width as i32;
163 let h = self.fb.height as i32;
164
165 let min_x = sv.iter().map(|v| v.x as i32).min().unwrap().max(0);
166 let max_x = sv.iter().map(|v| v.x as i32).max().unwrap().min(w - 1);
167 let min_y = sv.iter().map(|v| v.y as i32).min().unwrap().max(0);
168 let max_y = sv.iter().map(|v| v.y as i32).max().unwrap().min(h - 1);
169 if min_x > max_x || min_y > max_y {
170 return;
171 }
172
173 let edge = |a: &ScreenVert, b: &ScreenVert, px: f32, py: f32| -> f32 {
174 (b.x - a.x) * (py - a.y) - (b.y - a.y) * (px - a.x)
175 };
176
177 let area = edge(sv[0], sv[1], sv[2].x, sv[2].y);
178 if area.abs() < 1.0 {
179 return;
180 }
181
182 for py in min_y..=max_y {
183 for px in min_x..=max_x {
184 let fx = px as f32 + 0.5;
185 let fy = py as f32 + 0.5;
186 let w0 = edge(sv[1], sv[2], fx, fy);
187 let w1 = edge(sv[2], sv[0], fx, fy);
188 let w2 = edge(sv[0], sv[1], fx, fy);
189
190 if (area > 0.0 && w0 >= 0.0 && w1 >= 0.0 && w2 >= 0.0)
191 || (area < 0.0 && w0 <= 0.0 && w1 <= 0.0 && w2 <= 0.0)
192 {
193 let b0 = w0 / area;
194 let b1 = w1 / area;
195 let b2 = w2 / area;
196
197 let depth = b0 * sv[0].z + b1 * sv[1].z + b2 * sv[2].z;
198
199 let inv_w = b0 * sv[0].inv_w + b1 * sv[1].inv_w + b2 * sv[2].inv_w;
201 let u = (b0 * sv[0].u + b1 * sv[1].u + b2 * sv[2].u) / inv_w;
202 let v = (b0 * sv[0].v + b1 * sv[1].v + b2 * sv[2].v) / inv_w;
203
204 let n_local = normals[0] * b0 + normals[1] * b1 + normals[2] * b2;
206 let n_world = (model_mat * Vec4::new(n_local.x, n_local.y, n_local.z, 0.0))
207 .truncate()
208 .normalize_or_zero();
209
210 let ndotl = (-self.light_dir).dot(n_world).max(0.0);
212 let light = (self.ambient + (1.0 - self.ambient) * ndotl).min(1.0);
213
214 let albedo = material.sample_albedo(u, v);
215 let lit = Color::new(
216 albedo.r * light,
217 albedo.g * light,
218 albedo.b * light,
219 albedo.a,
220 );
221 let lit = (lit + material.emissive).clamp();
222
223 match material.alpha_mode {
224 AlphaMode::Mask { cutoff } => {
225 if lit.a < cutoff {
226 continue;
227 }
228 self.fb.set_pixel(px as u32, py as u32, lit, depth);
229 },
230 AlphaMode::Blend | AlphaMode::Premultiplied => {
231 self.fb.blend_pixel(px as u32, py as u32, lit, depth);
232 },
233 AlphaMode::Opaque => {
234 self.fb.set_pixel(px as u32, py as u32, lit, depth);
235 },
236 }
237 }
238 }
239 }
240 }
241}
242
243impl Default for SoftwareRenderer {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249impl Renderer for SoftwareRenderer {
250 fn begin_frame(&mut self, width: u32, height: u32, clear_color: Color) {
251 if self.fb.width != width || self.fb.height != height {
252 self.fb = FrameBuffer::new(width, height);
253 }
254 self.fb.clear(clear_color);
255 }
256
257 fn draw_mesh(
258 &mut self,
259 mesh: &Mesh,
260 transform: &Transform,
261 material: &Material,
262 camera: &Camera3D,
263 ) {
264 let model = transform.matrix();
265 let mvp = camera.view_proj() * model;
266
267 let sv: Vec<Option<ScreenVert>> =
268 mesh.vertices.iter().map(|v| self.project(mvp, v)).collect();
269
270 for tri in mesh.indices.chunks(3) {
271 let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
272 if let (Some(a), Some(b), Some(c)) = (&sv[i0], &sv[i1], &sv[i2]) {
273 let normals = [
274 mesh.vertices[i0].normal,
275 mesh.vertices[i1].normal,
276 mesh.vertices[i2].normal,
277 ];
278 self.rasterize([a, b, c], material, model, normals);
279 }
280 }
281 }
282
283 fn draw_text(
284 &mut self,
285 text: &str,
286 world_pos: Vec3,
287 font: &mut FontAtlas,
288 px: f32,
289 color: Color,
290 camera: &Camera3D,
291 ) {
292 let text_mesh = crate::font::generate_text_mesh(font, text, px, color);
293 let mut mat = Material::new(color);
294 mat.albedo_texture = Some(font.texture.clone());
295 let t = Transform::from_translation(world_pos);
296 self.draw_mesh(&text_mesh, &t, &mat, camera);
297 }
298
299 fn end_frame(&mut self) -> &FrameBuffer {
300 &self.fb
301 }
302}