Godot: TIME should have setting to be stopped while pause to stop shaders animation

Created on 16 Mar 2019  路  20Comments  路  Source: godotengine/godot

Godot version:
3.1

OS/device including version:
no matter

Issue description:
I firstly heard about this problem on russian forum and can confirm - currently TIME is not affected by get_tree().paused which leads to problem - if user want to stop all shader animations while pause he need to manually write shader material and pass a custom uniform with TIME, which is both time-consuming and error-prone

Minimal reproduction project:
TimeBug.zip

bug rendering shaders

Most helpful comment

I've changed my idea a bit so that TIME is scaled and we have TIME_UNSCALED, which is the special case.

I've implemented it here: https://github.com/RandomShaper/godot/commits/shader_time_scale_3.2

Feel free to have a look. If you like it, I'll eventually make a PR of it.

All 20 comments

However it has workaround if you setted up the following code in the example above...

if get_tree().paused:
Engine.time_scale = 0.0
else:
Engine.time_scale = 1.0

UPDATE: Yeah, looks like a false alarm - however I still want to hear opinions from other devs and then close this issue )

It's a bit tricky because pause is defined by SceneTree, but time scaling is controlled by the Main and Engine classes.

had the same problem, pausing my game, the shaders were still working in the background... not looking so good.
Fixed my problem with an workaround which is not that hard to implement by overwriting the _notification method of the Node class. Something like this:

func _notification(what: int) -> void:
    if what == NOTIFICATION_PAUSED:
        stop_shooting()

I have the exact same problem, what does your stop_shooting do to the shader?

in my case just deleted some points from a Line2D ... so the line is not visible anymore.
Depending on your case you can do many things. The simplest one would be to set visible to false for the object that has the shader. The nice one would be to set a uniform in your shader (eg enabled) and in that notification you call set_shader_param to disable the shader.

I feel there should be two shader built-ins. Something like TIME and TIME_WHILE_PAUSED. Most of the time, you want the shaders to pause while the game is paused, but there may be some situations where you, for example, have a shader in a main menu that you want to update while the game is paused. I feel the default TIME behavior should be to pause. I've been manually updating shaders with a custom time uniform, but it's starting to become unmanageable.

Note that @reduz recently implemented shaders global variables. So it could be possible to add a boolean there to know if the game is paused or not, and thus "Stop" the time in all your shaders if needed.

The shader globals seems like it'd be great for stuff like this! Wish we could get it pulled over to a 3.x patch. Could just have a paused time for that. Checking a boolean would mean the time would "jump" on shaders when the game was unpaused instead of resuming, which isn't great. I was poking around, and didn't find any easy/obvious way to get the paused time passed to the shaders. Looks like it's set here for gles3: state.ubo_data.time = storage->frame.time[0]; and here for gles2: state.scene_shader.set_uniform(SceneShaderGLES2::TIME, storage->frame.time[0]);

What's interesting is the particles freeze when the game pauses. Maybe they're not actively using the TIME built-in by default? shaders.particles.set_uniform(ParticlesShaderGLES3::TIME, frame.time[0]);

I do think pausing TIME while the game is paused should be the default behavior, though, as that's what would be desired in MOST cases.

I think the idea is that the TIME built in is for non-gameplay VFX where control is not needed. For example, having pulsating menu buttons, having slight movement in trees, etc. If a user needs to control some aspect of the shader, they should use their own uniform. The largest problem with this, of course, has been the pain of setting the same uniform across all shaders.

For particles it is a little different because they have their own processing time which can be modified with a speed scale property. This highlights the importance of using your own uniform for gameplay-sensitive timing.

Unfortunately, global uniforms are unlikely to be backported to 3.x, so it is the users responsibility to keep track of which shaders need custom time uniforms and to update it themselves.

Right, and I'm using TIME for that kind of stuff. The problem is, when everything else freezes and the trees still sway, rain still falls, pulsing lights still pulse, etc., it looks unpolished. I've been managing this by having a huge array of materials that use TIME and instead pass in a uniform time, but it's becoming a pain to manage. Not only that, but it's not optimal to loop through a huge list of materials and set time on them, when they may not even be in the scene. Also, it's less optimal to do this in general vs the ubo that's set in code that doesn't need to do string lookups and such.

