Skip to content

Commit

Permalink
Add support for infinite projection matrix
Browse files Browse the repository at this point in the history
  • Loading branch information
Flarkk committed Aug 22, 2024
1 parent 77dcf97 commit 3287465
Show file tree
Hide file tree
Showing 11 changed files with 54 additions and 25 deletions.
19 changes: 12 additions & 7 deletions core/math/projection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ bool Projection::get_endpoints(const Transform3D &p_transform, Vector3 *p_8point
return true;
}

Vector<Plane> Projection::get_projection_planes(const Transform3D &p_transform) const {
Vector<Plane> Projection::get_projection_planes(const Transform3D &p_transform, bool p_is_far_infinite) const {
/** Fast Plane Extraction from combined modelview/projection matrices.
* References:
* https://web.archive.org/web/20011221205252/https://www.markmorley.com/opengl/frustumculling.html
Expand All @@ -532,13 +532,18 @@ Vector<Plane> Projection::get_projection_planes(const Transform3D &p_transform)
planes.write[0] = p_transform.xform(new_plane);

///////--- Far Plane ---///////
new_plane = Plane(matrix[3] - matrix[2],
matrix[7] - matrix[6],
matrix[11] - matrix[10],
matrix[15] - matrix[14]);
if (p_is_far_infinite) {
new_plane.normal = -planes[0].normal; // take the opposite of near plane normal as the far plane's normal
new_plane.d = SIGN(new_plane.d) * INFINITY; // push far plane to infinity
} else {
new_plane = Plane(matrix[3] - matrix[2],
matrix[7] - matrix[6],
matrix[11] - matrix[10],
matrix[15] - matrix[14]);

new_plane.normal = -new_plane.normal;
new_plane.normalize();
new_plane.normal = -new_plane.normal;
new_plane.normalize();
}

planes.write[1] = p_transform.xform(new_plane);

Expand Down
2 changes: 1 addition & 1 deletion core/math/projection.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ struct [[nodiscard]] Projection {
real_t get_fov() const;
bool is_orthogonal() const;

Vector<Plane> get_projection_planes(const Transform3D &p_transform) const;
Vector<Plane> get_projection_planes(const Transform3D &p_transform, bool p_is_far_infinite = false) const;

bool get_endpoints(const Transform3D &p_transform, Vector3 *p_8points) const;
Vector2 get_viewport_half_extents() const;
Expand Down
6 changes: 6 additions & 0 deletions doc/classes/Camera3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
Returns the RID of a pyramid shape encompassing the camera's view frustum, ignoring the camera's near plane. The tip of the pyramid represents the position of the camera.
</description>
</method>
<method name="is_far_infinite" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if [code skip-lint]far[/code] value is too far apart from [code skip-lint]near[/code] and is going to be ignored during scene culling (no far culling will happen).
</description>
</method>
<method name="is_position_behind" qualifiers="const">
<return type="bool" />
<param index="0" name="world_point" type="Vector3" />
Expand Down
7 changes: 6 additions & 1 deletion scene/3d/camera_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ void Camera3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_near", "near"), &Camera3D::set_near);
ClassDB::bind_method(D_METHOD("get_projection"), &Camera3D::get_projection);
ClassDB::bind_method(D_METHOD("set_projection", "mode"), &Camera3D::set_projection);
ClassDB::bind_method(D_METHOD("is_far_infinite"), &Camera3D::is_far_infinite);
ClassDB::bind_method(D_METHOD("set_h_offset", "offset"), &Camera3D::set_h_offset);
ClassDB::bind_method(D_METHOD("get_h_offset"), &Camera3D::get_h_offset);
ClassDB::bind_method(D_METHOD("set_v_offset", "offset"), &Camera3D::set_v_offset);
Expand Down Expand Up @@ -641,6 +642,10 @@ void Camera3D::set_far(real_t p_far) {
_update_camera_mode();
}

bool Camera3D::is_far_infinite() const {
return _far - _near == _far + _near;
}

void Camera3D::set_cull_mask(uint32_t p_layers) {
layers = p_layers;
RenderingServer::get_singleton()->camera_set_cull_mask(camera, layers);
Expand Down Expand Up @@ -674,7 +679,7 @@ Vector<Plane> Camera3D::get_frustum() const {

Projection cm = _get_camera_projection(_near);

return cm.get_projection_planes(get_camera_transform());
return cm.get_projection_planes(get_camera_transform(), is_far_infinite());
}

TypedArray<Plane> Camera3D::_get_frustum() const {
Expand Down
2 changes: 2 additions & 0 deletions scene/3d/camera_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ class Camera3D : public Node3D {
void set_near(real_t p_near);
void set_frustum_offset(Vector2 p_offset);

bool is_far_infinite() const;

virtual Transform3D get_camera_transform() const;
virtual Projection get_camera_projection() const;

Expand Down
2 changes: 1 addition & 1 deletion scene/3d/xr_nodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ Vector<Plane> XRCamera3D::get_frustum() const {
Size2 viewport_size = get_viewport()->get_visible_rect().size;
// TODO Just use the first view for now, this is mostly for debugging so we may look into using our combined projection here.
Projection cm = xr_interface->get_projection_for_view(0, viewport_size.aspect(), get_near(), get_far());
return cm.get_projection_planes(get_camera_transform());
return cm.get_projection_planes(get_camera_transform(), is_far_infinite());
};

XRCamera3D::XRCamera3D() {
Expand Down
22 changes: 15 additions & 7 deletions servers/rendering/renderer_scene_cull.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2575,6 +2575,7 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu
Projection projection;
bool vaspect = camera->vaspect;
bool is_orthogonal = false;
bool is_far_infinite = camera->zfar - camera->znear == camera->zfar + camera->znear;

switch (camera->type) {
case Camera::ORTHOGONAL: {
Expand Down Expand Up @@ -2606,7 +2607,7 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu
} break;
}

camera_data.set_camera(transform, projection, is_orthogonal, vaspect, jitter, camera->visible_layers);
camera_data.set_camera(transform, projection, is_orthogonal, is_far_infinite, vaspect, jitter, camera->visible_layers);
} else {
// Setup our camera for our XR interface.
// We can support multiple views here each with their own camera
Expand All @@ -2617,6 +2618,7 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu
ERR_FAIL_COND_MSG(view_count == 0 || view_count > RendererSceneRender::MAX_RENDER_VIEWS, "Requested view count is not supported");

float aspect = p_viewport_size.width / (float)p_viewport_size.height;
bool is_far_infinite = camera->zfar - camera->znear == camera->zfar + camera->znear;

Transform3D world_origin = XRServer::get_singleton()->get_world_origin();

Expand All @@ -2628,9 +2630,9 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu
}

if (view_count == 1) {
camera_data.set_camera(transforms[0], projections[0], false, camera->vaspect, jitter, camera->visible_layers);
camera_data.set_camera(transforms[0], projections[0], false, is_far_infinite, camera->vaspect, jitter, camera->visible_layers);
} else if (view_count == 2) {
camera_data.set_multiview_camera(view_count, transforms, projections, false, camera->vaspect);
camera_data.set_multiview_camera(view_count, transforms, projections, false, is_far_infinite, camera->vaspect);
} else {
// this won't be called (see fail check above) but keeping this comment to indicate we may support more then 2 views in the future...
}
Expand Down Expand Up @@ -3027,7 +3029,7 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c
Instance *render_reflection_probe = instance_owner.get_or_null(p_reflection_probe); //if null, not rendering to it

// Prepare the light - camera volume culling system.
light_culler->prepare_camera(p_camera_data->main_transform, p_camera_data->main_projection);
light_culler->prepare_camera(p_camera_data->main_transform, p_camera_data->main_projection, p_camera_data->is_far_infinite);

Scenario *scenario = scenario_owner.get_or_null(p_scenario);
Vector3 camera_position = p_camera_data->main_transform.origin;
Expand Down Expand Up @@ -3078,7 +3080,13 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c

/* STEP 2 - CULL */

Vector<Plane> planes = p_camera_data->main_projection.get_projection_planes(p_camera_data->main_transform);
Vector<Plane> planes = p_camera_data->main_projection.get_projection_planes(p_camera_data->main_transform, p_camera_data->is_far_infinite);

if (p_camera_data->is_far_infinite) {
planes.write[1].normal = -planes[0].normal; // take the opposite of near plane normal as the far plane's normal
planes.write[1].d = SIGN(planes[1].d) * INFINITY; // push far plane to infinity
}

cull.frustum = Frustum(planes);

Vector<RID> directional_lights;
Expand Down Expand Up @@ -3474,7 +3482,7 @@ void RendererSceneCull::render_empty_scene(const Ref<RenderSceneBuffers> &p_rend
RENDER_TIMESTAMP("Render Empty 3D Scene");

RendererSceneRender::CameraData camera_data;
camera_data.set_camera(Transform3D(), Projection(), true, false);
camera_data.set_camera(Transform3D(), Projection(), true, false, false);

scene_render->render_scene(p_render_buffers, &camera_data, &camera_data, PagedArray<RenderGeometryInstance *>(), PagedArray<RID>(), PagedArray<RID>(), PagedArray<RID>(), PagedArray<RID>(), PagedArray<RID>(), PagedArray<RID>(), environment, RID(), compositor, p_shadow_atlas, RID(), scenario->reflection_atlas, RID(), 0, 0, nullptr, 0, nullptr, 0, nullptr);
#endif
Expand Down Expand Up @@ -3552,7 +3560,7 @@ bool RendererSceneCull::_render_reflection_probe_step(Instance *p_instance, int

RENDER_TIMESTAMP("Render ReflectionProbe, Step " + itos(p_step));
RendererSceneRender::CameraData camera_data;
camera_data.set_camera(xform, cm, false, false);
camera_data.set_camera(xform, cm, false, false, false);

Ref<RenderSceneBuffers> render_buffers = RSG::light_storage->reflection_probe_atlas_get_render_buffers(scenario->reflection_atlas);
_render_scene(&camera_data, render_buffers, environment, RID(), RID(), RSG::light_storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, RID(), shadow_atlas, reflection_probe->instance, p_step, mesh_lod_threshold, use_shadows);
Expand Down
8 changes: 5 additions & 3 deletions servers/rendering/renderer_scene_render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@
/////////////////////////////////////////////////////////////////////////////
// CameraData

void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_vaspect, const Vector2 &p_taa_jitter, const uint32_t p_visible_layers) {
void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_is_far_infinite, bool p_vaspect, const Vector2 &p_taa_jitter, const uint32_t p_visible_layers) {
view_count = 1;
is_orthogonal = p_is_orthogonal;
is_far_infinite = p_is_far_infinite;
vaspect = p_vaspect;

main_transform = p_transform;
Expand All @@ -47,12 +48,13 @@ void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform,
taa_jitter = p_taa_jitter;
}

void RendererSceneRender::CameraData::set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_vaspect) {
void RendererSceneRender::CameraData::set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_is_far_infinite, bool p_vaspect) {
ERR_FAIL_COND_MSG(p_view_count != 2, "Incorrect view count for stereoscopic view");

visible_layers = 0xFFFFFFFF;
view_count = p_view_count;
is_orthogonal = p_is_orthogonal;
is_far_infinite = p_is_far_infinite;
vaspect = p_vaspect;
Vector<Plane> planes[2];

Expand All @@ -61,7 +63,7 @@ void RendererSceneRender::CameraData::set_multiview_camera(uint32_t p_view_count

// 1. obtain our planes
for (uint32_t v = 0; v < view_count; v++) {
planes[v] = p_projections[v].get_projection_planes(p_transforms[v]);
planes[v] = p_projections[v].get_projection_planes(p_transforms[v], p_is_far_infinite);
}

// 2. average and normalize plane normals to obtain z vector, cross them to obtain y vector, and from there the x vector for combined camera basis.
Expand Down
5 changes: 3 additions & 2 deletions servers/rendering/renderer_scene_render.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ class RendererSceneRender {
// flags
uint32_t view_count;
bool is_orthogonal;
bool is_far_infinite;
uint32_t visible_layers;
bool vaspect;

Expand All @@ -308,8 +309,8 @@ class RendererSceneRender {
Projection view_projection[RendererSceneRender::MAX_RENDER_VIEWS];
Vector2 taa_jitter;

void set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_vaspect, const Vector2 &p_taa_jitter = Vector2(), uint32_t p_visible_layers = 0xFFFFFFFF);
void set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_vaspect);
void set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_is_far_infinite, bool p_vaspect, const Vector2 &p_taa_jitter = Vector2(), uint32_t p_visible_layers = 0xFFFFFFFF);
void set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_is_far_infinite, bool p_vaspect);
};

virtual void render_scene(const Ref<RenderSceneBuffers> &p_render_buffers, const CameraData *p_camera_data, const CameraData *p_prev_camera_data, const PagedArray<RenderGeometryInstance *> &p_instances, const PagedArray<RID> &p_lights, const PagedArray<RID> &p_reflection_probes, const PagedArray<RID> &p_voxel_gi_instances, const PagedArray<RID> &p_decals, const PagedArray<RID> &p_lightmaps, const PagedArray<RID> &p_fog_volumes, RID p_environment, RID p_camera_attributes, RID p_compositor, RID p_shadow_atlas, RID p_occluder_debug_tex, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, float p_screen_mesh_lod_threshold, const RenderShadowData *p_render_shadows, int p_render_shadow_count, const RenderSDFGIData *p_render_sdfgi_regions, int p_render_sdfgi_region_count, const RenderSDFGIUpdateData *p_sdfgi_update_data = nullptr, RenderingMethod::RenderInfo *r_render_info = nullptr) = 0;
Expand Down
4 changes: 2 additions & 2 deletions servers/rendering/rendering_light_culler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan
return true;
}

bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix) {
bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix, bool p_is_far_infinite) {
data.debug_count++;
if (data.debug_count >= 120) {
data.debug_count = 0;
Expand All @@ -495,7 +495,7 @@ bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, co
}

// Get the camera frustum planes in world space.
data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform);
data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform, p_is_far_infinite);
DEV_CHECK_ONCE(data.frustum_planes.size() == 6);

data.regular_cull_planes.num_cull_planes = 0;
Expand Down
2 changes: 1 addition & 1 deletion servers/rendering/rendering_light_culler.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class RenderingLightCuller {
public:
// Before each pass with a different camera, you must call this so the culler can pre-create
// the camera frustum planes and corner points in world space which are used for the culling.
bool prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix);
bool prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix, bool p_is_far_infinite);

// REGULAR LIGHTS (SPOT, OMNI).
// These are prepared then used for culling one by one, single threaded.
Expand Down

0 comments on commit 3287465

Please sign in to comment.