Skip to content

Commit 9ba8f22

Browse files
committed
WIP: Real time two_spheres
Works but the ray tracer is flipped left to right
1 parent 6411499 commit 9ba8f22

6 files changed

Lines changed: 75 additions & 31 deletions

File tree

TODO.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,8 @@ Once CPU-side camera is working:
8181

8282
## Technical Debt
8383

84-
1. [ ] `Array<T, N>` currently requires `T: Copy + Default` because we use `[Default::default(); N]` for initialization. This is overly restrictive. Ideally, `T` would only need to be "zeroable" (all zero bytes is a valid default). This would allow types like `Sphere` that aren't `Copy` but can be safely zero-initialized. The goal is something like `core::array::from_fn(|_| Default::default())` but in a form rust-gpu accepts.
84+
1. [ ] **Ray tracer flips image left-to-right**: The interactive camera controls have inverted signs on movement and mouse look to compensate. The ray tracer appears to be rendering the image mirrored horizontally. The workaround is in `host/src/main.rs` (`update_camera`). The root cause should be investigated - likely in `Camera::new()` or `Camera::get_ray()` in `rtx-util/src/cam.rs`, possibly related to how pixel coordinates map to ray directions.
85+
86+
2. [ ] **Crash when camera enters an object**: The program sometimes crashes, likely when the camera position moves inside geometry. This may cause issues with ray-object intersection tests (e.g., negative t values, NaN from inside-surface calculations). Needs investigation.
87+
88+
2. [ ] `Array<T, N>` currently requires `T: Copy + Default` because we use `[Default::default(); N]` for initialization. This is overly restrictive. Ideally, `T` would only need to be "zeroable" (all zero bytes is a valid default). This would allow types like `Sphere` that aren't `Copy` but can be safely zero-initialized. The goal is something like `core::array::from_fn(|_| Default::default())` but in a form rust-gpu accepts.

crates/host/src/gpu.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ impl GpuContext {
183183
time: 0.0,
184184
cursor_x: 0.0,
185185
cursor_y: 0.0,
186+
cam_pos: [278.0, 278.0, -800.0],
187+
cam_dir: [0.0, 0.0, 1.0], // Looking down +Z
186188
};
187189

