Skip to content

Commit

Permalink
Enable very large camera zfar
Browse files Browse the repository at this point in the history
  • Loading branch information
Flarkk committed Dec 3, 2024
2 parents 8404d84 + c14e29c commit b4b064b
Show file tree
Hide file tree
Showing 38 changed files with 784 additions and 199 deletions.
249 changes: 249 additions & 0 deletions core/math/frustum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/**************************************************************************/
/* frustum.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#include "frustum.h"

#include "core/math/projection.h"
#include "core/math/transform_3d.h"
#include "core/math/vector2.h"

void Frustum::set_perspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov) {
if (p_flip_fov) {
p_fovy_degrees = get_fovy(p_fovy_degrees, 1.0 / p_aspect);
}

// Inspired by https://iquilezles.org/articles/frustum/
real_t an = Math::deg_to_rad(p_fovy_degrees / 2);
real_t si = sin(an);
real_t co = cos(an);
real_t mag = Math::sqrt(co * co + si * si * p_aspect * p_aspect);

planes[Projection::PLANE_NEAR] = Plane(Vector3(0, 0, 1), -p_z_near);
planes[Projection::PLANE_FAR] = Plane(Vector3(0, 0, -1), p_z_far);
planes[Projection::PLANE_LEFT] = Plane(Vector3(-co, 0, si * p_aspect) / mag, 0);
planes[Projection::PLANE_TOP] = Plane(Vector3(0, co, si), 0);
planes[Projection::PLANE_RIGHT] = Plane(Vector3(co, 0, si * p_aspect) / mag, 0);
planes[Projection::PLANE_BOTTOM] = Plane(Vector3(0, -co, si), 0);
}

void Frustum::set_perspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov, int p_eye, real_t p_intraocular_dist, real_t p_convergence_dist) {
if (p_flip_fov) {
p_fovy_degrees = get_fovy(p_fovy_degrees, 1.0 / p_aspect);
}

real_t left, right, modeltranslation, ymax, xmax, frustumshift;

ymax = p_z_near * tan(Math::deg_to_rad(p_fovy_degrees / 2.0));
xmax = ymax * p_aspect;
frustumshift = (p_intraocular_dist / 2.0) * p_z_near / p_convergence_dist;

switch (p_eye) {
case 1: { // left eye
left = -xmax + frustumshift;
right = xmax + frustumshift;
modeltranslation = p_intraocular_dist / 2.0;
} break;
case 2: { // right eye
left = -xmax - frustumshift;
right = xmax - frustumshift;
modeltranslation = -p_intraocular_dist / 2.0;
} break;
default: { // mono, should give the same result as set_perspective(p_fovy_degrees,p_aspect,p_z_near,p_z_far,p_flip_fov)
left = -xmax;
right = xmax;
modeltranslation = 0.0;
} break;
}

set_frustum(left, right, -ymax, ymax, p_z_near, p_z_far);

// translate matrix by (modeltranslation, 0.0, 0.0)
planes[Projection::PLANE_LEFT].d = -modeltranslation * cos(atan2(left, p_z_near));
planes[Projection::PLANE_RIGHT].d = modeltranslation * cos(atan2(right, p_z_near));
}

void Frustum::set_for_hmd(int p_eye, real_t p_aspect, real_t p_intraocular_dist, real_t p_display_width, real_t p_display_to_lens, real_t p_oversample, real_t p_z_near, real_t p_z_far) {
// we first calculate our base frustum on our values without taking our lens magnification into account.
real_t f1 = (p_intraocular_dist * 0.5) / p_display_to_lens;
real_t f2 = ((p_display_width - p_intraocular_dist) * 0.5) / p_display_to_lens;
real_t f3 = (p_display_width / 4.0) / p_display_to_lens;

// now we apply our oversample factor to increase our FOV. how much we oversample is always a balance we strike between performance and how much
// we're willing to sacrifice in FOV.
real_t add = ((f1 + f2) * (p_oversample - 1.0)) / 2.0;
f1 += add;
f2 += add;
f3 *= p_oversample;

// always apply KEEP_WIDTH aspect ratio
f3 /= p_aspect;

switch (p_eye) {
case 1: { // left eye
set_frustum(-f2 * p_z_near, f1 * p_z_near, -f3 * p_z_near, f3 * p_z_near, p_z_near, p_z_far);
} break;
case 2: { // right eye
set_frustum(-f1 * p_z_near, f2 * p_z_near, -f3 * p_z_near, f3 * p_z_near, p_z_near, p_z_far);
} break;
default: { // mono, does not apply here!
} break;
}
}

void Frustum::set_orthogonal(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_znear, real_t p_zfar) {
planes[Projection::PLANE_NEAR] = Plane(Vector3(0, 0, 1), -p_znear);
planes[Projection::PLANE_FAR] = Plane(Vector3(0, 0, -1), p_zfar);
planes[Projection::PLANE_LEFT] = Plane(Vector3(-1, 0, 0), -p_left);
planes[Projection::PLANE_TOP] = Plane(Vector3(0, 1, 0), p_top);
planes[Projection::PLANE_RIGHT] = Plane(Vector3(1, 0, 0), p_right);
planes[Projection::PLANE_BOTTOM] = Plane(Vector3(0, -1, 0), -p_bottom);
}

void Frustum::set_orthogonal(real_t p_size, real_t p_aspect, real_t p_znear, real_t p_zfar, bool p_flip_fov) {
if (!p_flip_fov) {
p_size *= p_aspect;
}

set_orthogonal(-p_size / 2, +p_size / 2, -p_size / p_aspect / 2, +p_size / p_aspect / 2, p_znear, p_zfar);
}

void Frustum::set_frustum(real_t p_size, real_t p_aspect, Vector2 p_offset, real_t p_near, real_t p_far, bool p_flip_fov) {
if (!p_flip_fov) {
p_size *= p_aspect;
}

set_frustum(-p_size / 2 + p_offset.x, +p_size / 2 + p_offset.x, -p_size / p_aspect / 2 + p_offset.y, +p_size / p_aspect / 2 + p_offset.y, p_near, p_far);
}

void Frustum::set_frustum(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_near, real_t p_far) {
ERR_FAIL_COND(p_right <= p_left);
ERR_FAIL_COND(p_top <= p_bottom);
ERR_FAIL_COND(p_far <= p_near);

real_t left = atan2(p_left, p_near);
real_t top = atan2(p_top, p_near);
real_t right = atan2(p_right, p_near);
real_t bottom = atan2(p_bottom, p_near);

planes[Projection::PLANE_NEAR] = Plane(Vector3(0, 0, 1), -p_near);
planes[Projection::PLANE_FAR] = Plane(Vector3(0, 0, -1), p_far);
planes[Projection::PLANE_LEFT] = Plane(Vector3(-cos(left), 0, -sin(left)), 0);
planes[Projection::PLANE_TOP] = Plane(Vector3(0, cos(top), sin(top)), 0);
planes[Projection::PLANE_RIGHT] = Plane(Vector3(cos(right), 0, sin(right)), 0);
planes[Projection::PLANE_BOTTOM] = Plane(Vector3(0, -cos(bottom), -sin(bottom)), 0);
}

Vector<Plane> Frustum::get_projection_planes(const Transform3D &p_transform) const {
Vector<Plane> result;
Basis tr_inverse = p_transform.basis.inverse().transposed();
for (int i = 0; i < 6; i++) {
result.push_back(p_transform.xform_fast(planes[i], tr_inverse));
}
return result;
}

bool Frustum::is_orthogonal() const {
return (planes[Projection::PLANE_LEFT].normal / Math::abs(planes[Projection::PLANE_LEFT].normal.x)).is_equal_approx(Vector3(-1, 0, 0)) &&
(planes[Projection::PLANE_RIGHT].normal / Math::abs(planes[Projection::PLANE_RIGHT].normal.x)).is_equal_approx(Vector3(1, 0, 0)) &&
(planes[Projection::PLANE_TOP].normal / Math::abs(planes[Projection::PLANE_TOP].normal.y)).is_equal_approx(Vector3(0, 1, 0)) &&
(planes[Projection::PLANE_BOTTOM].normal / Math::abs(planes[Projection::PLANE_BOTTOM].normal.y)).is_equal_approx(Vector3(0, -1, 0));
}

Vector2 Frustum::get_viewport_half_extents() const {
Vector3 res;
planes[Projection::PLANE_NEAR].normalized().intersect_3(planes[Projection::PLANE_RIGHT].normalized(), planes[Projection::PLANE_TOP].normalized(), &res);
return Vector2(res.x, res.y);
}

Vector2 Frustum::get_far_plane_half_extents() const {
Vector3 res;
planes[Projection::PLANE_FAR].normalized().intersect_3(planes[Projection::PLANE_RIGHT].normalized(), planes[Projection::PLANE_TOP].normalized(), &res);
return Vector2(res.x, res.y);
}

bool Frustum::get_endpoints(const Transform3D &p_transform, Vector3 *p_8points) const {
const Projection::Planes intersections[8][3] = {
{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
};

for (int i = 0; i < 8; i++) {
Vector3 point;
Plane a = planes[intersections[i][0]];
Plane b = planes[intersections[i][1]];
Plane c = planes[intersections[i][2]];
bool res = a.intersect_3(b, c, &point);
ERR_FAIL_COND_V(!res, false);
p_8points[i] = p_transform.xform(point);
}

return true;
}

real_t Frustum::get_lod_multiplier() const {
if (is_orthogonal()) {
return get_viewport_half_extents().x;
} else {
const real_t zn = get_z_near();
const real_t width = get_viewport_half_extents().x * 2.0f;
return 1.0f / (zn / width);
}

// Usage is lod_size / (lod_distance * multiplier) < threshold
}

real_t Frustum::get_fov() const {
return Math::rad_to_deg(Math::acos(Math::abs(planes[Projection::PLANE_LEFT].normalized().normal.x))) + Math::rad_to_deg(Math::acos(Math::abs(planes[Projection::PLANE_RIGHT].normalized().normal.x)));
}

real_t Frustum::get_z_near() const {
return -planes[Projection::PLANE_NEAR].normalized().d;
}

real_t Frustum::get_z_far() const {
return planes[Projection::PLANE_FAR].normalized().d;
}

real_t Frustum::get_aspect() const {
Vector2 vp_he = get_viewport_half_extents();
return vp_he.x / vp_he.y;
}

Frustum::Frustum(const Vector<Plane> &p_planes) {
for (int i = 0; i < p_planes.size(); i++) {
planes[i] = p_planes[i];
}
}
85 changes: 85 additions & 0 deletions core/math/frustum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**************************************************************************/
/* frustum.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#ifndef FRUSTUM_H
#define FRUSTUM_H

#include "core/math/plane.h"

struct Transform3D;

struct Frustum {
/*
Planes are stored in the order defined in struct Projection :
enum Projection::Planes {
PLANE_NEAR,
PLANE_FAR,
PLANE_LEFT,
PLANE_TOP,
PLANE_RIGHT,
PLANE_BOTTOM
};
*/

