|
2778 | 2778 |
|
2779 | 2779 | <div class='together'>
|
2780 | 2780 | Now that we have boxes, we need to rotate them a bit to have them match the _real_ Cornell box. In
|
2781 |
| -ray tracing, this is usually done with an _instance_. An instance is a geometric primitive that has |
2782 |
| -been moved or rotated somehow. This is especially easy in ray tracing because we don’t move |
2783 |
| -anything; instead we move the rays in the opposite direction. For example, consider a _translation_ |
2784 |
| -(often called a _move_). We could take the pink box at the origin and add 2 to all its x components, |
2785 |
| -or (as we almost always do in ray tracing) leave the box where it is, but in its hit routine |
2786 |
| -subtract 2 off the x-component of the ray origin. |
| 2781 | +ray tracing, this is usually done with an _instance_. An instance is a copy of a geometric |
| 2782 | +primitive that has been placed into the scene. This instance is entirely independent of the other |
| 2783 | +copies of the primitive and can be moved or rotated around. In this case our geometric primitive is |
| 2784 | +our box hittable, and we want to rotate it somehow. This is especially easy in ray tracing because |
| 2785 | +we don’t move anything; instead we move the rays in the opposite direction. For example, consider a |
| 2786 | +_translation_ (often called a _move_). We could take the pink box at the origin and add 2 to all its |
| 2787 | +x components, or (as we almost always do in ray tracing) leave the box where it is, but in its hit |
| 2788 | +routine subtract 2 off the x-component of the ray origin. |
2787 | 2789 |
|
2788 | 2790 | ![Figure [ray-box]: Ray-box intersection with moved ray vs box](../images/fig-2.06-ray-box.jpg)
|
2789 | 2791 |
|
|
2793 | 2795 | Instance Translation
|
2794 | 2796 | ---------------------
|
2795 | 2797 | <div class='together'>
|
2796 |
| -Whether you think of this as a move or a change of coordinates is up to you. The code for this, to |
2797 |
| -move any underlying hittable is a _translate_ instance. |
| 2798 | +Whether you think of this as a move or a change of coordinates is up to you. The way to reason about |
| 2799 | +this is to think of moving the incident ray backwards the offset amount, determining if an |
| 2800 | +intersection occurs, and then moving that intersection point forward the offset amount. |
| 2801 | + |
| 2802 | +If we have an intersection along the offset ray, then we know that the incident ray will hit the |
| 2803 | +translated object. However, our offset ray is not the original ray, so if we just take the |
| 2804 | +intersection we found along the offset ray then our intersection will actually be wrong by the |
| 2805 | +offset amount, so we have to shift the intersection point back onto the original ray by the offset |
| 2806 | +amount. Let's add the code to make this happen. |
2798 | 2807 |
|
2799 | 2808 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2800 | 2809 | class translate : public hittable {
|
2801 | 2810 | public:
|
| 2811 | + ... |
| 2812 | + |
| 2813 | + bool hit(const ray& r, interval ray_t, hit_record& rec) const override { |
| 2814 | + // Move the ray backwards by the offset |
| 2815 | + ray offset_r(r.origin() - offset, r.direction(), r.time()); |
| 2816 | + |
| 2817 | + // Determine where (if any) an intersection occurs along the offset ray |
| 2818 | + if (!object->hit(offset_r, ray_t, rec)) |
| 2819 | + return false; |
| 2820 | + |
| 2821 | + // Move the intersection point forwards by the offset |
| 2822 | + rec.p += offset; |
| 2823 | + rec.set_face_normal(offset_r, rec.normal); |
| 2824 | + |
| 2825 | + return true; |
| 2826 | + } |
| 2827 | + |
| 2828 | + ... |
| 2829 | + }; |
| 2830 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 2831 | + [Listing [translate-hit]: <kbd>[hittable.h]</kbd> Hittable translation hit function] |
| 2832 | + |
| 2833 | +... and then flesh out the rest of the `translate` class: |
| 2834 | + |
| 2835 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
| 2836 | + class translate : public hittable { |
| 2837 | + public: |
| 2838 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
2802 | 2839 | translate(shared_ptr<hittable> p, const vec3& displacement)
|
2803 | 2840 | : object(p), offset(displacement)
|
2804 | 2841 | {
|
2805 | 2842 | bbox = object->bounding_box() + offset;
|
2806 | 2843 | }
|
| 2844 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
2807 | 2845 |
|
2808 | 2846 | bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
|
2809 |
| - ray moved_r(r.origin() - offset, r.direction(), r.time()); |
2810 |
| - if (!object->hit(moved_r, ray_t, rec)) |
| 2847 | + // Move the ray backwards by the offset |
| 2848 | + ray offset_r(r.origin() - offset, r.direction(), r.time()); |
| 2849 | + |
| 2850 | + // Determine where (if any) an intersection occurs along the offset ray |
| 2851 | + if (!object->hit(offset_r, ray_t, rec)) |
2811 | 2852 | return false;
|
2812 | 2853 |
|
| 2854 | + // Move the intersection point forwards by the offset |
2813 | 2855 | rec.p += offset;
|
2814 |
| - rec.set_face_normal(moved_r, rec.normal); |
| 2856 | + rec.set_face_normal(offset_r, rec.normal); |
2815 | 2857 |
|
2816 | 2858 | return true;
|
2817 | 2859 | }
|
2818 | 2860 |
|
| 2861 | + |
| 2862 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
2819 | 2863 | aabb bounding_box() const override { return bbox; }
|
2820 | 2864 |
|
2821 | 2865 | public:
|
2822 | 2866 | shared_ptr<hittable> object;
|
2823 | 2867 | vec3 offset;
|
2824 | 2868 | aabb bbox;
|
| 2869 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
2825 | 2870 | };
|
2826 | 2871 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2827 | 2872 | [Listing [translate-class]: <kbd>[hittable.h]</kbd> Hittable translation class]
|
2828 | 2873 | </div>
|
2829 | 2874 |
|
2830 |
| -The expression `object->bounding_box() + offset` above requires some additional support. |
| 2875 | +We need to remember to offset the bounding box, otherwise the incident ray might be looking in the |
| 2876 | +wrong place and trivially reject the intersection. The expression `object->bounding_box() + offset` |
| 2877 | +above requires some additional support. |
2831 | 2878 |
|
2832 | 2879 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2833 | 2880 | class aabb {
|
|
2904 | 2951 | $$ x' = \cos(\theta) \cdot x + \sin(\theta) \cdot z $$
|
2905 | 2952 | $$ z' = -\sin(\theta) \cdot x + \cos(\theta) \cdot z $$
|
2906 | 2953 |
|
2907 |
| -And about the x-axis: |
| 2954 | +And if we want to rotate about the x-axis: |
2908 | 2955 |
|
2909 | 2956 | $$ y' = \cos(\theta) \cdot y - \sin(\theta) \cdot z $$
|
2910 | 2957 | $$ z' = \sin(\theta) \cdot y + \cos(\theta) \cdot z $$
|
2911 | 2958 | </div>
|
2912 | 2959 |
|
2913 |
| -Unlike the situation with translations, the surface normal vector also changes, so we need to |
2914 |
| -transform directions too if we get a hit. Fortunately for rotations, the same formulas apply. If you |
2915 |
| -add scales, things get more complicated. See the web page https://in1weekend.blogspot.com/ for links |
2916 |
| -to that. |
| 2960 | +For a simple operation like a translation, thinking of the operation as a simple movement of the |
| 2961 | +initial ray is a fine way to reason about what's going on. But, for a more complex operation like a |
| 2962 | +rotation, it can be easy to accidentally get your terms crossed (or forget a negative sign), so it's |
| 2963 | +better to consider a rotation as a change of coordinates. The pseudocode for the `translate::hit` |
| 2964 | +function above describes the function in terms of _moving_: |
| 2965 | + |
| 2966 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
| 2967 | + bool hit(const ray& r, interval ray_t, hit_record& rec) const override { |
| 2968 | + // Move the ray backwards by the offset |
| 2969 | + |
| 2970 | + // Determine where (if any) an intersection occurs along the offset ray |
| 2971 | + |
| 2972 | + // Move the intersection point forwards by the offset |
| 2973 | + } |
| 2974 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 2975 | + [Listing [translate-hit-move]: <kbd>[hittable.h]</kbd> The moving translate hit code] |
| 2976 | + |
| 2977 | +But this can also be thought of in terms of a _changing of coordinates_: |
| 2978 | + |
| 2979 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
| 2980 | + bool hit(const ray& r, interval ray_t, hit_record& rec) const override { |
| 2981 | + // Change the ray from world space to object space |
| 2982 | + |
| 2983 | + // Determine where (if any) an intersection occurs in object space |
| 2984 | + |
| 2985 | + // Change the intersection point from object space to world space |
| 2986 | + } |
| 2987 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 2988 | + [Listing [translate-hit-coord]: <kbd>[hittable.h]</kbd> The change of coordinates translate hit code] |
2917 | 2989 |
|
2918 | 2990 | <div class='together'>
|
| 2991 | +Rotating an object will not only change the point of intersection, but will also change the surface |
| 2992 | +normal vector, which will change the direction of reflections and refractions. So we need to change the |
| 2993 | +normal as well. Fortuneatly, we can just treat the normal like a vector, and so use the same |
| 2994 | +formulas as above. |
| 2995 | + |
2919 | 2996 | For a y-rotation class we have:
|
2920 | 2997 |
|
2921 | 2998 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
2922 | 2999 | class rotate_y : public hittable {
|
2923 | 3000 | public:
|
2924 |
| - rotate_y(shared_ptr<hittable> p, double angle) : object(p) { |
| 3001 | + ... |
| 3002 | + |
| 3003 | + bool hit(const ray& r, interval ray_t, hit_record& rec) const override { |
| 3004 | + // Change the ray from world space to object space |
| 3005 | + auto origin = r.origin(); |
| 3006 | + auto direction = r.direction(); |
| 3007 | + |
| 3008 | + origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2]; |
| 3009 | + origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2]; |
| 3010 | + |
| 3011 | + direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2]; |
| 3012 | + direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2]; |
| 3013 | + |
| 3014 | + ray rotated_r(origin, direction, r.time()); |
| 3015 | + |
| 3016 | + // Determine where (if any) an intersection occurs in object space |
| 3017 | + if (!object->hit(rotated_r, ray_t, rec)) |
| 3018 | + return false; |
| 3019 | + |
| 3020 | + // Change the intersection point from object space to world space |
| 3021 | + auto p = rec.p; |
| 3022 | + p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2]; |
| 3023 | + p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2]; |
| 3024 | + |
| 3025 | + // Change the normal from object space to world space |
| 3026 | + auto normal = rec.normal |
| 3027 | + normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2]; |
| 3028 | + normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2]; |
| 3029 | + |
| 3030 | + rec.p = p; |
| 3031 | + rec.set_face_normal(rotated_r, normal); |
| 3032 | + |
| 3033 | + return true; |
| 3034 | + } |
| 3035 | + |
| 3036 | + ... |
| 3037 | + }; |
| 3038 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 3039 | + [Listing [rot-y-hit]: <kbd>[hittable.h]</kbd> Hittable rotate-Y hit function] |
| 3040 | + |
| 3041 | +</div> |
| 3042 | +The code for solving from world space to object space might at first appear incorrect because we are |
| 3043 | +rotating the opposite way. The negative sign of the `sin_theta` terms are flipped. While we want to |
| 3044 | +rotate around the y-axis by $\theta$, the formula used has our world space to object space solving |
| 3045 | +for $-\theta$. What we have above is actually correct. It's the exact same thing that we did above |
| 3046 | +for translation. Changing from world space to object space required us to change by |
| 3047 | +$-\text{offset}$. Changing from world space to object space for rotation requires us to rotate by |
| 3048 | +$-\theta$. And, just as before with translation, where the change from object space to world space |
| 3049 | +required a change of $+\text{offset}$, the change in rotation from object space to world space |
| 3050 | +requires $+\theta$. |
| 3051 | + |
| 3052 | +With all of that said, we can now flesh out the rest of the class: |
| 3053 | + |
| 3054 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
| 3055 | + class rotate_y : public hittable { |
| 3056 | + public: |
| 3057 | + |
| 3058 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
| 3059 | + rotate_y(shared_ptr<hittable> p, double angle) : object(p) { |
2925 | 3060 | auto radians = degrees_to_radians(angle);
|
2926 | 3061 | sin_theta = sin(radians);
|
2927 | 3062 | cos_theta = cos(radians);
|
|
2952 | 3087 |
|
2953 | 3088 | bbox = aabb(min, max);
|
2954 | 3089 | }
|
| 3090 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
2955 | 3091 |
|
2956 | 3092 | bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
|
2957 |
| - auto origin = r.origin(); |
2958 |
| - auto direction = r.direction(); |
2959 |
| - |
2960 |
| - origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2]; |
2961 |
| - origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2]; |
2962 |
| - |
2963 |
| - direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2]; |
2964 |
| - direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2]; |
2965 |
| - |
2966 |
| - ray rotated_r(origin, direction, r.time()); |
2967 |
| - |
2968 |
| - if (!object->hit(rotated_r, ray_t, rec)) |
2969 |
| - return false; |
2970 |
| - |
2971 |
| - auto p = rec.p; |
2972 |
| - auto normal = rec.normal; |
2973 |
| - |
2974 |
| - p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2]; |
2975 |
| - p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2]; |
2976 |
| - |
2977 |
| - normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2]; |
2978 |
| - normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2]; |
2979 |
| - |
2980 |
| - rec.p = p; |
2981 |
| - rec.set_face_normal(rotated_r, normal); |
2982 |
| - |
2983 |
| - return true; |
| 3093 | + ... |
2984 | 3094 | }
|
2985 | 3095 |
|
| 3096 | + |
| 3097 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
2986 | 3098 | aabb bounding_box() const override { return bbox; }
|
2987 | 3099 |
|
2988 | 3100 | public:
|
2989 | 3101 | shared_ptr<hittable> object;
|
2990 | 3102 | double sin_theta;
|
2991 | 3103 | double cos_theta;
|
2992 | 3104 | aabb bbox;
|
| 3105 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
2993 | 3106 | };
|
2994 | 3107 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2995 | 3108 | [Listing [rot-y]: <kbd>[hittable.h]</kbd> Hittable rotate-Y class]
|
2996 |
| -</div> |
2997 | 3109 |
|
2998 | 3110 | <div class='together'>
|
2999 | 3111 | And the changes to Cornell are:
|
|
0 commit comments