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