Plane planes[6];

_FORCE_INLINE_ Plane &operator[](int p_index) { return planes[p_index]; }
_FORCE_INLINE_ const Plane &operator[](int p_index) const { return planes[p_index]; }

void set_perspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov = false);
void set_perspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov, int p_eye, real_t p_intraocular_dist, real_t p_convergence_dist);
void set_for_hmd(int p_eye, real_t p_aspect, real_t p_intraocular_dist, real_t p_display_width, real_t p_display_to_lens, real_t p_oversample, real_t p_z_near, real_t p_z_far);
void set_orthogonal(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_znear, real_t p_zfar);
void set_orthogonal(real_t p_size, real_t p_aspect, real_t p_znear, real_t p_zfar, bool p_flip_fov = false);
void set_frustum(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_near, real_t p_far);
void set_frustum(real_t p_size, real_t p_aspect, Vector2 p_offset, real_t p_near, real_t p_far, bool p_flip_fov = false);

Vector<Plane> get_projection_planes(const Transform3D &p_transform) const;

bool is_orthogonal() const;
bool get_endpoints(const Transform3D &p_transform, Vector3 *p_8points) const;
Vector2 get_viewport_half_extents() const;
Vector2 get_far_plane_half_extents() const;
real_t get_lod_multiplier() const;
real_t get_fov() const;
real_t get_z_near() const;
real_t get_z_far() const;
real_t get_aspect() const;

