Skip to content

Conversation

@Calinou
Copy link
Member

@Calinou Calinou commented Apr 29, 2025

render_mode use_radiance_as_background; will make the sky render its background using the radiance map that is generated for reflections. This is an optimization that makes the sky rendering faster, especially on low-end GPUs limited by their fragment shading rate (which is common on mobile). The benefit is especially noticeable on demanding sky shaders, as they will skip all demanding computations for rendering the background.

The downside is that the background's resolution will depend on the radiance map's size configured in the Environment properties. Its update rate also depends on the chosen update mode, with the real-time update mode allowing for real-time updates at the cost of a fixed 256×256 resolution.

Testing project: test_radiance_as_background.zip

Preview

Radiance as Background OFF Radiance as Background ON
image image

Benchmark

PC specifications
  • CPU: Intel Core i9-13900K
  • GPU: NVIDIA GeForce RTX 4090
  • RAM: 64 GB (2×32 GB DDR5-5800 C30)
  • SSD: Solidigm P44 Pro 2 TB
  • OS: Linux (Fedora 42)

NVIDIA

Type Radiance as Background OFF Radiance as Background ON
Forward+, 3840×2160 3385 FPS (0.29 mspf) 3549 FPS (0.28 mspf)
Mobile, 3840×2160 4210 FPS (0.23 mspf) 5104 FPS (0.19 mspf)

Software rendering on CPU (Lavapipe)

Type Radiance as Background OFF Radiance as Background ON
Forward+, 1152×648 150 FPS (6.66 mspf) 159 FPS (6.28 mspf)
Mobile, 1152×648 193 FPS (5.18 mspf) 214 FPS (4.67 mspf)
Forward+, 3840×2160 26 FPS (38.46 mspf) 32 FPS (31.25 mspf)
Mobile, 3840×2160 30 FPS (33.33 mspf) 40 FPS (25.00 mspf)

TODO

  • Fix shader compilation in Compatibility. It seems SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP (along with other samplers used in the same shader) aren't defined in GLES3 (they are not present anywhere else in drivers/gles3/). See also my PR comment in Document sky shader half/quarter-res builtins not being available in Compatibility godot-docs#10905 which is tangentially related.
  • Add a project setting to force using the radiance as background on all sky shaders, similar to the existing project setting that forces vertex shading on all materials. This can be used as a mobile feature tag override for performance reasons.
  • Consider disabling debanding in the cubemap pass in Forward+ to avoid performing double debanding (both at the cubemap texture level and at the sky level). I had initially done this, but this is not sufficient in Mobile (there will still be a fair amount of banding in the cubemap texture itself).

Copy link
Member

@clayjohn clayjohn left a comment

Choose a reason for hiding this comment

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

I would just make this an option in the ProceduralSkyMaterial and the PhysicalSkyMaterial. I don't think there is any benefit gained by making this a render mode and I don't think it is needed for the PanoramaSkyMaterial (since it would just lower the effective resolution for no performance gain).

You can directly use the code I posted in the bug report #88394 (comment)

@mrjustaguy
Copy link
Contributor

mrjustaguy commented Apr 29, 2025

You may want to do this for complex custom sky shaders.

@Calinou
Copy link
Member Author

Calinou commented Apr 29, 2025

I would just make this an option in the ProceduralSkyMaterial and the PhysicalSkyMaterial

Is there a way to do this without imposing a runtime if? A render mode makes it possible for this to be done fully at compile-time using #ifdef in the code shader (since the cubemap pass actually uses its own shader).

I'm not sure if the Godot shader preprocessor is able to achieve this with #ifdef AT_CUBEMAP_PASS, since it can only compile a single shader at the end, without being able to distinguish the shader that is passed to the cubemap pass from the regular one.

For complex sky shaders, this can make a significant difference as the branch that is always false will still have its registers allocated outside the cubemap pass.

Note that this PR already doesn't expose the option in PanoramaSkyMaterial by design, just in ProceduralSkyMaterial and PhysicalSkyMaterial.

@clayjohn
Copy link
Member

You may want to do this for complex custom sky shaders.

You already can. I've been doing it in my cloud demo for 3 years https://github.com/clayjohn/godot-volumetric-cloud-demo/blob/main/clouds.gdshader

Is there a way to do this without imposing a runtime if? A render mode makes it possible for this to be done fully at compile-time using #ifdef in the code shader (since the cubemap pass actually uses its own shader).

Yes, my example code uses a compile time branch. My example code results in the exact same code as your render mode, but it is simpler and keeps the logic in the GDShader

@Calinou
Copy link
Member Author

Calinou commented Apr 29, 2025

