retrofire_core/
render.rs

1//! Turning 3D geometry into raster images.
2//!
3//! This module constitutes the core 3D rendering pipeline of `retrofire`.
4//! It contains code for [clipping][clip], [transforming, shading][shader],
5//! [texturing][tex], [rasterizing][raster], and [outputting][target] basic
6//! geometric shapes such as triangles.
7
8use alloc::{vec, vec::Vec};
9use core::fmt::Debug;
10
11use crate::geom::{Tri, Vertex};
12use crate::math::{
13    mat::{Mat4x4, RealToProj, RealToReal},
14    vary::Vary,
15    vec::{vec3, ProjVec4, Vec3},
16};
17
18use clip::{view_frustum, Clip, ClipVert};
19use ctx::{Context, DepthSort, FaceCull};
20use raster::tri_fill;
21use shader::{FragmentShader, VertexShader};
22use stats::Stats;
23use target::Target;
24
25pub mod batch;
26pub mod cam;
27pub mod clip;
28pub mod ctx;
29pub mod raster;
30pub mod shader;
31pub mod stats;
32pub mod target;
33pub mod tex;
34
35/// Model space coordinate basis.
36#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
37pub struct Model;
38
39/// World space coordinate basis.
40#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
41pub struct World;
42
43/// View (camera) space coordinate basis.
44#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
45pub struct View;
46
47/// NDC space coordinate basis (normalized device coordinates).
48#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
49pub struct Ndc;
50
51/// Screen space coordinate basis.
52#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
53pub struct Screen;
54
55// Mapping from model space to world space.
56pub type ModelToWorld = RealToReal<3, Model, World>;
57
58// Mapping from world space to view space.
59pub type WorldToView = RealToReal<3, World, View>;
60
61/// Mapping from model space to view space.
62pub type ModelToView = RealToReal<3, Model, View>;
63
64/// Mapping from model space to view space.
65pub type ModelToProj = RealToProj<Model>;
66
67/// Mapping from view space to projective space.
68pub type ViewToProj = RealToProj<View>;
69
70/// Mapping from NDC space to screen space.
71pub type NdcToScreen = RealToReal<3, Ndc, Screen>;
72
73/// Alias for combined vertex+fragment shader types
74pub trait Shader<Vtx, Var, Uni>:
75    VertexShader<Vtx, Uni, Output = Vertex<ProjVec4, Var>> + FragmentShader<Var>
76{
77}
78impl<S, Vtx, Var, Uni> Shader<Vtx, Var, Uni> for S where
79    S: VertexShader<Vtx, Uni, Output = Vertex<ProjVec4, Var>>
80        + FragmentShader<Var>
81{
82}
83
84/// Renders the given triangles into `target`.
85pub fn render<Vtx: Clone, Var: Vary, Uni: Copy, Shd>(
86    tris: impl AsRef<[Tri<usize>]>,
87    verts: impl AsRef<[Vtx]>,
88    shader: &Shd,
89    uniform: Uni,
90    to_screen: Mat4x4<NdcToScreen>,
91    target: &mut impl Target,
92    ctx: &Context,
93) where
94    Shd: Shader<Vtx, Var, Uni>,
95{
96    let mut stats = Stats::start();
97
98    stats.calls = 1.0;
99    stats.prims.i += tris.as_ref().len();
100    stats.verts.i += verts.as_ref().len();
101
102    // Vertex shader: transform vertices to clip space
103    let verts: Vec<_> = verts
104        .as_ref()
105        .iter()
106        // TODO Pass vertex as ref to shader
107        .cloned()
108        .map(|v| ClipVert::new(shader.shade_vertex(v, uniform)))
109        .collect();
110
111    // Map triangle vertex indices to actual vertices
112    let tris: Vec<_> = tris
113        .as_ref()
114        .iter()
115        .map(|Tri(vs)| Tri(vs.map(|i| verts[i].clone())))
116        .collect();
117
118    // Clip against the view frustum
119    let mut clipped = vec![];
120    tris.clip(&view_frustum::PLANES, &mut clipped);
121
122    if let Some(d) = ctx.depth_sort {
123        depth_sort(&mut clipped, d);
124    }
125
126    for Tri(vs) in clipped {
127        // Transform to screen space
128        let vs = vs.map(|v| {
129            let [x, y, _, w] = v.pos.0;
130            // Perspective division (projection to the real plane)
131            //
132            // We use the screen-space z coordinate to store the reciprocal
133            // of the original view-space depth. The interpolated reciprocal
134            // is used in fragment processing for depth testing (larger values
135            // are closer) and for perspective correction of the varyings.
136            let pos = vec3(x, y, 1.0).z_div(w);
137            Vertex {
138                // Viewport transform
139                pos: to_screen.apply(&pos),
140                // Perspective correction
141                attrib: v.attrib.z_div(w),
142            }
143        });
144
145        // Back/frontface culling
146        match ctx.face_cull {
147            Some(FaceCull::Back) if is_backface(&vs) => continue,
148            Some(FaceCull::Front) if !is_backface(&vs) => continue,
149            _ => {}
150        }
151
152        // Log output stats after culling
153        stats.prims.o += 1;
154        stats.verts.o += 3;
155
156        // Fragment shader and rasterization
157        tri_fill(vs, |scanline| {
158            // Convert to fragments and shade
159            stats.frags += target.rasterize(scanline, shader, ctx);
160        });
161    }
162    *ctx.stats.borrow_mut() += stats.finish();
163}
164
165fn depth_sort<A>(tris: &mut [Tri<ClipVert<A>>], d: DepthSort) {
166    tris.sort_unstable_by(|t, u| {
167        let z = t.0[0].pos.z() + t.0[1].pos.z() + t.0[2].pos.z();
168        let w = u.0[0].pos.z() + u.0[1].pos.z() + u.0[2].pos.z();
169        if d == DepthSort::FrontToBack {
170            z.total_cmp(&w)
171        } else {
172            w.total_cmp(&z)
173        }
174    });
175}
176
177fn is_backface<V>(vs: &[Vertex<Vec3<Screen>, V>]) -> bool {
178    let v = vs[1].pos - vs[0].pos;
179    let u = vs[2].pos - vs[0].pos;
180    v[0] * u[1] - v[1] * u[0] > 0.0
181}