static real_t get_fovy(real_t p_fovx, real_t p_aspect) {
return Math::rad_to_deg(Math::atan(p_aspect * Math::tan(Math::deg_to_rad(p_fovx) * 0.5)) * 2.0);
}

Frustum() = default;
Frustum(const Vector<Plane> &p_planes);
};

#endif // #define FRUSTUM_H
31 changes: 24 additions & 7 deletions core/math/projection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,23 @@ Vector4 Projection::xform_inv(const Vector4 &p_vec4) const {
columns[3][0] * p_vec4.x + columns[3][1] * p_vec4.y + columns[3][2] * p_vec4.z + columns[3][3] * p_vec4.w);
}

void Projection::adjust_perspective_znear(real_t p_new_znear) {
real_t zfar = get_z_far();
real_t znear = p_new_znear;
void Projection::adjust_perspective_znear_zfar(real_t p_new_znear, real_t p_new_zfar) {
real_t deltaZ = p_new_zfar - p_new_znear;
columns[2][2] = -(p_new_zfar + p_new_znear) / deltaZ;
columns[3][2] = -2 * p_new_znear * p_new_zfar / deltaZ;
}

void Projection::adjust_perspective_fov(real_t p_new_fovy_degrees) {
real_t sine, cotangent, aspect;
real_t radians = Math::deg_to_rad(p_new_fovy_degrees / 2.0);

sine = Math::sin(radians);

cotangent = Math::cos(radians) / sine;
aspect = columns[1][1] / columns[0][0];

real_t deltaZ = zfar - znear;
columns[2][2] = -(zfar + znear) / deltaZ;
columns[3][2] = -2 * znear * zfar / deltaZ;
columns[0][0] = cotangent / aspect;
columns[1][1] = cotangent;
}

Projection Projection::create_depth_correction(bool p_flip_y) {
Expand Down Expand Up @@ -164,7 +174,14 @@ Projection Projection::create_fit_aabb(const AABB &p_aabb) {

Projection Projection::perspective_znear_adjusted(real_t p_new_znear) const {
Projection proj = *this;
proj.adjust_perspective_znear(p_new_znear);
real_t zfar = proj.get_z_far();
proj.adjust_perspective_znear_zfar(p_new_znear, zfar);
return proj;
}

Projection Projection::perspective_fov_adjusted(real_t p_new_fovy_degrees) const {
Projection proj = *this;
proj.adjust_perspective_fov(p_new_fovy_degrees);
return proj;
}

Expand Down
Loading

0 comments on commit b4b064b

Please sign in to comment.