Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable rendering with unbounded far distance #99986

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Flarkk marked this conversation as resolved.
Show resolved Hide resolved

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
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
left = -xmax + frustumshift;
right = xmax + frustumshift;
modeltranslation = p_intraocular_dist / 2.0;
} break;
case 2: { // right eye
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
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)
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
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)
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
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.
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
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.
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
real_t add = ((f1 + f2) * (p_oversample - 1.0)) / 2.0;
f1 += add;
f2 += add;
f3 *= p_oversample;

// always apply KEEP_WIDTH aspect ratio
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
f3 /= p_aspect;

switch (p_eye) {
case 1: { // left eye
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
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
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
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!
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
} 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));
}
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
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
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
}

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 // 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;
Flarkk marked this conversation as resolved.
Show resolved Hide resolved
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
4 changes: 3 additions & 1 deletion core/math/projection.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ struct [[nodiscard]] Projection {
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);
void adjust_perspective_znear(real_t p_new_znear);
void adjust_perspective_znear_zfar(real_t p_new_znear, real_t p_new_zfar);
void adjust_perspective_fov(real_t p_new_fovy_degrees);

static Projection create_depth_correction(bool p_flip_y);
static Projection create_light_atlas_rect(const Rect2 &p_rect);
Expand All @@ -92,6 +93,7 @@ struct [[nodiscard]] Projection {
static Projection create_frustum_aspect(real_t p_size, real_t p_aspect, Vector2 p_offset, real_t p_near, real_t p_far, bool p_flip_fov = false);
static Projection create_fit_aabb(const AABB &p_aabb);
Projection perspective_znear_adjusted(real_t p_new_znear) const;
Projection perspective_fov_adjusted(real_t p_new_fovy_degrees) const;
Plane get_projection_plane(Planes p_plane) const;
Projection flipped_y() const;
Projection jitter_offseted(const Vector2 &p_offset) const;
Expand Down
Loading
Loading