WARNING
Content Under Development

See release page for latest official PDF version.

**Chapter 10: Positionable camera** Cameras, like dielectrics, are a pain to debug. So I always develop mine incrementally. First, let’s allow an adjustable field of view (_fov_). This is the angle you see through the portal. Since our image is not square, the fov is different horizontally and vertically. I always use vertical fov. I also usually specify it in degrees and change to radians inside a constructor -- a matter of personal taste. I first keep the rays coming from the origin and heading to the $z = -1$ plane. We could make it the $z = -2$ plane, or whatever, as long as we made $h$ a ratio to that distance. Here is our setup: ![Figure 10-1](../assets/fig10-1.jpg) This implies $h = tan(\theta/2)$. Our camera now becomes: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ #ifndef CAMERAH #define CAMERAH #include "ray.h" class camera { public: camera(float vfov, float aspect) { // vfov is top to bottom in degrees float theta = vfov*M_PI/180; float half_height = tan(theta/2); float half_width = aspect * half_height; lower_left_corner = vec3(-half_width, -half_height, -1.0); horizontal = vec3(2*half_width, 0.0, 0.0); vertical = vec3(0.0, 2*half_height, 0.0); origin = vec3(0.0, 0.0, 0.0); } ray get_ray(float u, float v) { return ray(origin, lower_left_corner + u*horizontal + v*vertical - origin); } vec3 origin; vec3 lower_left_corner; vec3 horizontal; vec3 vertical; }; #endif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When calling it with camera `cam(90, float(nx)/float(ny))` and these spheres: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ float R = cos(M_PI/4); list[0] = new sphere(vec3(-R,0,-1), R, new lambertian(vec3(0, 0, 1))); list[1] = new sphere(vec3( R,0,-1), R, new lambertian(vec3(1, 0, 0))); hitable *world = new hitable_list(list,2); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gives: ![Image 10-1](../assets/img10-1.jpg) To get an arbitrary viewpoint, let’s first name the points we care about. We’ll call the position where we place the camera _lookfrom_, and the point we look at _lookat_. (Later, if you want, you could define a direction to look in instead of a point to look at.) We also need a way to specify the roll, or sideways tilt, of the camera; the rotation around the lookat-lookfrom axis. Another way to think about it is even if you keep `lookfrom` and `lookat` constant, you can still rotate your head around your nose. What we need is a way to specify an up vector for the camera. Notice we already we already have a plane that the up vector should be in, the plane orthogonal to the view direction. ![Figure 10-2](../assets/fig10-2.jpg) We can actually use any up vector we want, and simply project it onto this plane to get an up vector for the camera. I use the common convention of naming a “view up” (_vup_) vector. A couple of cross products, and we now have a complete orthonormal basis (u,v,w) to describe our camera’s orientation. ![Figure 10-3](../assets/fig10-3.jpg) Remember that `vup`, `v`, and `w` are all in the same plane. Note that, like before when our fixed camera faced -Z, our arbitrary view camera faces -w. And keep in mind that we can -- but we don’t have to -- use world up (0,1,0) to specify vup. This is convenient and will naturally keep your camera horizontally level until you decide to experiment with crazy camera angles. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ #ifndef CAMERAH #define CAMERAH #include "ray.h" class camera { public: camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect) { // vfov is top to bottom in degrees vec3 u, v, w; float theta = vfov*M_PI/180; float half_height = tan(theta/2); float half_width = aspect * half_height; origin = lookatfrom; w = unit_vector(lookfrom - lookat); u = unit_vector(cross(vup, w)); v = cross(w, u); lower_left_corner = origin - half_width*u - half_height*v - w; horizontal = 2*half_width*u; vertical = 2*half_height*v; } ray get_ray(float s, float t) { return ray(origin, lower_left_corner + s*horizontal + t*vertical - origin); } vec3 origin; vec3 lower_left_corner; vec3 horizontal; vec3 vertical; }; #endif ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This allows us to change the viewpoint: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ camera cam(vec3(-2,2,1), vec3(0,0,-1), vec3(0,1,0), 90, float(nx)/float(ny)); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ to get: ![Image 10-2](../assets/img10-2.jpg) And we can change field of view to get: ![Image 10-3](../assets/img10-3.jpg)