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

Frustum culling #1642

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .mailmap
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ Tobias Feldballe <[email protected]> <[email protected]>
Tobias Feldballe <[email protected]> <[email protected]>
Jonas Borchelt <[email protected]>
Derek Frogget <[email protected]> <[email protected]>
Nikhil Ghosh <[email protected]>
1 change: 1 addition & 0 deletions copying.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ _the openage authors_ are:
| Astitva Kamble | askastitva | astitvakamble5 à gmail dawt com |
| Haoyang Bi | AyiStar | ayistar à outlook dawt com |
| Michael Seibt | RoboSchmied | github à roboschmie dawt de |
| Nikhil Ghosh | NikhilGhosh75 | nghosh606 à gmail dawt com
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the CI fails because you are missing an | at the end of the table. Then the mailmap entry is also not necessary.


If you're a first-time committer, add yourself to the above list. This is not
just for legal reasons, but also to keep an overview of all those nicknames.
Expand Down
1 change: 1 addition & 0 deletions libopenage/renderer/camera/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_sources(libopenage
camera.cpp
frustum.cpp
)
35 changes: 31 additions & 4 deletions libopenage/renderer/camera/camera.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022-2023 the openage authors. See copying.md for legal info.
// Copyright 2022-2024 the openage authors. See copying.md for legal info.

#include "camera.h"

