#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct SsrConfig {
pub max_steps: u32,
pub step_size: f32,
pub max_distance: f32,
pub thickness: f32,
pub edge_fade: f32,
pub roughness_cutoff: f32,
}
impl Default for SsrConfig {
fn default() -> Self {
Self {
max_steps: 64,
step_size: 0.1,
max_distance: 50.0,
thickness: 0.1,
edge_fade: 0.1,
roughness_cutoff: 0.5,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SsrHit {
pub uv: (f32, f32),
pub confidence: f32,
pub steps_taken: u32,
}
#[allow(dead_code)]
pub fn reflect(incident: [f32; 3], normal: [f32; 3]) -> [f32; 3] {
let d = 2.0 * dot(incident, normal);
[
incident[0] - d * normal[0],
incident[1] - d * normal[1],
incident[2] - d * normal[2],
]
}
#[allow(dead_code)]
pub fn screen_edge_fade(uv: (f32, f32), edge: f32) -> f32 {
if edge <= 0.0 {
return 1.0;
}
let fx = if uv.0 < edge { uv.0 / edge }
else if uv.0 > 1.0 - edge { (1.0 - uv.0) / edge }
else { 1.0 };
let fy = if uv.1 < edge { uv.1 / edge }
else if uv.1 > 1.0 - edge { (1.0 - uv.1) / edge }
else { 1.0 };
(fx.min(fy)).clamp(0.0, 1.0)
}
#[allow(dead_code)]
pub fn distance_fade(distance: f32, max_distance: f32) -> f32 {
if max_distance <= 0.0 {
return 0.0;
}
(1.0 - distance / max_distance).clamp(0.0, 1.0)
}
#[allow(dead_code)]
pub fn roughness_fade(roughness: f32, cutoff: f32) -> f32 {
if cutoff <= 0.0 {
return 0.0;
}
(1.0 - roughness / cutoff).clamp(0.0, 1.0)
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn ray_march(
view_pos: [f32; 3],
view_dir: [f32; 3],
depth_buffer: &[f32],
width: u32,
height: u32,
projection: (f32, f32, f32, f32),
config: &SsrConfig,
) -> Option<SsrHit> {
let (fx, fy, cx, cy) = projection;
if width == 0 || height == 0 {
return None;
}
let mut pos = view_pos;
for step in 0..config.max_steps {
pos[0] += view_dir[0] * config.step_size;
pos[1] += view_dir[1] * config.step_size;
pos[2] += view_dir[2] * config.step_size;
if pos[2] <= 0.0 {
return None;
}
let screen_x = fx * pos[0] / pos[2] + cx;
let screen_y = fy * pos[1] / pos[2] + cy;
let u = screen_x / width as f32;
let v = screen_y / height as f32;
if !(0.0..=1.0).contains(&u) || !(0.0..=1.0).contains(&v) {
return None;
}
let px = (screen_x as usize).min(width as usize - 1);
let py = (screen_y as usize).min(height as usize - 1);
let idx = py * width as usize + px;
if idx >= depth_buffer.len() {
return None;
}
let buffer_depth = depth_buffer[idx];
let depth_diff = pos[2] - buffer_depth;
if depth_diff > 0.0 && depth_diff < config.thickness {
let dist = ((pos[0] - view_pos[0]).powi(2) + (pos[1] - view_pos[1]).powi(2) + (pos[2] - view_pos[2]).powi(2)).sqrt();
let edge = screen_edge_fade((u, v), config.edge_fade);
let dist_f = distance_fade(dist, config.max_distance);
let confidence = edge * dist_f;
return Some(SsrHit {
uv: (u, v),
confidence,
steps_taken: step + 1,
});
}
}
None
}
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let c = SsrConfig::default();
assert_eq!(c.max_steps, 64);
}
#[test]
fn test_reflect_perpendicular() {
let r = reflect([0.0, -1.0, 0.0], [0.0, 1.0, 0.0]);
assert!((r[0]).abs() < 1e-5);
assert!((r[1] - 1.0).abs() < 1e-5);
assert!((r[2]).abs() < 1e-5);
}
#[test]
fn test_reflect_parallel() {
let r = reflect([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!((r[0] - 1.0).abs() < 1e-5);
}
#[test]
fn test_screen_edge_fade_centre() {
let f = screen_edge_fade((0.5, 0.5), 0.1);
assert!((f - 1.0).abs() < 1e-5);
}
#[test]
fn test_screen_edge_fade_border() {
let f = screen_edge_fade((0.0, 0.5), 0.1);
assert!(f.abs() < 1e-5);
}
#[test]
fn test_distance_fade_zero() {
let f = distance_fade(0.0, 50.0);
assert!((f - 1.0).abs() < 1e-5);
}
#[test]
fn test_distance_fade_max() {
let f = distance_fade(50.0, 50.0);
assert!(f.abs() < 1e-5);
}
#[test]
fn test_roughness_fade() {
let f = roughness_fade(0.0, 0.5);
assert!((f - 1.0).abs() < 1e-5);
let f2 = roughness_fade(0.5, 0.5);
assert!(f2.abs() < 1e-5);
}
#[test]
fn test_ray_march_empty_buffer() {
let r = ray_march([0.0, 0.0, 1.0], [0.0, 0.0, 1.0], &[], 0, 0,
(1.0, 1.0, 0.0, 0.0), &SsrConfig::default());
assert!(r.is_none());
}
#[test]
fn test_ray_march_behind_camera() {
let r = ray_march([0.0, 0.0, 0.1], [0.0, 0.0, -1.0], &[1.0; 100], 10, 10,
(10.0, 10.0, 5.0, 5.0), &SsrConfig { max_steps: 4, step_size: 0.2, ..Default::default() });
assert!(r.is_none());
}
}