enginerenderer 0.0.1

A zero-dependency offline rendering engine in pure Rust — CPU path tracing, BVH acceleration, 16-band spectral rendering, PBR materials, animation & video export.
Documentation
use crate::core::engine::rendering::framebuffer::FrameBuffer;
use crate::core::engine::rendering::raytracing::Vec3;

pub struct TaaAccumulator {
    pub history: Vec<Vec3>,
    pub width: usize,
    pub height: usize,
    pub blend_alpha: f64,
    pub variance_clamp: bool,
    pub frame_index: u64,
}

impl TaaAccumulator {
    pub fn new(width: usize, height: usize) -> Self {
        Self {
            history: vec![Vec3::ZERO; width * height],
            width,
            height,
            blend_alpha: 0.1,
            variance_clamp: true,
            frame_index: 0,
        }
    }

    pub fn halton_jitter(frame: u64) -> (f64, f64) {
        let jx = halton_sequence(frame as usize + 1, 2) - 0.5;
        let jy = halton_sequence(frame as usize + 1, 3) - 0.5;
        (jx, jy)
    }

    pub fn accumulate(&mut self, current: &mut FrameBuffer) {
        let w = self.width;
        let h = self.height;
        if self.history.len() != w * h {
            self.history = vec![Vec3::ZERO; w * h];
        }

        for y in 0..h {
            for x in 0..w {
                let idx = y * w + x;
                let cur = current.color[idx];

                let history_color = if self.variance_clamp {
                    variance_clamp_history(self.history[idx], current, x, y, w, h)
                } else {
                    self.history[idx]
                };

                let blended = history_color * (1.0 - self.blend_alpha) + cur * self.blend_alpha;
                self.history[idx] = blended;
                current.color[idx] = blended;
            }
        }

        self.frame_index += 1;
    }

    pub fn reset(&mut self) {
        for p in &mut self.history {
            *p = Vec3::ZERO;
        }
        self.frame_index = 0;
    }
}

fn variance_clamp_history(
    history: Vec3,
    fb: &FrameBuffer,
    x: usize,
    y: usize,
    w: usize,
    h: usize,
) -> Vec3 {
    let mut m1 = Vec3::ZERO;
    let mut m2 = Vec3::ZERO;
    let mut count = 0usize;

    let x0 = x.saturating_sub(1);
    let x1 = (x + 2).min(w);
    let y0 = y.saturating_sub(1);
    let y1 = (y + 2).min(h);

    for ny in y0..y1 {
        for nx in x0..x1 {
            let s = fb.color[ny * w + nx];
            m1 += s;
            m2 += Vec3::new(s.x * s.x, s.y * s.y, s.z * s.z);
            count += 1;
        }
    }

    let n = count as f64;
    let mean = m1 * (1.0 / n);
    let variance = m2 * (1.0 / n) - Vec3::new(mean.x * mean.x, mean.y * mean.y, mean.z * mean.z);
    let sigma = Vec3::new(
        variance.x.max(0.0).sqrt(),
        variance.y.max(0.0).sqrt(),
        variance.z.max(0.0).sqrt(),
    );
    let gamma = 1.25_f64;

    let lo = mean - sigma * gamma;
    let hi = mean + sigma * gamma;

    Vec3::new(
        history.x.max(lo.x).min(hi.x),
        history.y.max(lo.y).min(hi.y),
        history.z.max(lo.z).min(hi.z),
    )
}

fn halton_sequence(mut index: usize, base: usize) -> f64 {
    let mut result = 0.0_f64;
    let mut denom = 1.0_f64;
    while index > 0 {
        denom *= base as f64;
        result += (index % base) as f64 / denom;
        index /= base;
    }
    result
}

pub struct SpatialUpscaler {
    pub scale_factor: u32,
}

impl SpatialUpscaler {
    pub fn new(scale_factor: u32) -> Self {
        Self {
            scale_factor: scale_factor.max(1),
        }
    }

    pub fn upscale(&self, src: &FrameBuffer) -> FrameBuffer {
        let scale = self.scale_factor as usize;
        let dst_w = src.width * scale;
        let dst_h = src.height * scale;
        let len = dst_w * dst_h;
        let mut dst = FrameBuffer {
            width: dst_w,
            height: dst_h,
            color: vec![Vec3::ZERO; len],
            alpha: vec![1.0; len],
            depth: vec![f64::INFINITY; len],
            sample_count: vec![1; len],
        };

        for sy in 0..src.height {
            for sx in 0..src.width {
                let src_idx = sy * src.width + sx;
                let c = src.color[src_idx];
                let d = src.depth[src_idx];
                for dy in 0..scale {
                    for dx in 0..scale {
                        let dst_x = sx * scale + dx;
                        let dst_y = sy * scale + dy;
                        let dst_idx = dst_y * dst_w + dst_x;
                        dst.color[dst_idx] = c;
                        dst.depth[dst_idx] = d;
                    }
                }
            }
        }

        dst
    }

    pub fn upscale_bilinear(&self, src: &FrameBuffer) -> FrameBuffer {
        let scale = self.scale_factor as usize;
        let dst_w = src.width * scale;
        let dst_h = src.height * scale;
        let len = dst_w * dst_h;
        let mut dst = FrameBuffer {
            width: dst_w,
            height: dst_h,
            color: vec![Vec3::ZERO; len],
            alpha: vec![1.0; len],
            depth: vec![f64::INFINITY; len],
            sample_count: vec![1; len],
        };

        for dy in 0..dst_h {
            for dx in 0..dst_w {
                let fx = dx as f64 / scale as f64;
                let fy = dy as f64 / scale as f64;
                let x0 = (fx as usize).min(src.width - 1);
                let y0 = (fy as usize).min(src.height - 1);
                let x1 = (x0 + 1).min(src.width - 1);
                let y1 = (y0 + 1).min(src.height - 1);
                let tx = fx - x0 as f64;
                let ty = fy - y0 as f64;

                let c00 = src.color[y0 * src.width + x0];
                let c10 = src.color[y0 * src.width + x1];
                let c01 = src.color[y1 * src.width + x0];
                let c11 = src.color[y1 * src.width + x1];

                let color = c00 * ((1.0 - tx) * (1.0 - ty))
                    + c10 * (tx * (1.0 - ty))
                    + c01 * ((1.0 - tx) * ty)
                    + c11 * (tx * ty);

                dst.color[dy * dst_w + dx] = color;
                dst.depth[dy * dst_w + dx] = src.depth[y0 * src.width + x0];
            }
        }

        dst
    }
}