Source-like/3D models skybox & rendering sky shader to texture

Using Godot 4.3 dev 3

I have two questions regarding sky shader & skies. The quick version:

  1. Is there any way to make a skybox made from ingame 3D models that appear far into the distance, behind all of the regular geometry (like source engine games/maps) ?

  2. Is there a way to “render” the current sky shader to a (panorama) texture, so that it can be render once and then used in a static PanoramaSky later ?

More details:
1/ All tutorials on the subject point to the usage of multiple viewports, which seems really costly/ineffective to me (i tried it in godot 3 and having multiple camera was really costly on my poor ipgu, even with minimal cull masks).
I know for first person viewmodels, it’s common to pull a reprojection shader trick to make the model appear in front of everything, but I don’t know if it would be applicable for skyboxes.

2/ My basic sky shader (that depends on TIME) is very expensive on my laptop with an igpu, even with minimal radiance size, high quality incremental process mode, and a flat color for AT_CUBEMAP_PASS. So I figured I could have a “static sky” option that’s just a PanoramaSky.
But I can’t really see how to render a scene as a 360° panorama texture, is there a way to do this in godot ?

To answer your questions:

  1. Why do you want to render 3D models that are far away into the skybox? What you can do instead are the following options:
    • Use LODs to save up on the vertices you are rendering. Since the models are further away, less detail is visible and thereby not needed. You can use the built-in LOD options for this
    • Render the models into sprites and render those as billboards when reaching a specific distance (similar to 1.)
  2. If I am understanding you correctly you are using simply a flar color sky shader which is taking too much of a toll on your integrated GPU? Am I getting this right? What you CAN do is use Blender to create a highly detailed environment and then render that into a texture. You can find a tutorial for this here.

Thanks for the answers !

  1. It would allow to easily share a “background” (landscapes, buildings, etc) between multiple scenes, that stretch to the horizon (and even move a bit when the camera move to give the illusion that’s it’s really there). Without having to depend on an huge draw distance.

  2. Not exactly flat, but yeah that’s the idea, the “setup sky” step in visual profiler is very costly (shader posted below, but I don’t think any optimization would make a difference, even if i removed the few ‘if’, it’s a simple “scrolling grid” background).
    I’m asking if it’s possible in Godot directly, because that would allow me to keep the exact same “look” as the game for the skybox : same materials, same sky shaders.

Bonus, the culprit cool scrolling grid / virtual-training-room-like shader (that’s way too expensive for what it is):

shader_type sky;
render_mode use_half_res_pass, use_quarter_res_pass;

uniform vec3 sky_color : source_color = vec3( 0.05, 0.05, 0.05 );
uniform vec3 line_color : source_color = vec3( 0.08, 0.2, 0.1 );
uniform vec3 cube_size = vec3(1.,1.,1.);
uniform vec3 cube_offset = vec3(1.,1.,1.);
uniform float line_spacing = 0.01;
uniform float time_scale = 0.5;
uniform float line_width = 0.01;


void sky() {
	if (AT_CUBEMAP_PASS || AT_HALF_RES_PASS || AT_QUARTER_RES_PASS) {
        COLOR = (1.-line_width) * sky_color + line_width*line_color;
    }
	else
	{
		vec2 sky_uv_x = EYEDIR.yz / EYEDIR.x * cube_size.x;
	   	vec2 sky_uv_y = EYEDIR.xz / EYEDIR.y * cube_size.y;
		vec2 sky_uv_z = EYEDIR.xy / EYEDIR.z * cube_size.z;
		
		vec2 selected_uv = sky_uv_x;
		float other_axis = EYEDIR.x;
		if (length(sky_uv_y) < length(selected_uv))
		{
			selected_uv = sky_uv_y;
			other_axis = EYEDIR.y;
		}
		if (length(sky_uv_z) < length(selected_uv))
		{
			selected_uv = sky_uv_z;
			other_axis = EYEDIR.z;
		}
		
		float lines = (sin(TIME * time_scale + selected_uv * sign(other_axis) / line_spacing).x+1.)/2.;
		lines = (lines+1.)/2.;
		lines = step(1.-line_width, lines);
		float lines_2 = (sin(TIME * time_scale + selected_uv  * sign(other_axis) / line_spacing).y+1.)/2.;
		lines_2 = (lines_2+1.)/2.;
		lines_2 = step(1.-line_width, lines_2); 
		lines = max(lines, lines_2);
	    COLOR = (1.-lines) * sky_color + lines * line_color;
	}
}
  1. I don’t see an advantage of this when you can have the advantage of the models actually in the scene like I suggested above. But I am not sure what kind of game you are working on.
  2. Your shader seems to be extremely costly as you are doing the very expensive calculations outside of the cubemap passes contrary to what the documentation suggests. I would recommend reading this through first. Also, “simple” if’s are very costly in shaders if the input value changes during execution. Furthermore length() functions are also extremely expensive in shaders so I am not surprised that your shader is not that performant.
  1. I don’t need the models to really be in the scene. It’s just to give the impression there is a landscape. I’ll try to experiment and i’ll reply here if i manage to achieve this without multiple cameras. In any case, if anyone wants to achieve it, there is an example implementation with multiple camera/viewports here.

  2. I think the documentation does advise to do cheap computation in the cubemap (only used for reflections and stuff), and expensive computation outside (used to display the actual sky background when rendering).
    But my bad, I hadn’t tested it again on igpu after reducing the radiance size to 32, and it really helps a lot.
    I also replaced any branching/ifs with mix/step, but i think it’s basically nothing compared to the cost induced by a larger radiance size.