Expand All @@ -25,14 +25,19 @@ Camera::Camera(const std::shared_ptr<Renderer> &renderer,
moved{true},
zoom_changed{true},
view{Eigen::Matrix4f::Identity()},
proj{Eigen::Matrix4f::Identity()} {
proj{Eigen::Matrix4f::Identity()},
frustum_culling{false} {
this->look_at_scene(Eigen::Vector3f(0.0f, 0.0f, 0.0f));

resources::UBOInput view_input{"view", resources::ubo_input_t::M4F32};
resources::UBOInput proj_input{"proj", resources::ubo_input_t::M4F32};
auto ubo_info = resources::UniformBufferInfo{resources::ubo_layout_t::STD140, {view_input, proj_input}};
this->uniform_buffer = renderer->add_uniform_buffer(ubo_info);

// Make the frustum slightly bigger than the camera's view to ensure objects on the boundary get rendered
float real_zoom = 0.7f * this->default_zoom_ratio * this->zoom;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The factor used here (0.7f) should be configurable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you don't need to calculate the frustum here, since line 30 moves the camera (and calls move_to)

frustum.Recalculate(this->viewport_size, near_distance, far_distance, this->scene_pos, cam_direction, Eigen::Vector3f(0.0f, 1.0f, 0.0f), real_zoom);

log::log(INFO << "Created new camera at position "
<< "(" << this->scene_pos[0]
<< ", " << this->scene_pos[1]
Expand All @@ -44,7 +49,8 @@ Camera::Camera(const std::shared_ptr<Renderer> &renderer,
Eigen::Vector3f scene_pos,
float zoom,
float max_zoom_out,
float default_zoom_ratio) :
float default_zoom_ratio,
bool frustum_culling) :
scene_pos{scene_pos},
viewport_size{viewport_size},
zoom{zoom},
Expand All @@ -54,12 +60,17 @@ Camera::Camera(const std::shared_ptr<Renderer> &renderer,
zoom_changed{true},
viewport_changed{true},
view{Eigen::Matrix4f::Identity()},
proj{Eigen::Matrix4f::Identity()} {
proj{Eigen::Matrix4f::Identity()},
frustum_culling{frustum_culling} {
resources::UBOInput view_input{"view", resources::ubo_input_t::M4F32};
resources::UBOInput proj_input{"proj", resources::ubo_input_t::M4F32};
auto ubo_info = resources::UniformBufferInfo{resources::ubo_layout_t::STD140, {view_input, proj_input}};
this->uniform_buffer = renderer->add_uniform_buffer(ubo_info);

// Make the frustum slightly bigger than the camera's view to ensure objects on the boundary get rendered
float real_zoom = 0.7f * this->default_zoom_ratio * this->zoom;
frustum.Recalculate(this->viewport_size, near_distance, far_distance, this->scene_pos, cam_direction, Eigen::Vector3f(0.0f, 1.0f, 0.0f), real_zoom);
Comment on lines +71 to +72
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here for the factor.


log::log(INFO << "Created new camera at position "
<< "(" << this->scene_pos[0]
<< ", " << this->scene_pos[1]
Expand Down Expand Up @@ -113,6 +124,10 @@ void Camera::move_to(Eigen::Vector3f scene_pos) {

this->scene_pos = scene_pos;
this->moved = true;

// Make the frustum slightly bigger than the camera's view to ensure objects on the boundary get rendered
float real_zoom = 0.7f * this->default_zoom_ratio * this->zoom;
frustum.Recalculate(viewport_size, near_distance, far_distance, scene_pos, cam_direction, Eigen::Vector3f(0.0f, 1.0f, 0.0f), real_zoom);
Comment on lines +129 to +130
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here for the factor.

}

void Camera::move_rel(Eigen::Vector3f direction, float delta) {
Expand Down Expand Up @@ -269,4 +284,16 @@ const std::shared_ptr<renderer::UniformBuffer> &Camera::get_uniform_buffer() con
return this->uniform_buffer;
}

bool Camera::using_frustum_culling() const {
return this->frustum_culling;
}

bool Camera::is_in_frustum(Eigen::Vector3f pos) const{
if (!this->frustum_culling) {
return true;
}

return frustum.is_in_frustum(pos);
}

} // namespace openage::renderer::camera
25 changes: 24 additions & 1 deletion libopenage/renderer/camera/camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "coord/scene.h"
#include "util/vector.h"

#include "frustum.h"

namespace openage::renderer {
class Renderer;
class UniformBuffer;
Expand All @@ -30,6 +32,9 @@ static const Eigen::Vector3f cam_direction{
-1 * (std::sqrt(6.f) / 4),
};

static const float near_distance = 0.01f;
static const float far_distance = 100.0f;

Comment on lines +35 to +37
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be configured for the frustum class and not in the camera file.

/**
* Camera for selecting what part of the ingame world is displayed.
*
Expand All @@ -56,13 +61,15 @@ class Camera {
* @param zoom Zoom level of the camera (defaults to 1.0f).
* @param max_zoom_out Maximum zoom out level (defaults to 64.0f).
* @param default_zoom_ratio Default zoom level calibration (defaults to (1.0f / 49)).
* @param frustum_culling Is frustum culling enables (defaults to false).
*/
Camera(const std::shared_ptr<Renderer> &renderer,
util::Vector2s viewport_size,
Eigen::Vector3f scene_pos,
float zoom = 1.0f,
float max_zoom_out = 64.0f,
float default_zoom_ratio = 1.0f / 49);
float default_zoom_ratio = 1.0f / 49,
bool frustum_culling = false);
~Camera() = default;

/**
Expand Down Expand Up @@ -186,6 +193,10 @@ class Camera {
*/
const std::shared_ptr<renderer::UniformBuffer> &get_uniform_buffer() const;

bool using_frustum_culling() const;

bool is_in_frustum(Eigen::Vector3f pos) const;

private:
/**
* Position in the 3D scene.
Expand Down Expand Up @@ -272,6 +283,18 @@ class Camera {
* Uniform buffer for the camera matrices.
*/
std::shared_ptr<renderer::UniformBuffer> uniform_buffer;

/**
* Is frustum culling enabled? If true, perform frustum culling.
* If false, all frustum checks will return true
*/
bool frustum_culling;

/**
* The frustum used to cull objects
* Will be recalculated regardless of whether frustum culling is enabled
*/
Frustum frustum;
Comment on lines +287 to +297
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this for a while and I think it's better if the frustum is not a camera member for now. Instead, a frustum should be created and returned by the camera using a get_frustum(..) function. The reasoning behind this is that frustum culling will likely be more configurable if the Frustum is directly exposed. Accessing it through the camera class can make it harder to activate/deactivate when debugging. This means more frustums will be created at first (once per frame), but I think this is the better short-term option right now.

To create the frustum, you can add a function like this:

/**
 * Get a frustum for this camera
 *
 * @param scalefactor Resize the frustum to ensure objects on the boundary get rendered
 */
Frustum get_frustum(float scalefactor)

scalefactor would be the variabe that should be configurable that I mentioned earlier (the 0.7f one).

};

} // namespace camera
Expand Down
130 changes: 130 additions & 0 deletions libopenage/renderer/camera/frustum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2024-2024 the openage authors. See copying.md for legal info.

#include "frustum.h"

#include <array>
#include <numbers>
#include <string>
#include <utility>

#include "coord/pixel.h"
#include "coord/scene.h"
#include "renderer/renderer.h"
#include "renderer/resources/buffer_info.h"

namespace openage::renderer::camera {

Frustum::Frustum() :
top_face_normal{Eigen::Vector3f(0.0f, 0.0f, 0.0f)},
top_face_distance{0.0f},
bottom_face_normal{Eigen::Vector3f(0.0f, 0.0f, 0.0f)},
bottom_face_distance{0.0f},
right_face_normal{Eigen::Vector3f(0.0f, 0.0f, 0.0f)},
right_face_distance{0.0f},
left_face_normal{Eigen::Vector3f(0.0f, 0.0f, 0.0f)},
left_face_distance{0.0f},
far_face_normal{Eigen::Vector3f(0.0f, 0.0f, 0.0f)},
far_face_distance{0.0f},
near_face_normal{Eigen::Vector3f(0.0f, 0.0f, 0.0f)},
near_face_distance{0.0f}
{
}

void Frustum::Recalculate(util::Vector2s &viewport_size, float near_distance, float far_distance, Eigen::Vector3f &scene_pos, Eigen::Vector3f cam_direction, Eigen::Vector3f up_direction, float real_zoom) {
// offsets are adjusted by zoom
// this is the same calculation as for the projection matrix
float halfscreenwidth = viewport_size[0] / 2;
float halfscreenheight = viewport_size[1] / 2;

float halfwidth = halfscreenwidth * real_zoom;
float halfheight = halfscreenheight * real_zoom;

Eigen::Vector3f direction = cam_direction.normalized();
Eigen::Vector3f eye = scene_pos;
Eigen::Vector3f center = scene_pos + direction;

// calculate up (u) and right (s) vectors for camera
// these define the camera plane in 3D space that the input
Eigen::Vector3f f = center - eye;
f.normalize();
Eigen::Vector3f s = f.cross(up_direction);
s.normalize();
Eigen::Vector3f u = s.cross(f);
u.normalize();

Eigen::Vector3f near_position = scene_pos - direction * near_distance;
Eigen::Vector3f far_position = scene_pos + direction * far_distance;

// The frustum is a box with 8 points defining it (4 on the near plane, 4 on the far plane)
Eigen::Vector3f near_top_left = near_position - s * halfwidth + u * halfheight;
Eigen::Vector3f near_top_right = near_position + s * halfwidth + u * halfheight;
Eigen::Vector3f near_bottom_left = near_position - s * halfwidth - u * halfheight;
Eigen::Vector3f near_bottom_right = near_position + s * halfwidth - u * halfheight;

Eigen::Vector3f far_top_left = far_position - s * halfwidth + u * halfheight;
Eigen::Vector3f far_top_right = far_position + s * halfwidth + u * halfheight;
Eigen::Vector3f far_bottom_left = far_position - s * halfwidth - u * halfheight;
Eigen::Vector3f far_bottom_right = far_position + s * halfwidth - u * halfheight;

// The near and far planes are easiest to compute, as they should be in the direction of the camera
this->near_face_normal = -1.0f * cam_direction.normalized();
this->far_face_normal = cam_direction.normalized();

this->near_face_distance = this->near_face_normal.dot(near_position) * -1.0f;
this->far_face_distance = this->far_face_normal.dot(far_position) * -1.0f;

// Each left, right, top, and bottom plane are defined by three points on the plane
this->left_face_normal = (near_bottom_left - near_top_left).cross(far_bottom_left - near_bottom_left);
this->right_face_normal = (far_bottom_right - near_bottom_right).cross(near_bottom_right - near_top_right);
this->top_face_normal = (near_top_right - near_top_left).cross(near_top_left - far_top_left);
this->bottom_face_normal = (near_bottom_left - far_bottom_left).cross(near_bottom_right - near_bottom_left);

this->left_face_normal.normalize();
this->right_face_normal.normalize();
this->top_face_normal.normalize();
this->bottom_face_normal.normalize();

this->left_face_distance = this->left_face_normal.dot(near_bottom_left);
this->right_face_distance = this->right_face_normal.dot(far_bottom_right);
this->top_face_distance = this->top_face_normal.dot(near_top_right);
this->bottom_face_distance = this->bottom_face_normal.dot(near_bottom_left);
}

bool Frustum::is_in_frustum(Eigen::Vector3f& pos) const {
// For each plane, if a point is behind one of the frustum planes, it is not within the frustum
float distance;

distance = this->top_face_normal.dot(pos) - this->top_face_distance;
if (distance < 0) {
return false;
}

distance = this->bottom_face_normal.dot(pos) - this->bottom_face_distance;
if (distance < 0) {
return false;
}

distance = this->left_face_normal.dot(pos) - this->left_face_distance;
if (distance < 0) {
return false;
}

distance = this->right_face_normal.dot(pos) - this->right_face_distance;
if (distance < 0) {
return false;
}

distance = this->far_face_normal.dot(pos) - this->far_face_distance;
if (distance < 0) {
return false;
}

distance = this->bottom_face_normal.dot(pos) - this->bottom_face_distance;
if (distance < 0) {
return false;
}

return true;
}

}
46 changes: 46 additions & 0 deletions libopenage/renderer/camera/frustum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2024-2024 the openage authors. See copying.md for legal info.

#pragma once

#include <cmath>
#include <cstddef>
#include <memory>

#include <eigen3/Eigen/Dense>

#include "coord/pixel.h"
#include "coord/scene.h"
#include "util/vector.h"

namespace openage::renderer::camera {

class Frustum
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document al public functions :)

{
public:
Frustum();

void Recalculate(util::Vector2s& viewport_size, float near_distance, float far_distance, Eigen::Vector3f& scene_pos, Eigen::Vector3f cam_direction, Eigen::Vector3f up_direction, float real_zoom);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function names should be lower case, see our styleguides in https://github.com/SFTtech/openage/tree/master/doc/code_style


bool is_in_frustum(Eigen::Vector3f& pos) const;

private:
Eigen::Vector3f top_face_normal;
float top_face_distance;

Eigen::Vector3f bottom_face_normal;
float bottom_face_distance;

Eigen::Vector3f right_face_normal;
float right_face_distance;

Eigen::Vector3f left_face_normal;
float left_face_distance;

Eigen::Vector3f far_face_normal;
float far_face_distance;

Eigen::Vector3f near_face_normal;
float near_face_distance;
Comment on lines +27 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document these members :)


};
} // namespace openage::renderer::camera
1 change: 1 addition & 0 deletions libopenage/renderer/demo/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ add_sources(libopenage
demo_4.cpp
demo_5.cpp
stresstest_0.cpp
stresstest_1.cpp
tests.cpp
util.cpp
)
Expand Down