Yes, my example code uses a compile time branch. My example code results in the exact same code as your render mode, but it is simpler and keeps the logic in the GDShader

Does the Godot shader compiler fold if (AT_CUBEMAP_PASS) (or if (!AT_CUBEMAP_PASS)) to a compile-time branch? I thought it didn't do that.

`render_mode use_radiance_as_background;` will make the sky
render its background using the radiance map that is generated
for reflections. This is an optimization that makes the sky rendering
faster, especially on low-end GPUs limited by their fragment shading rate
(which is common on mobile). The benefit is especially noticeable
on demanding sky shaders, as they will skip all demanding computations
for rendering the background.

The downside is that the background's resolution will depend on the radiance
map's size configured in the Environment properties. Its update rate
also depends on the chosen update mode, with the real-time update
mode allowing for real-time updates at the cost of a fixed 256×256
resolution.
@clayjohn
Copy link
Member

Does the Godot shader compiler fold if (AT_CUBEMAP_PASS) (or if (!AT_CUBEMAP_PASS)) to a compile-time branch? I thought it didn't do that.

Not the Godot shader compiler. But the shader compiler will. AT_CUBEMAP_PASS is a macro that is simply true or false. So the code ends up being a compile time branch e.g. if (true) or if(false) so the compiler will remove the branch at compile time.

The docs also include an example of how to do this https://docs.godotengine.org/en/latest/tutorials/shaders/shader_reference/sky_shader.html

@Calinou Calinou force-pushed the sky-add-use-radiance-as-background-render-mode branch from 9b77400 to f5908f8 Compare April 29, 2025 15:03
@Calinou
Copy link
Member Author

Calinou commented Apr 29, 2025

I've pushed a second commit that switches to a shader-level toggle instead of a render mode. It also reorganizes code a bit to improve its formatting when converting it to a custom shader.

However, there are 2 issues:

  • The sky appears too dark when using the Mobile renderer, due to the luminance multiplier not being taken into account. Is params.luminance_multiplier accessible from the Godot shader language? Note that I'm not referring to exposure here, but the 2.0 multiplier that is used throughout the mobile renderer (it's 1.0 in Forward+).
  • The sky is black when using Compatibility, but shader compilation succeeds this time.

@clayjohn
Copy link
Member

The sky appears too dark when using the Mobile renderer, due to the luminance multiplier not being taken into account. Is params.luminance_multiplier accessible from the Godot shader language? Note that I'm not referring to exposure here, but the 2.0 multiplier that is used throughout the mobile renderer (it's 1.0 in Forward+).

The shader compiler has code to handle this. It might only trigger in scene shaders though. If so, this is a pre-existing bug and should be fixed. The relevant code is here:

code = "(" + code + " * vec4(vec3(sc_luminance_multiplier()), 1.0))";

Instead of just triggering for screen_texture it should trigger for RADIANCE as well

@Calinou
Copy link
Member Author

Calinou commented Apr 30, 2025

The shader compiler has code to handle this. It might only trigger in scene shaders though. If so, this is a pre-existing bug and should be fixed. The relevant code is here:

I've pushed a third commit that aims to address this, but despite the line of code in question being reached (as evidenced by the print), it's not having any effect.

@clayjohn
Copy link
Member

The shader compiler has code to handle this. It might only trigger in scene shaders though. If so, this is a pre-existing bug and should be fixed. The relevant code is here:

I've pushed a third commit that aims to address this, but despite the line of code in question being reached (as evidenced by the print), it's not having any effect.

This isn't in the correct place. It needs to be in the same location as the code I linked. The code you wrote is replacing RADIANCE with (RADIANCE * vec4(vec3(sc_luminance()), 1.0) right before replacing RADIANCE with its internal name radiance. So your changes get overriden. At any rate, if they didn't get overriden it would crash because you can't multiply a sampler2D and a vec4.

The code needs to be in the location where the texture() function is added. That is why I linked that code. Instead of checking if (is_screen_texture) the check should be if (is_screen_texture || is_sky_radiance_texture):

if (is_screen_texture && !texture_func_returns_data && actions.apply_luminance_multiplier) {
code = "(" + code + " * vec4(vec3(sc_luminance_multiplier()), 1.0))";
}

You can create a local variable bool is_sky_radiance_texture = false here:

bool is_texture_func = false;
bool is_screen_texture = false;
bool texture_func_no_uv = false;
bool texture_func_returns_data = false;

Then here, before the if statement you can add your own check for RADIANCE the same as what you did in this PR:

}
if (correct_texture_uniform && !RS::get_singleton()->is_low_end()) {

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.

proceduralsky is painfully slow on android compatibility mode

3 participants