From 8bed95f872d6276b80d5d070a348d295ba486c48 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 14:43:31 +0300 Subject: [PATCH 01/19] use mutable vectors, to collect into several at once --- src/lib.rs | 68 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index df375e9..926df41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use glam::{Mat4, UVec2, Vec3, vec3}; use crate::{ camera::OrbitalCamera, + ray::Ray, render::lines::{LookParams, Mesh, Pipeline, Vertex}, trace::{Scene, Source, Sphere}, }; @@ -189,36 +190,45 @@ impl Core { }; let mut prng = rand_pcg::Pcg64::new(42, 0); - let rays: Vec = (0..10000) - .flat_map(|_| { - let ray = source.make_ray(&mut prng); - if let Some(ray) = scene.trace_ray(ray) { - [ - Vertex { - pos: ray.base - 0.02 * ray.dir, - color: vec3(1., 1., 1.), - }, - Vertex { - pos: ray.base, - color: vec3(0., 1., 0.), - }, - ] - } else { - [ - Vertex { - pos: ray.base, - color: vec3(1., 1., 1.), - }, - Vertex { - pos: ray.base + 0.1 * ray.dir, - color: vec3(1., 0., 0.), - }, - ] - } - }) - .collect(); + let source_rays: Vec = (0..1024).map(|_| source.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()); + for ray in source_rays { + if let Some(hit) = scene.trace_ray(ray) { + hits.push(hit); + source_ray_display.extend([ + Vertex { + pos: ray.base, + color: vec3(1., 1., 1.), + }, + Vertex { + pos: ray.base + 0.1 * ray.dir, + color: vec3(0., 1., 0.), + }, + Vertex { + pos: hit.base - 0.02 * hit.dir, + color: vec3(0., 0., 1.), + }, + Vertex { + pos: hit.base, + color: vec3(1., 1., 1.), + }, + ]); + } else { + source_ray_display.extend([ + Vertex { + pos: ray.base, + color: vec3(1., 1., 1.), + }, + Vertex { + pos: ray.base + 0.1 * ray.dir, + color: vec3(1., 0., 0.), + }, + ]); + } + } self.pipeline - .render(&mut pass, [&Mesh::new(&self.device, &rays)]); + .render(&mut pass, [&Mesh::new(&self.device, &source_ray_display)]); drop(pass); self.queue.submit(std::iter::once(encoder.finish())); -- 2.47.3 From cf65929b99c52b3624b35b7e7f7c641d9c20cf0b Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 15:15:42 +0300 Subject: [PATCH 02/19] show current values in the ui --- ui/src/main_window.cxx | 9 +++++++-- ui/src/main_window.ui | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index 913d406..bdf890f 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -16,7 +16,7 @@ float deg_to_rad(float val) { } void PhotonLight::updateView() { - m_ui->viewport->setView(RedrawArgs{ + RedrawArgs args{ .camera_position = SphericalPosition{ .yaw = deg_to_rad(m_ui->cameraYaw->value()), .pitch = deg_to_rad(m_ui->cameraPitch->value()), @@ -29,7 +29,12 @@ void PhotonLight::updateView() { }, .light_radius = 0.125, .light_spread = 0.125, - }); + }; + m_ui->cameraYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.yaw)))); + m_ui->cameraPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.pitch)))); + m_ui->lightYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.yaw)))); + m_ui->lightPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.pitch)))); + m_ui->viewport->setView(args); } #include "moc_main_window.cpp" diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 3974303..26cf5b6 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -47,7 +47,7 @@ - + Yaw @@ -73,7 +73,7 @@ - + Pitch @@ -108,7 +108,7 @@ - + Yaw @@ -131,7 +131,7 @@ - + Pitch -- 2.47.3 From cd0c264c6ee03831ad9da62ddd439142fda7dead Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 15:25:08 +0300 Subject: [PATCH 03/19] basic two-way tracing --- src/lib.rs | 55 ++++++++++++++++++++++++++-- ui/src/api.hxx | 2 ++ ui/src/main_window.cxx | 4 +++ ui/src/main_window.ui | 81 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 926df41..457a3f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,8 @@ use std::{convert::identity, error::Error, f32::consts::PI}; -use glam::{Mat4, UVec2, Vec3, vec3}; +use glam::{Mat4, UVec2, Vec2, Vec3, vec3}; +use rand_distr::Distribution as _; use crate::{ camera::OrbitalCamera, @@ -33,6 +34,8 @@ pub struct RedrawArgs { pub light_position: SphericalPosition, pub light_radius: f32, pub light_spread: f32, + pub accum_sigma: f32, + pub accum_scale: f32, } pub struct Gpu { @@ -85,6 +88,22 @@ fn loop_list_ex( } } +impl OrbitalCamera { + fn make_ray(&self, rng: &mut impl rand::Rng) -> Ray { + let off: f32 = rand_distr::StandardUniform.sample(rng); + let side: Vec2 = rand_distr::UnitCircle.sample(rng).into(); + + let m = self.transform().inverse(); + let fwd = 1. - 0.1 * off; + let side_scale = (1. - fwd.powi(2)).sqrt(); + let dir = Vec3::from((side_scale * side, fwd)); + Ray { + base: self.position(), + dir: m.transform_vector3(dir), + } + } +} + impl Core { pub fn new(gpu: Gpu) -> Self { let Gpu { @@ -190,7 +209,8 @@ impl Core { }; let mut prng = rand_pcg::Pcg64::new(42, 0); - let source_rays: Vec = (0..1024).map(|_| source.make_ray(&mut prng)).collect(); + let source_rays: Vec = (0..10240).map(|_| source.make_ray(&mut prng)).collect(); + let camera_rays: Vec = (0..1024).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()); for ray in source_rays { @@ -227,8 +247,39 @@ impl Core { ]); } } + let mut camera_ray_display: Vec = Vec::with_capacity(camera_rays.len()); + let sigma2 = args.accum_sigma.powi(2); + let weight = (2. * PI * sigma2).sqrt().recip() * args.accum_scale; + for ray in camera_rays { + let Some(hit) = scene.trace_ray(ray) else { + continue; + }; + let mut value = 0.0f32; + for light_hit in &hits { + let d2 = hit.base.distance_squared(light_hit.base); + let w = (-0.5 * d2 / sigma2).exp(); + value += w; + } + value *= weight; + let r = 0.01; + let color = vec3(value, value - 1., value - 2.).clamp(Vec3::splat(0.), Vec3::splat(1.)); + let vertex = |off: Vec3| Vertex { + pos: hit.base + r * off, + color, + }; + camera_ray_display.extend([ + vertex(-Vec3::X), + vertex(Vec3::X), + vertex(-Vec3::Y), + vertex(Vec3::Y), + vertex(-Vec3::Z), + vertex(Vec3::Z), + ]); + } self.pipeline .render(&mut pass, [&Mesh::new(&self.device, &source_ray_display)]); + self.pipeline + .render(&mut pass, [&Mesh::new(&self.device, &camera_ray_display)]); drop(pass); self.queue.submit(std::iter::once(encoder.finish())); diff --git a/ui/src/api.hxx b/ui/src/api.hxx index 50b9e67..19b742c 100644 --- a/ui/src/api.hxx +++ b/ui/src/api.hxx @@ -18,6 +18,8 @@ struct RedrawArgs { SphericalPosition light_position; float light_radius; float light_spread; + float accum_sigma; + float accum_scale; }; } // namespace ffi diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index bdf890f..97d4302 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -29,11 +29,15 @@ void PhotonLight::updateView() { }, .light_radius = 0.125, .light_spread = 0.125, + .accum_sigma = exp10f(m_ui->accumSigma->value() / 25.0), + .accum_scale = exp10f(m_ui->accumScale->value() / 25.0), }; m_ui->cameraYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.yaw)))); m_ui->cameraPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.pitch)))); m_ui->lightYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.yaw)))); m_ui->lightPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.pitch)))); + m_ui->accumSigmaLabel->setText(tr("Sigma: %1").arg(QString::number(args.accum_sigma))); + m_ui->accumScaleLabel->setText(tr("Scale: %1").arg(QString::number(args.accum_scale))); m_ui->viewport->setView(args); } diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 26cf5b6..aef1ac0 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -162,6 +162,55 @@ + + + + Accumulating + + + + + + Sigma + + + + + + + -100 + + + 100 + + + Qt::Horizontal + + + + + + + Scale + + + + + + + -100 + + + 100 + + + Qt::Horizontal + + + + + + @@ -253,6 +302,38 @@ + + accumSigma + valueChanged(int) + MainWindow + updateView() + + + 729 + 479 + + + 399 + 299 + + + + + accumScale + valueChanged(int) + MainWindow + updateView() + + + 729 + 535 + + + 399 + 299 + + + updateView() -- 2.47.3 From aa5676865545fc4259ee8b3aeed16e2f69296290 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 15:40:07 +0300 Subject: [PATCH 04/19] fix dumb ui --- src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.rs b/src/main.rs index 615a203..e92ba99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,6 +50,8 @@ impl MainWindow { }, light_radius: 0.125, light_spread: 0.125, + accum_sigma: 0.025, + accum_scale: 0.01, }), _ => {} } -- 2.47.3 From bb8373fad5b87eba90cb19c34d7e71f4787542c8 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 18:04:18 +0300 Subject: [PATCH 05/19] optimize summation --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 457a3f1..9ae7d9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,6 +257,9 @@ impl Core { let mut value = 0.0f32; for light_hit in &hits { let d2 = hit.base.distance_squared(light_hit.base); + if d2 > 3. * sigma2 { + continue; + } let w = (-0.5 * d2 / sigma2).exp(); value += w; } -- 2.47.3 From 198dc06d02462e65d9cda09bac7598a9232006d5 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 18:05:00 +0300 Subject: [PATCH 06/19] nicer hit rendering --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9ae7d9d..515fb6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -264,7 +264,8 @@ impl Core { value += w; } value *= weight; - let r = 0.01; + value = 3. * (1. - (1. + value).recip()); + let r = args.accum_sigma; let color = vec3(value, value - 1., value - 2.).clamp(Vec3::splat(0.), Vec3::splat(1.)); let vertex = |off: Vec3| Vertex { pos: hit.base + r * off, -- 2.47.3 From 64925f96404c94ff5be5d4b417ee967188132773 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 18:05:14 +0300 Subject: [PATCH 07/19] update defaults --- src/lib.rs | 2 +- ui/src/main_window.ui | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 515fb6a..d535143 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -210,7 +210,7 @@ impl Core { 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..1024).map(|_| camera.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()); for ray in source_rays { diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index aef1ac0..fa9bb16 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -181,7 +181,10 @@ -100 - 100 + 0 + + + -40 Qt::Horizontal @@ -203,6 +206,9 @@ 100 + + -50 + Qt::Horizontal -- 2.47.3 From 12bc173fb28ff1974bf2d62c785e23597589b1a4 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 19:39:36 +0300 Subject: [PATCH 08/19] use Lambertian scattering --- src/lib.rs | 16 +++++++++------- src/trace.rs | 33 +++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d535143..7c6ce92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use crate::{ camera::OrbitalCamera, ray::Ray, render::lines::{LookParams, Mesh, Pipeline, Vertex}, - trace::{Scene, Source, Sphere}, + trace::{Hit, Scene, Source, Sphere}, }; mod camera; @@ -212,7 +212,7 @@ impl Core { 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 { if let Some(hit) = scene.trace_ray(ray) { hits.push(hit); @@ -226,11 +226,11 @@ impl Core { color: vec3(0., 1., 0.), }, Vertex { - pos: hit.base - 0.02 * hit.dir, + pos: hit.incident.base - 0.02 * hit.incident.dir, color: vec3(0., 0., 1.), }, Vertex { - pos: hit.base, + pos: hit.incident.base, color: vec3(1., 1., 1.), }, ]); @@ -256,19 +256,21 @@ impl Core { }; let mut value = 0.0f32; for light_hit in &hits { - let d2 = hit.base.distance_squared(light_hit.base); + let d2 = hit.incident.base.distance_squared(light_hit.incident.base); if d2 > 3. * sigma2 { continue; } + assert!(hit.incident.dir.is_normalized()); + let radiance = hit.normal.dot(-hit.incident.dir); let w = (-0.5 * d2 / sigma2).exp(); - value += w; + value += w * radiance; } value *= weight; value = 3. * (1. - (1. + value).recip()); let r = args.accum_sigma; let color = vec3(value, value - 1., value - 2.).clamp(Vec3::splat(0.), Vec3::splat(1.)); let vertex = |off: Vec3| Vertex { - pos: hit.base + r * off, + pos: hit.incident.base + r * off, color, }; camera_ray_display.extend([ diff --git a/src/trace.rs b/src/trace.rs index b1945c2..5f461e6 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -78,8 +78,14 @@ pub struct Sphere { pub radius: f32, } +struct Hit1 { + pos: Vec3, + dist: f32, + normal: Vec3, +} + impl Sphere { - fn trace_ray(&self, ray: Ray) -> Option { + fn trace_ray(&self, ray: Ray) -> Option { // let t: f32; // let hit = ray.base + t * ray.dir; // (hit - self.position).length() == self.radius; @@ -94,7 +100,10 @@ impl Sphere { if d4 < 0. { return None; } - Some((-b2 - d4.sqrt()) / a) + let dist = (-b2 - d4.sqrt()) / a; + let pos = ray.advance(dist).base; + let normal = (pos - self.position).normalize(); + Some(Hit1 { pos, dist, normal }) } } @@ -103,13 +112,25 @@ pub struct Scene { pub objects: Vec, } +#[derive(Debug, Clone, Copy)] +pub struct Hit { + pub incident: Ray, + pub normal: Vec3, +} + impl Scene { - pub fn trace_ray(&self, ray: Ray) -> Option { - let dist = self + pub fn trace_ray(&self, ray: Ray) -> Option { + let hit = self .objects .iter() .filter_map(|obj| obj.trace_ray(ray)) - .min_by(f32::total_cmp); - Some(ray.advance(dist?)) + .min_by(|a, b| f32::total_cmp(&a.dist, &b.dist))?; + Some(Hit { + incident: Ray { + base: hit.pos, + dir: ray.dir, + }, + normal: hit.normal, + }) } } -- 2.47.3 From 6ba9add6eae6c26a2a24997b1e07a1060f7c05e0 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 16 Nov 2025 20:18:05 +0300 Subject: [PATCH 09/19] a bit better ui --- src/lib.rs | 7 +------ ui/src/main_window.cxx | 4 ++-- ui/src/main_window.ui | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7c6ce92..420e73f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,12 +156,7 @@ impl Core { depth_slice: None, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.8, - a: 1.0, - }), + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })], diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index 97d4302..511b984 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -36,8 +36,8 @@ void PhotonLight::updateView() { m_ui->cameraPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.pitch)))); m_ui->lightYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.yaw)))); m_ui->lightPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.pitch)))); - m_ui->accumSigmaLabel->setText(tr("Sigma: %1").arg(QString::number(args.accum_sigma))); - m_ui->accumScaleLabel->setText(tr("Scale: %1").arg(QString::number(args.accum_scale))); + m_ui->accumSigmaLabel->setText(tr("Sigma: %1").arg(QString::number(args.accum_sigma, 'f', 3))); + m_ui->accumScaleLabel->setText(tr("Scale: %1").arg(QString::number(args.accum_scale, 'f', 3))); m_ui->viewport->setView(args); } diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index fa9bb16..9688b64 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -204,7 +204,7 @@ -100 - 100 + 0 -50 -- 2.47.3 From 2e6589e01bd69abcfb551d233a88908e81b42c82 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 18 Nov 2025 19:45:56 +0300 Subject: [PATCH 10/19] fix threshold --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 420e73f..09f5e5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -252,7 +252,7 @@ impl Core { let mut value = 0.0f32; for light_hit in &hits { let d2 = hit.incident.base.distance_squared(light_hit.incident.base); - if d2 > 3. * sigma2 { + if d2 > 9. * sigma2 { continue; } assert!(hit.incident.dir.is_normalized()); -- 2.47.3 From cbe0839cd6166981e3835cde6651e4cb46ba5c10 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 18 Nov 2025 19:46:08 +0300 Subject: [PATCH 11/19] sort out units --- src/lib.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 09f5e5a..5195e03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -244,26 +244,27 @@ impl Core { } let mut camera_ray_display: Vec = Vec::with_capacity(camera_rays.len()); let sigma2 = args.accum_sigma.powi(2); - let weight = (2. * PI * sigma2).sqrt().recip() * args.accum_scale; + let accum_normalizator = (2. * PI * sigma2).sqrt().recip(); for ray in camera_rays { let Some(hit) = scene.trace_ray(ray) else { continue; }; - let mut value = 0.0f32; + let mut total_cd = 0.0f32; for light_hit in &hits { let d2 = hit.incident.base.distance_squared(light_hit.incident.base); if d2 > 9. * sigma2 { continue; } assert!(hit.incident.dir.is_normalized()); - let radiance = hit.normal.dot(-hit.incident.dir); - let w = (-0.5 * d2 / sigma2).exp(); - value += w * radiance; + let in_lm = 1.0; + let out_cd = in_lm * hit.normal.dot(-hit.incident.dir) / PI; + let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); + total_cd += weight * out_cd; } - value *= weight; - value = 3. * (1. - (1. + value).recip()); + let brightness = 3. * (1. - (1. + total_cd * args.accum_scale).recip()); let r = args.accum_sigma; - let color = vec3(value, value - 1., value - 2.).clamp(Vec3::splat(0.), Vec3::splat(1.)); + let color = vec3(brightness, brightness - 1., brightness - 2.) + .clamp(Vec3::splat(0.), Vec3::splat(1.)); let vertex = |off: Vec3| Vertex { pos: hit.incident.base + r * off, color, -- 2.47.3 From 713fd25c9c88f03e576fb9a54bf7473bd56c0677 Mon Sep 17 00:00:00 2001 From: numzero Date: Thu, 20 Nov 2025 13:05:02 +0300 Subject: [PATCH 12/19] add generic reflector --- src/trace.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/trace.rs b/src/trace.rs index 5f461e6..baa2633 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -1,7 +1,7 @@ use std::f32::consts::PI; use glam::{Mat4, Vec2, Vec3, vec3}; -use rand_distr::Distribution; +use rand_distr::{Distribution, UnitSphere}; use crate::{camera::OrbitalCamera, ray::Ray}; @@ -134,3 +134,27 @@ impl Scene { }) } } + +pub trait Reflector { + fn brdf(&self, normal: Vec3, incident: Vec3, reflected: Vec3) -> f32 /* 1/sr */; + fn reflect(&self, rgen: &mut impl rand::Rng, normal: Vec3, incident: Vec3) -> Vec3; +} + +pub struct Lambertian; + +impl Reflector for Lambertian { + fn brdf(&self, _normal: Vec3, _incident: Vec3, _reflected: Vec3) -> f32 { + 1. / PI + } + + fn reflect(&self, rgen: &mut impl rand::Rng, normal: Vec3, _incident: Vec3) -> Vec3 { + let sphere: Vec3 = UnitSphere.sample(rgen).into(); + let sphere_n = normal.dot(sphere); // uniform on [-1, 1]! + let sphere_t = sphere - sphere_n * normal; + + let out_n_len2 = sphere_n.abs(); + let out_t = (1. + out_n_len2).recip().sqrt() * sphere_t; + let out_n = out_n_len2.sqrt() * normal; + out_t + out_n + } +} -- 2.47.3 From 91473f08aef45434f3d52f8ff09f6868d87f38ac Mon Sep 17 00:00:00 2001 From: numzero Date: Thu, 20 Nov 2025 13:10:21 +0300 Subject: [PATCH 13/19] use generic reflector for final computation --- src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5195e03..5d2fc5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use crate::{ camera::OrbitalCamera, ray::Ray, render::lines::{LookParams, Mesh, Pipeline, Vertex}, - trace::{Hit, Scene, Source, Sphere}, + trace::{Hit, Lambertian, Reflector, Scene, Source, Sphere}, }; mod camera; @@ -255,9 +255,13 @@ impl Core { if d2 > 9. * sigma2 { continue; } + assert!(hit.normal.is_normalized()); assert!(hit.incident.dir.is_normalized()); + let reflector = Lambertian; let in_lm = 1.0; - let out_cd = in_lm * hit.normal.dot(-hit.incident.dir) / PI; + let out_cd = in_lm + * hit.normal.dot(-hit.incident.dir) + * reflector.brdf(hit.normal, hit.incident.dir, -ray.dir); let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); total_cd += weight * out_cd; } -- 2.47.3 From 93c8652116586c10daaa8d61d3d1af047058eb25 Mon Sep 17 00:00:00 2001 From: numzero Date: Thu, 20 Nov 2025 13:23:04 +0300 Subject: [PATCH 14/19] =?UTF-8?q?ensure=20rays=20don=E2=80=99t=20propagate?= =?UTF-8?q?=20backwards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/trace.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/trace.rs b/src/trace.rs index baa2633..984f9d9 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -120,10 +120,12 @@ pub struct Hit { impl Scene { 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))?; Some(Hit { incident: Ray { -- 2.47.3 From a7746deec53a9c4ec597289b7475abf5a13e1512 Mon Sep 17 00:00:00 2001 From: numzero Date: Thu, 20 Nov 2025 13:23:19 +0300 Subject: [PATCH 15/19] add basic interreflections --- src/lib.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5d2fc5d..fa70501 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -242,6 +242,27 @@ impl Core { ]); } } + let mut hits2: Vec = Vec::with_capacity(hits.len()); + for hit in &hits { + let reflector = Lambertian; + let reflected = reflector.reflect(&mut prng, hit.normal, hit.incident.dir); + let ray = Ray::new(hit.incident.base, reflected); + let Some(hit2) = scene.trace_ray(ray) else { + continue; + }; + hits2.push(hit2); + source_ray_display.extend([ + Vertex { + pos: hit2.incident.base - 0.02 * hit2.incident.dir, + color: vec3(1., 0., 1.), + }, + Vertex { + pos: hit2.incident.base, + color: vec3(1., 1., 1.), + }, + ]); + } + hits.extend(hits2); let mut camera_ray_display: Vec = Vec::with_capacity(camera_rays.len()); let sigma2 = args.accum_sigma.powi(2); let accum_normalizator = (2. * PI * sigma2).sqrt().recip(); -- 2.47.3 From 61f19c85f4aa28c43b56c58559aac991e373e471 Mon Sep 17 00:00:00 2001 From: numzero Date: Thu, 20 Nov 2025 13:55:49 +0300 Subject: [PATCH 16/19] add sane-ish defaults to avoid ub --- ui/src/api.hxx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/src/api.hxx b/ui/src/api.hxx index 19b742c..c1f3b2a 100644 --- a/ui/src/api.hxx +++ b/ui/src/api.hxx @@ -8,18 +8,18 @@ namespace ffi { struct Core; struct SphericalPosition { - float yaw; - float pitch; - float distance; + float yaw = 0.; + float pitch = 0.; + float distance = 1.; }; struct RedrawArgs { SphericalPosition camera_position; SphericalPosition light_position; - float light_radius; - float light_spread; - float accum_sigma; - float accum_scale; + float light_radius = 1.; + float light_spread = 0.; + float accum_sigma = 1.; + float accum_scale = 1.; }; } // namespace ffi -- 2.47.3 From 5a69a3a1cb8f9b7d13074ceb1e1ca87ad2365fcf Mon Sep 17 00:00:00 2001 From: numzero Date: Thu, 20 Nov 2025 14:12:31 +0300 Subject: [PATCH 17/19] add custom display options --- src/lib.rs | 199 +++++++++++++++++------------- src/main.rs | 7 ++ ui/src/api.hxx | 7 ++ ui/src/main_window.cxx | 17 ++- ui/src/main_window.ui | 273 ++++++++++++++++++++++++++++++++++++++--- 5 files changed, 395 insertions(+), 108 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fa70501..ba7dd7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,13 @@ pub struct RedrawArgs { pub light_spread: f32, pub accum_sigma: f32, pub accum_scale: f32, + pub show_axes: bool, + pub show_shapes: bool, + pub show_hit_emission: bool, + pub show_miss_emission: bool, + pub show_direct_hit: bool, + pub show_indirect_hit: bool, + pub show_light: bool, } pub struct Gpu { @@ -163,7 +170,9 @@ impl Core { depth_stencil_attachment: None, ..Default::default() }); - self.pipeline.render(&mut pass, [&self.tripod]); + if args.show_axes { + self.pipeline.render(&mut pass, [&self.tripod]); + } let source = Source { position_yaw: args.light_position.yaw, @@ -173,14 +182,16 @@ impl Core { spread: args.light_spread, }; - let contour: Vec = loop_list(source.contour(17)) - .map(|pos| Vertex { - pos, - color: vec3(1., 1., 1.), - }) - .collect(); - self.pipeline - .render(&mut pass, [&Mesh::new(&self.device, &contour)]); + if args.show_shapes { + let contour: Vec = loop_list(source.contour(17)) + .map(|pos| Vertex { + pos, + color: vec3(1., 1., 1.), + }) + .collect(); + self.pipeline + .render(&mut pass, [&Mesh::new(&self.device, &contour)]); + } const BASE_R: f32 = 2.; const BASE_POS: Vec3 = vec3(0., 0., -BASE_R); @@ -211,35 +222,43 @@ impl Core { for ray in source_rays { if let Some(hit) = scene.trace_ray(ray) { hits.push(hit); - source_ray_display.extend([ - Vertex { - pos: ray.base, - color: vec3(1., 1., 1.), - }, - Vertex { - pos: ray.base + 0.1 * ray.dir, - color: vec3(0., 1., 0.), - }, - Vertex { - pos: hit.incident.base - 0.02 * hit.incident.dir, - color: vec3(0., 0., 1.), - }, - Vertex { - pos: hit.incident.base, - color: vec3(1., 1., 1.), - }, - ]); + if args.show_hit_emission { + source_ray_display.extend([ + Vertex { + pos: ray.base, + color: vec3(1., 1., 1.), + }, + Vertex { + pos: ray.base + 0.1 * ray.dir, + color: vec3(0., 1., 0.), + }, + ]); + } + if args.show_direct_hit { + source_ray_display.extend([ + Vertex { + pos: hit.incident.base - 0.02 * hit.incident.dir, + color: vec3(0., 0., 1.), + }, + Vertex { + pos: hit.incident.base, + color: vec3(1., 1., 1.), + }, + ]); + } } else { - source_ray_display.extend([ - Vertex { - pos: ray.base, - color: vec3(1., 1., 1.), - }, - Vertex { - pos: ray.base + 0.1 * ray.dir, - color: vec3(1., 0., 0.), - }, - ]); + if args.show_miss_emission { + source_ray_display.extend([ + Vertex { + pos: ray.base, + color: vec3(1., 1., 1.), + }, + Vertex { + pos: ray.base + 0.1 * ray.dir, + color: vec3(1., 0., 0.), + }, + ]); + } } } let mut hits2: Vec = Vec::with_capacity(hits.len()); @@ -251,62 +270,70 @@ impl Core { continue; }; hits2.push(hit2); - source_ray_display.extend([ - Vertex { - pos: hit2.incident.base - 0.02 * hit2.incident.dir, - color: vec3(1., 0., 1.), - }, - Vertex { - pos: hit2.incident.base, - color: vec3(1., 1., 1.), - }, - ]); + if args.show_indirect_hit { + source_ray_display.extend([ + Vertex { + pos: hit2.incident.base - 0.02 * hit2.incident.dir, + color: vec3(1., 0., 1.), + }, + Vertex { + pos: hit2.incident.base, + color: vec3(1., 1., 1.), + }, + ]); + } } hits.extend(hits2); let mut camera_ray_display: Vec = Vec::with_capacity(camera_rays.len()); - let sigma2 = args.accum_sigma.powi(2); - let accum_normalizator = (2. * PI * sigma2).sqrt().recip(); - for ray in camera_rays { - let Some(hit) = scene.trace_ray(ray) else { - continue; - }; - let mut total_cd = 0.0f32; - for light_hit in &hits { - let d2 = hit.incident.base.distance_squared(light_hit.incident.base); - if d2 > 9. * sigma2 { + if args.show_light { + let sigma2 = args.accum_sigma.powi(2); + let accum_normalizator = (2. * PI * sigma2).sqrt().recip(); + for ray in camera_rays { + let Some(hit) = scene.trace_ray(ray) else { continue; + }; + let mut total_cd = 0.0f32; + for light_hit in &hits { + let d2 = hit.incident.base.distance_squared(light_hit.incident.base); + if d2 > 9. * sigma2 { + continue; + } + assert!(hit.normal.is_normalized()); + assert!(hit.incident.dir.is_normalized()); + let reflector = Lambertian; + let in_lm = 1.0; + let out_cd = in_lm + * hit.normal.dot(-hit.incident.dir) + * reflector.brdf(hit.normal, hit.incident.dir, -ray.dir); + let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); + total_cd += weight * out_cd; } - assert!(hit.normal.is_normalized()); - assert!(hit.incident.dir.is_normalized()); - let reflector = Lambertian; - let in_lm = 1.0; - let out_cd = in_lm - * hit.normal.dot(-hit.incident.dir) - * reflector.brdf(hit.normal, hit.incident.dir, -ray.dir); - let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); - total_cd += weight * out_cd; + let brightness = 3. * (1. - (1. + total_cd * args.accum_scale).recip()); + let r = args.accum_sigma; + let color = vec3(brightness, brightness - 1., brightness - 2.) + .clamp(Vec3::splat(0.), Vec3::splat(1.)); + let vertex = |off: Vec3| Vertex { + pos: hit.incident.base + r * off, + color, + }; + camera_ray_display.extend([ + vertex(-Vec3::X), + vertex(Vec3::X), + vertex(-Vec3::Y), + vertex(Vec3::Y), + vertex(-Vec3::Z), + vertex(Vec3::Z), + ]); } - let brightness = 3. * (1. - (1. + total_cd * args.accum_scale).recip()); - let r = args.accum_sigma; - let color = vec3(brightness, brightness - 1., brightness - 2.) - .clamp(Vec3::splat(0.), Vec3::splat(1.)); - let vertex = |off: Vec3| Vertex { - pos: hit.incident.base + r * off, - color, - }; - camera_ray_display.extend([ - vertex(-Vec3::X), - vertex(Vec3::X), - vertex(-Vec3::Y), - vertex(Vec3::Y), - vertex(-Vec3::Z), - vertex(Vec3::Z), - ]); } - self.pipeline - .render(&mut pass, [&Mesh::new(&self.device, &source_ray_display)]); - self.pipeline - .render(&mut pass, [&Mesh::new(&self.device, &camera_ray_display)]); + if !source_ray_display.is_empty() { + self.pipeline + .render(&mut pass, [&Mesh::new(&self.device, &source_ray_display)]); + } + if !camera_ray_display.is_empty() { + self.pipeline + .render(&mut pass, [&Mesh::new(&self.device, &camera_ray_display)]); + } drop(pass); self.queue.submit(std::iter::once(encoder.finish())); diff --git a/src/main.rs b/src/main.rs index e92ba99..7f8a128 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,6 +52,13 @@ impl MainWindow { light_spread: 0.125, accum_sigma: 0.025, accum_scale: 0.01, + show_axes: true, + show_shapes: true, + show_hit_emission: false, + show_miss_emission: false, + show_direct_hit: false, + show_indirect_hit: false, + show_light: true, }), _ => {} } diff --git a/ui/src/api.hxx b/ui/src/api.hxx index c1f3b2a..ff7038e 100644 --- a/ui/src/api.hxx +++ b/ui/src/api.hxx @@ -20,6 +20,13 @@ struct RedrawArgs { float light_spread = 0.; float accum_sigma = 1.; float accum_scale = 1.; + bool show_axes = true; + bool show_shapes = true; + bool show_hit_emission = true; + bool show_miss_emission = true; + bool show_direct_hit = true; + bool show_indirect_hit = true; + bool show_light = true; }; } // namespace ffi diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index 511b984..033645f 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -20,24 +20,33 @@ void PhotonLight::updateView() { .camera_position = SphericalPosition{ .yaw = deg_to_rad(m_ui->cameraYaw->value()), .pitch = deg_to_rad(m_ui->cameraPitch->value()), - .distance = 3.0, + .distance = m_ui->cameraDistance->value() / 10.0f, }, .light_position = SphericalPosition{ .yaw = deg_to_rad(m_ui->lightYaw->value()), .pitch = deg_to_rad(m_ui->lightPitch->value()), - .distance = 1.0, + .distance = m_ui->lightDistance->value() / 10.0f, }, .light_radius = 0.125, .light_spread = 0.125, .accum_sigma = exp10f(m_ui->accumSigma->value() / 25.0), .accum_scale = exp10f(m_ui->accumScale->value() / 25.0), + .show_axes = m_ui->displayAxes->isChecked(), + .show_shapes = m_ui->displayShapes->isChecked(), + .show_hit_emission = m_ui->displayEmitted->isChecked(), + .show_miss_emission = m_ui->displayEmitted->isChecked(), + .show_direct_hit = m_ui->displayDirectHits->isChecked(), + .show_indirect_hit = m_ui->displayIndirectHits->isChecked(), + .show_light = m_ui->displayResult->isChecked(), }; m_ui->cameraYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.yaw)))); m_ui->cameraPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.pitch)))); + m_ui->cameraDistanceLabel->setText(tr("Distance: %1").arg(QString::number(args.camera_position.distance))); m_ui->lightYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.yaw)))); m_ui->lightPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.pitch)))); - m_ui->accumSigmaLabel->setText(tr("Sigma: %1").arg(QString::number(args.accum_sigma, 'f', 3))); - m_ui->accumScaleLabel->setText(tr("Scale: %1").arg(QString::number(args.accum_scale, 'f', 3))); + m_ui->lightDistanceLabel->setText(tr("Distance: %1").arg(QString::number(args.light_position.distance))); + m_ui->accumSigmaLabel->setText(tr("Averaging radius: %1").arg(QString::number(args.accum_sigma, 'f', 3))); + m_ui->accumScaleLabel->setText(tr("Brightness: %1").arg(QString::number(args.accum_scale, 'f', 3))); m_ui->viewport->setView(args); } diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 9688b64..44b57c5 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 600 + 1600 + 1200 @@ -25,7 +25,7 @@ 0 0 - 800 + 1600 38 @@ -98,6 +98,26 @@ + + + + Distance + + + + + + + 50 + + + 30 + + + Qt::Horizontal + + + @@ -159,19 +179,39 @@ + + + + Distance + + + + + + + 50 + + + 10 + + + Qt::Horizontal + + + - Accumulating + Lighting - Sigma + Averaging radius @@ -194,7 +234,7 @@ - Scale + Brightness @@ -217,6 +257,75 @@ + + + + Show + + + + + + Axes + + + true + + + + + + + Shapes + + + true + + + + + + + Emitted rays + + + false + + + + + + + Direct incident rays + + + false + + + + + + + Indirect incident rays + + + false + + + + + + + Average light + + + true + + + + + + @@ -251,8 +360,8 @@ updateView() - 688 - 159 + 1585 + 169 403 @@ -267,8 +376,8 @@ updateView() - 688 - 215 + 1585 + 225 403 @@ -283,8 +392,8 @@ updateView() - 688 - 319 + 1585 + 385 403 @@ -299,8 +408,8 @@ updateView() - 688 - 375 + 1585 + 441 403 @@ -315,8 +424,8 @@ updateView() - 729 - 479 + 1585 + 601 399 @@ -331,8 +440,8 @@ updateView() - 729 - 535 + 1585 + 657 399 @@ -340,6 +449,134 @@ + + cameraDistance + valueChanged(int) + MainWindow + updateView() + + + 1489 + 271 + + + 799 + 599 + + + + + lightDistance + valueChanged(int) + MainWindow + updateView() + + + 1489 + 487 + + + 799 + 599 + + + + + displayAxes + stateChanged(int) + MainWindow + updateView() + + + 1489 + 799 + + + 799 + 599 + + + + + displayDirectHits + stateChanged(int) + MainWindow + updateView() + + + 1489 + 901 + + + 799 + 599 + + + + + displayEmitted + stateChanged(int) + MainWindow + updateView() + + + 1489 + 867 + + + 799 + 599 + + + + + displayIndirectHits + stateChanged(int) + MainWindow + updateView() + + + 1489 + 935 + + + 799 + 599 + + + + + displayResult + stateChanged(int) + MainWindow + updateView() + + + 1489 + 969 + + + 799 + 599 + + + + + displayShapes + stateChanged(int) + MainWindow + updateView() + + + 1489 + 833 + + + 799 + 599 + + + updateView() -- 2.47.3 From e80e1d09fe142cb9508067a7c8f2b92485aa4a56 Mon Sep 17 00:00:00 2001 From: numzero Date: Thu, 20 Nov 2025 14:19:58 +0300 Subject: [PATCH 18/19] add interreflections --- src/lib.rs | 49 ++++++++++++++++++++++++------------------ src/main.rs | 1 + ui/src/api.hxx | 1 + ui/src/main_window.cxx | 1 + ui/src/main_window.ui | 33 ++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ba7dd7e..8b7384d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,7 @@ pub struct RedrawArgs { pub light_spread: f32, pub accum_sigma: f32, pub accum_scale: f32, + pub reflections: u32, pub show_axes: bool, pub show_shapes: bool, pub show_hit_emission: bool, @@ -261,29 +262,35 @@ impl Core { } } } - let mut hits2: Vec = Vec::with_capacity(hits.len()); - for hit in &hits { - let reflector = Lambertian; - let reflected = reflector.reflect(&mut prng, hit.normal, hit.incident.dir); - let ray = Ray::new(hit.incident.base, reflected); - let Some(hit2) = scene.trace_ray(ray) else { - continue; - }; - hits2.push(hit2); - if args.show_indirect_hit { - source_ray_display.extend([ - Vertex { - pos: hit2.incident.base - 0.02 * hit2.incident.dir, - color: vec3(1., 0., 1.), - }, - Vertex { - pos: hit2.incident.base, - color: vec3(1., 1., 1.), - }, - ]); + if args.reflections > 0 { + let mut hits1 = hits.clone(); + for _ in 0..args.reflections { + 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 ray = Ray::new(hit.incident.base, reflected); + let Some(hit2) = scene.trace_ray(ray) else { + continue; + }; + hits2.push(hit2); + if args.show_indirect_hit { + source_ray_display.extend([ + Vertex { + pos: hit2.incident.base - 0.02 * hit2.incident.dir, + color: vec3(1., 0., 1.), + }, + Vertex { + pos: hit2.incident.base, + color: vec3(1., 1., 1.), + }, + ]); + } + } + hits.extend(&hits2); + hits1 = hits2; } } - hits.extend(hits2); let mut camera_ray_display: Vec = Vec::with_capacity(camera_rays.len()); if args.show_light { let sigma2 = args.accum_sigma.powi(2); diff --git a/src/main.rs b/src/main.rs index 7f8a128..f08bf16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,6 +52,7 @@ impl MainWindow { light_spread: 0.125, accum_sigma: 0.025, accum_scale: 0.01, + reflections: 2, show_axes: true, show_shapes: true, show_hit_emission: false, diff --git a/ui/src/api.hxx b/ui/src/api.hxx index ff7038e..caf32cd 100644 --- a/ui/src/api.hxx +++ b/ui/src/api.hxx @@ -20,6 +20,7 @@ struct RedrawArgs { float light_spread = 0.; float accum_sigma = 1.; float accum_scale = 1.; + std::uint32_t reflections = 0; bool show_axes = true; bool show_shapes = true; bool show_hit_emission = true; diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index 033645f..dff2f7d 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -31,6 +31,7 @@ void PhotonLight::updateView() { .light_spread = 0.125, .accum_sigma = exp10f(m_ui->accumSigma->value() / 25.0), .accum_scale = exp10f(m_ui->accumScale->value() / 25.0), + .reflections = std::uint32_t(m_ui->reflections->value()), .show_axes = m_ui->displayAxes->isChecked(), .show_shapes = m_ui->displayShapes->isChecked(), .show_hit_emission = m_ui->displayEmitted->isChecked(), diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 44b57c5..23bf13b 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -254,6 +254,23 @@ + + + + Reflections + + + + + + + 20 + + + 2 + + + @@ -577,6 +594,22 @@ + + reflections + valueChanged(int) + MainWindow + updateView() + + + 1489 + 712 + + + 799 + 599 + + + updateView() -- 2.47.3 From 96e5c78a59d421bb6630fa9788718b64c2c39fde Mon Sep 17 00:00:00 2001 From: numzero Date: Thu, 20 Nov 2025 14:23:29 +0300 Subject: [PATCH 19/19] ui hints --- ui/src/main_window.ui | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 23bf13b..697c566 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -51,6 +51,9 @@ Yaw + + cameraYaw + @@ -77,6 +80,9 @@ Pitch + + cameraPitch + @@ -103,6 +109,9 @@ Distance + + cameraDistance + @@ -132,6 +141,9 @@ Yaw + + lightYaw + @@ -155,6 +167,9 @@ Pitch + + lightPitch + @@ -184,6 +199,9 @@ Distance + + lightDistance + @@ -213,6 +231,9 @@ Averaging radius + + accumSigma + @@ -236,6 +257,9 @@ Brightness + + accumScale + @@ -259,6 +283,9 @@ Reflections + + reflections + -- 2.47.3