Skip to content

Commit 03ff8ee

Browse files
committed
1 parent 541b940 commit 03ff8ee

File tree

10 files changed

+876
-10
lines changed

10 files changed

+876
-10
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ colorous = "1.0.12"
135135
web-time = "1.0.0"
136136
winit = "0.30.0"
137137
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
138+
glam = { version = "0.30.10", features = ["rand"] }
139+
rand = "0.9.2"
138140

139141
[target.'cfg(target_os = "android")'.dev-dependencies]
140142
winit = { version = "0.30.0", features = ["android-native-activity"] }

examples/raytracing/camera.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use rand::Rng;
2+
3+
use crate::ray::Ray;
4+
use crate::vec3;
5+
use crate::vec3::Point3;
6+
use crate::vec3::Vec3;
7+
8+
pub struct Camera {
9+
origin: Point3,
10+
lower_left_corner: Point3,
11+
horizontal: Vec3,
12+
vertical: Vec3,
13+
u: Vec3,
14+
v: Vec3,
15+
_w: Vec3,
16+
lens_radius: f32,
17+
}
18+
19+
impl Camera {
20+
pub fn new(
21+
position: Point3,
22+
direction: Point3,
23+
up: Vec3,
24+
fov: f32, // Vertical field-of-view in degrees
25+
aspect_ratio: f32,
26+
aperture: f32,
27+
focus_dist: f32,
28+
) -> Self {
29+
let theta = fov.to_radians();
30+
let h = f32::tan(theta / 2.0);
31+
let viewport_height = 2.0 * h;
32+
let viewport_width = aspect_ratio * viewport_height;
33+
34+
let w = (-direction).normalize();
35+
let u = (up.cross(w)).normalize();
36+
let v = w.cross(u);
37+
38+
let origin = position;
39+
let horizontal = focus_dist * viewport_width * u;
40+
let vertical = focus_dist * viewport_height * v;
41+
let lower_left_corner = origin - horizontal / 2.0 - vertical / 2.0 - focus_dist * w;
42+
43+
let lens_radius = aperture / 2.0;
44+
45+
Self {
46+
origin,
47+
lower_left_corner,
48+
horizontal,
49+
vertical,
50+
u,
51+
v,
52+
_w: w,
53+
lens_radius,
54+
}
55+
}
56+
57+
pub fn get_ray(&self, s: f32, t: f32, rng: &mut impl Rng) -> Ray {
58+
let rd = self.lens_radius * vec3::random_in_unit_disk(rng);
59+
let offset = self.u * rd.x + self.v * rd.y;
60+
61+
Ray::new(
62+
self.origin + offset,
63+
self.lower_left_corner + s * self.horizontal + t * self.vertical - self.origin - offset,
64+
)
65+
}
66+
}

