Blender V4.3
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 "testing/testing.h"
6
7#include "util/math.h"
8#include "util/types.h"
9
12
13#include "kernel/types.h"
14
17
19
26TEST(KernelCamera, FisheyeLensPolynomialRoundtrip)
27{
28 const float fov = 150.0f * (M_PI_F / 180.0f);
29 const float width = 36.0f;
30 const float height = 41.142857142857144f;
31
32 /* Trivial case: The coefficients create a perfect equidistant fisheye */
33 const float4 k_equidistant = make_float4(-5.79e-02f, 0.0f, 0.0f, 0.0f);
34
35 /* The coefficients mimic a stereographic fisheye model */
36 const float4 k_stereographic = make_float4(-5.79e-02f, 0.0f, 9.48e-05f, -7.67e-06f);
37
38 /* The coefficients mimic a rectilinear camera (badly, but the point is to have a wide range of
39 * tests). */
40 const float4 k_rectilinear = make_float4(-6.50e-02f, 0.0f, 8.32e-05f, -1.80e-06f);
41
42 const float4 parameters[]{k_equidistant, k_stereographic, k_rectilinear};
43
44 const std::pair<float, float> points[]{
45 {0.1f, 0.4f},
46 {0.1f, 0.5f},
47 {0.1f, 0.7f},
48 {0.5f, 0.5f},
49 {0.5f, 0.9f},
50 {0.6f, 0.9f},
51 };
52
53 /* In the test cases k0 = k2 = 0, because for non-zero values the model is not smooth at the
54 * center, but real lenses are really smooth near the center. In order to test the method
55 * thoroughly, nonzero values are tested for both parameters. */
56 for (const float k0 : {0.0f, -1e-2f, -2e-2f, -5e-2f, -1e-1f}) {
57 for (const float k2 : {0.0f, -1e-4f, 1e-4f, -2e-4f, 2e-4f}) {
58 for (float4 k : parameters) {
59 k.y = k2;
60 for (std::pair<float, float> const &pt : points) {
61 const float x = pt.first;
62 const float y = pt.second;
64 pt.first, pt.second, k0, k, fov, width, height);
65
66 EXPECT_NEAR(len(direction), 1, 1e-6) << "x: " << x << std::endl
67 << "y: " << y << std::endl
68 << "k0: " << k0 << std::endl
69 << "k2: " << k2;
70
72 direction, k0, k, width, height);
73
74 EXPECT_NEAR(reprojection.x, x, 1e-6) << "k0: " << k0 << std::endl
75 << "k1: " << k.x << std::endl
76 << "k2: " << k.y << std::endl
77 << "k3: " << k.z << std::endl
78 << "k4: " << k.w << std::endl;
79 EXPECT_NEAR(reprojection.y, y, 3e-6) << "k0: " << k0 << std::endl
80 << "k1: " << k.x << std::endl
81 << "k2: " << k.y << std::endl
82 << "k3: " << k.z << std::endl
83 << "k4: " << k.w << std::endl;
84 }
85 }
86 }
87 }
88}
89
93TEST(KernelCamera, FisheyeLensPolynomialToDirectionSymmetry)
94{
95 const float fov = M_PI_F;
96 const float width = 1.0f;
97 const float height = 1.0f;
98
99 /* Trivial case: The coefficients create a perfect equidistant fisheye */
100 const float4 k_equidistant = make_float4(-1.0f, 0.0f, 0.0f, 0.0f);
101 const float k0 = 0.0f;
102
103 /* Symmetry tests */
104 const float2 center{0.5f, 0.5f};
105 const float2 offsets[]{
106 {0.00f, 0.00f},
107 {0.25f, 0.00f},
108 {0.00f, 0.25f},
109 {0.25f, 0.25f},
110
111 {0.5f, 0.0f},
112 {0.0f, 0.5f},
113 {0.5f, 0.5f},
114
115 {0.75f, 0.00f},
116 {0.00f, 0.75f},
117 {0.75f, 0.75f},
118 };
119
120 for (float2 const &offset : offsets) {
121 const float2 point = center + offset;
123 point.x, point.y, k0, k_equidistant, fov, width, height);
124 EXPECT_NEAR(len(direction), 1.0, 1e-6);
125
126 const float2 point_mirror = center - offset;
127 const float3 direction_mirror = fisheye_lens_polynomial_to_direction(
128 point_mirror.x, point_mirror.y, k0, k_equidistant, fov, width, height);
129 EXPECT_NEAR(len(direction_mirror), 1.0, 1e-6);
130
131 EXPECT_NEAR(direction.x, +direction_mirror.x, 1e-6)
132 << "offset: (" << offset.x << ", " << offset.y << ")";
133 EXPECT_NEAR(direction.y, -direction_mirror.y, 1e-6)
134 << "offset: (" << offset.x << ", " << offset.y << ")";
135 ;
136 EXPECT_NEAR(direction.z, -direction_mirror.z, 1e-6)
137 << "offset: (" << offset.x << ", " << offset.y << ")";
138 ;
139 }
140}
141
146TEST(KernelCamera, FisheyeLensPolynomialToDirection)
147{
148 const float fov = M_PI_F;
149 const float k0 = 0.0f;
150
151 const float rad60 = M_PI_F / 3.0f;
152 const float cos60 = 0.5f;
153 const float sin60 = M_SQRT3_F / 2.0f;
154
155 const float rad30 = M_PI_F / 6.0f;
156 const float cos30 = M_SQRT3_F / 2.0f;
157 const float sin30 = 0.5f;
158
159 const float rad45 = M_PI_4F;
160 const float cos45 = M_SQRT1_2F;
161 const float sin45 = M_SQRT1_2F;
162
163 const std::pair<float2, float3> tests[]{
164 /* Center (0°) */
165 {make_float2(0.0f, 0.0f), make_float3(1.0f, 0.0f, 0.0f)},
166
167 /* 60° */
168 {make_float2(0.0f, +rad60), make_float3(cos60, 0.0f, +sin60)},
169 {make_float2(0.0f, -rad60), make_float3(cos60, 0.0f, -sin60)},
170 {make_float2(+rad60, 0.0f), make_float3(cos60, -sin60, 0.0f)},
171 {make_float2(-rad60, 0.0f), make_float3(cos60, +sin60, 0.0f)},
172
173 /* 45° */
174 {make_float2(0.0f, +rad45), make_float3(cos45, 0.0f, +sin45)},
175 {make_float2(0.0f, -rad45), make_float3(cos45, 0.0f, -sin45)},
176 {make_float2(+rad45, 0.0f), make_float3(cos45, -sin45, 0.0f)},
177 {make_float2(-rad45, 0.0f), make_float3(cos45, +sin45, 0.0f)},
178
179 {make_float2(+rad45 * M_SQRT1_2F, +rad45 * M_SQRT1_2F), make_float3(cos45, -0.5f, +0.5f)},
180 {make_float2(-rad45 * M_SQRT1_2F, +rad45 * M_SQRT1_2F), make_float3(cos45, +0.5f, +0.5f)},
181 {make_float2(+rad45 * M_SQRT1_2F, -rad45 * M_SQRT1_2F), make_float3(cos45, -0.5f, -0.5f)},
182 {make_float2(-rad45 * M_SQRT1_2F, -rad45 * M_SQRT1_2F), make_float3(cos45, +0.5f, -0.5f)},
183
184 /* 30° */
185 {make_float2(0.0f, +rad30), make_float3(cos30, 0.0f, +sin30)},
186 {make_float2(0.0f, -rad30), make_float3(cos30, 0.0f, -sin30)},
187 {make_float2(+rad30, 0.0f), make_float3(cos30, -sin30, 0.0f)},
188 {make_float2(-rad30, 0.0f), make_float3(cos30, +sin30, 0.0f)},
189 };
190
191 for (auto [offset, direction] : tests) {
192 const float2 sensor = offset + make_float2(0.5f, 0.5f);
193 for (float const scale : {1.0f, 0.5f, 2.0f, 0.25f, 4.0f, 0.125f, 8.0f, 0.0625f, 16.0f}) {
194 const float width = 1.0f / scale;
195 const float height = 1.0f / scale;
196 /* Trivial case: The coefficients create a perfect equidistant fisheye */
197 const float4 k_equidistant = make_float4(-scale, 0.0f, 0.0f, 0.0f);
198
200 sensor.x, sensor.y, k0, k_equidistant, fov, width, height);
201
202 EXPECT_NEAR(direction.x, computed.x, 1e-6)
203 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
204 << "scale: " << scale;
205 EXPECT_NEAR(direction.y, computed.y, 1e-6)
206 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
207 << "scale: " << scale;
208 EXPECT_NEAR(direction.z, computed.z, 1e-6)
209 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
210 << "scale: " << scale;
211
213 direction, k0, k_equidistant, width, height);
214
215 EXPECT_NEAR(sensor.x, reprojected.x, 1e-6) << "scale: " << scale;
216 EXPECT_NEAR(sensor.y, reprojected.y, 1e-6) << "scale: " << scale;
217 }
218 }
219}
220
232 virtual double threshold() const
233 {
234 return 2e-6;
235 }
236
241 virtual bool skip_invalid() const
242 {
243 return false;
244 }
245};
246
247struct Spherical : public CommonValues {
249 float const fov,
250 float const width,
251 float const height)
252 {
253 return direction_to_spherical(dir);
254 }
255 static float3 sensor_to_direction(float2 const &sensor,
256 float const fov,
257 float const width,
258 float const height)
259 {
260 return spherical_to_direction(sensor.x, sensor.y);
261 }
262};
263
266 float const fov,
267 float const width,
268 float const height)
269 {
271 }
272 static float3 sensor_to_direction(float2 const &sensor,
273 float const fov,
274 float const width,
275 float const height)
276 {
277 return equirectangular_to_direction(sensor.x, sensor.y);
278 }
279};
280
283 float const fov,
284 float const width,
285 float const height)
286 {
287 return direction_to_fisheye(dir, fov);
288 }
289 static float3 sensor_to_direction(float2 const &sensor,
290 float const fov,
291 float const width,
292 float const height)
293 {
294 return fisheye_to_direction(sensor.x, sensor.y, fov);
295 }
296};
297
299 bool skip_invalid() const
300 {
301 return true;
302 }
303
304 static constexpr float lens = 15.0f;
305
307 float const fov,
308 float const width,
309 float const height)
310 {
311 return direction_to_fisheye_equisolid(dir, lens, width, height);
312 }
313 static float3 sensor_to_direction(float2 const &sensor,
314 float const fov,
315 float const width,
316 float const height)
317 {
318 return fisheye_equisolid_to_direction(sensor.x, sensor.y, lens, fov, width, height);
319 }
320};
321
322struct MirrorBall : public CommonValues {
324 float const fov,
325 float const width,
326 float const height)
327 {
328 return direction_to_mirrorball(dir);
329 }
330 static float3 sensor_to_direction(float2 const &sensor,
331 float const fov,
332 float const width,
333 float const height)
334 {
335 return mirrorball_to_direction(sensor.x, sensor.y);
336 }
337};
338
341 float const fov,
342 float const width,
343 float const height)
344 {
346 }
347 static float3 sensor_to_direction(float2 const &sensor,
348 float const fov,
349 float const width,
350 float const height)
351 {
352 return equiangular_cubemap_face_to_direction(sensor.x, sensor.y);
353 }
354};
355
356template<typename T> class PanoramaProjection : public testing::Test {};
357using MyTypes = ::testing::Types<Spherical,
364
372{
373
374 TypeParam test;
375
376 const float2 sensors[]{{0.5f, 0.5f},
377 {0.4f, 0.4f},
378 {0.3f, 0.3f},
379 {0.4f, 0.6f},
380 {0.3f, 0.7f},
381 {0.2f, 0.8f},
382 {0.5f, 0.9f},
383 {0.5f, 0.1f},
384 {0.1f, 0.5f},
385 {0.9f, 0.5f}};
386
387 for (float const size : {36.0f, 24.0f, 6.0f * M_PI_F}) {
388 float const width = size;
389 float const height = size;
390 for (const float fov : {2.0f * M_PI_F, M_PI_F, M_PI_2_F, M_PI_4_F, 1.0f, 2.0f}) {
391 size_t test_count = 0;
392 for (const float2 &sensor : sensors) {
393 const float3 direction = TypeParam::sensor_to_direction(sensor, fov, width, height);
394 if (test.skip_invalid() && len(direction) < 0.9f) {
395 continue;
396 }
397 test_count++;
398 EXPECT_NEAR(len(direction), 1.0, 1e-6)
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 const float2 projection = TypeParam::direction_to_sensor(direction, fov, width, height);
404 EXPECT_NEAR(sensor.x, projection.x, test.threshold())
405 << "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
406 << std::endl
407 << "fov: " << fov << std::endl
408 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
409 EXPECT_NEAR(sensor.y, projection.y, test.threshold())
410 << "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
411 << std::endl
412 << "fov: " << fov << std::endl
413 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
414 }
415 EXPECT_GE(test_count, 2) << "fov: " << fov << std::endl << "size: " << size << std::endl;
416 }
417 }
418}
419
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a point
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_fisheye(float3 dir, float fov)
ccl_device float3 fisheye_to_direction(float u, float v, float fov)
ccl_device float2 direction_to_mirrorball(float3 dir)
ccl_device float2 direction_to_equiangular_cubemap_face(float3 dir)
ccl_device float2 direction_to_fisheye_lens_polynomial(float3 dir, float coeff0, float4 coeffs, float width, float height)
CCL_NAMESPACE_BEGIN ccl_device float2 direction_to_spherical(float3 dir)
ccl_device float3 equirectangular_to_direction(float u, float v)
ccl_device float2 direction_to_equirectangular(float3 dir)
ccl_device float2 direction_to_fisheye_equisolid(float3 dir, float lens, float width, float height)
ccl_device_inline float3 fisheye_equisolid_to_direction(float u, float v, float lens, float fov, float width, float height)
ccl_device float3 spherical_to_direction(float theta, float phi)
ccl_device float3 mirrorball_to_direction(float u, float v)
ccl_device_inline float3 fisheye_lens_polynomial_to_direction(float u, float v, float coeff0, float4 coeffs, float fov, float width, float height)
#define CCL_NAMESPACE_END
ccl_device_forceinline float4 make_float4(const float x, const float y, const float z, const float w)
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
ccl_device_forceinline float2 make_float2(const float x, const float y)
int len
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)
TYPED_TEST(PanoramaProjection, round_trip)
Test <projection>to_direction and its inverse direction_to<projection> by checking if sensor position...
::testing::Types< Spherical, Equirectangular, FisheyeEquidistant, FisheyeEquisolid, MirrorBall, EquiangularCubemapFace > MyTypes
#define M_PI_F
Definition mikk_util.hh:15
#define M_PI_2_F
Definition sky_float3.h:20
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(float3 const &dir, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
bool skip_invalid() const
If skip_invalid returns true, invalid unprojections are ignored in the test.
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
float x
float y
float z
Definition sky_float3.h:27
float y
Definition sky_float3.h:27
float x
Definition sky_float3.h:27
VecBase< float, 4 > float4