use super::*;
pub(crate) fn pixel_direction(basis: &FrameBasis, sample: &PixelSample) -> Vector3D {
let ndc_x: f64 =
((sample.get_x() + sample.get_offset_x()) / basis.get_width() as f64) * 2.0 - 1.0;
let ndc_y: f64 =
1.0 - ((sample.get_y() + sample.get_offset_y()) / basis.get_height() as f64) * 2.0;
let tan_half: f64 = basis.get_tan_half_fov();
(basis.get_forward()
+ basis
.get_right()
.scaled(ndc_x * basis.get_aspect() * tan_half)
+ basis.get_up().scaled(ndc_y * tan_half))
.normalized()
}
pub fn gamma_byte(channel: f64) -> u8 {
let clamped: f64 = channel.clamp(0.0, 1.0);
let encoded: f64 = clamped.powf(1.0 / RAYTRACE_GAMMA);
(encoded * 255.0).round().clamp(0.0, 255.0) as u8
}
pub fn render_raytrace_rgba(
scene: &RayTraceScene,
frame: &RayTraceFrame<'_>,
out: &mut Vec<u8>,
) -> Result<(), String> {
let lights: &LightingUniforms = frame.get_lights();
frame.get_settings().validate()?;
let basis: FrameBasis = frame_basis(frame.get_camera(), frame.get_width(), frame.get_height())?;
let pixel_count: usize = (basis.get_width() as usize) * (basis.get_height() as usize);
let byte_count: usize = match pixel_count.checked_mul(4) {
Some(count) => count,
None => return Err(RAYTRACE_ERROR_DIMENSION_OVERFLOW.to_string()),
};
let samples: u32 = frame.get_settings().get_samples_per_axis();
let sample_scale: f64 = 1.0 / samples as f64;
let rays: f64 = (samples * samples) as f64;
let bounces: u32 = frame.get_settings().get_max_bounces();
out.clear();
out.resize(byte_count, 0);
for y in 0..basis.get_height() {
for x in 0..basis.get_width() {
let mut accumulator: Vector3D = Vector3D::zero();
for sy in 0..samples {
for sx in 0..samples {
let sample: PixelSample = PixelSample {
x: x as f64,
y: y as f64,
offset_x: (sx as f64 + 0.5) * sample_scale,
offset_y: (sy as f64 + 0.5) * sample_scale,
};
let direction: Vector3D = pixel_direction(&basis, &sample);
let ray: Ray = Ray::new(basis.get_eye(), direction)
.with_range(basis.get_near_t(), basis.get_far_t());
accumulator += scene.trace_with_bounces(ray, lights, bounces);
}
}
let linear: Vector3D = accumulator.scaled(1.0 / rays);
let offset: usize = ((y as usize) * (basis.get_width() as usize) + x as usize) * 4;
out[offset] = gamma_byte(linear.get_x());
out[offset + 1] = gamma_byte(linear.get_y());
out[offset + 2] = gamma_byte(linear.get_z());
out[offset + 3] = 255;
}
}
Ok(())
}