Blender V5.0
kernel_camera_projection_test.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include <gtest/gtest.h>
6
7#include "util/math.h"
8#include "util/types.h"
9
11
13
20TEST(KernelCamera, FisheyeLensPolynomialRoundtrip)
21{
22 const float fov = 150.0f * (M_PI_F / 180.0f);
23 const float width = 36.0f;
24 const float height = 41.142857142857144f;
25
26 /* Trivial case: The coefficients create a perfect equidistant fisheye */
27 const float4 k_equidistant = make_float4(-5.79e-02f, 0.0f, 0.0f, 0.0f);
28
29 /* The coefficients mimic a stereographic fisheye model */
30 const float4 k_stereographic = make_float4(-5.79e-02f, 0.0f, 9.48e-05f, -7.67e-06f);
31
32 /* The coefficients mimic a rectilinear camera (badly, but the point is to have a wide range of
33 * tests). */
34 const float4 k_rectilinear = make_float4(-6.50e-02f, 0.0f, 8.32e-05f, -1.80e-06f);
35
36 const float4 parameters[]{k_equidistant, k_stereographic, k_rectilinear};
37
38 const std::pair<float, float> points[]{
39 {0.1f, 0.4f},
40 {0.1f, 0.5f},
41 {0.1f, 0.7f},
42 {0.5f, 0.5f},
43 {0.5f, 0.9f},
44 {0.6f, 0.9f},
45 };
46
47 /* In the test cases k0 = k2 = 0, because for non-zero values the model is not smooth at the
48 * center, but real lenses are really smooth near the center. In order to test the method
49 * thoroughly, nonzero values are tested for both parameters. */
50 for (const float k0 : {0.0f, -1e-2f, -2e-2f, -5e-2f, -1e-1f}) {
51 for (const float k2 : {0.0f, -1e-4f, 1e-4f, -2e-4f, 2e-4f}) {
52 for (float4 k : parameters) {
53 k.y = k2;
54 for (const std::pair<float, float> &pt : points) {
55 const float x = pt.first;
56 const float y = pt.second;
58 pt.first, pt.second, k0, k, fov, width, height);
59
60 EXPECT_NEAR(len(direction), 1, 1e-6) << "x: " << x << std::endl
61 << "y: " << y << std::endl
62 << "k0: " << k0 << std::endl
63 << "k2: " << k2;
64
66 direction, k0, k, width, height);
67
68 EXPECT_NEAR(reprojection.x, x, 1e-6) << "k0: " << k0 << std::endl
69 << "k1: " << k.x << std::endl
70 << "k2: " << k.y << std::endl
71 << "k3: " << k.z << std::endl
72 << "k4: " << k.w << std::endl;
73 EXPECT_NEAR(reprojection.y, y, 3e-6) << "k0: " << k0 << std::endl
74 << "k1: " << k.x << std::endl
75 << "k2: " << k.y << std::endl
76 << "k3: " << k.z << std::endl
77 << "k4: " << k.w << std::endl;
78 }
79 }
80 }
81 }
82}
83
87TEST(KernelCamera, FisheyeLensPolynomialToDirectionSymmetry)
88{
89 const float fov = M_PI_F;
90 const float width = 1.0f;
91 const float height = 1.0f;
92
93 /* Trivial case: The coefficients create a perfect equidistant fisheye */
94 const float4 k_equidistant = make_float4(-1.0f, 0.0f, 0.0f, 0.0f);
95 const float k0 = 0.0f;
96
97 /* Symmetry tests */
98 const float2 center{0.5f, 0.5f};
99 const float2 offsets[]{
100 {0.00f, 0.00f},
101 {0.25f, 0.00f},
102 {0.00f, 0.25f},
103 {0.25f, 0.25f},
104
105 {0.5f, 0.0f},
106 {0.0f, 0.5f},
107 {0.5f, 0.5f},
108
109 {0.75f, 0.00f},
110 {0.00f, 0.75f},
111 {0.75f, 0.75f},
112 };
113
114 for (const float2 &offset : offsets) {
115 const float2 point = center + offset;
117 point.x, point.y, k0, k_equidistant, fov, width, height);
118 EXPECT_NEAR(len(direction), 1.0, 1e-6);
119
120 const float2 point_mirror = center - offset;
121 const float3 direction_mirror = fisheye_lens_polynomial_to_direction(
122 point_mirror.x, point_mirror.y, k0, k_equidistant, fov, width, height);
123 EXPECT_NEAR(len(direction_mirror), 1.0, 1e-6);
124
125 EXPECT_NEAR(direction.x, +direction_mirror.x, 1e-6)
126 << "offset: (" << offset.x << ", " << offset.y << ")";
127 EXPECT_NEAR(direction.y, -direction_mirror.y, 1e-6)
128 << "offset: (" << offset.x << ", " << offset.y << ")";
129 ;
130 EXPECT_NEAR(direction.z, -direction_mirror.z, 1e-6)
131 << "offset: (" << offset.x << ", " << offset.y << ")";
132 ;
133 }
134}
135
140TEST(KernelCamera, FisheyeLensPolynomialToDirection)
141{
142 const float fov = M_PI_F;
143 const float k0 = 0.0f;
144
145 const float rad60 = M_PI_F / 3.0f;
146 const float cos60 = 0.5f;
147 const float sin60 = M_SQRT3_F / 2.0f;
148
149 const float rad30 = M_PI_F / 6.0f;
150 const float cos30 = M_SQRT3_F / 2.0f;
151 const float sin30 = 0.5f;
152
153 const float rad45 = M_PI_4F;
154 const float cos45 = M_SQRT1_2F;
155 const float sin45 = M_SQRT1_2F;
156
157 const std::pair<float2, float3> tests[]{
158 /* Center (0°) */
159 {make_float2(0.0f, 0.0f), make_float3(1.0f, 0.0f, 0.0f)},
160
161 /* 60° */
162 {make_float2(0.0f, +rad60), make_float3(cos60, 0.0f, +sin60)},
163 {make_float2(0.0f, -rad60), make_float3(cos60, 0.0f, -sin60)},
164 {make_float2(+rad60, 0.0f), make_float3(cos60, -sin60, 0.0f)},
165 {make_float2(-rad60, 0.0f), make_float3(cos60, +sin60, 0.0f)},
166
167 /* 45° */
168 {make_float2(0.0f, +rad45), make_float3(cos45, 0.0f, +sin45)},
169 {make_float2(0.0f, -rad45), make_float3(cos45, 0.0f, -sin45)},
170 {make_float2(+rad45, 0.0f), make_float3(cos45, -sin45, 0.0f)},
171 {make_float2(-rad45, 0.0f), make_float3(cos45, +sin45, 0.0f)},
172
173 {make_float2(+rad45 * M_SQRT1_2F, +rad45 * M_SQRT1_2F), make_float3(cos45, -0.5f, +0.5f)},
174 {make_float2(-rad45 * M_SQRT1_2F, +rad45 * M_SQRT1_2F), make_float3(cos45, +0.5f, +0.5f)},
175 {make_float2(+rad45 * M_SQRT1_2F, -rad45 * M_SQRT1_2F), make_float3(cos45, -0.5f, -0.5f)},
176 {make_float2(-rad45 * M_SQRT1_2F, -rad45 * M_SQRT1_2F), make_float3(cos45, +0.5f, -0.5f)},
177
178 /* 30° */
179 {make_float2(0.0f, +rad30), make_float3(cos30, 0.0f, +sin30)},
180 {make_float2(0.0f, -rad30), make_float3(cos30, 0.0f, -sin30)},
181 {make_float2(+rad30, 0.0f), make_float3(cos30, -sin30, 0.0f)},
182 {make_float2(-rad30, 0.0f), make_float3(cos30, +sin30, 0.0f)},
183 };
184
185 for (auto [offset, direction] : tests) {
186 const float2 sensor = offset + make_float2(0.5f, 0.5f);
187 for (const float scale : {1.0f, 0.5f, 2.0f, 0.25f, 4.0f, 0.125f, 8.0f, 0.0625f, 16.0f}) {
188 const float width = 1.0f / scale;
189 const float height = 1.0f / scale;
190 /* Trivial case: The coefficients create a perfect equidistant fisheye */
191 const float4 k_equidistant = make_float4(-scale, 0.0f, 0.0f, 0.0f);
192
194 sensor.x, sensor.y, k0, k_equidistant, fov, width, height);
195
196 EXPECT_NEAR(direction.x, computed.x, 1e-6)
197 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
198 << "scale: " << scale;
199 EXPECT_NEAR(direction.y, computed.y, 1e-6)
200 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
201 << "scale: " << scale;
202 EXPECT_NEAR(direction.z, computed.z, 1e-6)
203 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
204 << "scale: " << scale;
205
207 direction, k0, k_equidistant, width, height);
208
209 EXPECT_NEAR(sensor.x, reprojected.x, 1e-6) << "scale: " << scale;
210 EXPECT_NEAR(sensor.y, reprojected.y, 1e-6) << "scale: " << scale;
211 }
212 }
213}
214
226 virtual double threshold() const
227 {
228 return 2e-6;
229 }
230
235 virtual bool skip_invalid() const
236 {
237 return false;
238 }
239};
240
241struct Spherical : public CommonValues {
243 const float fov,
244 const float width,
245 const float height)
246 {
247 return direction_to_spherical(dir);
248 }
249 static float3 sensor_to_direction(const float2 &sensor,
250 const float fov,
251 const float width,
252 const float height)
253 {
254 return spherical_to_direction(sensor.x, sensor.y);
255 }
256};
257
260 const float fov,
261 const float width,
262 const float height)
263 {
265 }
266 static float3 sensor_to_direction(const float2 &sensor,
267 const float fov,
268 const float width,
269 const float height)
270 {
271 return equirectangular_to_direction(sensor.x, sensor.y);
272 }
273};
274
277 const float fov,
278 const float width,
279 const float height)
280 {
281 return direction_to_fisheye_equidistant(dir, fov);
282 }
283 static float3 sensor_to_direction(const float2 &sensor,
284 const float fov,
285 const float width,
286 const float height)
287 {
288 return fisheye_equidistant_to_direction(sensor.x, sensor.y, fov);
289 }
290};
291
293 bool skip_invalid() const override
294 {
295 return true;
296 }
297
298 static constexpr float lens = 15.0f;
299
301 const float fov,
302 const float width,
303 const float height)
304 {
305 return direction_to_fisheye_equisolid(dir, lens, width, height);
306 }
307 static float3 sensor_to_direction(const float2 &sensor,
308 const float fov,
309 const float width,
310 const float height)
311 {
312 return fisheye_equisolid_to_direction(sensor.x, sensor.y, lens, fov, width, height);
313 }
314};
315
316struct MirrorBall : public CommonValues {
318 const float fov,
319 const float width,
320 const float height)
321 {
322 return direction_to_mirrorball(dir);
323 }
324 static float3 sensor_to_direction(const float2 &sensor,
325 const float fov,
326 const float width,
327 const float height)
328 {
329 return mirrorball_to_direction(sensor.x, sensor.y);
330 }
331};
332
335 const float fov,
336 const float width,
337 const float height)
338 {
340 }
341 static float3 sensor_to_direction(const float2 &sensor,
342 const float fov,
343 const float width,
344 const float height)
345 {
346 return equiangular_cubemap_face_to_direction(sensor.x, sensor.y);
347 }
348};
349
350template<typename T> class PanoramaProjection : public testing::Test {};
351using MyTypes = ::testing::Types<Spherical,
358
366{
367
368 const TypeParam test;
369
370 const float2 sensors[]{{0.5f, 0.5f},
371 {0.4f, 0.4f},
372 {0.3f, 0.3f},
373 {0.4f, 0.6f},
374 {0.3f, 0.7f},
375 {0.2f, 0.8f},
376 {0.5f, 0.9f},
377 {0.5f, 0.1f},
378 {0.1f, 0.5f},
379 {0.9f, 0.5f}};
380
381 for (const float size : {36.0f, 24.0f, 6.0f * M_PI_F}) {
382 const float width = size;
383 const float height = size;
384 for (const float fov : {2.0f * M_PI_F, M_PI_F, M_PI_2_F, M_PI_4_F, 1.0f, 2.0f}) {
385 size_t test_count = 0;
386 for (const float2 &sensor : sensors) {
387 const float3 direction = TypeParam::sensor_to_direction(sensor, fov, width, height);
388 if (test.skip_invalid() && len(direction) < 0.9f) {
389 continue;
390 }
391 test_count++;
392 EXPECT_NEAR(len(direction), 1.0, 1e-6)
393 << "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
394 << std::endl
395 << "fov: " << fov << std::endl
396 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
397 const float2 projection = TypeParam::direction_to_sensor(direction, fov, width, height);
398 EXPECT_NEAR(sensor.x, projection.x, test.threshold())
399 << "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
400 << std::endl
401 << "fov: " << fov << std::endl
402 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
403 EXPECT_NEAR(sensor.y, projection.y, test.threshold())
404 << "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
405 << std::endl
406 << "fov: " << fov << std::endl
407 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
408 }
409 EXPECT_GE(test_count, 2) << "fov: " << fov << std::endl << "size: " << size << std::endl;
410 }
411 }
412}
413
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
ccl_device float3 equiangular_cubemap_face_to_direction(float u, float v)
ccl_device float2 direction_to_mirrorball(float3 dir)
ccl_device float2 direction_to_fisheye_equidistant(const float3 dir, const float fov)
ccl_device_inline float3 fisheye_lens_polynomial_to_direction(float u, float v, float coeff0, const float4 coeffs, const float fov, const float width, const float height)
ccl_device float3 fisheye_equidistant_to_direction(float u, float v, float fov)
ccl_device float2 direction_to_fisheye_equisolid(const float3 dir, const float lens, const float width, const float height)
ccl_device_inline float3 fisheye_equisolid_to_direction(float u, float v, float lens, const float fov, const float width, const float height)
ccl_device float2 direction_to_fisheye_lens_polynomial(float3 dir, const float coeff0, const float4 coeffs, const float width, const float height)
ccl_device float3 mirrorball_to_direction(const float u, const float v)
ccl_device float2 direction_to_equiangular_cubemap_face(const float3 dir)
ccl_device float2 direction_to_equirectangular(const float3 dir)
ccl_device float3 equirectangular_to_direction(const float u, const float v)
CCL_NAMESPACE_BEGIN ccl_device float2 direction_to_spherical(const float3 dir)
ccl_device float3 spherical_to_direction(const float theta, const float phi)
#define M_PI_2_F
#define M_PI_4_F
#define M_SQRT1_2F
#define M_SQRT3_F
#define M_PI_4F
#define CCL_NAMESPACE_END
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
CCL_NAMESPACE_BEGIN TEST(KernelCamera, FisheyeLensPolynomialRoundtrip)
Test fisheye_lens_polynomial_to_direction and its inverse direction_to_fisheye_lens_polynomial by che...
TYPED_TEST_SUITE(PanoramaProjection, MyTypes)
::testing::Types< Spherical, Equirectangular, FisheyeEquidistant, FisheyeEquisolid, MirrorBall, EquiangularCubemapFace > MyTypes
TYPED_TEST(PanoramaProjection, round_trip)
Test <projection>to_direction and its inverse direction_to<projection> by checking if sensor position...
#define make_float2
#define make_float4
#define M_PI_F
The CommonValues struct contains information about the tests which is common across the different tes...
virtual bool skip_invalid() const
If skip_invalid returns true, invalid unprojections are ignored in the test.
virtual double threshold() const
Threshold for the reprojection error.
static float2 direction_to_sensor(const float3 &dir, const float fov, const float width, const float height)
static float3 sensor_to_direction(const float2 &sensor, const float fov, const float width, const float height)
static float2 direction_to_sensor(const float3 &dir, const float fov, const float width, const float height)
static float3 sensor_to_direction(const float2 &sensor, const float fov, const float width, const float height)
static float3 sensor_to_direction(const float2 &sensor, const float fov, const float width, const float height)
static float2 direction_to_sensor(const float3 &dir, const float fov, const float width, const float height)
bool skip_invalid() const override
If skip_invalid returns true, invalid unprojections are ignored in the test.
static float2 direction_to_sensor(const float3 &dir, const float fov, const float width, const float height)
static float3 sensor_to_direction(const float2 &sensor, const float fov, const float width, const float height)
static float3 sensor_to_direction(const float2 &sensor, const float fov, const float width, const float height)
static float2 direction_to_sensor(const float3 &dir, const float fov, const float width, const float height)
static float2 direction_to_sensor(const float3 &dir, const float fov, const float width, const float height)
static float3 sensor_to_direction(const float2 &sensor, const float fov, const float width, const float height)
float x
float y
float z
Definition sky_math.h:136
float y
Definition sky_math.h:136
float x
Definition sky_math.h:136
uint len