Rotating Equirectangular Projections in Visual Sky Shaders?

Godot Version

4.2

Question

Howdy Godotnauts,

I’m a recent Unity refugee and I’m running into issues porting a Sky shader over from Unity Shader Graph. The long and short of it is I’m trying to rotate a star map with an equirectangular projection. I can project this onto my sky no problem using the sky_coords node’s UVs plugged into a Texture2D node:

However, I’m unsure how to rotate this to allow for accurate positioning of stars in the night sky. In Unity there was a Rotate About Axis mode, but that doesn’t appear to be available in the Godot Sky shader nodes.

I know I can rotate the whole sky in the World Environment node, but that rotates the sun and moon as well, which is not what I want.

I feel like I’m missing something obvious. Am I just going about this the completely wrong way? Any help or pointers would be appreciated!

Hey there, there is no RotateUV node in Godot built-in. However you can try out Visual Shader addons like mine: GitHub - paddy-exe/GodotVisualShader-Extras: Visual Shader Nodes Library

There is a RotateUV Node which may be able to solve what you try to achieve. I hope this helps!

Edit: Just tried it out myself and it doesn’t seem to work with my UVRotate Node. Will investigate further

After trying out the RotationByAxis node which is intended for spatial shaders I can’t really make it work either. Can you tell a bit more how you would expect it to work in Unity and what you shader consisted of there?

Alright after some more testing I think the best way for you to have a “rotating” star effect would be to just add a time variable to the sky_coords input which then just transforms the image like this:


1 Like

Hey, thanks so much for looking into this for me! This solution seems to work great for horizontal rotation, but I’m still stuck on vertical rotation.

Here’s the relevant bit from my Unity Shader Graph if it helps visualize the goal a little better:

Essentially the goal is to calculate the altitude and azimuth of the celestial sphere for a given time and location, plug that result in to the shader, and rotate the stars accordingly.

I’ll keep playing around with it and see if I stumble into a solution, but I’d love to hear any more thoughts or insight you might have.

1 Like

Interesting… I believe that should be doable. I just tried to use a cubemap parameter with the given equirectengular texture and the EYEDIR input as the uvw coordinates which seems to work fine (except for the obvious seams but that is because it is not a cubemap yet).

What you can do now is use a converter like this: GitHub - indus/kubi: a fast and flexible cubemap generator and convert the equirectangular texture into a cubemap and use it like this:

As a custom node for the RotationByAxis node you can use this script I quickly created from the already built-in node (it is currently only exposed to Spatial shaders) and you can then try to put it all together like you had in Unity.

Code
@tool
extends VisualShaderNodeCustom
class_name VisualShaderNodeRotationByAxis3D

func _init():
	set_input_port_default_value(1, 10.0)
	set_input_port_default_value(2, Vector3(0.0,0.0, 0.0))

func _get_name():
	return "RotationByAxis3D"

func _get_category():
	return "VisualShaderExtras"

func _get_description():
	return "RotationByAxis3D"

func _get_return_icon_type():
	return VisualShaderNode.PORT_TYPE_VECTOR_3D

func _get_input_port_count():
	return 3

func _get_input_port_name(port):
	match port:
		0:
			return "Input"
		1:
			return "Angle"
		2:
			return "Axis"

func _get_input_port_type(port):
	match port:
		0:
			return VisualShaderNode.PORT_TYPE_VECTOR_3D
		1:
			return VisualShaderNode.PORT_TYPE_SCALAR
		2:
			return VisualShaderNode.PORT_TYPE_VECTOR_3D

func _get_output_port_count():
	return 1

func _get_output_port_name(port):
	return "Output"

func _get_output_port_type(port):
	return VisualShaderNode.PORT_TYPE_VECTOR_3D

func _get_global_code(mode):
	return """
		vec3 rotation_by_axis_3d(vec3 __in_vec, float __angle, vec3 __axis) {
			__axis = normalize(__axis);
			mat3 __rot_matrix = mat3(
				vec3( cos(__angle)+__axis.x*__axis.x*(1.0 - cos(__angle)), __axis.x*__axis.y*(1.0-cos(__angle))-__axis.z*sin(__angle), __axis.x*__axis.z*(1.0-cos(__angle))+__axis.y*sin(__angle) ),
				vec3( __axis.y*__axis.x*(1.0-cos(__angle))+__axis.z*sin(__angle), cos(__angle)+__axis.y*__axis.y*(1.0-cos(__angle)), __axis.y*__axis.z*(1.0-cos(__angle))-__axis.x*sin(__angle) ),
				vec3( __axis.z*__axis.x*(1.0-cos(__angle))-__axis.y*sin(__angle), __axis.z*__axis.y*(1.0-cos(__angle))+__axis.x*sin(__angle), cos(__angle)+__axis.z*__axis.z*(1.0-cos(__angle)) )
			);
			return __in_vec * __rot_matrix;
		}
	"""

func _get_code(input_vars, output_vars, mode, type):
	return "%s.xyz = rotation_by_axis_3d(%s.xyz, %s, %s.xyz);" % [output_vars[0], input_vars[0], input_vars[1], input_vars[2]]
1 Like

After some trial and error (mostly on the cubemap side of things—I think Godot’s documentation is a little unclear on what its expectations are), I think I’ve gotten this to a working state.

Thanks again for your help! Really appreciate it!

1 Like

Great news! Did you do any additional steps? Would love to know this for documentation purposes. I am happy it helped :grin:

Nope, pretty much exactly as you described. I copied the script and added it as a custom node and it works a charm! Here’s the final structure in case it helps anyone else:

1 Like

Thanks! But I meant more in terms of the texture. Do you use the converter I suggested and if so, with what arguments?

Oh, gotcha! No, I ended up just using this online one and assembling them in Photoshop. That one you linked to was helpful in terms of showing me what layout Godot expected. I used a 2x3 layout:

I’m sure that one you linked to is super useful, I just didn’t quite feel like wrapping my head around it quite yet.

1 Like