14 相机
目标完善相机类,支持视野(fov),位置,朝向和散焦模糊。第八版渲染器结构如下:光源:无。场景:地面,一个漫反射材质的球,一个金属球,一个中空玻
目标
完善相机类,支持视野(fov),位置,朝向和散焦模糊。第八版渲染器结构如下:
- 光源:无。
- 场景:地面,一个漫反射材质的球,一个金属球,一个中空玻璃球。
- 摄像机:参数可调。
- 光线:每像素16个。
- 渲染算法:光线多次弹射,颜色依据物体材质计算。
- 输出:400 * 225像素的无锯齿图像。
数学模型
视野
- 视野指一个镜头能覆盖的范围,用角度来表示。
- 根据视野和焦距,可以求出视窗大小。
位置和朝向
- 相机的位置和朝向决定了如何将相机坐标变换到世界坐标系中。
- 相机坐标系的原点映射到世界坐标系的相机位置(lookfrom)。
- 相加坐标系的z轴映射到-w,也就是lookfrom - lookat
- x轴映射到u
- y轴映射到v
- 一般指定一个相机朝上的大概方向vup,然后计算与w和vup所在平面垂直的朝右方向u,再利用w和u的叉积求出v。
散焦模糊
- 真实相机不是完美的小孔,只能清晰成像景深范围内的物体。
- 渲染器的镜头是完美的小孔,需要模拟镜头模糊效果。
计算模型
- 添加新参数到Camera类。
- 修改坐标变换
- 光线起点做随机偏移,模拟焦散。
代码
camera.rs
- 根据fov和焦距求出视窗尺寸
- 求出uvw向量
- 将相机坐标系映射到世界坐标系下。
- 在光圈范围内随机一个发射点,从这里发射光线到成像平面
use crate::math::random;nuse crate::math::ray;nuse crate::math::vector;npub struct Camera {n origin: vector::Point3,n lower_left_corner_world: vector::Point3,n horizontal: vector::Point3,n vertical: vector::Point3,n u : vector::Point3,n v : vector::Point3,n lens_radius: f64,n}nimpl Camera {n pub fn new(n look_from: vector::Point3,n look_at: vector::Point3,n vup: vector::Dir3,n vfov: f64,n aspect_ratio: f64,n aperture: f64,n focus_dist: f64,n ) -> Camera {n // 1n let theta = vfov * std::f64::consts::PI / 180.0;n let h = (theta * 0.5).tan() * focus_dist;n let viewport_height = 2.0 * h;n let viewport_width = viewport_height * aspect_ratio;n // 2 n let mut w = look_from.clone() - look_at;n w.normalize();n let mut u = vector::Vec3::cross(&vup, &w);n u.normalize();n let v = vector::Vec3::cross(&w, &u);n // 3n let horizontal = u.clone() * viewport_width;n let vertical = v.clone() * viewport_height;n let origin = look_from;n let lower_left_corner_view =n -horizontal.clone() / 2.0 - vertical.clone() / 2.0 - w * focus_dist;n let lower_left_corner_world = origin.clone() + lower_left_corner_view;n Camera {n origin: origin,n lower_left_corner_world: lower_left_corner_world,n horizontal: horizontal,n vertical: vertical,n u : u,n v : v,n lens_radius: aperture / 2.0,n }n }n pub fn get_ray(&self, s: f64, t: f64) -> ray::Ray {n // 4n let rd = Camera::random_in_unit_disk() * self.lens_radius;n let offset = self.u.clone() * rd.x() + self.v.clone() * rd.y();n let p = self.lower_left_corner_world.clone()n + self.horizontal.clone() * sn + self.vertical.clone() * t;n let dir = p - &self.origin - &offset;n ray::Ray::new(self.origin.clone() + offset, dir)n }n fn random_in_unit_disk() -> vector::Vec3 {n loop {n let p = vector::Vec3::new(n random::generate_range(-1.0, 1.0),n random::generate_range(-1.0, 1.0),n 0.0,n );n if p.length_squared() < 1.0 {n return p;n }n }n }n
main.rs
设置参数
let look_from = vector::Point3::new(3.0, 3.0, 2.0);n let look_at = vector::Point3::new(0.0, 0.0, -1.0);n let focus_dist = (look_from.clone() - &look_at).length();n let cam = camera::Camera::new(look_from, look_at, vector::Dir3::new(0.0, 1.0, 0.0), 20.0, aspect_ratio, 2.0, focus_dist);n
完整代码
https://github.com/thomation/rePleuX/releases/tag/v0.0.8
运行结果
挑战
- 修改main.rs中的camera参数,观察运行结果的变化
- 修改第4章的矩阵,加入相机的位置和朝向。
延伸阅读
《Ray Tracing in One Weekend》11
《Ray Tracing in One Weekend》 12