Strange color band when blending textures based on a height factor.

Godot Version

Godot 4.x

Question

Hey! I’m trying to blend some GradientTexture1Ds across a procedurally generated surface, based on a height typed into an exported field in the editor. Instead of having the intended effect, I’m getting an odd color band across the center that has the colors of both of my test gradients, that shifts up and down based on the aforementioned height value.

Here is the odd behavior:


Here is what I would expect from the given values (roughly)

Here is the code for the shader itself:

shader_type spatial;

uniform float min_height;
uniform float max_height;
uniform sampler2D height_color;

varying float height;

void fragment(){
	float t = height / (max_height - min_height) - (min_height / (max_height - min_height));
	vec3 color = texture(height_color, vec2(t + 0.01, UV.y)).rgb;
	ALBEDO.rgb = color;
}


void vertex() {
	// Called for every vertex the material is visible on.
	height = length(VERTEX);
}
//void light() {
	// Called for every pixel for every light affecting the material.
	// Uncomment to replace the default light processing function with this one.
//}

Here is (most) of the code that provides the shader with it’s information:

func regen_mesh(planet_data : PlanetData):
	var arrays := []
	arrays.resize(Mesh.ARRAY_MAX)
	
	# Declare an array for the verticies and arrays for the properties of each vertex
	var vertex_array := PackedVector3Array()
	var uv_array := PackedVector2Array()
	var normal_array := PackedVector3Array()
	var index_array := PackedInt32Array()
	
	# Resolution determines # of faces across the shape
	var resolution := planet_data.resolution
	
	var num_vertices : int = resolution * resolution
	var num_indicies : int = (resolution - 1) * (resolution - 1) * 6
	
	vertex_array.resize(num_vertices)
	uv_array.resize(num_vertices)
	normal_array.resize(num_vertices)
	index_array.resize(num_indicies)
	
	
	var triangle_index : int = 0
	
	# Grab x and y vectors based off of the normals provided from the editor
	var axisA := Vector3(normal.y, normal.z, normal.x)
	var axisB : Vector3 = normal.cross(axisA)
	
	# Iterate through all possible vertices and apply their properties to the relevant array
	for y in range(resolution):
		for x in range(resolution):
			var index : int = x + y * resolution
			# Normalize the x and y values as a percentage across the resolution
			var percent := Vector2(x,y) / (resolution - 1)
			# Modify the normal vector by applying the normalized percentages to shift it across the face of the cube
			#to get the actual location
			var pointOnUnitCube : Vector3 = normal + (percent.x - 0.5) * 2.0 * axisA + (percent.y - 0.5) * 2.0 * axisB
			var pointOnUnitSphere : Vector3 = pointOnUnitCube.normalized()
			var biome_percent : float = planet_data.biome_percent_from_point(pointOnUnitSphere)
			var pointOnPlanet := planet_data.point_on_planet(pointOnUnitSphere)
			# Insert the vertex into the array based on the index.
			vertex_array[index] = pointOnPlanet
			uv_array[index] = Vector2(0.0, biome_percent )
			## print("Biome percent: ", biome_percent)
			## print("UV array val: ", uv_array[index])
			var l = pointOnPlanet.length()
			if l < planet_data.min_height:
				planet_data.min_height = l
			if l > planet_data.max_height:
				planet_data.max_height = l
			# Pass indices into the index array for triangle generation
			if x != resolution - 1 and y != resolution - 1:
				index_array[triangle_index + 2] = index
				index_array[triangle_index + 1] = index + resolution + 1
				index_array[triangle_index] = index + resolution
				index_array[triangle_index + 5] = index
				index_array[triangle_index + 4] = index + 1
				index_array[triangle_index + 3] = index + resolution + 1
				triangle_index += 6
				
	#for i in range(0, uv_array.size()):
		#print("UV value: ", uv_array[i])
	# Loop through the verticies of the mesh to recalculate the normals
	for i in range(0, index_array.size(), 3):
		var j : int = i + 1
		var k : int = i + 2
		# Grabs the vectors that make the edges of a given triangle
		var ij : Vector3 = vertex_array[index_array[j]] - vertex_array[index_array[i]]
		var jk : Vector3 = vertex_array[index_array[k]] - vertex_array[index_array[j]]
		var ki : Vector3 = vertex_array[index_array[i]] - vertex_array[index_array[k]]
		# Acquire the face normals, -1.0 faces them outwards
		var cross_ijjk : Vector3  = ij.cross(jk) * -1.0
		var cross_jkki : Vector3 = jk.cross(ki) * -1.0
		var cross_kiij : Vector3 = ki.cross(ij) * -1.0
		# Assigns the nomals to their relevant index 
		#while accounting for shared vertices across triangles
		normal_array[index_array[i]] += cross_ijjk + cross_jkki + cross_kiij
		normal_array[index_array[j]] += cross_ijjk + cross_jkki + cross_kiij
		normal_array[index_array[k]] += cross_ijjk + cross_jkki + cross_kiij
		
	for i in range(normal_array.size()):
		normal_array[i] = normal_array[i].normalized()
		
	arrays[Mesh.ARRAY_VERTEX] = vertex_array
	arrays[Mesh.ARRAY_NORMAL] = normal_array
	arrays[Mesh.ARRAY_TEX_UV] = uv_array
	arrays[Mesh.ARRAY_INDEX] = index_array
	
	call_deferred("_update_mesh", arrays, planet_data)
	
func _update_mesh(arrays : Array, planet_data : PlanetData):
	var _mesh := ArrayMesh.new()
	_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
	self.mesh = _mesh
	
	material_override.set_shader_parameter("min_height", planet_data.min_height)
	material_override.set_shader_parameter("max_height", planet_data.max_height)
	material_override.set_shader_parameter("height_color", planet_data.update_biome_texture())

func update_biome_texture() -> ImageTexture:
	var image_texture = ImageTexture.new()
	var dyn_image = Image.new()
	var h : int = biomes.size()
	if h > 0:
		var data : PackedByteArray = []
		var w : int = biomes[0].gradient.width
		for b in biomes:
			data.append_array(b.gradient.get_image().get_data())
		dyn_image = Image.create_from_data(w, h, false, Image.FORMAT_RGBA8, data)
		image_texture = ImageTexture.create_from_image(dyn_image)
		image_texture.resource_name = "Biome Texture"
		
	return image_texture

func biome_percent_from_point(point_on_unit_sphere : Vector3) -> float:
	var normalized_height : float = (point_on_unit_sphere.y + 1.0) / 2.0
	var biome_index : float = 0.0 # Index of the biome being modified
	var num_biome : float = biomes.size() # Number of biomes

	for i in range(num_biome): 
		if normalized_height < biomes[i].start_height:
			break
		biome_index = i
		print("Biome Index: ", biome_index)
	return biome_index / max(1.0, num_biome - 1.0)

length(VERTEX) is the distance from the center. You want VERTEX.y, I think.