examples/raytracing/game.rs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
use std::time::{Duration, Instant};
2+
3+
use rand::rngs::SmallRng;
4+
use rand::{Rng, SeedableRng};
5+
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
6+
use softbuffer::Buffer;
7+
8+
use crate::camera::Camera;
9+
use crate::vec3::{Color, Point3, Vec3};
10+
use crate::world::World;
11+
12+
pub struct Game {
13+
world: World,
14+
up: Vec3,
15+
camera_position: Point3,
16+
pub camera_yaw: f32,
17+
pub camera_pitch: f32,
18+
/// x = right, y = up, z = forwards
19+
pub camera_velocity: Vec3,
20+
elapsed_time: Instant,
21+
}
22+
23+
const SAMPLES_PER_PIXEL: i32 = 3;
24+
const MAX_DEPTH: i32 = 5;
25+
pub const MOVEMENT_SPEED: f32 = 10.0;
26+
pub const MOUSE_SENSITIVITY: f32 = 0.005;
27+
const DURATION_BETWEEN_TICKS: Duration = Duration::from_millis(10);
28+
29+
impl Game {
30+
pub fn new() -> Self {
31+
let mut rng = SmallRng::from_os_rng();
32+
let position = Point3::new(13.0, 2.0, 3.0);
33+
let looking_at = Point3::new(0.0, 0.0, 0.0);
34+
let camera_direction = (looking_at - position).normalize();
35+
let up = Vec3::new(0.0, 1.0, 0.0);
36+
Self {
37+
world: World::random_scene(&mut rng),
38+
up,
39+
camera_position: position,
40+
camera_yaw: camera_direction.x.atan2(camera_direction.z),
41+
camera_pitch: camera_direction.y.clamp(-1.0, 1.0).asin(),
42+
camera_velocity: Vec3::new(0.0, 0.0, 0.0),
43+
elapsed_time: Instant::now(),
44+
}
45+
}
46+
47+
pub fn draw(
48+
&self,
49+
buffer: &mut Buffer<'_, impl HasDisplayHandle, impl HasWindowHandle>,
50+
scale_factor: f32,
51+
) {
52+
self.draw_scene(buffer, scale_factor);
53+
self.draw_ui(buffer, scale_factor);
54+
}
55+
56+
/// Draw the 3D scene.
57+
fn draw_scene(
58+
&self,
59+
buffer: &mut Buffer<'_, impl HasDisplayHandle, impl HasWindowHandle>,
60+
scale_factor: f32,
61+
) {
62+
// Raytracing is expensive, so we only do it once every 4x4 pixel.
63+
//
64+
// FIXME(madsmtm): Avoid the need for this once we can do hardware scaling.
65+
// https://github.com/rust-windowing/softbuffer/issues/177
66+
let scale_factor = scale_factor * 4.0;
67+
68+
let width = buffer.width().get() as f32 / scale_factor;
69+
let height = buffer.height().get() as f32 / scale_factor;
70+
71+
let dist_to_focus = 10.0;
72+
let aperture = 0.1;
73+
let cam = Camera::new(
74+
self.camera_position,
75+
self.camera_direction(),
76+
self.up,
77+
20.0,
78+
width / height,
79+
aperture,
80+
dist_to_focus,
81+
);
82+
83+
let mut pixels = vec![0; width as usize * height as usize];
84+
85+
let each_pixel = |rng: &mut SmallRng, i, pixel: &mut u32| {
86+
let y = i % (width as usize);
87+
let x = i / (width as usize);
88+
let mut pixel_color = Color::default();
89+
for _ in 0..SAMPLES_PER_PIXEL {
90+
let s = (y as f32 + rng.random::<f32>()) / (width - 1.0);
91+
let t = 1.0 - (x as f32 + rng.random::<f32>()) / (height - 1.0);
92+
let r = cam.get_ray(s, t, rng);
93+
pixel_color += r.trace(&self.world, MAX_DEPTH, rng);
94+
}
95+
*pixel = color_to_pixel(pixel_color, SAMPLES_PER_PIXEL);
96+
};
97+
98+
// Render in parallel with rayon.
99+
#[cfg(not(target_family = "wasm"))]
100+
{
101+
use rayon::prelude::*;
102+
103+
pixels
104+
.par_iter_mut()
105+
.enumerate()
106+
.for_each_init(SmallRng::from_os_rng, move |rng, (i, pixel)| {
107+
each_pixel(rng, i, pixel)
108+
});
109+
};
110+
#[cfg(target_family = "wasm")]
111+
{
112+
let mut rng = SmallRng::from_os_rng();
113+
pixels
114+
.iter_mut()
115+
.enumerate()
116+
.for_each(|(i, pixel)| each_row(&mut rng, i, pixel));
117+
}
118+
119+
// Upscale by `scale_factor`.
120+
let width = buffer.width().get() as usize;
121+
buffer.iter_mut().enumerate().for_each(|(i, pixel)| {
122+
let y = i % width;
123+
let x = i / width;
124+
let y = (y as f32 / scale_factor) as usize;
125+
let x = (x as f32 / scale_factor) as usize;
126+
if let Some(x) = pixels.get(x * (width as f32 / scale_factor) as usize + y) {
127+
*pixel = *x;
128+
}
129+
});
130+
}
131+
132+
/// Draw a simple example UI on top of the scene.
133+
fn draw_ui(
134+
&self,
135+
buffer: &mut Buffer<'_, impl HasDisplayHandle, impl HasWindowHandle>,
136+
scale_factor: f32,
137+
) {
138+
struct Rect {
139+
left: f32,
140+
right: f32,
141+
top: f32,
142+
bottom: f32,
143+
color: u32,
144+
}
145+
146+
let width = buffer.width().get() as f32 / scale_factor;
147+
let height = buffer.height().get() as f32 / scale_factor;
148+
let rects = &[
149+
Rect {
150+
left: 10.0,
151+
right: width - 10.0,
152+
top: height - 90.0,
153+
bottom: height - 10.0,
154+
color: 0x00eeaaaa,
155+
},
156+
Rect {
157+
left: 30.0,
158+
right: 70.0,
159+
top: height - 70.0,
160+
bottom: height - 30.0,
161+
color: 0x00aaaaee,
162+
},
163+
];
164+
165+
let width = buffer.width().get();
166+
for (y, row) in buffer.chunks_exact_mut(width as usize).enumerate() {
167+
for rect in rects {
168+
let rect_vertical =
169+
(rect.top * scale_factor) as usize..(rect.bottom * scale_factor) as usize;
170+
let rect_horizontal =
171+
(rect.left * scale_factor) as usize..(rect.right * scale_factor) as usize;
172+
if rect_vertical.contains(&y) {
173+
if let Some(row) = row.get_mut(rect_horizontal) {
174+
row.fill(rect.color);
175+
}
176+
}
177+
}
178+
}
179+
}
180+
181+
fn tick(&mut self) {
182+
let forward = self.camera_direction().with_y(0.0);
183+
let right = forward.cross(self.up).normalize();
184+
let up = right.cross(forward);
185+
let movement = forward * self.camera_velocity.z
186+
+ up * self.camera_velocity.y
187+
+ right * self.camera_velocity.x;
188+
self.camera_position += movement * DURATION_BETWEEN_TICKS.as_secs_f32();
189+
}
190+
191+
pub fn update(&mut self) {
192+
// Update game state.
193+
let now = Instant::now();
194+
while let Some(_remainder) = now
195+
.duration_since(self.elapsed_time)
196+
.checked_sub(DURATION_BETWEEN_TICKS)
197+
{
198+
self.elapsed_time += DURATION_BETWEEN_TICKS;
199+
self.tick();
200+
}
201+
}
202+
203+
fn camera_direction(&self) -> Vec3 {
204+
Vec3::new(
205+
self.camera_pitch.cos() * self.camera_yaw.sin(),
206+
self.camera_pitch.sin(),
207+
self.camera_pitch.cos() * self.camera_yaw.cos(),
208+
)
209+
}
210+
}
211+
212+
fn color_to_pixel(pixel_color: Color, samples_per_pixel: i32) -> u32 {
213+
let mut r = pixel_color.x;
214+
let mut g = pixel_color.y;
215+
let mut b = pixel_color.z;
216+
217+
let scale = 1.0 / samples_per_pixel as f32;
218+
r = f32::sqrt(scale * r);
219+
g = f32::sqrt(scale * g);
220+
b = f32::sqrt(scale * b);
221+
222+
(256.0 * b.clamp(0.0, 0.999)) as u32
223+
| (((256.0 * g.clamp(0.0, 0.999)) as u32) << 8)
224+
| (((256.0 * r.clamp(0.0, 0.999)) as u32) << 16)
225+
}

0 commit comments

Comments
 (0)