library: render: bidirectional: first draft

This commit is contained in:
Antoine Martin 2020-05-09 14:13:29 +02:00
parent d86b47f111
commit a50732b998
2 changed files with 130 additions and 19 deletions

View file

@ -1,13 +1,15 @@
use super::super::Renderer; use super::super::Renderer;
use super::path::*; use super::path::*;
use crate::core::LinearColor;
use crate::material::Material; use crate::material::Material;
use crate::render::utils::sample_hemisphere; use crate::render::utils::{buffer_to_image, sample_hemisphere};
use crate::scene::{Object, Scene}; use crate::scene::{Object, Scene};
use crate::shape::Shape; use crate::shape::Shape;
use crate::{Point, Vector}; use crate::{Point, Vector};
use beevee::ray::Ray; use beevee::ray::Ray;
use image::RgbImage; use image::RgbImage;
use nalgebra::Unit; use nalgebra::Unit;
use rayon::prelude::*;
/// Render the [`Scene`] using Bidirectional-Pathtracing /// Render the [`Scene`] using Bidirectional-Pathtracing
/// ///
@ -30,12 +32,126 @@ impl BidirectionalPathtracer {
/// ///
/// [`Scene`]: ../scene/scene/struct.Scene.html /// [`Scene`]: ../scene/scene/struct.Scene.html
pub fn render(&self) -> RgbImage { pub fn render(&self) -> RgbImage {
todo!() let (width, height) = (
self.scene.camera.film().width(),
self.scene.camera.film().height(),
);
let total = width * height;
let (img_buf, _) = (0..self.scene.shot_rays.max(1))
.map(|_| {
let mut buffer: Vec<LinearColor> = Vec::new();
buffer.resize_with(total as usize, LinearColor::black);
buffer
.par_chunks_mut(width as usize)
.enumerate()
.for_each(|(y, row)| {
for x in 0..width {
row[x as usize] = self.pixel_ray(x as f32, y as f32);
}
});
buffer
})
.fold(
{
let mut vec = Vec::new();
vec.resize_with(total as usize, LinearColor::black);
let count = 0usize;
(vec, count)
},
|(mut acc, count), buf| {
for (i, pixel) in buf.into_iter().enumerate() {
acc[i] += pixel;
}
let count = count + 1; // Because count is 0-indexed
if self.scene.steps.contains(&count) {
let image = buffer_to_image(&acc, count as u32, width, height);
image
.save(format!("{}_passes.png", count))
.expect("writing image failed!");
}
(acc, count) // Count has been updated previously
},
);
buffer_to_image(&img_buf, self.scene.shot_rays, width, height)
}
fn pixel_ray(&self, x: f32, y: f32) -> LinearColor {
let light_paths = self
.scene
.lights
.sample_lights_iter()
.map(|l| {
let light_ray = l.sample_ray();
self.construct_light_path(light_ray.origin, light_ray.direction, l.luminance())
})
.collect::<Vec<_>>();
let (x, y) = self.scene.camera.film().pixel_ratio(x, y);
let ray = self.scene.camera.ray_with_ratio(x, y);
self.cast_ray(ray).map_or_else(
|| self.scene.background.clone(),
|(t, obj)| self.radiance(ray, t, obj, &light_paths, self.scene.reflection_limit),
)
}
fn radiance(
&self,
ray: Ray,
t: f32,
obj: &Object,
light_paths: &[Path],
limit: u32,
) -> LinearColor {
let hit_pos = ray.origin + ray.direction.as_ref() * t;
let texel = obj.shape.project_texel(&hit_pos);
let properties = obj.material.properties(texel);
let mut light_samples = LinearColor::black();
for path in light_paths {
for point in &path.points {
light_samples += point.luminance.clone() / (hit_pos - point.point).norm();
}
}
if limit == 0 {
return properties.emitted;
}
let brdf = properties.diffuse;
let normal = obj.shape.normal(&hit_pos);
let new_direction = sample_hemisphere(normal);
let new_ray = Ray::new(hit_pos + new_direction.as_ref() * 0.001, new_direction);
let incoming = self
.cast_ray(new_ray)
.map_or_else(LinearColor::black, |(t, obj)| {
self.radiance(new_ray, t, obj, light_paths, limit - 1)
});
light_samples + properties.emitted + (brdf * incoming)
} }
#[allow(unused)] #[allow(unused)]
fn construct_path(&self, mut origin: Point, mut direction: Unit<Vector>) -> Path { fn construct_light_path(
&self,
mut origin: Point,
mut direction: Unit<Vector>,
luminance: LinearColor,
) -> Path {
let mut res = Path::new(origin); let mut res = Path::new(origin);
let mut previous_luminance = luminance.clone();
let light_point = PathPoint::new(origin, luminance);
res.push_point(light_point);
for _ in 0..self.scene.reflection_limit { for _ in 0..self.scene.reflection_limit {
let ray = Ray::new(origin, direction); let ray = Ray::new(origin, direction);
match self.cast_ray(ray) { match self.cast_ray(ray) {
@ -43,15 +159,20 @@ impl BidirectionalPathtracer {
let hit_pos = origin + direction.as_ref() * distance; let hit_pos = origin + direction.as_ref() * distance;
let texel = obj.shape.project_texel(&hit_pos); let texel = obj.shape.project_texel(&hit_pos);
let properties = obj.material.properties(texel); let properties = obj.material.properties(texel);
let emitted = properties.emitted;
let diffuse = properties.diffuse;
let normal = obj.shape.normal(&hit_pos); let normal = obj.shape.normal(&hit_pos);
let p = PathPoint::new(origin, direction, normal, properties);
let luminance = emitted + (diffuse * (previous_luminance / distance));
let p = PathPoint::new(hit_pos, luminance.clone());
res.push_point(p); res.push_point(p);
let new_direction = sample_hemisphere(normal); let new_direction = sample_hemisphere(normal);
// Calculate the incoming light along the new ray // Calculate the incoming light along the new ray
origin = hit_pos + new_direction.as_ref() * 0.001; origin = hit_pos + new_direction.as_ref() * 0.001;
direction = new_direction; direction = new_direction;
previous_luminance = luminance;
} }
None => break, None => break,
} }

View file

@ -1,25 +1,15 @@
use crate::core::LightProperties; use crate::core::LinearColor;
use crate::{Point, Vector}; use crate::Point;
use nalgebra::Unit;
pub struct PathPoint { pub struct PathPoint {
pub point: Point, pub point: Point,
pub pdf: f32, pub luminance: LinearColor,
pub properties: LightProperties,
} }
impl PathPoint { impl PathPoint {
#[allow(unused)] #[allow(unused)]
pub fn new( pub fn new(point: Point, luminance: LinearColor) -> Self {
point: Point, PathPoint { point, luminance }
pdf: 32,
properties: LightProperties,
) -> Self {
PathPoint {
point,
pdf,
properties,
}
} }
} }