-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcamera.rs
More file actions
executable file
·207 lines (174 loc) · 7.26 KB
/
camera.rs
File metadata and controls
executable file
·207 lines (174 loc) · 7.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
use super::geometry::Point3;
use super::interval::Interval;
use super::raytracer::{HitRecord, Hittable, Ray};
use super::vec;
use super::vec::vec3;
use rand::Rng;
use std::io::Write;
pub type Color = vec3;
// impl Color {
// pub const BLACK: Color = Color { e: [0.0, 0.0, 0.0] };
// }
#[allow(dead_code)]
pub struct Camera {
aspect_ratio: f64,
image_width: i32,
image_height: i32,
pos: Point3,
pixel00_loc: Point3,
pixel_delta_u: vec3,
pixel_delta_v: vec3,
samples_per_pixel: i32,
pixel_samples_scale: f64,
max_depth: i32,
}
impl Camera {
pub fn init(aspect_ratio: f64, image_width: i32) -> Self {
let mut image_height = (image_width as f64 / aspect_ratio) as i32;
if image_height < 1 {
image_height = 1;
}
// Camera information
// Viewport is the plane where we do all the calculations.
// It is different from image information, which is the actual pixel output
let viewport_height = 2.0;
// let viewport_width = viewport_height * aspect_ratio;
let viewport_width = viewport_height * (image_width as f64 / image_height as f64);
let camera_pos = Point3::zero();
let focal_length = 1.0;
// the viewport v vector is negative because we want it to increase from 0,0,
// the upper left position
let viewport_u = vec3::new(viewport_width, 0.0, 0.0);
let viewport_v = vec3::new(0.0, -viewport_height, 0.0);
// the pixel delta is the distance between pixels in real number,
// calculated starting from the viewport and the dimension of the image
let pixel_delta_u = viewport_u / image_width as f64;
let pixel_delta_v = viewport_v / image_height as f64;
// println!("{} {}", pixel_delta_u, pixel_delta_v);
// let's calculate the position of the upper left pixel with respect to the
// camera position.
// focal length is the distance of the viewport from the camera center
// we subtract the
let viewport_upper_left: Point3 =
camera_pos - vec3::new(0.0, 0.0, focal_length) - viewport_u / 2.0 - viewport_v / 2.0;
// questo è il punto esatto in cui comincia il pixel00, l'origine del viewport
let pixel00_loc: Point3 = (viewport_upper_left) + (pixel_delta_u + pixel_delta_v) * 0.5;
let samples_per_pixel = 100;
let pixel_samples_scale = 1.0 / samples_per_pixel as f64;
let max_depth = 3;
Camera {
aspect_ratio,
image_width,
image_height,
pos: camera_pos,
pixel00_loc,
pixel_delta_u,
pixel_delta_v,
samples_per_pixel,
pixel_samples_scale,
max_depth,
}
}
pub fn render(&self, world: &dyn Hittable) -> std::io::Result<()> {
let mut file = std::fs::File::create("output.ppm")?;
file.write_all(
format!("P3\n{} {}\n255\n", self.image_width, self.image_height).as_bytes(),
)?;
for i in 0..self.image_height {
for j in 0..self.image_width {
let mut pixel_color = Color::new(0.0, 0.0, 0.0);
for _ in 0..self.samples_per_pixel {
let ray = self.get_ray(j, i);
pixel_color += self.ray_color(&ray, world, self.max_depth);
}
self.write_color(&file, &(pixel_color * self.pixel_samples_scale))?;
}
}
Ok(())
}
/// This function writes a color to a buffer (file or stdout).
fn write_color<W: Write>(&self, mut writer: W, pixel_color: &Color) -> std::io::Result<()> {
// In Rust, you can create a function that writes to any stream
// (like stdout, a file, or a network socket) by accepting a parameter
// that implements the std::io::Write trait. This way, your function
// can be generic and work with various types of output streams
// without needing to specify each type individually.
let r: f64 = self.linear_to_gamma(pixel_color.x());
let g: f64 = self.linear_to_gamma(pixel_color.y());
let b: f64 = self.linear_to_gamma(pixel_color.z());
let intensity = Interval::new(0.000, 0.999);
let rbyte = (intensity.clamp(r) * 256.0) as u8;
let gbyte = (intensity.clamp(g) * 256.0) as u8;
let bbyte = (intensity.clamp(b) * 256.0) as u8;
// let rbyte: u8 = (255.999 * r) as u8;
// let gbyte: u8 = (255.999 * g) as u8;
// let bbyte: u8 = (255.999 * b) as u8;
let buf = [
rbyte.to_string().as_bytes(),
b" ",
gbyte.to_string().as_bytes(),
b" ",
bbyte.to_string().as_bytes(),
b"\n",
]
.concat();
writer.write_all(&buf)?;
writer.flush()?;
Ok(())
}
/// This function brings color from linear space to gamma space
#[inline(always)]
fn linear_to_gamma(&self, linear_comp: f64) -> f64 {
if linear_comp > 0.0 {
return linear_comp.sqrt();
}
0.0
}
/// Given a ray and list of objects that can be hit by it, this function returns the pixel color
fn ray_color(&self, ray: &Ray, world: &dyn Hittable, depth: i32) -> Color {
let mut rec = HitRecord::empty();
if depth <= 0 {
return Color::new(0.0, 0.0, 0.0);
}
if world.hit(ray, &Interval::new(0.001, f64::INFINITY), &mut rec) {
// return (rec.normal + Color::new(1.0, 1.0, 1.0)) * 0.5;
// let rand_dir = vec::vec3::random_on_hemisphere(&rec.normal);
// let rand_dir = rec.normal + vec3::random_unit_vector();
let mut scattered = Ray::empty();
let mut attenuation = Color::zero();
let ray_color: Color = match rec.mat {
Some(ref mat) => {
if mat.scatter(ray, &rec, &mut attenuation, &mut scattered) {
return attenuation * self.ray_color(&scattered, world, depth - 1);
}
attenuation
}
None => {
attenuation // it's already set to zero
}
};
return ray_color;
// return self.ray_color(&Ray::new(rec.p, rand_dir), world, depth-1) * 0.7;
}
let unit_direction: vec3 = vec::unit_vector(ray.direction());
let a = 0.5 * (unit_direction.y() + 1.0);
// Sky color
Color::new(1.0, 1.0, 1.0) * (1.0 - a) + Color::new(0.5, 0.7, 1.0) * a
}
/// Given a pixel position, this function returns a Ray that originates from the camera and directed from the pixel to the camera eye
fn get_ray(&self, u: i32, v: i32) -> Ray {
let offset = self.random_square();
let pixel_sample = self.pixel00_loc
+ (self.pixel_delta_u * (u as f64 + offset.x()))
+ (self.pixel_delta_v * (v as f64 + offset.y()));
let ray_direction = pixel_sample - self.pos;
Ray::new(self.pos, ray_direction)
}
/// This function returns a random position
fn random_square(&self) -> vec3 {
let mut rng = rand::thread_rng();
let x: f64 = rng.gen::<f64>() - 0.5;
let y: f64 = rng.gen::<f64>() - 0.5;
vec3::new(x, y, 0.0)
}
}