Implementing SkyImageUV as Custom Node?

Godot Version

4.2.2

Question

I am trying to add a Texture2D (a moon) to my Visual Sky Shader utilizing the SkyImageUV shader found here: Add a SkyImageUV node to render texture in a sky shader · Issue #3534 · godotengine/godot-proposals · GitHub

That shader on its own works exactly as intended and is perfect for my needs. However, I am having difficulty integrating it with my existing Visual Shader.

I played around a little with Expression and Global Expression nodes, but I’m not sure if that’s the right path. The Visual Shader Custom Node seems well-suited for this task, but I’m struggling at the finish line, specifically the syntax for _get_code for which I’m not finding a whole lot of examples to guide me, and the proposal itself from which I obtained the shader code is still open two years later.

Here is what I have so far, and would appreciate any pointers on how to complete OR how best to implement the SkyImageUV shader code if this is not even the right path:

@tool
extends VisualShaderNodeCustom
class_name VisualShaderNodeSkyImageUV

func _get_name():
	return "SkyImageUV"

func _get_category():
	return "VisualShaderExtras"

func _get_description():
	return "Projects a 2D image into the skybox (by Ansraer)"
	
func _get_return_icon_type():
	return VisualShaderNode.PORT_TYPE_VECTOR_4D

func _get_input_port_count():
	return 4

func _get_input_port_name(port):
	match port:
		0:
			return "rotation"
		1:
			return "altitude"
		2:	
			return "imageSize"
		3:
			return "imageTexture"

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

func _get_outport_port_count():
	return 1

func _get_output_port_name(port):
	return "finalImage"

func _get_output_port_type(port):
	return VisualShaderNode.PORT_TYPE_VECTOR_3D

func _get_global_code(mode):
	return """
		float map(float value, float srcMin, float srcMax, float destMin, float destMax){
	return destMin + (value-srcMin) * (destMax-destMin) / (srcMax-srcMin);
}
 
mat3 rotation3dY(float angle) {
  float s = sin(angle);
  float c = cos(angle);
  return mat3(
	vec3(c, 0.0, -s),
	vec3(0.0, 1.0, 0.0),
	vec3(s, 0.0, c)
  );
}
 
vec4 projImage(float _altitude, float _rot_x, float _viewVec_x, float _imageSize, sampler2D _image) {
	if(_rot_x > _altitude-_imageSize && _rot_x < _altitude+_imageSize){
  	  float xSizeFactor = 2.0*PI;
  	  if(_viewVec_x > -_imageSize*xSizeFactor && _viewVec_x < _imageSize*xSizeFactor){
  		  float v = map(_rot_x-_altitude, -_imageSize, _imageSize, 1.0, 0.0);
  		  float u = map(_viewVec_x, -_imageSize*xSizeFactor, _imageSize*xSizeFactor, 0.0, 1.0);
 
  		  return texture(_image, vec2(u, v));
  	  }
	}
	return vec4(0.0);
}
 
vec4 skyImageProj(vec3 viewVec, float _rotation, float _altitude, float _imageSize, sampler2D _imageSampler) {
	_altitude = fract(_altitude);
	vec3 _EYEDIR = rotation3dY(_rotation) * viewVec;
	//Since we applied the rotation directly to the viewVec we can just ingore it from now on.
 
	//float rot_z = atan(_EYEDIR.y, _EYEDIR.x)/(2.0*PI)+0.5;
	float rot_x = atan(_EYEDIR.y, _EYEDIR.z)/(2.0*PI)+0.5;
	//float rot_y = atan(_EYEDIR.z, _EYEDIR.x)/(2.0*PI)+0.5;
 
	vec4 result = projImage(_altitude, rot_x, _EYEDIR.x, _imageSize, _imageSampler);
 
	//make sure the image isn't clipped when altitude approaches the max/min values
	if(_altitude > 0.75) {
  	  result = max(result, projImage(_altitude-1.0, rot_x, _EYEDIR.x, _imageSize, _imageSampler));
	} else if (_altitude < 0.25) {
  	  result = max(result, projImage(_altitude+1.0, rot_x, _EYEDIR.x, _imageSize, _imageSampler));
	}
	return result;
}

	vec4 finalImage = skyImageProj(EYEDIR, rotation, altitude, imageSize, imageTexture);
"""

func _get_code(input_vars, output_vars, mode, type):
	return output_vars[0] + " = vec4(0,0,0,0)" //This vec4 is just temporary to prevent errors

I have basically no idea what syntax is expected at the end to get the output. I can’t even get an output port to show up, let alone a functioning one.

I found another post that refers to converting input_ and output_vars to strings, but I’m still not quite wrapping my head around how to accomplish that.

Anyway, any pointers or corrections in my way of thinking would be much appreciated! Happy to do more reading, I just…don’t know what I don’t know?

1 Like

I’ve done some additional tinkering which, while producing no discernable results, feels like it’s a step in the right direction?

func _get_global_code(mode):
	return "#include \"res://skyimageuv.gdshaderinc\"" //NOTE: Moved the shader code to a Shader Include resource

func _get_code(input_vars, output_vars, mode, type):
	
	var EYEDIR: String = input_vars[0] //NOTE: I added an EYEDIR input to the input ports earlier
	var rotation: String = input_vars[1]
	var altitude: String = input_vars[2]
	var imageSize: String = input_vars[3]
	var imageTexture: String = input_vars[4]
	
	return output_vars[0] + " = skyImageProj(vec4(%s, %s, %s, %s, %s));" % [EYEDIR, rotation, altitude, imageSize, imageTexture]

There is still no output port being created in the Visual Shader, and I’m getting “UNUSED_PARAMETER” warnings for the output port name & type, _get_global_code(), and _get_code() sections, so I know I’m still missing pieces of the puzzle, but hopefully this is getting closer?

@flamelizard, years ago you served my father in the Clone Wars (aka helped with another visual shader custom node problem I had) — perhaps you have some insight?

Okay, so, for the time being the far simpler solution was to copy the generated code from the Visual Shader into a new shader and simply plug in the SkyImageUV code into that, but if anyone comes up with a Visual Shader solution I think it would be helpful for others.

Hey @CreatureWOSpecies. You might be finding something in here that could be useful for your reference: GitHub - paddy-exe/GodotVisualShader-Extras: Visual Shader Nodes Library

1 Like