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

Conversation

Flarkk
Copy link
Contributor

@Flarkk Flarkk commented Dec 3, 2024

This PR allows rendering with unlimited camera zfar - or rather limited by the sole floating point range i.e. ~3.4e+38 in single precision.

This effectively untaps the potential of reverse-z depth buffer with single precision build.

Outline

Currently zfar cannot be pushed further than barely ~1e6 times znear because of numerical precision limitations with some methods of struct Projection.
With the default znear = 0.05 this sets the maximum view distance to ~50 km.
While it's enough in most cases, it can be a hard limitation for open worlds, space games, and any very large scene.
Beyond this limit, scene rendering currently breaks and Godot starts spaming errors.

This PR removes this limitation with 3 key changes :

  • Stores the 6 frustum planes alongside the projection matrix in the rendering server's internals. Both are equivalent representations except that the former allows retrieving near/far distances and boundary points with much more accurate numerical precision
  • Restricts the use of the matrix representation to the sole cases where projections / un-projections are performed. Use the 6 planes in any other situation (typically to retrieve znear / zfar, get boundary points, etc...)
  • Bundles the 6 planes representation into a new struct Frustum with a few helper methods for convenience. This avoids relying of Vector<Plane> everywhere and improves readability

This PR focuses on in-game rendering and camera preview in Editor.
On top of it, PR #100896 allows editing very distant objects right in Godot's editor

TODOs (now complete)

Demo : full-scale scene

large_zfar.zip

Vegetation is ~1m tall and ~10m from the observer
The terrain is 10 km large
The moon's radius is 1,700 km and is 400,000 km from the observer
The Andromeda galaxy is 152,000 light-years wide and is 2.5 million light years from the observer

image

Performance

This PR improves very slightly the frame process time of an empty scene by ~ 0.5%.
This is likely due to the significant number of planes retrieval operations avoided by the fact the 6 planes are already made available.

Before After
Capture d’écran du 2024-12-06 13-16-21 Capture d’écran du 2024-12-06 12-56-13

Effects compatibility

I spent a considerable amount of time making sure all effects work with large zfar.
Some require additional PRs to be merged.
Test project for effects : effects.zip (make sure to preview the large znear camera in each scene)

Effects Works with large zfar
Bokeh DOF ✔️ with #99755
SSR ✔️ with #99693
SSIL ✔️
SSAO ✔️
Sky ✔️
Shadows ✔️
SDFGI ✔️ with #104120
SSS ✔️ with #99755
Fog ✔️

Caveats

  • XR is left unchanged for now because this would require the XR APIs to return frustum planes in addition to the projection matrix. This may be the subject of another PR
  • Unlike Forward+, Mobile and Compatibility renderers use 24bit UNORM depth buffers which don't get any benefit from reverse-z, so z-fighting will happen near the far plane when zfar is very large. This can notably cause the sky to occlude very distant objects.

Optional dependencies

#99961 and #99962 may help ensuring non-regression on Viewport and Camera3D.


Closes godotengine/godot-proposals#10515
Closes #55070
Supersedes #95944

@Flarkk Flarkk requested review from a team as code owners December 3, 2024 21:43
@Flarkk
Copy link
Contributor Author

Flarkk commented Dec 4, 2024

Rebased and fixed style issues to allow CI to run.

@Flarkk Flarkk force-pushed the large_zfar branch 3 times, most recently from 06693c1 to b8eda47 Compare December 4, 2024 09:09
@Mickeon Mickeon added this to the 4.x milestone Dec 4, 2024
@Flarkk Flarkk changed the title Unbound camera zfar for scene rendering Enable rendering with unbounded far distance Dec 5, 2024
@Flarkk
Copy link
Contributor Author

Flarkk commented Dec 9, 2024

Rebased on top of #99974

@Calinou
Copy link
Member

Calinou commented Dec 23, 2024

Objects farther than 1e20 disappear when Occlusion culling is on

This can probably be worked around by assuming the object is visible when it's further away from the camera than this distance (and skipping any occlusion culling checks).

Embree doesn't support double precision, so precision=double builds can't do any better here.

@Flarkk
Copy link
Contributor Author

Flarkk commented Dec 23, 2024

Haven't got back to this PR yet, but I think it's likely caused by an overflow with normalize() or length() somewhere.

1e19 ~ 1e20 is the threshold value that exceeds the maximum representable float value when squared (~1e38).

If this overflow happens in Embree then yes, we don't have any other choice but trying to work around it. Will take a look at your suggestion once I complete the work on another incoming related PR : uncapping zfar in the editor itself.

@Flarkk
Copy link
Contributor Author

Flarkk commented Dec 29, 2024

I got the culprit : indeed a call to length() that overflows on very far away geometry :

float min_depth = (closest_point - p_cam_position).length();

Just issued #100907 that solves the problem, and enables proper occlusion culling even on very far away objects.


Edit : turns out that this fix inadvertently reverts another fix. I'm figuring out alternatives

@Monniasza
Copy link

Is this issue being worked on? I'm trying to make a spaceflight simulation game that has objects both near and far.

@Flarkk
Copy link
Contributor Author

Flarkk commented Feb 20, 2025

Yes it is, although I couldn't spare time the past weeks.

