Skip to content

Apply sky brightness multiplier in correct color space#110911

Open
Kaleb-Reid wants to merge 1 commit into
godotengine:masterfrom
Kaleb-Reid:fix-clear-colour
Open

Apply sky brightness multiplier in correct color space#110911
Kaleb-Reid wants to merge 1 commit into
godotengine:masterfrom
Kaleb-Reid:fix-clear-colour

Conversation

@Kaleb-Reid

@Kaleb-Reid Kaleb-Reid commented Sep 25, 2025

Copy link
Copy Markdown
Contributor

In the RD renderers, if we are using fog and a solid colour background, clear_color should not be in linear encoding before being passed to the shader, as this will be converted again later. If we are using a solid colour background and not using fog, the energy needs to be multiplied after converting to linear encoding. In scenarios where the sky is drawn and we are not using fog or not using solid colour background, the multiplication happens in the shader.

Compatibility is similar except after the energy is multiplied in, clear_color needs to be converted back to non-linear srgb encoding. This pr also moves the energy multiplication in the shader to be after converting the fragment colour to linear encoding.

This should:

  • Apply brightness multipliers for the sky on colours with the correct color encoding.
  • Fix applying the brightness multiplier twice with solid colour background and fog.
  • Fix incorrectly passing clear_color with linear encoding to sky material in the RD renderers, which converts it again later.

Fixes #108779

@Kaleb-Reid Kaleb-Reid requested a review from a team as a code owner September 25, 2025 21:31
@AThousandShips AThousandShips added bug topic:rendering topic:3d cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release labels Sep 26, 2025
@AThousandShips AThousandShips added this to the 4.6 milestone Sep 26, 2025
@AThousandShips AThousandShips changed the title Use correct colour space for fog clear colour in Forward+ and Mobile Use correct color space for fog clear color in Forward+ and Mobile Sep 26, 2025
@allenwp

allenwp commented Sep 26, 2025

Copy link
Copy Markdown
Contributor

All Color in Godot is assumed to be encoded using the nonlinear sRGB transfer function. Any cases where this is not the case must be clearly documented. (As in, any time a Color is expected to use linear encoding, it should be clearly documented.)

Would you mind going through the chain of functions and clearly documenting (with comments or otherwise) when functions accept linear-encoded values instead of the expected nonlinear sRGB-encoded values?

When writing this, you can simply say that “color must be linear-encoded” or “color must use linear encoding”.

As a side note, I see some multiplications against an energy multiplier that are happening on the colour channels: indeed, this multiplication must occur on linear-encoded values otherwise it will produce incorrect results.

@Kaleb-Reid

Kaleb-Reid commented Sep 26, 2025

Copy link
Copy Markdown
Contributor Author

@allenwp I updated the pr with a better description of what is happening. Where I removed the srgb_to_linear call, clear_color is in fact encoded using the nonlinear sRGB transfer function. The issue is that converting this to linear now is incorrect because it will be converted a second time later when it is put into the uniform buffer for the sky shader.

Not sure if the comments should still be added given that the value is not being passed in linear encoding.

@allenwp

allenwp commented Sep 26, 2025

Copy link
Copy Markdown
Contributor

@allenwp I updated the pr with a better description of what is happening. Where I removed the srgb_to_linear call, clear_color is in fact encoded using the nonlinear sRGB transfer function. The issue is that converting this to linear now is incorrect because it will be converted a second time later when it is put into the uniform buffer for the sky shader.

Not sure if the comments should still be added given that the value is not being passed in linear encoding.

Ah, so there is another problem that colour/lighting maths are being performed on these nonlinear sRGB-encoded values (when they are multiplied by the energy multiplier). This is likely incorrect and should be addressed.

To put it simply, no maths of any sort should be performed on nonlinear sRGB-encoded values. The only exception is if you are using the math as a layer of "encoding" that will be directly reversed before doing any further operations, like with a "luminance multiplier".

@Kaleb-Reid

Copy link
Copy Markdown
Contributor Author

@allenwp I've updated the pr to correct this. If fog is enabled, bg_energy_multiplier is already applied to linear colours in the shader via sky_energy_multiplier, so it doesn't need to be multiplied into the colour before the shader. For the cases where the sky is not drawn (clear colour / background colour with no fog), the colour is converted to linear encoding, multiplied, and converted back.

@Kaleb-Reid Kaleb-Reid changed the title Use correct color space for fog clear color in Forward+ and Mobile Apply sky brightness multiplier in correct color space Sep 27, 2025
@Kaleb-Reid

Kaleb-Reid commented Sep 27, 2025

Copy link
Copy Markdown
Contributor Author

