pbrt/src/core/camera.rs
2026-05-20 20:14:58 +01:00

418 lines
17 KiB
Rust

use crate::core::image::{HostImage, ImageIO, ImageMetadata};
use crate::globals::get_options;
use crate::utils::read_float_file;
use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload};
use anyhow::{anyhow, Result};
use shared::cameras::*;
use shared::core::camera::{Camera, CameraBase, CameraTrait, CameraTransform};
use shared::core::color::SRGB;
use shared::core::film::Film;
use shared::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f, Vector3f};
use shared::core::image::PixelFormat;
use shared::core::medium::Medium;
use shared::utils::math::square;
use shared::{Float, Ptr, PI};
use std::path::Path;
use std::sync::Arc;
#[repr(C)]
#[derive(Clone, Debug)]
pub struct CameraBaseParameters {
pub camera_transform: CameraTransform,
pub shutter_open: Float,
pub shutter_close: Float,
pub film: Arc<Film>,
pub medium: Option<Arc<Medium>>,
}
impl CameraBaseParameters {
pub fn new(
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Option<Arc<Medium>>,
params: &ParameterDictionary,
loc: &FileLoc,
) -> Result<Self> {
let mut shutter_open = params.get_one_float("shutteropen", 0.)?;
let mut shutter_close = params.get_one_float("shutterclose", 1.)?;
if shutter_close < shutter_open {
eprint!(
"{}: Shutter close time {} < shutter open {}. Swapping",
loc, shutter_close, shutter_open
);
std::mem::swap(&mut shutter_open, &mut shutter_close);
}
Ok(CameraBaseParameters {
camera_transform: camera_transform.clone(),
shutter_open,
shutter_close,
film,
medium,
})
}
}
pub trait CameraBaseFactory {
fn create(p: CameraBaseParameters) -> CameraBase {
CameraBase {
camera_transform: p.camera_transform.clone(),
shutter_open: p.shutter_open,
shutter_close: p.shutter_close,
film: Ptr::from(p.film.clone().as_ref()),
medium: match p.medium {
Some(ref m) => Ptr::from(m.as_ref()),
None => Ptr::null(),
},
min_pos_differential_x: Vector3f::default(),
min_pos_differential_y: Vector3f::default(),
min_dir_differential_x: Vector3f::default(),
min_dir_differential_y: Vector3f::default(),
}
}
}
impl CameraBaseFactory for CameraBase {}
pub trait InitMetadata {
fn init_metadata(&self, metadata: &mut ImageMetadata);
}
impl InitMetadata for CameraBase {
fn init_metadata(&self, metadata: &mut ImageMetadata) {
let camera_from_world = self.camera_transform.camera_from_world(self.shutter_open);
metadata.camera_from_world = Some(camera_from_world.get_matrix());
}
}
impl InitMetadata for Camera {
fn init_metadata(&self, metadata: &mut ImageMetadata) {
self.base().init_metadata(metadata);
}
}
pub trait CameraFactory {
fn create(
name: &str,
params: &ParameterDictionary,
camera_transform: &CameraTransform,
medium: Option<Arc<Medium>>,
film: Arc<Film>,
loc: &FileLoc,
arena: &Arena,
) -> Result<Self>
where
Self: Sized;
}
impl CameraFactory for Camera {
fn create(
name: &str,
params: &ParameterDictionary,
camera_transform: &CameraTransform,
medium: Option<Arc<Medium>>,
film: Arc<Film>,
loc: &FileLoc,
arena: &Arena,
) -> Result<Self>
where
Self: Sized,
{
match name {
"perspective" => {
let full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
let base = CameraBase::create(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.)?;
let focal_distance = params.get_one_float("focaldistance", 1e6)?;
let frame = params.get_one_float(
"frameaspectratio",
full_res.x() as Float / full_res.y() as Float,
)?;
let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
} else {
Bounds2f::from_points(
Point2f::new(-1., -1. / frame),
Point2f::new(1., 1. / frame),
)
};
let sw = params.get_float_array("screenwindow")?;
if !sw.is_empty() {
if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode");
} else {
if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else {
return Err(anyhow!(
"{}: screenwindow param must have four values",
loc
));
}
}
}
let fov = params.get_one_float("fov", 90.)?;
let camera = PerspectiveCamera::new(base, fov, screen, lens_radius, focal_distance);
arena.alloc(camera);
Ok(Camera::Perspective(camera))
}
"orthographic" => {
let full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
let base = CameraBase::create(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.)?;
let focal_distance = params.get_one_float("focaldistance", 1e6)?;
let frame = params.get_one_float(
"frameaspectratio",
full_res.x() as Float / full_res.y() as Float,
)?;
let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
} else {
Bounds2f::from_points(
Point2f::new(-1., -1. / frame),
Point2f::new(1., 1. / frame),
)
};
let sw = params.get_float_array("screenwindow")?;
if !sw.is_empty() {
if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode");
} else {
if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else {
return Err(anyhow!(
"{}: screenwindow param must have four values",
loc
));
}
}
}
let camera = OrthographicCamera::new(base, screen, lens_radius, focal_distance);
arena.alloc(camera);
Ok(Camera::Orthographic(camera))
}
"realistic" => {
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
let base = CameraBase::create(camera_params);
let aperture_diameter = params.get_one_float("aperturediameter", 1.)?;
let focal_distance = params.get_one_float("focaldistance", 10.)?;
let lens_file = params.get_one_string("lensfile", "")?;
if lens_file.is_empty() {
return Err(anyhow!("{}: No lens file supplied", loc));
}
let lens_params = read_float_file(lens_file.as_str()).map_err(|e| anyhow!(e))?;
if lens_params.len() % 4 != 0 {
return Err(anyhow!(
"{}: excess values in lens specification file; must be multiple-of-four values, read {}",
loc,
lens_params.len()
));
}
let builtin_res = 256;
let rasterize = |vert: &[Point2f]| -> HostImage {
let mut image = HostImage::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB.into(),
);
let res = image.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
// Map pixel to [-1, 1] range
let p = Point2f::new(
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
);
let mut winding_number = 0;
// Winding number test against edges
for i in 0..vert.len() {
let i1 = (i + 1) % vert.len();
let v_i = vert[i];
let v_i1 = vert[i1];
let e = (p.x() - v_i.x()) * (v_i1.y() - v_i.y())
- (p.y() - v_i.y()) * (v_i1.x() - v_i.x());
if v_i.y() <= p.y() {
if v_i1.y() > p.y() && e > 0.0 {
winding_number += 1;
}
} else if v_i1.y() <= p.y() && e < 0.0 {
winding_number -= 1;
}
}
image.set_channel(
Point2i::new(x, y),
0,
if winding_number == 0 { 0.0 } else { 1.0 },
);
}
}
image
};
let aperture_name = params.get_one_string("aperture", "")?;
let mut aperture_image: Option<HostImage> = None;
if !aperture_name.is_empty() {
match aperture_name.as_str() {
"gaussian" => {
let mut img = HostImage::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB.into(),
);
let res = img.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let uv = Point2f::new(
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
);
let r2 = square(uv.x()) + square(uv.y());
let sigma2 = 1.0;
let v = ((-r2 / sigma2).exp() - (-1.0 / sigma2).exp()).max(0.0);
img.set_channel(Point2i::new(x, y), 0, v);
}
}
aperture_image = Some(img);
}
"square" => {
let mut img = HostImage::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB.into(),
);
let low = (0.25 * builtin_res as Float) as i32;
let high = (0.75 * builtin_res as Float) as i32;
for y in low..high {
for x in low..high {
img.inner.set_channel(Point2i::new(x, y), 0, 4.0);
}
}
aperture_image = Some(img);
}
"pentagon" => {
let c1 = (5.0f32.sqrt() - 1.0) / 4.0;
let c2 = (5.0f32.sqrt() + 1.0) / 4.0;
let s1 = (10.0 + 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
let s2 = (10.0 - 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
let mut vert = [
Point2f::new(0.0, 1.0),
Point2f::new(s1, c1),
Point2f::new(s2, -c2),
Point2f::new(-s2, -c2),
Point2f::new(-s1, c1),
];
for v in vert.iter_mut() {
*v = Point2f::from(Vector2f::from(*v) * 0.8);
}
aperture_image = Some(rasterize(&vert));
}
"star" => {
let mut vert = Vec::with_capacity(10);
for i in 0..10 {
let r = if i % 2 == 1 {
1.0
} else {
(72.0f32.to_radians().cos()) / (36.0f32.to_radians().cos())
};
let angle = PI * i as Float / 5.0;
vert.push(Point2f::new(r * angle.cos(), r * angle.sin()));
}
vert.reverse();
aperture_image = Some(rasterize(&vert));
}
_ => {
if let Ok(im) = HostImage::read(Path::new(&aperture_name), None) {
if im.image.n_channels() > 1 {
let mut mono = HostImage::new(
PixelFormat::F32,
im.image.resolution(),
&["Y"],
SRGB.into(),
);
let res = mono.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let avg =
im.image.get_channels(Point2i::new(x, y)).average();
mono.inner.set_channel(Point2i::new(x, y), 0, avg);
}
}
aperture_image = Some(mono);
} else {
aperture_image = Some(im.image);
}
}
}
}
}
let camera = RealisticCamera::new(
base,
&lens_params,
focal_distance,
aperture_diameter,
arena.upload(aperture_image)
);
arena.alloc(camera);
Ok(Camera::Realistic(camera))
}
"spherical" => {
let _full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
let base = CameraBase::create(camera_params);
let m = params.get_one_string("mapping", "equalarea")?;
let mapping = match m.as_str() {
"equalarea" => Mapping::EqualArea,
"equirectangular" => Mapping::EquiRectangular,
_ => {
return Err(anyhow!(
"{}: unknown mapping for spherical camera at {}",
m,
loc
));
}
};
let camera = SphericalCamera { mapping, base };
arena.alloc(camera);
Ok(Camera::Spherical(camera))
}
_ => Err(anyhow!("Camera type '{}' unknown at {}", name, loc)),
}
}
}