|
466 | 466 | that returns the color of the background (a simple gradient).
|
467 | 467 |
|
468 | 468 | I’ve often gotten into trouble using square images for debugging because I transpose $x$ and $y$ too
|
469 |
| -often, so I’ll stick with a 200×100 image. I’ll put the “eye” (or camera center if you think of a |
470 |
| -camera) at $(0,0,0)$. I will have the y-axis go up, and the x-axis to the right. In order to respect |
471 |
| -the convention of a right handed coordinate system, into the screen is the negative z-axis. I will |
472 |
| -traverse the screen from the lower left hand corner, and use two offset vectors along the screen |
473 |
| -sides to move the ray endpoint across the screen. Note that I do not make the ray direction a unit |
474 |
| -length vector because I think not doing that makes for simpler and slightly faster code. |
| 469 | +often, so I’ll use a non-square image. For now we'll use a 16:9 aspect ratio, since that's so |
| 470 | +common. |
| 471 | + |
| 472 | +In addition to setting up the pixel dimensions for the rendered image, we also need to set up a |
| 473 | +virtual viewport through which to pass our scene rays. For the standard square pixel spacing, the |
| 474 | +viewport's aspect ratio should be the same as our rendered image. We'll just pick a viewport two |
| 475 | +units in height. We'll also set the distance between the projection plane and the projection point |
| 476 | +to be one unit. This is referred to as the “focal length”, not to be confused with “focus distance”, |
| 477 | +which we'll present later. |
| 478 | + |
| 479 | +I’ll put the “eye” (or camera center if you think of a camera) at $(0,0,0)$. I will have the y-axis |
| 480 | +go up, and the x-axis to the right. In order to respect the convention of a right handed coordinate |
| 481 | +system, into the screen is the negative z-axis. I will traverse the screen from the lower left hand |
| 482 | +corner, and use two offset vectors along the screen sides to move the ray endpoint across the |
| 483 | +screen. Note that I do not make the ray direction a unit length vector because I think not doing |
| 484 | +that makes for simpler and slightly faster code. |
475 | 485 |
|
476 | 486 | ![Figure [cam-geom]: Camera geometry](../images/fig.cam-geom.jpg)
|
477 | 487 |
|
|
497 | 507 |
|
498 | 508 | std::cout << "P3\n" << image_width << " " << image_height << "\n255\n";
|
499 | 509 |
|
| 510 | + |
500 | 511 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
501 |
| - point3 origin(0.0, 0.0, 0.0); |
502 |
| - vec3 horizontal(4.0, 0.0, 0.0); |
503 |
| - vec3 vertical(0.0, 2.25, 0.0); |
504 |
| - point3 lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0,0,1); |
| 512 | + auto viewport_height = 2.0; |
| 513 | + auto viewport_width = aspect_ratio * viewport_height; |
| 514 | + auto focal_length = 1.0; |
| 515 | + |
| 516 | + auto origin = point3(0, 0, 0); |
| 517 | + auto horizontal = vec3(viewport_width, 0, 0); |
| 518 | + auto vertical = vec3(0, viewport_height, 0); |
| 519 | + auto lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0, 0, focal_length); |
505 | 520 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
| 521 | + |
506 | 522 | for (int j = image_height-1; j >= 0; --j) {
|
507 | 523 | std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush;
|
508 | 524 | for (int i = 0; i < image_width; ++i) {
|
509 | 525 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
510 | 526 | auto u = double(i) / (image_width-1);
|
511 | 527 | auto v = double(j) / (image_height-1);
|
512 |
| - ray r(origin, lower_left_corner + u*horizontal + v*vertical); |
| 528 | + ray r(origin, lower_left_corner + u*horizontal + v*vertical - origin); |
513 | 529 | color pixel_color = ray_color(r);
|
514 | 530 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
515 | 531 | write_color(std::cout, pixel_color);
|
|
1201 | 1217 |
|
1202 | 1218 | std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";
|
1203 | 1219 |
|
1204 |
| - point3 lower_left_corner(-2.0, -1.0, -1.0); |
1205 |
| - vec3 horizontal(4.0, 0.0, 0.0); |
1206 |
| - vec3 vertical(0.0, 2.0, 0.0); |
1207 |
| - point3 origin(0.0, 0.0, 0.0); |
| 1220 | + auto viewport_height = 2.0; |
| 1221 | + auto viewport_width = aspect_ratio * viewport_height; |
| 1222 | + auto focal_length = 1.0; |
| 1223 | + |
| 1224 | + auto origin = point3(0, 0, 0); |
| 1225 | + auto horizontal = vec3(viewport_width, 0, 0); |
| 1226 | + auto vertical = vec3(0, viewport_height, 0); |
| 1227 | + auto lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0, 0, focal_length); |
1208 | 1228 |
|
1209 | 1229 |
|
1210 | 1230 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
|
1313 | 1333 | </div>
|
1314 | 1334 |
|
1315 | 1335 | <div class='together'>
|
1316 |
| -Putting that all together yields a camera class encapsulating our simple axis-aligned camera from |
| 1336 | +Now's a good time to create a `camera` class to manage our virtual camera and the related tasks of |
| 1337 | +scene scampling. The following class implements a simple camera using the axis-aligned camera from |
1317 | 1338 | before:
|
1318 | 1339 |
|
1319 | 1340 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
|
1325 | 1346 | class camera {
|
1326 | 1347 | public:
|
1327 | 1348 | camera() {
|
1328 |
| - lower_left_corner = point3(-2.0, -1.0, -1.0); |
1329 |
| - horizontal = vec3(4.0, 0.0, 0.0); |
1330 |
| - vertical = vec3(0.0, 2.0, 0.0); |
1331 |
| - origin = point3(0.0, 0.0, 0.0); |
| 1349 | + auto aspect_ratio = 16.0 / 9.0; |
| 1350 | + auto viewport_height = 2.0; |
| 1351 | + auto viewport_width = aspect_ratio * viewport_height; |
| 1352 | + auto focal_length = 1.0; |
| 1353 | + |
| 1354 | + origin = point3(0, 0, 0); |
| 1355 | + horizontal = vec3(viewport_width, 0.0, 0.0); |
| 1356 | + vertical = vec3(0.0, viewport_height, 0.0); |
| 1357 | + lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0, 0, focal_length); |
1332 | 1358 | }
|
1333 | 1359 |
|
1334 | 1360 | ray get_ray(double u, double v) const {
|
|
1399 | 1425 | world.add(make_shared<sphere>(point3(0,0,-1), 0.5));
|
1400 | 1426 | world.add(make_shared<sphere>(point3(0,-100.5,-1), 100));
|
1401 | 1427 |
|
| 1428 | + |
1402 | 1429 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
1403 | 1430 | camera cam;
|
1404 | 1431 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
| 1432 | + |
1405 | 1433 | for (int j = image_height-1; j >= 0; --j) {
|
1406 | 1434 | std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush;
|
1407 | 1435 | for (int i = 0; i < image_width; ++i) {
|
|
1882 | 1910 | double t;
|
1883 | 1911 | bool front_face;
|
1884 | 1912 |
|
1885 |
| - |
1886 | 1913 | inline void set_face_normal(const ray& r, const vec3& outward_normal) {
|
1887 | 1914 | front_face = dot(r.direction(), outward_normal) < 0;
|
1888 | 1915 | normal = front_face ? outward_normal :-outward_normal;
|
|
2107 | 2134 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2108 | 2135 |
|
2109 | 2136 | camera cam;
|
| 2137 | + |
2110 | 2138 | for (int j = image_height-1; j >= 0; --j) {
|
2111 | 2139 | std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush;
|
2112 | 2140 | for (int i = 0; i < image_width; ++i) {
|
|
2551 | 2579 | double vfov, // vertical field-of-view in degrees
|
2552 | 2580 | double aspect_ratio
|
2553 | 2581 | ) {
|
2554 |
| - origin = point3(0.0, 0.0, 0.0); |
2555 |
| - |
2556 | 2582 | auto theta = degrees_to_radians(vfov);
|
2557 |
| - auto half_height = tan(theta/2); |
2558 |
| - auto half_width = aspect_ratio * half_height; |
| 2583 | + auto h = tan(theta/2); |
| 2584 | + auto viewport_height = 2.0 * h; |
| 2585 | + auto viewport_width = aspect_ratio * viewport_height; |
2559 | 2586 |
|
2560 |
| - lower_left_corner = point3(-half_width, -half_height, -1.0); |
| 2587 | + auto focal_length = 1.0; |
2561 | 2588 |
|
2562 |
| - horizontal = vec3(2*half_width, 0.0, 0.0); |
2563 |
| - vertical = vec3(0.0, 2*half_height, 0.0); |
| 2589 | + origin = point3(0, 0, 0); |
| 2590 | + horizontal = vec3(viewport_width, 0.0, 0.0); |
| 2591 | + vertical = vec3(0.0, viewport_height, 0.0); |
| 2592 | + lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0, 0, focal_length); |
2564 | 2593 | }
|
2565 | 2594 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2566 | 2595 |
|
|
2626 | 2655 | public:
|
2627 | 2656 | camera(
|
2628 | 2657 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
2629 |
| - point3 lookfrom, point3 lookat, vec3 vup, |
| 2658 | + point3 lookfrom, |
| 2659 | + point3 lookat, |
| 2660 | + vec3 vup, |
2630 | 2661 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2631 | 2662 | double vfov, // vertical field-of-view in degrees
|
2632 | 2663 | double aspect_ratio
|
2633 | 2664 | ) {
|
2634 |
| - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
2635 |
| - origin = lookfrom; |
2636 |
| - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
2637 |
| - vec3 u, v, w; |
2638 |
| - |
2639 | 2665 | auto theta = degrees_to_radians(vfov);
|
2640 |
| - auto half_height = tan(theta/2); |
2641 |
| - auto half_width = aspect_ratio * half_height; |
| 2666 | + auto h = tan(theta/2); |
| 2667 | + auto viewport_height = 2.0 * h; |
| 2668 | + auto viewport_width = aspect_ratio * viewport_height; |
2642 | 2669 |
|
2643 |
| - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
2644 |
| - w = unit_vector(lookfrom - lookat); |
2645 |
| - u = unit_vector(cross(vup, w)); |
2646 |
| - v = cross(w, u); |
2647 | 2670 |
|
2648 |
| - lower_left_corner = origin - half_width*u - half_height*v - w; |
| 2671 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
| 2672 | + auto w = unit_vector(lookfrom - lookat); |
| 2673 | + auto u = unit_vector(cross(vup, w)); |
| 2674 | + auto v = cross(w, u); |
2649 | 2675 |
|
2650 |
| - horizontal = 2*half_width*u; |
2651 |
| - vertical = 2*half_height*v; |
| 2676 | + origin = lookfrom; |
| 2677 | + horizontal = viewport_width * u; |
| 2678 | + vertical = viewport_height * v; |
| 2679 | + lower_left_corner = origin - horizontal/2 - vertical/2 - w; |
2652 | 2680 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2653 | 2681 | }
|
2654 | 2682 |
|
|
2701 | 2729 | The reason we defocus blur in real cameras is because they need a big hole (rather than just a
|
2702 | 2730 | pinhole) to gather light. This would defocus everything, but if we stick a lens in the hole, there
|
2703 | 2731 | will be a certain distance where everything is in focus. You can think of a lens this way: all light
|
2704 |
| -rays coming _from_ a specific point at the focal distance -- and that hit the lens -- will be bent |
| 2732 | +rays coming _from_ a specific point at the focus distance -- and that hit the lens -- will be bent |
2705 | 2733 | back _to_ a single point on the image sensor.
|
2706 | 2734 |
|
2707 |
| -In a physical camera, the distance to that plane where things are in focus is controlled by the |
2708 |
| -distance between the lens and the film/sensor. That is why you see the lens move relative to the |
2709 |
| -camera when you change what is in focus (that may happen in your phone camera too, but the sensor |
2710 |
| -moves). The “aperture” is a hole to control how big the lens is effectively. For a real camera, if |
2711 |
| -you need more light you make the aperture bigger, and will get more defocus blur. For our virtual |
2712 |
| -camera, we can have a perfect sensor and never need more light, so we only have an aperture when we |
2713 |
| -want defocus blur. |
| 2735 | +We call the distance between the projection point and the plane where everything is in perfect focus |
| 2736 | +the _focus distance_. Be aware that the focus distance is not the same as the focal length -- the |
| 2737 | +_focal length_ is the distance between the projection point and the image plane. |
| 2738 | + |
| 2739 | +In a physical camera, the focus distance is controlled by the distance between the lens and the |
| 2740 | +film/sensor. That is why you see the lens move relative to the camera when you change what is in |
| 2741 | +focus (that may happen in your phone camera too, but the sensor moves). The “aperture” is a hole to |
| 2742 | +control how big the lens is effectively. For a real camera, if you need more light you make the |
| 2743 | +aperture bigger, and will get more defocus blur. For our virtual camera, we can have a perfect |
| 2744 | +sensor and never need more light, so we only have an aperture when we want defocus blur. |
2714 | 2745 |
|
2715 | 2746 |
|
2716 | 2747 | A Thin Lens Approximation
|
|
2728 | 2759 | <div class="together">
|
2729 | 2760 | We don’t need to simulate any of the inside of the camera. For the purposes of rendering an image
|
2730 | 2761 | outside the camera, that would be unnecessary complexity. Instead, I usually start rays from the
|
2731 |
| -surface of the lens, and send them toward a virtual film plane, by finding the projection of the |
2732 |
| -film on the plane that is in focus (at the distance `focus_dist`). |
| 2762 | +lens, and send them toward the focus plane (`focus_dist` away from the lens), where everything on |
| 2763 | +that plane is in perfect focus. |
2733 | 2764 |
|
2734 | 2765 | ![Figure [cam-film-plane]: Camera focus plane](../images/fig.cam-film-plane.jpg)
|
2735 | 2766 |
|
|
2759 | 2790 | class camera {
|
2760 | 2791 | public:
|
2761 | 2792 | camera(
|
2762 |
| - point3 lookfrom, point3 lookat, vec3 vup, |
| 2793 | + point3 lookfrom, |
| 2794 | + point3 lookat, |
| 2795 | + vec3 vup, |
2763 | 2796 | double vfov, // vertical field-of-view in degrees
|
| 2797 | + double aspect_ratio, |
2764 | 2798 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
2765 |
| - double aspect_ratio, double aperture, double focus_dist |
2766 |
| - ) { |
2767 |
| - origin = lookfrom; |
2768 |
| - lens_radius = aperture / 2; |
| 2799 | + double aperture, |
| 2800 | + double focus_dist |
2769 | 2801 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2770 |
| - |
| 2802 | + ) { |
2771 | 2803 | auto theta = degrees_to_radians(vfov);
|
2772 |
| - auto half_height = tan(theta/2); |
2773 |
| - auto half_width = aspect_ratio * half_height; |
| 2804 | + auto h = tan(theta/2); |
| 2805 | + auto viewport_height = 2.0 * h; |
| 2806 | + auto viewport_width = aspect_ratio * viewport_height; |
2774 | 2807 |
|
| 2808 | + |
| 2809 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
2775 | 2810 | w = unit_vector(lookfrom - lookat);
|
2776 | 2811 | u = unit_vector(cross(vup, w));
|
2777 | 2812 | v = cross(w, u);
|
| 2813 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
2778 | 2814 |
|
| 2815 | + origin = lookfrom; |
2779 | 2816 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
2780 |
| - lower_left_corner = origin |
2781 |
| - - half_width * focus_dist * u |
2782 |
| - - half_height * focus_dist * v |
2783 |
| - - focus_dist * w; |
| 2817 | + horizontal = focus_dist * viewport_width * u; |
| 2818 | + vertical = focus_dist * viewport_height * v; |
| 2819 | + lower_left_corner = origin - horizontal/2 - vertical/2 - focus_dist*w; |
2784 | 2820 |
|
2785 |
| - horizontal = 2*half_width*focus_dist*u; |
2786 |
| - vertical = 2*half_height*focus_dist*v; |
| 2821 | + lens_radius = aperture / 2; |
2787 | 2822 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2788 | 2823 | }
|
2789 | 2824 |
|
|
0 commit comments