188190
{

crates/host/src/main.rs

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ struct RustShaderSandboxApp {
7575
cursor_y: f32,
7676
// Camera state
7777
cam_pos: [f32; 3],
78-
cam_yaw: f32, // radians, 0 = looking down -Z
79-
cam_pitch: f32, // radians, 0 = horizontal
78+
cam_dir: [f32; 3], // normalized forward direction
79+
cam_yaw: f32, // radians, 0 = looking down -Z
80+
cam_pitch: f32, // radians, 0 = horizontal
8081
keys_held: KeysHeld,
8182
last_cursor_x: f32,
8283
last_cursor_y: f32,
@@ -85,12 +86,18 @@ struct RustShaderSandboxApp {
8586

8687
impl RustShaderSandboxApp {
8788
fn new(scene: String) -> Self {
88-
// Default camera: Cornell box position, looking into the box
89-
// Cornell box camera: lookfrom (278, 278, -800), lookat (278, 278, 0)
90-
// This means looking down +Z from a negative Z position
91-
let cam_pos = [278.0, 278.0, -800.0];
92-
let cam_yaw = PI; // Looking down +Z (opposite of default -Z)
93-
let cam_pitch = 0.0;
89+
// Default camera: two_spheres position
90+
// two_spheres camera: lookfrom (0, 1, 5), lookat (0, 0, 0)
91+
// Direction to origin: (0, -1, -5), normalized ≈ (0, -0.196, -0.98)
92+
let cam_pos = [0.0, 1.0, 5.0];
93+
let cam_yaw: f32 = 0.0; // Looking down -Z
94+
let cam_pitch: f32 = -0.197; // Slightly down to look at origin
95+
// Initial direction from yaw/pitch
96+
let cam_dir = [
97+
cam_yaw.sin() * cam_pitch.cos(),
98+
cam_pitch.sin(),
99+
cam_yaw.cos() * cam_pitch.cos(),
100+
];
94101

95102
Self {
96103
scene,
@@ -103,6 +110,7 @@ impl RustShaderSandboxApp {
103110
cursor_x: 0.0,
104111
cursor_y: 0.0,
105112
cam_pos,
113+
cam_dir,
106114
cam_yaw,
107115
cam_pitch,
108116
keys_held: KeysHeld::default(),
@@ -120,12 +128,23 @@ impl RustShaderSandboxApp {
120128
self.last_frame = now;
121129

122130
// Mouse look: compute delta from last cursor position
123-
let mouse_dx = self.cursor_x - self.last_cursor_x;
124-
let mouse_dy = self.cursor_y - self.last_cursor_y;
131+
// Skip if last_cursor is uninitialized (first frame)
132+
let first_frame = self.last_cursor_x == 0.0 && self.last_cursor_y == 0.0;
133+
let mouse_dx = if first_frame {
134+
0.0
135+
} else {
136+
self.cursor_x - self.last_cursor_x
137+
};
138+
let mouse_dy = if first_frame {
139+
0.0
140+
} else {
141+
self.cursor_y - self.last_cursor_y
142+
};
125143
self.last_cursor_x = self.cursor_x;
126144
self.last_cursor_y = self.cursor_y;
127145

128146
// Update yaw/pitch from mouse (sensitivity factor)
147+
// Signs inverted to compensate for ray tracer's left-right flip
129148
let sensitivity = 0.003;
130149
self.cam_yaw -= mouse_dx * sensitivity;
131150
self.cam_pitch -= mouse_dy * sensitivity;
@@ -141,9 +160,10 @@ impl RustShaderSandboxApp {
141160
let right_z = (self.cam_yaw - PI / 2.0).cos();
142161

143162
// Movement speed (units per second)
144-
let speed = 200.0 * dt;
163+
let speed = 5.0 * dt;
145164

146165
// Apply movement based on held keys
166+
// Signs inverted to compensate for ray tracer's left-right flip
147167
if self.keys_held.w {
148168
self.cam_pos[0] += forward_x * speed;
149169
self.cam_pos[2] += forward_z * speed;
@@ -167,28 +187,22 @@ impl RustShaderSandboxApp {
167187
self.cam_pos[1] -= speed;
168188
}
169189

170-
// Compute lookat point (1 unit in front of camera)
171-
let look_forward_x = self.cam_yaw.sin() * self.cam_pitch.cos();
172-
let look_forward_y = self.cam_pitch.sin();
173-
let look_forward_z = self.cam_yaw.cos() * self.cam_pitch.cos();
174-
175-
let lookat = [
176-
self.cam_pos[0] + look_forward_x,
177-
self.cam_pos[1] + look_forward_y,
178-
self.cam_pos[2] + look_forward_z,
190+
// Compute forward direction (normalized)
191+
self.cam_dir = [
192+
self.cam_yaw.sin() * self.cam_pitch.cos(),
193+
self.cam_pitch.sin(),
194+
self.cam_yaw.cos() * self.cam_pitch.cos(),
179195
];
180196

181197
// Log camera params
182198
log::debug!(
183-
"Camera: pos=({:.1}, {:.1}, {:.1}) lookat=({:.1}, {:.1}, {:.1}) yaw={:.2} pitch={:.2}",
199+
"Camera: pos=({:.1}, {:.1}, {:.1}) dir=({:.2}, {:.2}, {:.2})",
184200
self.cam_pos[0],
185201
self.cam_pos[1],
186202
self.cam_pos[2],
187-
lookat[0],
188-
lookat[1],
189-
lookat[2],
190-
self.cam_yaw,
191-
self.cam_pitch
203+
self.cam_dir[0],
204+
self.cam_dir[1],
205+
self.cam_dir[2],
192206
);
193207
}
194208

@@ -278,6 +292,8 @@ impl RustShaderSandboxApp {
278292
time: self.start.elapsed().as_secs_f32(),
279293
cursor_x: self.cursor_x,
280294
cursor_y: self.cursor_y,
295+
cam_pos: self.cam_pos,
296+
cam_dir: self.cam_dir,
281297
};
282298

283299
{

crates/shader/src/lib.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,26 @@ pub fn two_spheres_fs(
109109
#[spirv(push_constant)] constants: &ShaderConstants,
110110
output: &mut Vec4,
111111
) {
112-
let (cam, mat_table, tex_table, world) =
113-
scene::two_spheres(constants.width as usize, constants.height as usize);
112+
use spirv_std::glam::Vec3;
113+
114+
let lookfrom = Vec3::new(
115+
constants.cam_pos[0],
116+
constants.cam_pos[1],
117+
constants.cam_pos[2],
118+
);
119+
let cam_dir = Vec3::new(
120+
constants.cam_dir[0],
121+
constants.cam_dir[1],
122+
constants.cam_dir[2],
123+
);
124+
let lookat = lookfrom + cam_dir;
125+
126+
let (cam, mat_table, tex_table, world) = scene::two_spheres(
127+
constants.width as usize,
128+
constants.height as usize,
129+
lookfrom,
130+
lookat,
131+
);
114132

115133
let i = frag_coord.y as usize;
116134
let j = frag_coord.x as usize;

crates/shader/src/scene.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ pub fn dielectric_test(
558558
pub fn two_spheres(
559559
img_width: usize,
560560
img_height: usize,
561+
lookfrom: Point3,
562+
lookat: Point3,
561563
) -> (Camera, MaterialTable, TextureTable, Scene) {
562564
let mut tex_table = TextureTable::new();
563565
tex_table
@@ -594,8 +596,8 @@ pub fn two_spheres(
594596
};
595597

596598
let cam = Camera::new(CameraParams {
597-
lookfrom: Point3::new(0., 1., 5.),
598-
lookat: Point3::new(0., 0., 0.),
599+
lookfrom,
600+
lookat,
599601
vup: Vec3::new(0., 1., 0.),
600602
fov_v: 40.0,
601603
defocus_angle: 0.0,

crates/shared/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ pub struct ShaderConstants {
1111
pub time: f32,
1212
pub cursor_x: f32,
1313
pub cursor_y: f32,
14+
pub cam_pos: [f32; 3],
15+
pub cam_dir: [f32; 3],
1416
}

0 commit comments

Comments
 (0)