0%

RayTracing.vol6

Github Repo


如果坐标系内有多个球体会怎么样?无论是一个球或是多个球,将射线可能射到的物体化为一个抽象类 Hittable,但是由于 Rust 没有继承的概念,所以将其定义为 trait.Hittable trait 有一个 hit 功能用来吸收射线。同时给 t 设置一个区间,当 $t_{min} < t < t_{max}$才算相交。

[hittable.rs] 定义 Hittable trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
use crate::rayh::Ray;
use ray::Point3;
use ray::Vec3;

#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Hit_record {
pub p: Point3,
pub normal: Vec3,
pub t: f32,
}

pub trait Hittable {
fn hit(&self, r: &Ray, t_min: f32, t_max: f32, rec: &mut Hit_record) -> bool;
}

然后是定义 sphere 类

[hittable.rs] 定义 Hittable trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
use crate::rayh::Ray;
use ray::Point3;
use ray::Vec3;

#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Hit_record {
pub p: Point3,
pub normal: Vec3,
pub t: f32,
}

pub trait Hittable {
fn hit(&self, r: &Ray, t_min: f32, t_max: f32, rec: &mut Hit_record) -> bool;
}

对射线同样要进行分情况讨论,定义法线方向永远朝外,若是在球外与球相交的射线,法线方向与射线方向相反,射线若是在球内相交,法线方向与射线方向相同;又或者,可以定义法线永远指向射线的反方向,射线在球内,法线方向向内,射线在球外,法线方向向外。
如图所示:

如果我们定义法线方向永远朝外,则对射线着色时需确定射线在哪一侧,这可以通过比较射线与法线所得,如果射线与法线方向相同,则射线在物体内部,若相反则在物体外部。这可以根据两向量的点乘看出,点乘为正则方向相同。

1
2
3
4
5
6
7
if dot(ray_direction, outward_normal) > 0.0{
// 射线在物体内部
...
} else{
// 射线在物体外部
...
}

相对的,如果我们定义法线方向与射线方向永远相反,则无法通过点乘确定射线位于表面的哪一侧,因此,我们需要储存这个信息。

1
2
3
4
5
6
7
8
9
10
front_face: bool
if dot(ray_direction, outward_normal) > 0.0{
// 射线在物体内部
normal = -outward_normal;
front_face = false;
} else{
// 射线在物体外部
normal = outward_normal;
front_face = true;
}

定义法线方向永远朝外或是法线方向永远与射线方向相反,取决于是要在几何相交时还是在着色时确定表面的侧面。演示中含有的材料类型比集合类型多,所以将重点放在几何相交上。
给 Hit_record 加上 front_face 以储存方向信息,给 Hittable trait 增加 set_face_normal() 方法

% [hittable.rs]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Hit_record {
pub p: Point3,
pub normal: Vec3,
pub t: f32,
pub front_face: bool,
}

...

impl Hit_record {
pub fn set_face_normal(&mut self, r: &Ray, outward_normal: Vec3) {
self.front_face = dot(r.direction(), outward_normal) < 0.0;
self.normal = if self.front_face {
outward_normal
} else {
-outward_normal
};
}
}

再将 set_face_normal() 用于 Sphere.hit() 中

% [sphere.rs]
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
...
impl Hittable for Sphere {
fn hit(&self, r: &Ray, t_min: f32, t_max: f32, rec: &mut Hit_record) -> bool {
let oc: Vec3 = r.origin() - self.center;
let a = r.direction().length_squared();
let half_b = dot(oc, r.direction());
let c = oc.length_squared() - self.radius * self.radius;
let discriminant = half_b * half_b - a * c;
if discriminant < 0.0 {
let root = discriminant.sqrt();

let temp = (-half_b - root) / a;
if temp < t_max && temp > t_min {
rec.t = temp;
rec.p = r.at(rec.t);
let outward_normal = (rec.p - self.center) / self.radius;
rec.set_face_normal(&r, outward_normal);
return true;
}

let temp = (-half_b - root) / a;
if temp < t_max && temp > t_min {
rec.t = temp;
rec.p = r.at(rec.t);
let outward_normal = (rec.p - self.center) / self.radius;
rec.set_face_normal(&r, outward_normal);
return true;
}
}
false
}
}

定义一个类来储存一堆有 Hittable trait 的泛型数据类型,同时也要为其定义 Hittable trait

