1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2018 Jino Park, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28 
29 // Interface header.
30 #include "fisheyelenscamera.h"
31 
32 // appleseed.renderer headers.
33 #include "renderer/global/globallogger.h"
34 #include "renderer/global/globaltypes.h"
35 #include "renderer/kernel/shading/shadingray.h"
36 #include "renderer/modeling/camera/perspectivecamera.h"
37 #include "renderer/utility/transformsequence.h"
38 
39 // appleseed.foundation headers.
40 #include "foundation/image/canvasproperties.h"
41 #include "foundation/image/image.h"
42 #include "foundation/math/dual.h"
43 #include "foundation/math/matrix.h"
44 #include "foundation/math/transform.h"
45 #include "foundation/math/vector.h"
46 #include "foundation/platform/compiler.h"
47 #include "foundation/utility/api/apistring.h"
48 #include "foundation/utility/api/specializedapiarrays.h"
49 #include "foundation/utility/autoreleaseptr.h"
50 
51 // Standard headers.
52 #include <cstddef>
53 
54 // Forward declarations.
55 namespace foundation    { class IAbortSwitch; }
56 namespace renderer      { class OnRenderBeginRecorder; }
57 
58 using namespace foundation;
59 using namespace std;
60 
61 namespace renderer
62 {
63 
64 namespace
65 {
66     const char* Model = "fisheyelens_camera";
67 
68     class FisheyeLensCamera
69       : public PerspectiveCamera
70     {
71       public:
FisheyeLensCamera(const char * name,const ParamArray & params)72         FisheyeLensCamera(
73             const char*             name,
74             const ParamArray&       params)
75           : PerspectiveCamera(name, params)
76         {
77         }
78 
release()79         void release() override
80         {
81             delete this;
82         }
83 
get_model() const84         const char* get_model() const override
85         {
86             return Model;
87         }
88 
print_settings() const89         void print_settings() const override
90         {
91             const char* projection_type;
92             switch (m_projection_type)
93             {
94               case Projection::EquisolidAngle:
95                 projection_type = "equisolid angle";
96                 break;
97 
98               case Projection::Equidistant:
99                 projection_type = "equidistant";
100                 break;
101 
102               case Projection::Stereographic:
103                 projection_type = "stereographic";
104                 break;
105 
106               case Projection::Thoby:
107                 projection_type = "thoby";
108                 break;
109 
110               default:
111                 projection_type = "unknown";
112                 break;
113             }
114 
115             RENDERER_LOG_INFO(
116                 "camera \"%s\" settings:\n"
117                 "  model                         %s\n"
118                 "  film width                    %f\n"
119                 "  film height                   %f\n"
120                 "  focal length                  %f\n"
121                 "  near-z                        %f\n"
122                 "  shutter open begin time       %f\n"
123                 "  shutter open end time         %f\n"
124                 "  shutter close begin time      %f\n"
125                 "  shutter close end time        %f\n"
126                 "  projection type               %s",
127                 get_path().c_str(),
128                 Model,
129                 m_film_dimensions[0],
130                 m_film_dimensions[1],
131                 m_focal_length,
132                 m_near_z,
133                 m_shutter_open_begin_time,
134                 m_shutter_open_end_time,
135                 m_shutter_close_begin_time,
136                 m_shutter_close_end_time,
137                 projection_type);
138         }
139 
on_render_begin(const Project & project,const BaseGroup * parent,OnRenderBeginRecorder & recorder,IAbortSwitch * abort_switch)140         bool on_render_begin(
141             const Project&          project,
142             const BaseGroup*        parent,
143             OnRenderBeginRecorder&  recorder,
144             IAbortSwitch*           abort_switch) override
145         {
146             if (!PerspectiveCamera::on_render_begin(project, parent, recorder, abort_switch))
147                 return false;
148 
149             const string projection_type = m_params.get_required<string>("projection_type", "equisolid_angle");
150 
151             if (projection_type == "equisolid_angle")
152                  m_projection_type = Projection::EquisolidAngle;
153              else if (projection_type == "equidistant")
154                  m_projection_type = Projection::Equidistant;
155              else if (projection_type == "stereographic")
156                  m_projection_type = Projection::Stereographic;
157              else if (projection_type == "thoby")
158                  m_projection_type = Projection::Thoby;
159              else
160              {
161                  RENDERER_LOG_ERROR(
162                      "invalid value \"%s\" for parameter \"projection_type\", "
163                      "using default value \"equisolid_angle\".",
164                      projection_type.c_str());
165                  m_projection_type = Projection::EquisolidAngle;
166              }
167 
168             return true;
169         }
170 
spawn_ray(SamplingContext & sampling_context,const Dual2d & ndc,ShadingRay & ray) const171         void spawn_ray(
172             SamplingContext&        sampling_context,
173             const Dual2d&           ndc,
174             ShadingRay&             ray) const override
175         {
176             // Initialize the ray.
177             initialize_ray(sampling_context, ray);
178 
179             // Retrieve the camera transform.
180             Transformd scratch;
181             const Transformd& transform =
182                 m_transform_sequence.evaluate(ray.m_time.m_absolute, scratch);
183 
184             // Compute ray origin and direction.
185             ray.m_org = transform.get_local_to_parent().extract_translation();
186             ray.m_dir = normalize(transform.vector_to_parent(-ndc_to_camera(ndc.get_value())));
187 
188             // Compute ray derivatives.
189             if (ndc.has_derivatives())
190             {
191                 const Vector2d px(ndc.get_value() + ndc.get_dx());
192                 const Vector2d py(ndc.get_value() + ndc.get_dy());
193                 ray.m_rx.m_org = ray.m_org;
194                 ray.m_ry.m_org = ray.m_org;
195                 ray.m_rx.m_dir = normalize(transform.vector_to_parent(-ndc_to_camera(px)));
196                 ray.m_ry.m_dir = normalize(transform.vector_to_parent(-ndc_to_camera(py)));
197                 ray.m_has_differentials = true;
198             }
199         }
200 
connect_vertex(SamplingContext & sampling_context,const float time,const Vector3d & point,Vector2d & ndc,Vector3d & outgoing,float & importance) const201         bool connect_vertex(
202             SamplingContext&        sampling_context,
203             const float             time,
204             const Vector3d&         point,
205             Vector2d&               ndc,
206             Vector3d&               outgoing,
207             float&                  importance) const override
208         {
209             // Project the point onto the film plane.
210             if (!project_point(time, point, ndc))
211                 return false;
212 
213             // The connection is impossible if the projected point lies outside the film.
214             if (ndc[0] < 0.0 || ndc[0] >= 1.0 ||
215                 ndc[1] < 0.0 || ndc[1] >= 1.0)
216                 return false;
217 
218             // Retrieve the camera transform.
219             Transformd scratch;
220             const Transformd& transform = m_transform_sequence.evaluate(time, scratch);
221 
222             // Compute the outgoing direction vector in world space.
223             outgoing = point - transform.get_local_to_parent().extract_translation();
224 
225             // Compute the emitted importance.
226             const Vector3d film_point = ndc_to_camera(ndc);
227             const double square_dist_film_lens = square_norm(film_point);
228             const double dist_film_lens = sqrt(square_dist_film_lens);
229             const double cos_theta = m_focal_length / dist_film_lens;
230             const double solid_angle = m_pixel_area * cos_theta / square_dist_film_lens;
231             importance = 1.0f / static_cast<float>(square_norm(outgoing) * solid_angle);
232 
233             // The connection was possible.
234             return true;
235         }
236 
237       private:
238         enum class Projection
239         {
240             EquisolidAngle,
241             Equidistant,
242             Stereographic,
243             Thoby
244         };
245 
246         Projection m_projection_type;
247 
248         //
249         //                    |  axis
250         //                    |
251         //        #-----------------------------------  _
252         //       # \                                ^ \  \.
253         //      #   \                             ^    \  \ radius_2
254         //     #     \        |                 ^       \  \.
255         //    #       \                       ^          \  \.
256         //    #        \                    ^             \  \.
257         //   #         *------------------^----------------\  \. _
258         //   #        *  \    |         ^   )           ""  \  \  \.
259         //   #       *    \           ^       )    ""        \  \  \ radius_1
260         //   #      *      \        ^        "")              \  \  \.
261         //   #      *       \     ^   ""       ) theta_2       \  \  \.
262         //   #      *        \| ^"  ) theta_1 )                 \  _  _
263         //   #      *         o--------------------------------------------------> axis
264         //    #      *       /                                   |
265         //     #      *     /                               m_focal_length
266         //      #      *   /
267         //       #      * / radius_1                                """""""" : Direction 1.
268         //        #      /                                          ^^^^^^^^ : Direction 2.
269         //          #   /
270         //            #/ radius_2
271         //            /
272         //           /
273         //          /  axis
274         //
275         //
276         // Fisheye lens is implemented in a way to distort ray direction in pinhole
277         // camera. Direction 1 is same as ray direction in perspective camera. It
278         // represents direction to NDC. Direction 2 is distorted ray direction to render
279         // in fisheye lens camera. It represents actual ray direction in camera space.
280         //
281 
282         // Transforms ray direction from 1 to 2.
ndc_to_camera(const Vector2d & point) const283         Vector3d ndc_to_camera(const Vector2d& point) const
284         {
285             const double x = (0.5 - point.x) * m_film_dimensions[0];
286             const double y = (point.y - 0.5) * m_film_dimensions[1];
287 
288             const double radius_1 = sqrt(x * x + y * y);
289             const double rcp_radius_1 = 1.0 / radius_1;
290 
291             const double tan_theta_1 = radius_1 / m_focal_length;
292             double theta_2 = 0.0;
293 
294             switch (m_projection_type)
295             {
296               case Projection::EquisolidAngle:
297                 theta_2 = 2.0 * asin(tan_theta_1 * 0.5);
298                 break;
299 
300               case Projection::Equidistant:
301                 theta_2 = tan_theta_1;
302                 break;
303 
304               case Projection::Stereographic:
305                 theta_2 = 2.0 * atan(tan_theta_1 * 0.5);
306                 break;
307 
308               case Projection::Thoby:
309                 theta_2 = asin(tan_theta_1 * 0.68027) * 1.40252;
310                 break;
311 
312               default:
313                 assert(false);
314             }
315 
316             const double radius_diff = tan(theta_2) * m_focal_length - radius_1;
317 
318             return
319                 Vector3d(
320                     x + radius_diff * x * rcp_radius_1 - m_shift.x,
321                     y + radius_diff * y * rcp_radius_1 - m_shift.y,
322                     m_focal_length);
323         }
324 
325         // Transforms ray direction from 2 to 1.
camera_to_ndc(const Vector3d & point) const326         Vector2d camera_to_ndc(const Vector3d& point) const
327         {
328             const double k = m_focal_length / point.z;
329 
330             const double x = 0.5 - (point.x * k * m_rcp_film_width);
331             const double y = 0.5 + (point.y * k * m_rcp_film_height);
332 
333             const double radius_2 = sqrt(x * x + y * y);
334             const double rcp_radius_2 = 1.0 / radius_2;
335 
336             const double theta_2 = atan(radius_2 / m_focal_length);
337             double tan_theta_1 = 0.0;
338 
339             switch (m_projection_type)
340             {
341               case Projection::EquisolidAngle:
342                 tan_theta_1 = 2.0 * sin(theta_2 * 0.5);
343                 break;
344 
345               case Projection::Equidistant:
346                 tan_theta_1 = theta_2;
347                 break;
348 
349               case Projection::Stereographic:
350                 tan_theta_1 = 2.0 * tan(theta_2 * 0.5);
351                 break;
352 
353               case Projection::Thoby:
354                 tan_theta_1 = 1.47 * sin(0.713 * theta_2);
355                 break;
356 
357               default:
358                 assert(false);
359             }
360 
361             const double radius_1 = tan_theta_1 * m_focal_length;
362 
363             return
364                 Vector2d(
365                     radius_1 * x * rcp_radius_2 - m_shift.x,
366                     radius_1 * y * rcp_radius_2 - m_shift.y);
367         }
368     };
369 }
370 
371 
372 //
373 // FisheyeLensCameraFactory class implementation.
374 //
375 
release()376 void FisheyeLensCameraFactory::release()
377 {
378     delete this;
379 }
380 
get_model() const381 const char* FisheyeLensCameraFactory::get_model() const
382 {
383     return Model;
384 }
385 
get_model_metadata() const386 Dictionary FisheyeLensCameraFactory::get_model_metadata() const
387 {
388     return
389         Dictionary()
390             .insert("name", Model)
391             .insert("label", "Fisheye Lens Camera")
392             .insert("default_model", "true");
393 }
394 
get_input_metadata() const395 DictionaryArray FisheyeLensCameraFactory::get_input_metadata() const
396 {
397     DictionaryArray metadata = CameraFactory::get_input_metadata();
398 
399     CameraFactory::add_film_metadata(metadata);
400     CameraFactory::add_lens_metadata(metadata);
401     CameraFactory::add_clipping_metadata(metadata);
402     CameraFactory::add_shift_metadata(metadata);
403 
404     metadata.push_back(
405     Dictionary()
406         .insert("name", "projection_type")
407         .insert("label", "Projection Type")
408         .insert("type", "enumeration")
409         .insert("items",
410             Dictionary()
411                 .insert("Equisolid Angle", "equisolid_angle")
412                 .insert("Equidistant", "equidistant")
413                 .insert("Stereographic", "stereographic")
414                 .insert("Thoby", "thoby"))
415         .insert("default", "equisolid_angle")
416         .insert("use", "required"));
417 
418     return metadata;
419 }
420 
create(const char * name,const ParamArray & params) const421 auto_release_ptr<Camera> FisheyeLensCameraFactory::create(
422     const char*         name,
423     const ParamArray&   params) const
424 {
425     return auto_release_ptr<Camera>(new FisheyeLensCamera(name, params));
426 }
427 
428 }   // namespace renderer
429