Alrighty: Got some really amazing stuff working. The world script uses the camera view position, the shader was rewritten almost entirely to simplify it while supporting more features and extra detail such as the horizon lighting up as the sun approaches it. My version is meant to work with two lights, a sun and a moon. Here's a view of it with the script and shader:
extends WorldEnvironment
@onready var cam = get_viewport().get_camera_3d()
func _process(_delta):
var pos = cam.global_position.normalized()
environment.sky.sky_material.set_shader_parameter("inv_horizon_matrix", Quaternion(pos, Vector3.UP))
// Rotating sky shader similar to Godot 4.2.2 ProceduralSkyMaterial
// Accepts two lights (sun and moon) with light color and intensity controlling the sky
shader_type sky;
render_mode use_debanding;
uniform mat3 inv_horizon_matrix;
uniform vec4 sky_top_color : source_color = vec4(0.4, 0.6, 0.8, 1.0);
uniform vec4 sky_horizon_color : source_color = vec4(0.6, 0.7, 0.8, 1.0);
uniform vec4 sky_bottom_color : source_color = vec4(0.2, 0.1, 0.1, 1.0);
uniform float sky_top_curve : hint_range(0, 1) = 0.25;
uniform float sky_bottom_curve : hint_range(0, 1) = 0.025;
uniform float sky_energy = 0.0;
uniform float cover_energy = 1.0;
uniform float sun_energy = 1.0;
uniform float moon_energy = 1.0;
uniform float sun_scale = 1.0;
uniform float moon_scale = 1.0;
uniform float sun_disk : hint_range(0, 1) = 0.05;
uniform float moon_disk : hint_range(0, 1) = 0.05;
uniform sampler2D sky_cover : filter_linear, source_color, hint_default_black;
void sky() {
vec3 eyedir_horiz = inv_horizon_matrix * EYEDIR;
float v_angle = acos(clamp(eyedir_horiz.y, -1.0, 1.0));
float a0 = (1.0 - v_angle / (PI * 0.5));
float a1 = (v_angle - (PI * 0.5)) / (PI * 0.5);
vec3 sky_gradient = mix(sky_top_color.rgb, sky_horizon_color.rgb, clamp(pow(1.0 - a0, 1.0 / sky_top_curve), 0.0, 1.0));
vec3 sky = sky_gradient * sky_energy;
if (LIGHT0_ENABLED) {
float angle = acos(dot(LIGHT0_DIRECTION, EYEDIR));
if (angle < sun_scale) {
float c1 = (angle - LIGHT0_SIZE) / (sun_scale - LIGHT0_SIZE);
float c2 = clamp(sun_disk + pow(1.0 - a0, 1.0 / sky_top_curve), 0.0, 1.0);
sky = mix(sky, LIGHT0_COLOR * LIGHT0_ENERGY, clamp(pow(1.0 - c1, 1.0 / c2), 0.0, 1.0));
}
sky += (sky_gradient * LIGHT0_COLOR) * sun_energy * LIGHT0_ENERGY * (1.0 - angle / PI / 2.0);
}
if (LIGHT1_ENABLED) {
float angle = acos(dot(LIGHT1_DIRECTION, EYEDIR));
if (angle < moon_scale) {
float c1 = (angle - LIGHT1_SIZE) / (moon_scale - LIGHT1_SIZE);
float c2 = clamp(moon_disk + pow(1.0 - a0, 1.0 / sky_top_curve), 0.0, 1.0);
sky = mix(sky, LIGHT1_COLOR * LIGHT1_ENERGY, clamp(pow(1.0 - c1, 1.0 / c2), 0.0, 1.0));
}
sky += (sky_gradient * LIGHT1_COLOR) * moon_energy * LIGHT1_ENERGY * (1.0 - angle / PI / 2.0);
}
vec4 sky_cover_texture = texture(sky_cover, SKY_COORDS);
sky += sky_cover_texture.rgb * sky_cover_texture.a * cover_energy;
COLOR = mix(sky, sky_gradient * sky_bottom_color.rgb, clamp(1.0 - pow(1.0 - a1, 1.0 / sky_bottom_curve), 0.0, 1.0));
}
One more algorithm for it to work, last thing and very simple at least for math experts 😅 On the GDScript side I need a float that tells me by what amount the player is standing under the sun based on the light's rotation: I'll be using it to change the color of the light so it gets whiter / yellower / redder, or even better plug it into the temperature which I see is an upcoming feature in Godot 4.3. No need to worry about the colorization part, my only issue is the vector conversion for extracting the 0 - 1 range indicating the player's location relative to the sun, it's always the complex mathematics I get stuck on.
The variable should be 1.0 when the player is standing right under the sun and it's exactly above your head, 0.5 when the player is on the planet's equator relative to the sun meaning they're right on the rim of where the sun is shining and seeing it right on the horizon, 0.0 when the player is oppositely on the other side of the planet from the sun's perspective.