% [hittable_list.rs]
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
use crate::hittable::*;
use crate::rayh::*;
use ray::*;

pub struct Hittable_list<T> {
pub objects: Vec<T>,
}

impl<T: Hittable> Hittable_list<T> {
pub fn new(object: T) -> Hittable_list<T> {
Hittable_list {
objects: vec![object],
}
}
pub fn clear(&mut self) {
(*self).objects.truncate(0);
}
pub fn add(&mut self, object: T) {
(*self).objects.push(object);
}
}
impl<T: Hittable> Hittable for Hittable_list<T> {
fn hit(&self, r: &Ray, t_min: f32, t_max: f32, rec: &mut Hit_record) -> bool {
let mut temp_rec = Hit_record {
p: Vec3(0.0, 0.0, 0.0),
normal: Vec3(0.0, 0.0, 0.0),
t: 0.0,
front_face: true,
};
let mut hit_anything = false;
let mut closest_so_far = t_max;
let mut ar_temp_rec = temp_rec;

for object in &self.objects {
if object.hit(r, t_min, closest_so_far, &mut temp_rec) {
hit_anything = true;
closest_so_far = temp_rec.t;
ar_temp_rec = temp_rec;
}
}
*rec = ar_temp_rec;
hit_anything
}
}

定义常用数学常量

% [rtweekend.rs]
1
2
3
4
5
6
pub const infinity: f32 = f32::INFINITY;
pub const PI: f32 = 3.14159274f32;

pub fn degrees_to_radians(degrees: f32) -> f32 {
degrees * PI / 180.0
}

更改 main.rs

% [main.rs]
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
mod color;
mod hittable;
mod hittable_list;
mod rayh;
mod rtweekend;
mod sphere;
use color::*;
use hittable::*;
use hittable_list::*;
use ray::*;
use rayh::*;
use rtweekend::*;
use sphere::*;

const IMAGE_WIDTH: u16 = 400;
const ASPECT_RATIO: f32 = 16.0 / 9.0;

fn ray_color<T: Hittable>(r: &Ray, world: &T) -> Color {
let mut rec = Hit_record {
p: Vec3(0.0, 0.0, 0.0),
normal: Vec3(0.0, 0.0, 0.0),
t: 0.0,
front_face: true,
};
if world.hit(r, 0.0, infinity, &mut rec) {
return 0.5 * (rec.normal + Vec3(1.0, 1.0, 1.0));
}
let unit_direction = unit_vector(r.direction());
let t = 0.5 * (unit_direction.y() + 1.0);
(1.0 - t) * Vec3(1.0, 1.0, 1.0) + t * Vec3(0.5, 0.7, 1.0)
}
fn main() {
let IMAGE_HEIGHT = unsafe { (IMAGE_WIDTH as f32 / ASPECT_RATIO).to_int_unchecked::<u16>() };

let mut world = Hittable_list {
objects: Vec::new(),
};
world.add(Sphere {
center: Vec3(0.0, 0.0, -1.0),
radius: 0.5,
});
world.add(Sphere {
center: Vec3(0.0, -100.5, -1.0),
radius: 100.0,
});

println! {"P3\n{} {}\n255",IMAGE_WIDTH,IMAGE_HEIGHT};
let viewport_height = 2.0;
let viewport_width: f32 = ASPECT_RATIO * viewport_height;
let focal_length = 1.0;

let origin: Point3 = Vec3(0.0, 0.0, 0.0);
let horizontal = Vec3(viewport_width, 0.0, 0.0);
let vertical = Vec3(0.0, viewport_height, 0.0);
let lower_left_corner =
origin - horizontal / 2 as f32 - vertical / 2 as f32 - Vec3(0.0, 0.0, focal_length);
for j in (0..IMAGE_HEIGHT).rev() {
eprintln!("\rScanlines remaining:{}", j);
for i in 0..IMAGE_WIDTH {
let u = i as f32 / (IMAGE_WIDTH - 1) as f32;
let v = j as f32 / (IMAGE_HEIGHT - 1) as f32;
let r = Ray {
orig: origin,
dir: lower_left_corner + u * horizontal + v * vertical - origin,
};
let pixel_color = ray_color(&r, &world);
print!("{}", write_color(pixel_color));
}
}
eprintln!("\nDone");
}

最后得出的图片如下图所示