diff --git a/src/lib.rs b/src/lib.rs index 942ae73..8fc132d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -292,30 +292,45 @@ impl Core { const BASE: Sphere = Sphere { position: vec3(0., 0., -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 { position: pos, radius: BASE_POS.distance(pos) - BASE_R, + color, } } let scene = Scene { objects: vec![ BASE, - sphere(vec3(0., 0., 0.1)), - sphere(vec3(0.3, 0., 0.1)), - sphere(vec3(0.1, 0.3, 0.1)), + sphere(vec3(0., 0., 0.1), vec3(1., 1., 1.)), + sphere(vec3(0.3, 0., 0.1), vec3(1., 1., 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 source_rays: Vec = (0..10240).map(|_| source.make_ray(&mut prng)).collect(); let camera_rays: Vec = (0..10240).map(|_| camera.make_ray(&mut prng)).collect(); let mut source_ray_display: Vec = Vec::with_capacity(source_rays.len()); - let mut hits: Vec = Vec::with_capacity(source_rays.len()); + let mut hits: Vec = Vec::with_capacity(source_rays.len()); for ray in source_rays { + let light = Vec3::splat(1.); 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 { source_ray_display.extend([ Vertex { @@ -332,7 +347,7 @@ impl Core { source_ray_display.extend([ Vertex { pos: hit.incident.base - 0.02 * hit.incident.dir, - color: vec3(0., 0., 1.), + color: light, }, Vertex { pos: hit.incident.base, @@ -358,20 +373,26 @@ impl Core { if args.reflections > 0 { let mut hits1 = hits.clone(); for _ in 0..args.reflections { - let mut hits2: Vec = Vec::with_capacity(hits1.len()); + let mut hits2: Vec = Vec::with_capacity(hits1.len()); for hit in &hits1 { let reflector = Lambertian; 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 Some(hit2) = scene.trace_ray(ray) else { continue; }; - hits2.push(hit2); + hits2.push(LightHit { + incident: hit2.incident, + normal: hit2.normal, + light: light, + object: hit2.object, + }); if args.show_indirect_hit { source_ray_display.extend([ Vertex { pos: hit2.incident.base - 0.02 * hit2.incident.dir, - color: vec3(1., 0., 1.), + color: light, }, Vertex { pos: hit2.incident.base, @@ -388,8 +409,8 @@ impl Core { let sigma2 = args.accum_sigma.powi(2); let accum_normalizator = (2. * PI * sigma2).recip(); let hits = &hits; - move |hit: Hit| -> f32 { - let mut total_cd = 0.0f32; + move |hit: Hit| -> Vec3 { + let mut total_cd = Vec3::splat(0.); for light_hit in hits { let d2 = hit.incident.base.distance_squared(light_hit.incident.base); if d2 > 9. * sigma2 { @@ -402,16 +423,18 @@ impl Core { assert!(normal.is_normalized()); assert!(hit.incident.dir.is_normalized()); let reflector = Lambertian; - let in_lm = 1.0; - let out_cd = - in_lm * reflector.brdf(normal, light_hit.incident.dir, -hit.incident.dir); + let in_lm = light_hit.light; + let out_cd = in_lm + * reflector.brdf(normal, light_hit.incident.dir, -hit.incident.dir) + * hit.object.color; let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); total_cd += weight * out_cd; } 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()); vec3(brightness, brightness - 1., brightness - 2.) .clamp(Vec3::splat(0.), Vec3::splat(1.)) @@ -455,11 +478,11 @@ impl Core { if !dir.is_finite() { dir = camera.direction(); } - let light = light_at(Hit { + let color = light_at(Hit { incident: Ray::new(pos, dir), normal, + object: obj, }); - let color = Vec3::splat(light); render::faces::Vertex { pos, color } }) .collect::>(), diff --git a/src/render/colormap.wgsl b/src/render/colormap.wgsl index e2af29d..ac7e350 100644 --- a/src/render/colormap.wgsl +++ b/src/render/colormap.wgsl @@ -14,9 +14,10 @@ struct Varying { @group(0) @binding(0) var look: LookParams; -fn colormap(light: f32) -> vec3f { - let brightness = 3. * (1. - 1. / (1. + light)); - return vec3(brightness, brightness - 1., brightness - 2.); +fn colormap(light: vec3f) -> vec3f { + let avg = dot(light, vec3f(0.2126, 0.7152, 0.0722)); + let scale = 1. / (1. + avg); + return scale * light; } @vertex @@ -28,5 +29,5 @@ fn on_vertex(in: Vertex) -> Varying { @fragment 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); } diff --git a/src/trace.rs b/src/trace.rs index 984f9d9..6f55949 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -76,6 +76,7 @@ impl Source { pub struct Sphere { pub position: Vec3, pub radius: f32, + pub color: Vec3, } struct Hit1 { @@ -113,26 +114,28 @@ pub struct Scene { } #[derive(Debug, Clone, Copy)] -pub struct Hit { +pub struct Hit<'a> { pub incident: Ray, pub normal: Vec3, + pub object: &'a Sphere, } impl Scene { - pub fn trace_ray(&self, ray: Ray) -> Option { + pub fn trace_ray(&self, ray: Ray) -> Option> { const EPS: f32 = -1e-3; let hit = self .objects .iter() - .filter_map(|obj| obj.trace_ray(ray)) - .filter(|h| h.dist >= EPS) - .min_by(|a, b| f32::total_cmp(&a.dist, &b.dist))?; + .filter_map(|obj| obj.trace_ray(ray).map(|hit| (obj, hit))) + .filter(|(_obj, h)| h.dist >= EPS) + .min_by(|a, b| f32::total_cmp(&a.1.dist, &b.1.dist))?; Some(Hit { incident: Ray { - base: hit.pos, + base: hit.1.pos, dir: ray.dir, }, - normal: hit.normal, + normal: hit.1.normal, + object: hit.0, }) } }