mirror of
https://github.com/orange-cpp/omath.git
synced 2026-04-25 12:23:26 +00:00
Merge pull request #185 from orange-cpp/feature/unreal-engine-projection-fix
Feature/unreal engine projection fix
This commit is contained in:
@@ -9,5 +9,5 @@
|
|||||||
|
|
||||||
namespace omath::unreal_engine
|
namespace omath::unreal_engine
|
||||||
{
|
{
|
||||||
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::ZERO_TO_ONE, {.inverted_right = true}>;
|
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::ZERO_TO_ONE>;
|
||||||
} // namespace omath::unreal_engine
|
} // namespace omath::unreal_engine
|
||||||
@@ -707,6 +707,59 @@ namespace omath
|
|||||||
else
|
else
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Horizontal-FOV variants — use these when the engine reports FOV as
|
||||||
|
// horizontal (UE's FMinimalViewInfo::FOV, Quake-family fov_x, etc.).
|
||||||
|
// X and Y scales derived as: X = 1 / tan(hfov/2), Y = aspect / tan(hfov/2).
|
||||||
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat<4, 4, Type, St> mat_perspective_left_handed_horizontal_fov(const float horizontal_fov,
|
||||||
|
const float aspect_ratio, const float near,
|
||||||
|
const float far) noexcept
|
||||||
|
{
|
||||||
|
const float inv_tan_half_hfov = 1.f / std::tan(angles::degrees_to_radians(horizontal_fov) / 2.f);
|
||||||
|
const float x_axis = inv_tan_half_hfov;
|
||||||
|
const float y_axis = inv_tan_half_hfov * aspect_ratio;
|
||||||
|
|
||||||
|
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
|
||||||
|
return {{x_axis, 0.f, 0.f, 0.f},
|
||||||
|
{0.f, y_axis, 0.f, 0.f},
|
||||||
|
{0.f, 0.f, far / (far - near), -(near * far) / (far - near)},
|
||||||
|
{0.f, 0.f, 1.f, 0.f}};
|
||||||
|
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
|
return {{x_axis, 0.f, 0.f, 0.f},
|
||||||
|
{0.f, y_axis, 0.f, 0.f},
|
||||||
|
{0.f, 0.f, (far + near) / (far - near), -(2.f * near * far) / (far - near)},
|
||||||
|
{0.f, 0.f, 1.f, 0.f}};
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
|
||||||
|
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
|
||||||
|
[[nodiscard]]
|
||||||
|
Mat<4, 4, Type, St> mat_perspective_right_handed_horizontal_fov(const float horizontal_fov,
|
||||||
|
const float aspect_ratio, const float near,
|
||||||
|
const float far) noexcept
|
||||||
|
{
|
||||||
|
const float inv_tan_half_hfov = 1.f / std::tan(angles::degrees_to_radians(horizontal_fov) / 2.f);
|
||||||
|
const float x_axis = inv_tan_half_hfov;
|
||||||
|
const float y_axis = inv_tan_half_hfov * aspect_ratio;
|
||||||
|
|
||||||
|
if constexpr (DepthRange == NDCDepthRange::ZERO_TO_ONE)
|
||||||
|
return {{x_axis, 0.f, 0.f, 0.f},
|
||||||
|
{0.f, y_axis, 0.f, 0.f},
|
||||||
|
{0.f, 0.f, -far / (far - near), -(near * far) / (far - near)},
|
||||||
|
{0.f, 0.f, -1.f, 0.f}};
|
||||||
|
else if constexpr (DepthRange == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
|
return {{x_axis, 0.f, 0.f, 0.f},
|
||||||
|
{0.f, y_axis, 0.f, 0.f},
|
||||||
|
{0.f, 0.f, -(far + near) / (far - near), -(2.f * near * far) / (far - near)},
|
||||||
|
{0.f, 0.f, -1.f, 0.f}};
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
|
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR,
|
||||||
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
|
NDCDepthRange DepthRange = NDCDepthRange::NEGATIVE_ONE_TO_ONE>
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
|
|||||||
@@ -38,25 +38,24 @@ namespace omath::source_engine
|
|||||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
||||||
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
||||||
{
|
{
|
||||||
// NOTE: Need magic number to fix fov calculation, since source inherit Quake proj matrix calculation
|
// Source (inherited from Quake) stores FOV as horizontal FOV at a 4:3
|
||||||
constexpr auto k_multiply_factor = 0.75f;
|
// reference aspect. Convert to true vertical FOV, then delegate to the
|
||||||
|
// standard vertical-FOV left-handed builder with the caller's actual
|
||||||
const float fov_half_tan = std::tan(angles::degrees_to_radians(field_of_view) / 2.f) * k_multiply_factor;
|
// aspect ratio.
|
||||||
|
// vfov = 2 · atan( tan(hfov_4:3 / 2) / (4/3) )
|
||||||
|
constexpr float k_source_reference_aspect = 4.f / 3.f;
|
||||||
|
const float half_hfov_4_3 = angles::degrees_to_radians(field_of_view) / 2.f;
|
||||||
|
const float vfov_deg = angles::radians_to_degrees(
|
||||||
|
2.f * std::atan(std::tan(half_hfov_4_3) / k_source_reference_aspect));
|
||||||
|
|
||||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
return {
|
return mat_perspective_left_handed<
|
||||||
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
{0, 1.f / (fov_half_tan), 0, 0},
|
vfov_deg, aspect_ratio, near, far);
|
||||||
{0, 0, far / (far - near), -(near * far) / (far - near)},
|
|
||||||
{0, 0, 1, 0},
|
|
||||||
};
|
|
||||||
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
return {
|
return mat_perspective_left_handed<
|
||||||
{1.f / (aspect_ratio * fov_half_tan), 0, 0, 0},
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
||||||
{0, 1.f / (fov_half_tan), 0, 0},
|
vfov_deg, aspect_ratio, near, far);
|
||||||
{0, 0, (far + near) / (far - near), -(2.f * far * near) / (far - near)},
|
|
||||||
{0, 0, 1, 0},
|
|
||||||
};
|
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
} // namespace omath::source_engine
|
} // namespace omath::source_engine
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Created by Vlad on 3/22/2025.
|
// Created by Vlad on 3/22/2025.
|
||||||
//
|
//
|
||||||
#include "omath/engines/unreal_engine/formulas.hpp"
|
#include "omath/engines/unreal_engine/formulas.hpp"
|
||||||
|
|
||||||
namespace omath::unreal_engine
|
namespace omath::unreal_engine
|
||||||
{
|
{
|
||||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
|
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
|
||||||
@@ -25,22 +24,34 @@ namespace omath::unreal_engine
|
|||||||
}
|
}
|
||||||
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
|
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
|
||||||
{
|
{
|
||||||
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), -right_vector(angles),
|
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
|
||||||
up_vector(angles), cam_origin);
|
up_vector(angles), cam_origin);
|
||||||
}
|
}
|
||||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
|
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
|
||||||
{
|
{
|
||||||
return mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.roll)
|
// UE FRotator is intrinsic Z-Y-X (Yaw → Pitch → Roll applied in local
|
||||||
* mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.yaw)
|
// frame), which for column-vector composition is Rz·Ry·Rx.
|
||||||
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(-angles.pitch);
|
// Pitch and roll axes in omath spin opposite to UE's convention, so
|
||||||
|
// both carry a sign flip.
|
||||||
|
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.yaw)
|
||||||
|
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(-angles.pitch)
|
||||||
|
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(-angles.roll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
||||||
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
||||||
{
|
{
|
||||||
|
// UE stores horizontal FOV in FMinimalViewInfo — use the left-handed
|
||||||
|
// horizontal-FOV builder directly.
|
||||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||||
return mat_perspective_left_handed<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
return mat_perspective_left_handed_horizontal_fov<
|
||||||
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||||
field_of_view, aspect_ratio, near, far);
|
field_of_view, aspect_ratio, near, far);
|
||||||
|
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||||
return mat_perspective_left_handed(field_of_view, aspect_ratio, near, far);
|
return mat_perspective_left_handed_horizontal_fov<
|
||||||
|
float, MatStoreType::ROW_MAJOR, NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
||||||
|
field_of_view, aspect_ratio, near, far);
|
||||||
|
std::unreachable();
|
||||||
}
|
}
|
||||||
} // namespace omath::unreal_engine
|
} // namespace omath::unreal_engine
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ TEST(unit_test_unreal_engine, ForwardVectorRotationRoll)
|
|||||||
{
|
{
|
||||||
omath::unreal_engine::ViewAngles angles;
|
omath::unreal_engine::ViewAngles angles;
|
||||||
|
|
||||||
angles.roll = omath::unreal_engine::RollAngle::from_degrees(-90.f);
|
angles.roll = omath::unreal_engine::RollAngle::from_degrees(90.f);
|
||||||
|
|
||||||
const auto forward = omath::unreal_engine::up_vector(angles);
|
const auto forward = omath::unreal_engine::up_vector(angles);
|
||||||
EXPECT_NEAR(forward.x, omath::unreal_engine::k_abs_right.x, 0.00001f);
|
EXPECT_NEAR(forward.x, omath::unreal_engine::k_abs_right.x, 0.00001f);
|
||||||
|
|||||||
Reference in New Issue
Block a user