Compare commits
6 Commits
8441f9f2f1
...
3619b97071
| Author | SHA1 | Date | |
|---|---|---|---|
| 3619b97071 | |||
| 21dec30715 | |||
| 6f4ce25cc8 | |||
| a98eda1616 | |||
| 13fb710dc7 | |||
| ca460bc0d9 |
5
README.md
Normal file
5
README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Forward ray tracing demo
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A simple forward ray tracing demo. “Forward” means the rays are traced starting from the light (unlike common “reverse” ray tracing where rays are traced from the camera).
|
||||||
BIN
screenshot.jpg
Normal file
BIN
screenshot.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
64
src/lib.rs
64
src/lib.rs
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
DEPTH_FORMAT, OUTPUT_FORMAT,
|
DEPTH_FORMAT, OUTPUT_FORMAT,
|
||||||
lines::{LookParams, Mesh, Pipeline, Vertex},
|
lines::{LookParams, Mesh, Pipeline, Vertex},
|
||||||
},
|
},
|
||||||
trace::{Hit, Lambertian, Reflector, Scene, Source, Sphere},
|
trace::{DiscReflector, Hit, Lambertian, Reflector, Scene, Source, Sphere},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod camera;
|
mod camera;
|
||||||
|
|
@ -234,30 +234,45 @@ impl Core {
|
||||||
const BASE: Sphere = Sphere {
|
const BASE: Sphere = Sphere {
|
||||||
position: vec3(0., 0., -BASE_R),
|
position: vec3(0., 0., -BASE_R),
|
||||||
radius: BASE_R,
|
radius: BASE_R,
|
||||||
|
color: vec3(0.01, 0.03, 0.30),
|
||||||
};
|
};
|
||||||
fn sphere(pos: Vec3) -> Sphere {
|
fn sphere(pos: Vec3, color: Vec3) -> Sphere {
|
||||||
Sphere {
|
Sphere {
|
||||||
position: pos,
|
position: pos,
|
||||||
radius: BASE_POS.distance(pos) - BASE_R,
|
radius: BASE_POS.distance(pos) - BASE_R,
|
||||||
|
color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let scene = Scene {
|
let scene = Scene {
|
||||||
objects: vec![
|
objects: vec![
|
||||||
BASE,
|
BASE,
|
||||||
sphere(vec3(0., 0., 0.1)),
|
sphere(vec3(0., 0., 0.1), vec3(1., 1., 1.)),
|
||||||
sphere(vec3(0.3, 0., 0.1)),
|
sphere(vec3(0.3, 0., 0.1), vec3(1., 1., 1.)),
|
||||||
sphere(vec3(0.1, 0.3, 0.1)),
|
sphere(vec3(0.1, 0.3, 0.1), vec3(0.5, 0.03, 0.01)),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct LightHit<'a> {
|
||||||
|
incident: Ray,
|
||||||
|
normal: Vec3,
|
||||||
|
light: Vec3,
|
||||||
|
object: &'a Sphere,
|
||||||
|
}
|
||||||
let mut prng = rand_pcg::Pcg64::new(42, 0);
|
let mut prng = rand_pcg::Pcg64::new(42, 0);
|
||||||
let source_rays: Vec<Ray> = (0..10240).map(|_| source.make_ray(&mut prng)).collect();
|
let source_rays: Vec<Ray> = (0..10240).map(|_| source.make_ray(&mut prng)).collect();
|
||||||
let camera_rays: Vec<Ray> = (0..10240).map(|_| camera.make_ray(&mut prng)).collect();
|
let camera_rays: Vec<Ray> = (0..10240).map(|_| camera.make_ray(&mut prng)).collect();
|
||||||
let mut source_ray_display: Vec<Vertex> = Vec::with_capacity(source_rays.len());
|
let mut source_ray_display: Vec<Vertex> = Vec::with_capacity(source_rays.len());
|
||||||
let mut hits: Vec<Hit> = Vec::with_capacity(source_rays.len());
|
let mut hits: Vec<LightHit> = Vec::with_capacity(source_rays.len());
|
||||||
for ray in source_rays {
|
for ray in source_rays {
|
||||||
|
let light = Vec3::splat(1.);
|
||||||
if let Some(hit) = scene.trace_ray(ray) {
|
if let Some(hit) = scene.trace_ray(ray) {
|
||||||
hits.push(hit);
|
hits.push(LightHit {
|
||||||
|
incident: hit.incident,
|
||||||
|
normal: hit.normal,
|
||||||
|
light,
|
||||||
|
object: hit.object,
|
||||||
|
});
|
||||||
if args.show_hit_emission {
|
if args.show_hit_emission {
|
||||||
source_ray_display.extend([
|
source_ray_display.extend([
|
||||||
Vertex {
|
Vertex {
|
||||||
|
|
@ -274,7 +289,7 @@ impl Core {
|
||||||
source_ray_display.extend([
|
source_ray_display.extend([
|
||||||
Vertex {
|
Vertex {
|
||||||
pos: hit.incident.base - 0.02 * hit.incident.dir,
|
pos: hit.incident.base - 0.02 * hit.incident.dir,
|
||||||
color: vec3(0., 0., 1.),
|
color: light,
|
||||||
},
|
},
|
||||||
Vertex {
|
Vertex {
|
||||||
pos: hit.incident.base,
|
pos: hit.incident.base,
|
||||||
|
|
@ -297,23 +312,29 @@ impl Core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let reflector = DiscReflector { roughness: 0.25 };
|
||||||
if args.reflections > 0 {
|
if args.reflections > 0 {
|
||||||
let mut hits1 = hits.clone();
|
let mut hits1 = hits.clone();
|
||||||
for _ in 0..args.reflections {
|
for _ in 0..args.reflections {
|
||||||
let mut hits2: Vec<Hit> = Vec::with_capacity(hits1.len());
|
let mut hits2: Vec<LightHit> = Vec::with_capacity(hits1.len());
|
||||||
for hit in &hits1 {
|
for hit in &hits1 {
|
||||||
let reflector = Lambertian;
|
|
||||||
let reflected = reflector.reflect(&mut prng, hit.normal, hit.incident.dir);
|
let reflected = reflector.reflect(&mut prng, hit.normal, hit.incident.dir);
|
||||||
|
let light = hit.light * hit.object.color;
|
||||||
let ray = Ray::new(hit.incident.base, reflected);
|
let ray = Ray::new(hit.incident.base, reflected);
|
||||||
let Some(hit2) = scene.trace_ray(ray) else {
|
let Some(hit2) = scene.trace_ray(ray) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
hits2.push(hit2);
|
hits2.push(LightHit {
|
||||||
|
incident: hit2.incident,
|
||||||
|
normal: hit2.normal,
|
||||||
|
light: light,
|
||||||
|
object: hit2.object,
|
||||||
|
});
|
||||||
if args.show_indirect_hit {
|
if args.show_indirect_hit {
|
||||||
source_ray_display.extend([
|
source_ray_display.extend([
|
||||||
Vertex {
|
Vertex {
|
||||||
pos: hit2.incident.base - 0.02 * hit2.incident.dir,
|
pos: hit2.incident.base - 0.02 * hit2.incident.dir,
|
||||||
color: vec3(1., 0., 1.),
|
color: light,
|
||||||
},
|
},
|
||||||
Vertex {
|
Vertex {
|
||||||
pos: hit2.incident.base,
|
pos: hit2.incident.base,
|
||||||
|
|
@ -330,8 +351,8 @@ impl Core {
|
||||||
let sigma2 = args.accum_sigma.powi(2);
|
let sigma2 = args.accum_sigma.powi(2);
|
||||||
let accum_normalizator = (2. * PI * sigma2).recip();
|
let accum_normalizator = (2. * PI * sigma2).recip();
|
||||||
let hits = &hits;
|
let hits = &hits;
|
||||||
move |hit: Hit| -> f32 {
|
move |hit: Hit| -> Vec3 {
|
||||||
let mut total_cd = 0.0f32;
|
let mut total_cd = Vec3::splat(0.);
|
||||||
for light_hit in hits {
|
for light_hit in hits {
|
||||||
let d2 = hit.incident.base.distance_squared(light_hit.incident.base);
|
let d2 = hit.incident.base.distance_squared(light_hit.incident.base);
|
||||||
if d2 > 9. * sigma2 {
|
if d2 > 9. * sigma2 {
|
||||||
|
|
@ -343,17 +364,18 @@ impl Core {
|
||||||
};
|
};
|
||||||
assert!(normal.is_normalized());
|
assert!(normal.is_normalized());
|
||||||
assert!(hit.incident.dir.is_normalized());
|
assert!(hit.incident.dir.is_normalized());
|
||||||
let reflector = Lambertian;
|
let in_lm = light_hit.light;
|
||||||
let in_lm = 1.0;
|
let out_cd = in_lm
|
||||||
let out_cd =
|
* reflector.brdf(normal, light_hit.incident.dir, -hit.incident.dir)
|
||||||
in_lm * reflector.brdf(normal, light_hit.incident.dir, -hit.incident.dir);
|
* hit.object.color;
|
||||||
let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp();
|
let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp();
|
||||||
total_cd += weight * out_cd;
|
total_cd += weight * out_cd;
|
||||||
}
|
}
|
||||||
total_cd * args.accum_scale
|
total_cd * args.accum_scale
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let colormap = |light: f32| {
|
let colormap = |light: Vec3| {
|
||||||
|
let light = light.dot(Vec3::splat(1. / 3.));
|
||||||
let brightness = 3. * (1. - (1. + light).recip());
|
let brightness = 3. * (1. - (1. + light).recip());
|
||||||
vec3(brightness, brightness - 1., brightness - 2.)
|
vec3(brightness, brightness - 1., brightness - 2.)
|
||||||
.clamp(Vec3::splat(0.), Vec3::splat(1.))
|
.clamp(Vec3::splat(0.), Vec3::splat(1.))
|
||||||
|
|
@ -397,11 +419,11 @@ impl Core {
|
||||||
if !dir.is_finite() {
|
if !dir.is_finite() {
|
||||||
dir = camera.direction();
|
dir = camera.direction();
|
||||||
}
|
}
|
||||||
let light = light_at(Hit {
|
let color = light_at(Hit {
|
||||||
incident: Ray::new(pos, dir),
|
incident: Ray::new(pos, dir),
|
||||||
normal,
|
normal,
|
||||||
|
object: obj,
|
||||||
});
|
});
|
||||||
let color = Vec3::splat(light);
|
|
||||||
render::faces::Vertex { pos, color }
|
render::faces::Vertex { pos, color }
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ struct Varying {
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> look: LookParams;
|
@group(0) @binding(0) var<uniform> look: LookParams;
|
||||||
|
|
||||||
fn colormap(light: f32) -> vec3f {
|
fn colormap(light: vec3f) -> vec3f {
|
||||||
let brightness = 3. * (1. - 1. / (1. + light));
|
let avg = dot(light, vec3f(0.2126, 0.7152, 0.0722));
|
||||||
return vec3(brightness, brightness - 1., brightness - 2.);
|
let scale = 1. / (1. + avg);
|
||||||
|
return scale * light;
|
||||||
}
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
|
|
@ -28,5 +29,5 @@ fn on_vertex(in: Vertex) -> Varying {
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn on_fragment(in: Varying) -> @location(0) vec4f {
|
fn on_fragment(in: Varying) -> @location(0) vec4f {
|
||||||
return vec4f(colormap(in.color.x), 1.0);
|
return vec4f(colormap(in.color.rgb), in.color.a);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
src/trace.rs
21
src/trace.rs
|
|
@ -5,6 +5,10 @@ use rand_distr::{Distribution, UnitSphere};
|
||||||
|
|
||||||
use crate::{camera::OrbitalCamera, ray::Ray};
|
use crate::{camera::OrbitalCamera, ray::Ray};
|
||||||
|
|
||||||
|
pub use reflective::DiscReflector;
|
||||||
|
|
||||||
|
mod reflective;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Source {
|
pub struct Source {
|
||||||
/// Horizontal position (angle), in radians from +X towards +Y.
|
/// Horizontal position (angle), in radians from +X towards +Y.
|
||||||
|
|
@ -76,6 +80,7 @@ impl Source {
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
pub position: Vec3,
|
pub position: Vec3,
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
|
pub color: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Hit1 {
|
struct Hit1 {
|
||||||
|
|
@ -113,26 +118,28 @@ pub struct Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Hit {
|
pub struct Hit<'a> {
|
||||||
pub incident: Ray,
|
pub incident: Ray,
|
||||||
pub normal: Vec3,
|
pub normal: Vec3,
|
||||||
|
pub object: &'a Sphere,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
pub fn trace_ray(&self, ray: Ray) -> Option<Hit> {
|
pub fn trace_ray(&self, ray: Ray) -> Option<Hit<'_>> {
|
||||||
const EPS: f32 = -1e-3;
|
const EPS: f32 = -1e-3;
|
||||||
let hit = self
|
let hit = self
|
||||||
.objects
|
.objects
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|obj| obj.trace_ray(ray))
|
.filter_map(|obj| obj.trace_ray(ray).map(|hit| (obj, hit)))
|
||||||
.filter(|h| h.dist >= EPS)
|
.filter(|(_obj, h)| h.dist >= EPS)
|
||||||
.min_by(|a, b| f32::total_cmp(&a.dist, &b.dist))?;
|
.min_by(|a, b| f32::total_cmp(&a.1.dist, &b.1.dist))?;
|
||||||
Some(Hit {
|
Some(Hit {
|
||||||
incident: Ray {
|
incident: Ray {
|
||||||
base: hit.pos,
|
base: hit.1.pos,
|
||||||
dir: ray.dir,
|
dir: ray.dir,
|
||||||
},
|
},
|
||||||
normal: hit.normal,
|
normal: hit.1.normal,
|
||||||
|
object: hit.0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
src/trace/reflective.rs
Normal file
46
src/trace/reflective.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use glam::Vec3;
|
||||||
|
use rand_distr::{Distribution, Uniform, UnitSphere};
|
||||||
|
|
||||||
|
use super::Reflector;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DiscReflector {
|
||||||
|
pub roughness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reflector for DiscReflector {
|
||||||
|
fn brdf(&self, normal: Vec3, incident: Vec3, reflected: Vec3) -> f32 {
|
||||||
|
let specular = incident.reflect(normal);
|
||||||
|
let convergence = reflected.dot(specular);
|
||||||
|
if convergence <= 1. - self.roughness {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
let c = -incident.dot(normal) * reflected.dot(normal);
|
||||||
|
if c < 0. {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
0.5 / (self.roughness * PI * (specular.dot(normal) * reflected.dot(normal)).sqrt())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reflect(&self, rgen: &mut impl rand::Rng, normal: Vec3, incident: Vec3) -> Vec3 {
|
||||||
|
let specular = incident.reflect(normal);
|
||||||
|
|
||||||
|
let dist_theta = Uniform::new(0., (1. - self.roughness).acos()).unwrap();
|
||||||
|
let dist_sphere = UnitSphere;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let theta: f32 = dist_theta.sample(rgen);
|
||||||
|
let (xy, z) = theta.sin_cos();
|
||||||
|
|
||||||
|
let spherical: Vec3 = dist_sphere.sample(rgen).into();
|
||||||
|
let off_dir = spherical.reject_from_normalized(specular).normalize();
|
||||||
|
|
||||||
|
let reflected = xy * off_dir + z * specular;
|
||||||
|
if reflected.dot(normal) >= 0. {
|
||||||
|
return reflected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user