On top of that, it means if I want to edit my shaders and see how they look, I have to swap out the uniform time for the built-in TIME temporarily. Not only that, but sometimes I have to have unique instances of the same material, meaning just putting the material resource in an array isn't enough. Lots of somewhat minor stuff, but it adds up to be a lot, and inevitably there's that one material that gets missed.

I would argue that the menu pulsing and such should be the exception, not the rule, and those could be handled by a uniform to updated with the time since hovering over them or something. Having 2 uniforms would be the ideal case, though, in my opinion.

What would you think about having the following?

  • VisualServer.set_shader_time_scale() that accepts a float (default: 1.0).
  • Time is accumulated unscaled into TIME and scaled into TIME_SCALED.
  • Time rollover is applied to both, individually.

For the simplest use case of just enabling/disabling passage of time, you would set the shader time scale like this in your game:

func set_game_pause(paused):
    get_tree().paused = paused
    VisualServer.set_shader_time_scale(0.0 if paused else 1.0)

This would be a bit more verbose than just having a zero-setup TIME_PAUSED --not a lot of effort, though--, but would open the door to more use cases in exchange.

UPDATE: Here I'm talking about 3.2. In 4.0, thanks to shader globals, I guess you can build your own TIME_PAUSED/TIME_SCALED/TIME_WHATEVER feature quite conveniently.

I've changed my idea a bit so that TIME is scaled and we have TIME_UNSCALED, which is the special case.

I've implemented it here: https://github.com/RandomShaper/godot/commits/shader_time_scale_3.2

Feel free to have a look. If you like it, I'll eventually make a PR of it.

@RandomShaper Nice.

It is a curious thing, because for compatibility you'd think TIME_SCALED
would be the one being added. Though I don't think the majority of cases the desire is that the time runs independently. It would be for something like UI effects, and be needed less often. So it seems like something that is being fixed.

Will be testing it out sometime soon, curious how it works with pause modes, and the individual settings for nodes along the tree.

@RandomShaper Tested it out. From what I can tell UNSCALED_TIME appears to be affected by scaling and no noticeable difference. Pausing also has no effect on the scaled time.

shader test.zip

About which the special case is, my point is that if we introduce VisualServer.set_shader_time_scale(), you'll expect that to affect TIME and most of the _time_ (馃槅) you'll use that. TIME_UNSCALED would therefore be the one you'd use for those things that should keep running unaware of the time scale.

Regarding my implementation, I'll have a look to see what's wrong. Just note that in this approach the pause state of nodes is completely separate from this. See the comment where I tried to explain it for details. In short, I think that having pause and scaled shader time uncoupled gives you more flexibility while not making it much harder to use them together for cases like having the game screen frozen during a pause screen.

@RandomShaper Ah, oh sorry I misunderstood. I see you weren't posing a hypothetical question about future API, it's actually set up this way in the commit. I'll go test again. 馃弮

In the meanwhile, I now am pondering about when would one change the Engine time_scale and VS shader timescale different values.

I was try to manipulate this with the Engine time scaling. The shared intuition I suspect is Engine.time_scale affects everything. If the shader time scale is used, it probably should multiply in the Engine.time_scale as well. So perhaps it is working as intended.

In that case, if only the shader time scale needs playing with separately, it can be done. Then if different scaling values are needed (hard to imagine the use case), a cancelling factor would be used on the shader timescale.

func only_shader(scale):
    VisualServer.set_shader_time_scale(scale)
func slow(scale)
    Engine.timescale = scale
    VisualServer.set_shader_time_scale(0.7 * (1/scale))

And also the TIME_UNSCALED is available always. I'll go see if the second case works.

If this comes to be, definitely attach this VS function signature and notes about Engine time scaling to the TIME / TIME_UNSCALED bits in the docs. 馃樃

@RandomShaper Looking fine actually, expected results. Retested with the VS shader scaling method.
shader test.zip

@avencherus, thanks for testing and feedback! I've finally opened a PR: https://github.com/godotengine/godot/pull/38995

@RandomShaper No problem. Thanks for creating and sharing it. Having something global here will clean things up a lot in projects that have to pause shaders and do so passing deltas into them via local GDScripts.

Fixed by #38995 in 3.2.2.
For 4.0, global uniforms can be used to that effect.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bojidar-bg picture bojidar-bg  路  3Comments

Zylann picture Zylann  路  3Comments

testman42 picture testman42  路  3Comments

RebelliousX picture RebelliousX  路  3Comments

gonzo191 picture gonzo191  路  3Comments