Black Sun rising at certain rotation angles

Godot 4.7.2

I started exploring skies and day-night cycles, and been trying out a setup I found that’s simple enough and works quite nice. If the DirectionalLight3D that represents the sun starts in the Rotation position 0.0/90.0/0.0°. This makes the scene always dark in the editor, so I wanted to have the sun default position be at noon, so 0/-90/0. And here the weirdest thing happens:

As the sun touches the horizon, it turns black for the night. But when it comes up over the horizon again, it just stays black and doesn’t turn back to emitting light as it normally does.

I barely have touched any code before this and I don’t really understand most of it, but this seems like really odd behavior and I would like to understand what’s causing it.

Here’s the script I am using:

xtends Node

@export var sun_light:DirectionalLight3D
@export var world_environment:WorldEnvironment
@export var day_duration:float = 10.0

@export var time_of_day:float = 0.0

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	sun_light.rotation_degrees.x = time_of_day


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
	time_of_day += delta / day_duration
	if time_of_day >= 1.0:
		time_of_day -= 1.0
		
	sun_light.rotation_degrees.x = time_of_day * 360.0
	_update_environment()
	
func _update_environment() -> void:
	var deg_x:float = sun_light.rotation_degrees.x
	
	if is_daytime(deg_x):
		var intensity = _sun_intensity(deg_x)
		sun_light.light_energy = intensity
		
func is_daytime(deg_x:float) -> bool:
	return deg_x > 90.0 and deg_x < 270.0
	
func is_nighttime(deg_x:float) -> bool:
	return not is_daytime(deg_x)
	
func _sun_intensity(deg_x:float) -> float:
	var normalized = (deg_x - 90.0) / 180.0
	return sin(normalized * PI)

0.0/-90.0/0 means x = 0, y = -90, z = 0. Changing the y axis does not raise or lower the sun, it rotates it around the horizon.


func is_daytime(deg_x) -> bool:
	return deg_x > 90.0 and deg_x < 270.0

It seems that because of this, you assume noon is at 180 degrees, but noon is actually at 270 degrees on the x axis (or -90). You can see in the editor than when rotation.x is at 0, the sun is actually on the horizon, pointing sideways, and points down when x is -90 or 270

This means the sunrise is at 180, and sunset is at 360, but in _sun_intensity you start ramping up light at 90 and it goes dark at 270.

Do this instead:

func is_daytime(deg_x:float) -> bool:
	return deg_x > 180.0 and deg_x < 360.0

And change the offset in _sun_intensity to 180 instead of 90

func _sun_intensity(deg_x:float) -> float:
	var normalized = (deg_x - 180.0) / 180.0
	return sin(normalized * PI)

However, I recommend another approach entirely, since angles can be difficult to keep track of, and there is no point in reading back the rotation from the sun after setting it, since you already keep track of time inside time_of_day.

time_of_day should be the only source of truth. It should be what dictates the rotation of the sun, and it should also dictate the intensity of the light, instead of indirectly delegating that job to the rotation.

time_of_day and day_duration should hold values in seconds. Since you want your night time to last for 180 degrees of rotation, the equivalent in seconds would be half of day_duration, or day_duration * 0.5.
Do time_of_day += delta to keep track of seconds instead of dividing by day_duration.
Divide here instead sun_light.rotation_degrees.x = time_of_day * 360.0 / day_duration
Use seconds in your _sun_intensity() function too.

A bit of a nitpick but no need for a separate is_nighttime function, simply call “if not is_daytime()”, or do

if is_daytime():
else:

Here’s the full script, I took the liberty of renaming variables and functions for readability, change them back if you feel like it was unnecessary.
Also since you export a WorldEnvironment, I assume you want to change the environment based on the time of day, so I commented some pseudo-code that would fit the new structure

extends Node

@export var sun_light:DirectionalLight3D
@export var world_environment:WorldEnvironment

# Night starts at 0.0
# Day starts at day_duration * 0.5
# Noon starts at day_duration * 0.75
@export var day_duration :float = 10.0
var time :float = day_duration * 0.75
var half_day :float = day_duration * 0.5

func _ready() -> void:
	sun_light.rotation_degrees.x = time * 360.0 / day_duration

func _process(delta: float) -> void:
	time += delta
	if time >= day_duration:
		time -= day_duration

	sun_light.rotation_degrees.x = time * 360.0 / day_duration
	_update_environment()
	
func _update_environment() -> void:
	# If you want the sun to turn black at night instead of emitting no light (light_energy -1.0 instead of 0.0),
	# VVV then uncomment this line here outside of the if statement VVV
	## sun_light.light_energy = get_sun_intensity()
	# And remove the other lines setting sunlight.light_energy below
	if is_daytime():
		sun_light.light_energy = get_sun_intensity()
		# Set world_environment for day? e.g.: _set_world_environment(is_daytime())
	else:
		sun_light.light_energy = 0.0
		# Set world_environment for night? e.g.: _set_world_environment(is_daytime())
		
# func _set_world_environment(day :bool) -> void:
# 	if day:
# 		Set day environment
# 	elif not day:
#		 Set night environment

func is_daytime() -> bool:
	return true if time > day_duration / 2 else false
	
func get_sun_intensity() -> float:
	# Returns a value from 1.0 to -1.0 depending on time of day
	var day_progress: float = (time - half_day) / half_day
	return sin(day_progress * PI)