Note that if the farthest objects in your scene are under ~1e19m close to the camera you will not encounter any occlusion culling issue with this PR. Also you can still exclude individual objects from occlusion culling processing in case they're farther than that.

@Flarkk
Copy link
Contributor Author

Flarkk commented Feb 27, 2025

Objects farther than 1e20 disappear when Occlusion culling is on

Just worked around this issue by skipping occlusion checks when the object is too far away as @Calinou suggested :
https://github.com/Flarkk/godot/blob/5eb25f064e1da061991da1658d258b812ebf1b99/servers/rendering/renderer_scene_occlusion_cull.h#L78-L80

A clean fix would be to move the occlusion culling buffer back to linear z instead of the linear euclidian distance to the fragment. This would get rid of float min_depth = (closest_point - p_cam_position).length(); in favor of something like float min_z = closest_point.z - p_cam_position.z; which doesn't invoke the overflowing length() function anymore.

This was actually already implemented in #97712 but abandoned for other reasons (see below).
I'd advocate for revisiting and merging #97712 as it would solve both the above issue and the one that originally prompted it.


The occlusion culling buffer was recently moved to euclidian distance by #98257 as one way of solving #94210.
Moving it to linear z instead was the second considered option at that time (#97712), but it was abandoned due to an open question about some edge case.

I wrapped my mind around it since then and I believe the abandoned option was actually the best choice performance wise and that the edge case is not worth the overhead introduced by the merged PR.

See the discussion in #100907

@Flarkk
Copy link
Contributor Author

Flarkk commented Mar 4, 2025

For reference - Talking points from today's rendering meeting, with my comments added.
It deserves further discussion next time.

can this be used to also implement the custom projections proposal

Not specifically, but it doesn’t prevent it either.

can the culler handle infinite far or will it lead to problems?

Yes it can. This is one of the key benefits of this PR.
When zfar is very large, the matrix becomes an infinite projection and zfar cannot be retrieved anymore, which used to break the culler.
Now this PR also stores the 6 planes aside of the matrix, zfar is read directly from the far plane.

maybe expose this in the camera node, with either a way to specify a completely custom matrix or a checkbox that just enables inf far while still using all the other camera options.

This is not how it works. This PR doesn't force zfar to inf or to anything.
It takes whatever the user input is, compute the 6 frustum planes, store them, and use them to retrieve frustum boundaries wherever needed across the codebase, in place of the projection matrix.
The main benefit is users can now set zfar to any value in the float32 range without breaking rendering.

maybe split this into two PRs?

More context needed

@Flarkk
Copy link
Contributor Author

Flarkk commented Mar 14, 2025

I just fixed the crash with SDFGI (see #104120).
Now all effects work as expected with this PR and large zfar.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

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

Tested locally, it works as expected. I've tested all 3D demo projects with all rendering methods and couldn't notice a visual regression. I also made a testing project that showcases an exponentially faster-moving camera and spheres, inspired by this demo:

Testing project: test_pr_99986.zip

I suggest modifying the 3D editor's View configuration dialog to allow using larger Z far values than 1 million units:

image

For comparison, this is already allowed in the Camera3D node properties.

Setting the maximum to INT32_MAX - 1 should be good enough, I suppose. (If double-precision builds support higher values, then the maximum value should be adjusted accordingly when using a double-precision build.)

@Flarkk
Copy link
Contributor Author

Flarkk commented Mar 15, 2025

Thanks @Calinou for giving it a test.

I suggest modifying the 3D editor's View configuration dialog to allow using larger Z far values than 1 million units

Very large z-far in editor comes with another round of numerical precision issues with picking and gizmos.
PR #100896 solves all that and is the natural next step after this one gets merged. It's already in a finalized state so you can give it a try now as well.

Copy link
Member

@AThousandShips AThousandShips left a comment

Choose a reason for hiding this comment

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

Also need to resolve the previous suggestions marked as resolved

@Flarkk
Copy link
Contributor Author

Flarkk commented Mar 16, 2025

Thanks @AThousandShips. Did I miss anything specific on the previous reviews? Will take a closer look.

@AThousandShips
Copy link
Member

All the comments on the comments format by arkology that you marked as resolved

@Flarkk
Copy link
Contributor Author

Flarkk commented Mar 16, 2025

Oh I see. I can't remember how I could close them without fixing... My bad. Will do it ASAP

@Flarkk Flarkk force-pushed the large_zfar branch 2 times, most recently from dad54d2 to a69004f Compare March 17, 2025 10:59
@Flarkk
Copy link
Contributor Author

Flarkk commented Mar 17, 2025

The latest pushes :

  • Resolve all suggestions so far
  • Remove a few unnecessary functions of struct Frustum now some of Projection's functions can be brought back without numerical precision issue1
  • Document the assumptions taken in Frustum's implementation as done in Projection
  • Harmonize unit tests of Frustum with those of Projection

Footnotes

  1. Since Simplify and fix Projection's getter functions #100209 is merged, get_lod_multiplier(), get_fov() and get_aspect() do not depend on zfar extraction anymore in struct Projection so they are now numerically reliable even with large zfar. Their Frustum counterparts are not necessary anymore.

@Flarkk
Copy link
Contributor Author

Flarkk commented Mar 17, 2025

Added a bunch of comments to make the use of Frustum vs Projection clearer, and hopefully avoid regressions related to numerical precision in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants