散焦模糊,通俗来讲就是在聚焦区域外的东西,都是模糊的。通常这种效果被称为 Depth Of Field(DOF),也就是景深。最终效果如下
对于真实的相机来说,实现这个效果的过程,是很复杂的。但是对于在光线追踪中实现景深,就简单了许多,一根光线的目标点不变,我们只需要在虚拟的光圈内,随机一个新的起点,即可。
首先,在 Vec3 的实现中,添加一个新的函数,用于生成在单位圆上的上下左右偏移。
// src/vec3.rs
pub fn random_in_unit_disk() -> Vec3 {
let mut rng = rand::thread_rng();
loop {
let p = Vec3::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0), 0.0);
if p.length_squared() >= 1.0 {
continue;
}
return p;
}
}
然后修改 Camera 结构体,将 u、v、w 以一个新的变量,透镜半径,lens_radius 加入到结构体中。在 Camera 的创建函数 new
的参数中,需要传递 aperture
也就是我们定义的光圈大小,focus_dist
是聚焦平面到相机的距离,其他的不变,与上一节一样。
通过 focus_dist
可以计算出聚焦平面的横向和纵向,以及左下角的位置。透镜的半径等于光圈大小的一半。
而在 get_ray
函数中,将射线的起点和方向,都加了一个随机的偏移。
// src/camera.rs
use crate::ray::Ray;
use crate::vec3::Vec3;
pub struct Camera {
origin: Vec3,
lower_left_corner: Vec3,
horizontal: Vec3,
vertical: Vec3,
u: Vec3,
v: Vec3,
w: Vec3,
lens_radius: f64,
}
impl Camera {
pub fn new(
lookfrom: Vec3,
lookat: Vec3,
vup: Vec3,
vfov: f64,
aspect_ratio: f64,
aperture: f64,
focus_dist: f64,
) -> Self {
let theta = vfov * std::f64::consts::PI / 180.0;
let h = (theta / 2.0).tan();
let viewport_height = 2.0 * h;
let viewport_width = aspect_ratio * viewport_height;
let w = Vec3::unit_vector(lookfrom - lookat);
let u = Vec3::unit_vector(Vec3::cross(vup, w));
let v = Vec3::cross(w, u);
let origin = lookfrom;
let horizontal = focus_dist * viewport_width * u;
let vertical = focus_dist * viewport_height * v;
let lower_left_corner = origin - horizontal / 2.0 - vertical / 2.0 - focus_dist * w;
let lens_radius = aperture / 2.0;
Self {
origin,
lower_left_corner,
horizontal,
vertical,
u,
v,
w,
lens_radius,
}
}
pub fn get_ray(&self, s: f64, t: f64) -> Ray {
let rd = self.lens_radius * Vec3::random_in_unit_disk();
let offset = self.u * rd.x + self.v * rd.y;
let direction = self.lower_left_corner + s * self.horizontal + t * self.vertical
- (self.origin + offset);
Ray::new(self.origin + offset, direction)
}
}
cargo run --release > depth_of_field.ppm
将生成文章开头的效果图。
完整代码:https://github.com/moeif/rtiow-rs/tree/13