Skip to content

Add Always Incremental update mode + queue_update() in ReflectionProbe#55929

Closed
Calinou wants to merge 1 commit into
godotengine:masterfrom
Calinou:reflectionprobe-add-queue-update
Closed

Add Always Incremental update mode + queue_update() in ReflectionProbe#55929
Calinou wants to merge 1 commit into
godotengine:masterfrom
Calinou:reflectionprobe-add-queue-update

Conversation

@Calinou
Copy link
Copy Markdown
Member

@Calinou Calinou commented Dec 14, 2021

The Always Incremental update mode behaves like the Once update mode, but it always updates even when its parameters aren't changed. Since it updates one cubemap face per frame, it will update at 10 Hz when running at 60 FPS. This is not quite as smooth for fast-moving objects such as cars, but it can be sufficient for slower-moving objects and when targeting low-end configurations.

Always Incremental update mode also uses higher-quality roughness filtering compared to the Always Full update mode.

queue_update() can be used to manually force an update on a ReflectionProbe without having to move it for at least one frame. This is useful when modifying a nearby object to ensure the ReflectionProbe has up-to-date reflections.

This change can be backported to 3.x if desired (both GLES3 and GLES2).

This closes #26292. See also godotengine/godot-proposals#2934.

Testing project: test_reflection_probe_queue_update.zip
Press C to cycle between ReflectionProbe update modes, F to force a ReflectionProbe update (only relevant when using the Once update mode) and Space to toggle the dynamic object's visibility.

Performance

OS: Fedora 34
CPU: Intel Core i7-6700K
GPU: GeForce GTX 1080

Update mode FPS
Once 391 FPS (2.56 mspf)
Always Incremental 340 FPS (2.94 mspf)
Always Full 217 FPS (4.61 mspf)

Preview

Update Mode: Once

simplescreenrecorder-2021-12-14_15.16.35.mp4

Update Mode: Always Incremental

simplescreenrecorder-2021-12-14_15.16.45.mp4

Update Mode: Always Full

simplescreenrecorder-2021-12-14_15.16.56.mp4

The Always Incremental update mode behaves like the Once update mode,
but it always updates even when its parameters aren't changed.
Since it updates one cubemap face per frame, it will update at 10 Hz
when running at 60 FPS. This is not quite as smooth for fast-moving
objects such as cars, but it can be sufficient for slower-moving objects
and when targeting low-end configurations.

Always Incremental update mode also uses higher-quality roughness
filtering compared to the Always Full update mode.

`queue_update()` can be used to manually force an update on a
ReflectionProbe without having to move it for at least one frame.
This is useful when modifying a nearby object to ensure the
ReflectionProbe has up-to-date reflections.
@clayjohn
Copy link
Copy Markdown
Member

We need to do a proposal for time slicing of reflection probes. While I am certain this is an improvement over the previous options. I am not sure it will be ideal in practice. The reason for that is that the performance tradeoffs involved will change depending on the complexity of the scene. For a relatively simple scene we can almost ignore the cost of updating the base layer of the reflection map. But for a complex project, rendering the base layer may be even more expensive than updating the roughness mipmaps.

With this PR updates are done as follows:

  1. UPDATE_ONCE: 6 frames to update base reflection, 6 frames to update Mip1, 1 frame for each additional mip level
  2. UPDATE_INCREMENTAL: 6 frames to update base reflection, 6 frames to update Mip1, 1 frame for each additional mip level
  3. UPDATE_FULL: updates all reflections and mips in each frame

I have a feeling we need to untie the rendering update from the roughness map processing. So that users can choose what tradeoffs they would like to make.

@Calinou
Copy link
Copy Markdown
Member Author

Calinou commented Dec 14, 2021

I have a feeling we need to untie the rendering update from the roughness map processing. So that users can choose what tradeoffs they would like to make.

Indeed, I was thinking about this while working on this PR. Maybe we should have 3 properties:

Filter Mode:

  • High Quality
  • Real-Time

Update Policy:

Update Slicing:

  • 1 Face per Frame (Faster, More Latency)
  • 2 Faces per Frame
  • 3 Faces per Frame
  • 6 Faces per Frame (Slower, Less Latency)

From my understanding, the real-time roughness filter needs all 6 faces of the cubemap to be available at the same time. Therefore, choosing it would ignore the Update Slicing property's value.

If this sounds good to you, I can close godotengine/godot-proposals#2934 and open another proposal to describe the changes proposed here.

The sky radiance map generation could also be reworked to use similar properties (with the exception of the Always update policy, which doesn't make sense for the environment sky as the of TIME is detected automatically).

@clayjohn
Copy link
Copy Markdown
Member

That mostly sounds good to me. One thing to keep in mind is that the roughness mips get updated after the base reflection is updated regardless. Accordingly, you don't need to ignore update slicing when using the "real-time" roughness filter. For example, if you set time slice to 6, it will take seven frames to update when using real time filtering.

For update policy, we should connect it to the objects it draws so that it only updates when objects within its range update. I think shadows do this to some extent. So update policy should be "once, on change, always" (there might not be a reason to update always) I am also not sure about update_once. I think it may become unnecessary if we allow a separate sky for reflections.

With respect to the update slicing options, we probably don't need the granularity of 1, 2, 3, and 6. I took a peak at Unity, and they only allow 3 options. All update in one frame, update base in one frame and roughness over 8, and update base over 6 frames and roughness over 8. It may be best for us to just offer 1 and 6. But I don't feel strongly about that. Let's see what users would prefer.

@Calinou
Copy link
Copy Markdown
Member Author

Calinou commented Dec 14, 2021

I think it may become unnecessary if we allow a separate sky for reflections.

Indeed, I agree that with the workaround presented in godotengine/godot-proposals#3593 (comment), there isn't much need to support update_once anymore. Renaming the existing "Once" update mode to "When Changed" will make it more descriptive too. Linking updates to nearby dynamic objects would be useful, but this may impact performance negatively in some cases, so it needs careful thinking.

@jtnicholl
Copy link
Copy Markdown
Contributor

Will this make it into 4.0? If not, could we at least split off the queue_update() method to its own PR and get that in? Moving probes to make them update feels like such a hack.

@Calinou
Copy link
Copy Markdown
Member Author

Calinou commented Sep 15, 2022

Will this make it into 4.0? If not, could we at least split off the queue_update() method to its own PR and get that in? Moving probes to make them update feels like such a hack.

queue_update() can be split into its own PR, but I don't have time to do this right now. I'd recommend you open a pull request for this if you need this in 4.0 (or at least in your own fork).

@Calinou
Copy link
Copy Markdown
Member Author

Calinou commented Oct 3, 2024

I've redone this PR from scratch following #55929 (comment), which adds many new options to choose from.

@Calinou Calinou closed this Oct 3, 2024
@AThousandShips AThousandShips removed this from the 4.x milestone Oct 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reflection probes only refresh on editor restart

5 participants