sometimes you just have to pull random levers until it starts working

The kitchen level in lost contact, which is not very bright and has dark shadows.

The kitchen level in lost contact, which is brighter and has light shadows.

I wrote some level loading code in Godot during my stream about a week ago, and I couldn’t figure out why the lighting in this kitchen scene was not consistent. If I transitioned to the scene after starting the game in a different scene, the lighting was considerably darker and the shadows were very dark. If I played the kitchen scene directly from the editor, the lighting was brighter and the shadows were lighter. How strange!

I complain a lot about how much I hate mutable state — and this is one of those situations. When I looked into it, I found this Github post (quoted here):

Generally the scene change API just shuffles nodes around and it’s nothing you can’t do yourself, so for example updating Environment effects, SDFGI or VoxelGI should often be done manually if the scene geometry or node structure changes enough. Many do this by for example disabling WorldEnviroment for a frame so that all relevant effect data will be re-created and updated. In the demo, you can do this by for example adding this to frame_look_camera.gd:

func _ready():
   var world = get_node("../WorldEnvironment") as WorldEnvironment
 
   var env = world.environment
   world.environment = null
   await RenderingServer.frame_post_draw
   world.environment = env

Usually any visual flashing etc. during these “resets” can be masked with a loading screen, fades etc. Usually only the programmer (you) knows what and how much must be reset when chaning the node tree, so if Godot tries to guess this it can result in some inefficient or redundant updates.

Unfortunately, the posted solution didn’t work well for me, partially because I only wanted this to happen when I transitioned to a new scene so I could hide the change in rendering state during the black screen in a transition. However, it did point me in the right direction. After an extended period of messing around with the code, I finally landed on the following workaround:

# Transition to a new scene
await Fade.fade_out().finished
var packed_scene: PackedScene = load(to_scene)
var new_scene = packed_scene.instantiate()
var tree = get_tree()
tree.root.remove_child(tree.current_scene)
tree.root.add_child(new_scene)
tree.current_scene = new_scene
 
# Disable rendering stuff.
# `Lookup` is a static helper class I wrote that finds nodes of a certain type.
var lights = \
	Lookup.children_where(tree.root, func(a): return a is OmniLight3D)
for light in lights:
	light.visible = false
var world_env: WorldEnvironment = \
	Lookup.child_where(tree.root, func(a): return a is WorldEnvironment)
var sdfgi
if is_instance_valid(world_env):
	sdfgi = world_env.environment.sdfgi_enabled
	world_env.environment.sdfgi_enabled = false
 
# Wait for rendering data to be reset
await new_scene.get_tree().process_frame
 
# Re-enable rendering stuff
for light in lights:
	light.visible = true
if is_instance_valid(world_env):
	world_env.environment.sdfgi_enabled = sdfgi
 
Fade.fade_in()

All of this code really rubs me the wrong way because I’m manipulating a bunch of external things to get the side effect of refreshing whatever effect data SDFGI uses and I think it’s unacceptable that I have to do this. I wish I could just directly tell Godot to refresh the effect data for me, instead of depending on a side effect to do it, but this is the only way I have found for solving this problem :/