I've added a similar change to compatibility, which applies the brightness multiplier to linear-encoded colours.

@allenwp

allenwp commented Sep 27, 2025

Copy link
Copy Markdown
Contributor

Thanks! It will take me a while to review this, and I'm not familiar with this part of the code base, but at a glance, I believe the intent of this updated PR is correct.

A couple of things that I can mention before doing a thorough review: dithering must always be applied as the final step before quantization. The dithering in the sky shaders is implemented in quite a weird way, and probably can be improved, but for now it should be left as-is, as the final step in the shader.

Another thing to mention is that conversions to and from nonlinear sRGB encoding are expensive and should be minimized. So if there's a way to keep things in linear (and document it with comments) the whole way through to limit conversions back and forth, that is preferred for performance reasons.

Finally, the Compatibility renderer intentionally performs some colour maths incorrectly for performance reasons, so avoiding decoding and re-encoding in this renderer may actually be the intended design, even though it produces incorrect results. You can see this comment for more details about this. Conversely, this sort of optimization to perform colour maths on nonlinear sRGB-encoded values should never happen in the Mobile/Forward+ renderers.

Anyway, at some point I'll take a deeper look into this PR to verify it.

And thanks for prompting me to write all of this out. Our discussion here is a good template for some colour documentation for contributors...

@Kaleb-Reid

Copy link
Copy Markdown
Contributor Author

@allenwp I can probably optimize the RD renderers and remove the extra conversions, as after what I added they are only accessed in linear anyways.

As for Compatibility, I wasn't originally going to add the conversions but I saw that other environment colours (e.g. ambient light) are converted to linear before energy calculations, and it's also done on the cpu and not the gpu so I decided to add it anyways. In terms of the gpu work, I only rearranged the order of calculations to multiply with the energy while the colour is in linear and did not add anything. I did move the dithering before the conversion back to srgb because it uses the energy multiplier and I wasn't sure if it mattered.

If parts / all of Compatibility changes need to be removed / reworked I can do that. I will start with moving the dithering back to where it was.

@allenwp

allenwp commented Sep 27, 2025

Copy link
Copy Markdown
Contributor

and it's also done on the cpu and not the gpu so I decided to add it anyways.

Great; this sounds entirely reasonable and does not go against the spirit of the Compatibility renderer!

@allenwp

allenwp commented Sep 27, 2025

Copy link
Copy Markdown
Contributor

If parts / all of Compatibility changes need to be removed / reworked I can do that. I will start with moving the dithering back to where it was.

Based on what you said, the dithering change is the only part that should be reverted. All the rest sounds good and doesn't need to be reworked!

In the future, we can improve the dithering to not depend on the energy multiplier or revisit this to come to some other sort of solution...

@Kaleb-Reid Kaleb-Reid force-pushed the fix-clear-colour branch 2 times, most recently from d501c3f to 75492e8 Compare September 27, 2025 05:13
@Kaleb-Reid

Copy link
Copy Markdown
Contributor Author

@allenwp I just finished making all of the changes and also updated the pr description.

Comment thread drivers/gles3/shaders/sky.glsl
@Kaleb-Reid Kaleb-Reid force-pushed the fix-clear-colour branch 2 times, most recently from bc1cc70 to cb106a8 Compare October 6, 2025 09:05
@allenwp allenwp removed the needs work label Oct 6, 2025

@allenwp allenwp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks correct and I have confirmed this PR fixes #108779 for Forward+, Mobile, and Compatibility (with a background energy multiplier) for HDR 2D enabled and disabled.

Thanks!

@Kaleb-Reid

Copy link
Copy Markdown
Contributor Author

The only thing I've changed in my most recent push is that I've done some deduplication of the code, so it should still work the same.

@Kaleb-Reid

Copy link
Copy Markdown
Contributor Author

I'm not sure what difference it makes but I believe the energy multiplier should always be multiplied into the clear colour after sending it to the shader so that the framebuffer is cleared with the correct color so I've changed that.

@Repiteo Repiteo modified the milestones: 4.6, 4.x Jan 26, 2026

@Calinou Calinou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally (rebased on top of master 833889a), it works as expected. Code looks good to me.

Before

sky_brightness_before.mp4

After

sky_brightness_after.mp4

@Repiteo Repiteo removed the cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release label May 18, 2026

@blueskythlikesclouds blueskythlikesclouds left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, it all seems consistent now.

@Repiteo

Repiteo commented May 22, 2026

Copy link
Copy Markdown
Contributor

Needs rebase

@Repiteo Repiteo modified the milestones: 4.x, 4.7, 4.8 May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enabling Fog in WorldEnvironment changes background color even when Sky Affect is set to 0

7 participants