shader_type sky;
render_mode use_half_res_pass, use_quarter_res_pass;

uniform vec3 sky_color : source_color = vec3( 0.05, 0.05, 0.05 );
uniform vec3 line_color : source_color = vec3( 0.08, 0.2, 0.1 );
uniform vec3 cube_size = vec3(1.,1.,1.);
uniform vec3 cube_offset = vec3(1.,1.,1.);
uniform float line_spacing = 0.01;
uniform float time_scale = 0.5;
uniform float line_width = 0.01;

float lengthSquared(vec2 v)
{
	return dot(v,v);
}

void sky() {
	if (AT_CUBEMAP_PASS || AT_HALF_RES_PASS || AT_QUARTER_RES_PASS) {
        COLOR = (1.-line_width) * sky_color + line_width*line_color;
    }
	else
	{
		vec2 sky_uv_x = EYEDIR.yz / EYEDIR.x * cube_size.x;
	   	vec2 sky_uv_y = EYEDIR.xz / EYEDIR.y * cube_size.y;
		vec2 sky_uv_z = EYEDIR.xy / EYEDIR.z * cube_size.z;
		
		float sky_uv_x_len = lengthSquared(sky_uv_x);
		float sky_uv_y_len = lengthSquared(sky_uv_y);
		float sky_uv_z_len = lengthSquared(sky_uv_z);
		
		vec2 selected_uv = sky_uv_x;
		float other_axis = EYEDIR.x;
		float selected_len = sky_uv_x_len;
		
		float choose_y = step(sky_uv_y_len, selected_len);
		selected_len = mix(selected_len, sky_uv_y_len, choose_y);
		selected_uv = mix(selected_uv, sky_uv_y, choose_y);
		other_axis = mix(other_axis, EYEDIR.y, choose_y);
		
		float choose_z = step(sky_uv_z_len, selected_len);
		selected_len = mix(selected_len, sky_uv_z_len, choose_z);
		selected_uv = mix(selected_uv, sky_uv_z, choose_z);
		other_axis = mix(other_axis, EYEDIR.z, choose_z);
		
		float lines = (sin(TIME * time_scale + selected_uv * sign(other_axis) / line_spacing).x+1.)/2.;
		lines = (lines+1.)/2.;
		lines = step(1.-line_width, lines);
		float lines_2 = (sin(TIME * time_scale + selected_uv  * sign(other_axis) / line_spacing).y+1.)/2.;
		lines_2 = (lines_2+1.)/2.;
		lines_2 = step(1.-line_width, lines_2); 
		lines = max(lines, lines_2);
	    COLOR = (1.-lines) * sky_color + lines * line_color;
	}
}

STILL I would be interested if anyone finds a way to render a godot scene to a panorama texture/360° screenshot.

we used such techniques a long time ago. The method was simple. a different scene than the environment was rendered to the viewport
the first pass rendered the scene first and then rendered the level. and it was visually the same as a cubemap. It was faster, of course.

no shader was needed, not to mention that it worked on old Intel Pentiums when there weren’t even 3D cards yet, maybe voodoo appeared then :slight_smile:

dunno if you were able to do what you wanted, but i kinda do the same stuff, so with shaders we can make a panorama of the scene, with help of 6 cameras, then we put that thing inside of TextureRect for example, then, because we use shader, we cant just get texture, get image or whatever, we need to use stupid viewport, but somebody had mercy on us, and gave us subviewport, and with that, we can make texture, which we can store, and apply into skybox