mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Add round-trip tests for *_to_direction functions and fix some of them
The function `direction_to_<some projection model>` computes the inverse of `<some projection model>_to_direction`. Some of these functions had a bug where they mirror the x-axis, and some of them could be simplified. I added round-trip tests for all of them. This MR might change the behavior of the renderer when using equiangular_cubemap_face_to_direction: I normalized the result vector. I looked at the usages and I think it's normalized later anyways, but someone else should probably verify that this doesn't cause issues. Pull Request: https://projects.blender.org/blender/blender/pulls/123932
This commit is contained in:
parent
d8675a3f6a
commit
717c970297
2 changed files with 214 additions and 25 deletions
|
|
@ -25,7 +25,7 @@ ccl_device float2 direction_to_spherical(float3 dir)
|
|||
ccl_device float3 spherical_to_direction(float theta, float phi)
|
||||
{
|
||||
float sin_theta = sinf(theta);
|
||||
return make_float3(sin_theta * cosf(phi), sin_theta * sinf(phi), cosf(theta));
|
||||
return make_float3(sin_theta * sinf(phi), sin_theta * cosf(phi), cosf(theta));
|
||||
}
|
||||
|
||||
/* Equirectangular coordinates <-> Cartesian direction */
|
||||
|
|
@ -63,13 +63,9 @@ ccl_device float3 equirectangular_to_direction(float u, float v)
|
|||
|
||||
ccl_device float2 direction_to_fisheye(float3 dir, float fov)
|
||||
{
|
||||
float r = atan2f(sqrtf(dir.y * dir.y + dir.z * dir.z), dir.x) / fov;
|
||||
float phi = atan2f(dir.z, dir.y);
|
||||
|
||||
float u = r * cosf(phi) + 0.5f;
|
||||
float v = r * sinf(phi) + 0.5f;
|
||||
|
||||
return make_float2(u, v);
|
||||
const float r = atan2f(len(make_float2(dir.y, dir.z)), dir.x) / fov;
|
||||
const float2 uv = r * safe_normalize(make_float2(dir.y, dir.z));
|
||||
return make_float2(0.5f - uv.x, uv.y + 0.5f);
|
||||
}
|
||||
|
||||
ccl_device float3 fisheye_to_direction(float u, float v, float fov)
|
||||
|
|
@ -93,14 +89,11 @@ ccl_device float3 fisheye_to_direction(float u, float v, float fov)
|
|||
|
||||
ccl_device float2 direction_to_fisheye_equisolid(float3 dir, float lens, float width, float height)
|
||||
{
|
||||
float theta = safe_acosf(dir.x);
|
||||
float r = 2.0f * lens * sinf(theta * 0.5f);
|
||||
float phi = atan2f(dir.z, dir.y);
|
||||
const float theta = safe_acosf(dir.x);
|
||||
const float r = 2.0f * lens * sinf(theta * 0.5f);
|
||||
|
||||
float u = r * cosf(phi) / width + 0.5f;
|
||||
float v = r * sinf(phi) / height + 0.5f;
|
||||
|
||||
return make_float2(u, v);
|
||||
const float2 uv = r * safe_normalize(make_float2(dir.y, dir.z));
|
||||
return make_float2(0.5f - uv.x / width, uv.y / height + 0.5f);
|
||||
}
|
||||
|
||||
ccl_device_inline float3
|
||||
|
|
@ -225,21 +218,17 @@ ccl_device float2 direction_to_mirrorball(float3 dir)
|
|||
* https://blog.google/products/google-ar-vr/bringing-pixels-front-and-center-vr-video/ */
|
||||
ccl_device float3 equiangular_cubemap_face_to_direction(float u, float v)
|
||||
{
|
||||
u = (1.0f - u);
|
||||
u = tanf((0.5f - u) * M_PI_2_F);
|
||||
v = tanf((v - 0.5f) * M_PI_2_F);
|
||||
|
||||
u = tanf(u * M_PI_2_F - M_PI_4_F);
|
||||
v = tanf(v * M_PI_2_F - M_PI_4_F);
|
||||
|
||||
return make_float3(1.0f, u, v);
|
||||
return normalize(make_float3(1.0f, u, v));
|
||||
}
|
||||
|
||||
ccl_device float2 direction_to_equiangular_cubemap_face(float3 dir)
|
||||
{
|
||||
float u = atan2f(dir.y, dir.x) * 2.0f / M_PI_F + 0.5f;
|
||||
float u = 0.5f - atan2f(dir.y, dir.x) * 2.0f / M_PI_F;
|
||||
float v = atan2f(dir.z, dir.x) * 2.0f / M_PI_F + 0.5f;
|
||||
|
||||
u = 1.0f - u;
|
||||
|
||||
return make_float2(u, v);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,16 +10,17 @@
|
|||
#include "kernel/device/cpu/compat.h"
|
||||
#include "kernel/device/cpu/globals.h"
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
#include "kernel/camera/camera.h"
|
||||
#include "kernel/camera/projection.h"
|
||||
#include "kernel/types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* @brief Test #fisheye_lens_polynomial_to_direction and its inverse
|
||||
* #direction_to_fisheye_lens_polynomial by checking if sensor position equals
|
||||
* direction_to_fisheye_lens_polynomial(fisheye_lens_polynomial_to_direction/sensor position))
|
||||
* direction_to_fisheye_lens_polynomial(fisheye_lens_polynomial_to_direction(sensor position))
|
||||
* for a couple of sensor positions and a couple of different sets of parameters.
|
||||
*/
|
||||
TEST(KernelCamera, FisheyeLensPolynomialRoundtrip)
|
||||
|
|
@ -217,4 +218,203 @@ TEST(KernelCamera, FisheyeLensPolynomialToDirection)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The CommonValues struct contains information about the tests
|
||||
* which is common across the different tests.
|
||||
* Derived classes may override functions to make tests less strict
|
||||
* if necessary.
|
||||
*/
|
||||
struct CommonValues {
|
||||
/**
|
||||
* @brief Threshold for the reprojection error.
|
||||
* @return
|
||||
*/
|
||||
virtual double threshold() const
|
||||
{
|
||||
return 2e-6;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief If skip_invalid returns true, invalid unprojections are ignored in the test.
|
||||
* @return
|
||||
*/
|
||||
virtual bool skip_invalid() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct Spherical : public CommonValues {
|
||||
static float2 direction_to_sensor(float3 const &dir,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return direction_to_spherical(dir);
|
||||
}
|
||||
static float3 sensor_to_direction(float2 const &sensor,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return spherical_to_direction(sensor.x, sensor.y);
|
||||
}
|
||||
};
|
||||
|
||||
struct Equirectangular : public CommonValues {
|
||||
static float2 direction_to_sensor(float3 const &dir,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return direction_to_equirectangular(dir);
|
||||
}
|
||||
static float3 sensor_to_direction(float2 const &sensor,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return equirectangular_to_direction(sensor.x, sensor.y);
|
||||
}
|
||||
};
|
||||
|
||||
struct FisheyeEquidistant : public CommonValues {
|
||||
static float2 direction_to_sensor(float3 const &dir,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return direction_to_fisheye(dir, fov);
|
||||
}
|
||||
static float3 sensor_to_direction(float2 const &sensor,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return fisheye_to_direction(sensor.x, sensor.y, fov);
|
||||
}
|
||||
};
|
||||
|
||||
struct FisheyeEquisolid : public CommonValues {
|
||||
bool skip_invalid() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr float lens = 15.0f;
|
||||
|
||||
static float2 direction_to_sensor(float3 const &dir,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return direction_to_fisheye_equisolid(dir, lens, width, height);
|
||||
}
|
||||
static float3 sensor_to_direction(float2 const &sensor,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return fisheye_equisolid_to_direction(sensor.x, sensor.y, lens, fov, width, height);
|
||||
}
|
||||
};
|
||||
|
||||
struct MirrorBall : public CommonValues {
|
||||
static float2 direction_to_sensor(float3 const &dir,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return direction_to_mirrorball(dir);
|
||||
}
|
||||
static float3 sensor_to_direction(float2 const &sensor,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return mirrorball_to_direction(sensor.x, sensor.y);
|
||||
}
|
||||
};
|
||||
|
||||
struct EquiangularCubemapFace : public CommonValues {
|
||||
static float2 direction_to_sensor(float3 const &dir,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return direction_to_equiangular_cubemap_face(dir);
|
||||
}
|
||||
static float3 sensor_to_direction(float2 const &sensor,
|
||||
float const fov,
|
||||
float const width,
|
||||
float const height)
|
||||
{
|
||||
return equiangular_cubemap_face_to_direction(sensor.x, sensor.y);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> class PanoramaProjection : public testing::Test {};
|
||||
using MyTypes = ::testing::Types<Spherical,
|
||||
Equirectangular,
|
||||
FisheyeEquidistant,
|
||||
FisheyeEquisolid,
|
||||
MirrorBall,
|
||||
EquiangularCubemapFace>;
|
||||
TYPED_TEST_SUITE(PanoramaProjection, MyTypes);
|
||||
|
||||
/**
|
||||
* @brief Test <projection>_to_direction and its inverse
|
||||
* direction_to_<projection> by checking if sensor position equals
|
||||
* direction_to_<projection>(<projection>_to_direction(sensor position))
|
||||
* for a couple of sensor positions and a couple of different sets of parameters.
|
||||
*/
|
||||
TYPED_TEST(PanoramaProjection, round_trip)
|
||||
{
|
||||
|
||||
TypeParam test;
|
||||
|
||||
const float2 sensors[]{{0.5f, 0.5f},
|
||||
{0.4f, 0.4f},
|
||||
{0.3f, 0.3f},
|
||||
{0.4f, 0.6f},
|
||||
{0.3f, 0.7f},
|
||||
{0.2f, 0.8f},
|
||||
{0.5f, 0.9f},
|
||||
{0.5f, 0.1f},
|
||||
{0.1f, 0.5f},
|
||||
{0.9f, 0.5f}};
|
||||
|
||||
for (float const size : {36.0f, 24.0f, 6.0f * M_PI_F}) {
|
||||
float const width = size;
|
||||
float const height = size;
|
||||
for (const float fov : {2.0f * M_PI_F, M_PI_F, M_PI_2_F, M_PI_4_F, 1.0f, 2.0f}) {
|
||||
size_t test_count = 0;
|
||||
for (const float2 &sensor : sensors) {
|
||||
const float3 direction = TypeParam::sensor_to_direction(sensor, fov, width, height);
|
||||
if (test.skip_invalid() && len(direction) < 0.9f) {
|
||||
continue;
|
||||
}
|
||||
test_count++;
|
||||
EXPECT_NEAR(len(direction), 1.0, 1e-6)
|
||||
<< "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
|
||||
<< std::endl
|
||||
<< "fov: " << fov << std::endl
|
||||
<< "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
|
||||
const float2 projection = TypeParam::direction_to_sensor(direction, fov, width, height);
|
||||
EXPECT_NEAR(sensor.x, projection.x, test.threshold())
|
||||
<< "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
|
||||
<< std::endl
|
||||
<< "fov: " << fov << std::endl
|
||||
<< "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
|
||||
EXPECT_NEAR(sensor.y, projection.y, test.threshold())
|
||||
<< "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
|
||||
<< std::endl
|
||||
<< "fov: " << fov << std::endl
|
||||
<< "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
|
||||
}
|
||||
EXPECT_GE(test_count, 2) << "fov: " << fov << std::endl << "size: " << size << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue