Repository: godot
Issue: canvas_item light() significantly darker in editor than in debugging
Labels: bug,topic:editor,topic:2d

### Tested versions

Tested in 4.2 stable only, I'd test in other versions but it's a bit of a pain rn .-.

### System information

Arch linux kernel 6.8.1-arch1-1, wayland, hyprland, I5-1240P w/ integrated graphics (Iris XE)

### Issue description

Title pretty much says it, but essentially when run in the editor my canvas_item shaders are rendering significantly darker in the editor than in a compiled test. I'm not entirely sure why this could be happening, as it happens even when there is only one light (assuming the issue is caused by the lights being averaged, perhaps?)-- all lights in the scene have a blend mode of add, with shadows completely disabled. The CanvasModulate is set to rgb(0,0,0), so that shouldn't be a factor. Tonemapping has no impact either, so at the moment I have no clue what the issue could be.

Here's background.gdscript, which is running for the background
```glsl
shader_type canvas_item;

uniform bool apply_distance_fade;

uniform bool shadows;
uniform bool soft_shadows;
uniform float light_radius;
uniform uint shadow_march_count;
uniform float shadow_march_min_step;
uniform float shadow_march_step_scale;

uniform float ambient_amount : hint_range(0,1);
uniform bool ambient_occlusion;
uniform uint ao_step_count;
uniform float ao_step_size;
uniform float ao_influence : hint_range(0.0, 1.0);

varying vec2 screen_pix_size;
varying mat4 inv_canvas_matrix;
varying mat4 canvas_matrix;

// Beckmann distribution
float kSpec(vec3 N, vec3 H, float m) {
	float alpha = acos(dot(N,H));
	// alpha = max(abs(alpha) - angleTolerance, 0.0) * sign(alpha);
	float mSquared = m * m;
	return (exp(-pow(tan(alpha) / mSquared, 2.0)) / (PI * mSquared * pow(cos(alpha), 4.0)));
}

void vertex() {
	// whyyyy can't I have the matrices in fragment() or light()
	inv_canvas_matrix = inverse(CANVAS_MATRIX);
	canvas_matrix = CANVAS_MATRIX;
}

void fragment() {
	// whyyyyy can't I have SCREEN_PIXEL_SIZE in vertex() or light()
	screen_pix_size = SCREEN_PIXEL_SIZE;
}

vec2 UVtoWorld(vec2 uv) {
	return (vec4(uv / screen_pix_size, 0, 0) * inv_canvas_matrix).xy;
}

vec2 worldToUV(vec2 coord) {
	return (vec4(coord, 0, 0) * inv_canvas_matrix).xy * screen_pix_size;
}

void light() {
	vec2 marchPos = UVtoWorld(SCREEN_UV);
	vec2 lightPos = UVtoWorld(LIGHT_POSITION.xy * screen_pix_size);
	// We need screen positions for some weirdness later
	vec2 lightPosScreen = LIGHT_POSITION.xy;
	vec2 toLight = normalize(lightPos - marchPos);
	// We also need the directions relative to the screen for weirdness mentioned above
	vec2 toLightScreen = normalize(lightPosScreen - SCREEN_UV / screen_pix_size);
	float distToLight = distance(marchPos, lightPos);
	
	float d, t = 0.0;
	// Obscurity - basically, how obscured our ray is
	float obs = 1.0;
	vec2 screenPos = SCREEN_UV / screen_pix_size;
	
	// We can figure out how much the distances should be scaled by figuring out the difference between a screenspace distance of 1px and a worldspace distance
	// of those points
	vec2 worldspaceScaleCalcA = UVtoWorld(vec2(0));
	vec2 worldspaceScaleCalcB = UVtoWorld(vec2(1,0) * screen_pix_size);
	float worldspaceDistanceScale = 1.0 / distance(worldspaceScaleCalcA, worldspaceScaleCalcB);
	
	// Ideally I'd make this its own function but texture_sdf has forced my hand
	if(shadows) {
		obs = 0.0;
		for(uint i = uint(0); i < shadow_march_count; i++) {
			// texture_sdf returns screenspace distance, but we want world space distance
			d = texture_sdf(screen_uv_to_sdf(screenPos * screen_pix_size)) * worldspaceDistanceScale;
			// If we aren't using soft shadows, there's no reason to march through objects
			if(!soft_shadows && d <= 0.0001) {
				obs = 1.0;
				break;
			}
			// I don't entirely understand how this works, but the gist is that we can get a very good looking soft shadow effect by storing how close the ray
			// got to an obstacle
			if(soft_shadows) obs = max(0.5 - d * distToLight / (2.0 * light_radius * t), obs);
			// If we're in shadow no need to continue
			if(obs > 0.99) break;
			// Prevent getting stuck on the border of objects
			// 0.1 is a number I pulled out of my ass but it seems to work well enough
			// light_radius*t/distToLight is the relative size of the light or something idk I'm dumb
			d = max(d * shadow_march_step_scale, shadow_march_min_step  * light_radius*t/distToLight);
			// Apply distance
			t += d;
			// If we pass the light we know we're done
			if(t >= distToLight) break;
			marchPos += d * toLight;
			screenPos += (d / worldspaceDistanceScale) * toLightScreen;
		}
		// Make sure we don't have any weird values lol
		obs = 1.0 - clamp(obs, 0.0, 1.0);
	}
	
	float ao = 1.0;
	if(ambient_occlusion) {
		marchPos = SCREEN_UV / screen_pix_size;
		vec2 Epsilon = vec2(2.0, 0.0);
		// These normals are horrible but they seem to work well enough
		vec2 norm = texture_sdf_normal(sdf_to_screen_uv(SCREEN_UV));
		float res = 0.0;
		// Step size
		vec2 rp;
		// TBH I like vaguely understand how this works but not enough to explain it
		for(uint i = uint(1); i<ao_step_count; i++) {
			rp = marchPos + ao_step_size*float(i)*norm * screen_pix_size;
			res+=(1.0/pow(2.0,float(i)))*(ao_step_size*float(i)-(texture_sdf(screen_uv_to_sdf(rp * screen_pix_size))));
		}
		if(res >= 0.0) {
			ao = 1.0 - ao_influence * clamp(1.0 - ao_step_size / res, 0.0, 1.0);
		}
	}
	
	// Finally get the world distance (including height) for the final magnitude
	vec2 worldPos = (vec4(SCREEN_UV / screen_pix_size, 0, 0) * inv_canvas_matrix).xy;
	vec3 worldPosLight = (vec4(LIGHT_POSITION, 0) * inv_canvas_matrix).xyz;
	float worldDist = distance(vec3(worldPos, 0), worldPosLight);
	
	// PBR Lighting
	float roughness = SPECULAR_SHININESS.r;
	float specular = 1.0 - SPECULAR_SHININESS.g;
	float metallic = SPECULAR_SHININESS.b;
	
	float distance_fade = 1.0 / pow(worldDist / 200.0, 2.0);
	
	LIGHT = COLOR * ambient_amount * dot(NORMAL, vec3(0,0,1)) * ao;
	

	vec4 specularColor = mix(vec4(1), COLOR, metallic);
	vec4 specularAddition = specularColor * LIGHT_COLOR * LIGHT_ENERGY * max(kSpec(NORMAL, (LIGHT_DIRECTION + vec3(0,0,1)) * 0.5, specular), 0.0) * obs;
	vec4 diffuseAddition = roughness * LIGHT_COLOR * LIGHT_ENERGY * COLOR * max(dot(NORMAL, LIGHT_DIRECTION), 0.0) * obs;
	if(apply_distance_fade) {
		specularAddition *= distance_fade;
		diffuseAddition *= distance_fade;
	}
	LIGHT += specularAddition + diffuseAddition;
}
```
And here's foreground.gdshader, running for the objects
```glsl
shader_type canvas_item;

uniform float ambient_amount : hint_range(0,1);

uniform float light_radius;

varying mat4 inv_canvas_matrix;
varying vec2 screen_pix_size;

// Beckmann distribution
float kSpec(vec3 N, vec3 H, float m) {
	float alpha = acos(dot(N,H));
	// alpha = max(abs(alpha) - angleTolerance, 0.0) * sign(alpha);
	float mSquared = m * m;
	return (exp(-pow(tan(alpha) / mSquared, 2.0)) / (PI * mSquared * pow(cos(alpha), 4.0)));
}

void vertex() {
	// whyyyy can't I have the matrices in fragment() or light()
	inv_canvas_matrix = inverse(CANVAS_MATRIX);
	UV = UV + vec2(0.0);
}

void fragment() {
	// whyyyy can't I have SCREEN_PIXEL_SIZE in vertex() or light()
	screen_pix_size = SCREEN_PIXEL_SIZE;
} 

void light() {
	// This one is way simpler
	// World distance including height
	vec2 worldPos = (vec4(SCREEN_UV / screen_pix_size, 0, 0) * inv_canvas_matrix).xy;
	vec3 worldPosLight = (vec4(LIGHT_POSITION, 0) * inv_canvas_matrix).xyz;
	float worldDist = distance(vec3(worldPos, 0), worldPosLight);
	// PBR Lighting
	float roughness = SPECULAR_SHININESS.r;
	float specular = 1.0 - SPECULAR_SHININESS.g;
	float metallic = SPECULAR_SHININESS.b;
	
	// float visibleWidth = light_radius / worldDist;
	// float visibleTheta = 0.0;
	
	LIGHT += COLOR * ambient_amount * dot(NORMAL, vec3(0,0,1));
	
	vec4 specularColor = mix(vec4(1), COLOR, metallic);
	LIGHT += specularColor * LIGHT_COLOR * LIGHT_ENERGY * max(kSpec(NORMAL, (LIGHT_DIRECTION + vec3(0,0,1)) * 0.5, specular), 0.0) / pow(worldDist / 200.0, 2.0);
	LIGHT += roughness * LIGHT_COLOR * LIGHT_ENERGY * COLOR * max(dot(NORMAL, LIGHT_DIRECTION), 0.0) / pow(worldDist / 100.0, 2.0);
}
```
![2024-03-21T12:12:02,051538540-04:00](https://github.com/godotengine/godot/assets/77183348/95dfe94d-25d8-4896-adc5-c65b4a4088c9)
![2024-03-21T12:12:05,163330877-04:00](https://github.com/godotengine/godot/assets/77183348/131cf7d3-6b81-41f4-8eb6-6ac59893e64d)


### Steps to reproduce

Apply the above code as a ShaderMaterial to the various objects. Settings I'm using are this for the background:
![image](https://github.com/godotengine/godot/assets/77183348/95c97e45-f5dc-4385-99f5-9889fbcdefb1)
And then this for the foreground:
![image](https://github.com/godotengine/godot/assets/77183348/a4cff525-da31-4914-8729-d325d3067190)

### Minimal reproduction project (MRP)

[project.zip](https://github.com/godotengine/godot/files/14704288/project.zip)