Nested Tinted Zooms

Displays expanding, nested, tinted versions of a sprite2d. The effect texture does not need to match the base texture – e.g. expanding hearts behind a chess piece. Lots of options: scale, pivot point, layer count, color count, color selection, hide/show base image …

Use: set a base texture for the sprite2D, and drop in a texture in the [sampler] parameter – it can match the base texture or not. If  you want color, specify them a RGB in the [colors array] parameter. Fiddle with other parameters as you like 🙂

Shader code
shader_type canvas_item;

										//** Nested Tinted Zooms **//

// Displays expanding nested tinted copies of <sample> behind a Sprite2D.texture 
// Notes on prefixes: t_ is for local (temp) variables; a_ is for func arguments
// Questions? corpsinhere@gmail.com

uniform sampler2D sample;				// The image being duplicated
uniform float speed = 1.0;				// Cycles per second
uniform float max_zoom = 2.0;			// Scale of max zoom
uniform int layer_count = 3;			// Count of duplicates of <sample> to display
uniform int color_count = 3;			// Count of colors to cycle through - taken from top items in <color_array>
uniform vec2 pivot = vec2(0.5, 0.5);	// Point duplicates zoom from [0.0, 1.0]
uniform bool is_only_bigger = true;		// Should duplicates start at zoom = 1.0?  Else start at zoom = 0.0
uniform bool is_show_original = true;	// As it says
uniform bool is_use_colors = true;		// If false, layers will use original colors
uniform float boarder_zoom = 2.0;		// Canvas size of texture - if smaller than <max_zoom> clipping will occur
										// 0.0 = max, 1-0 = min contrast of tinted layers
uniform float contrast_offset : hint_range(0.0, 1.0, 0.1) = 0.1;
uniform vec3 color_array[9];			// Holds all tints. Shader will only use the first <color_count>


// Expands extents to match <boarder_zoom> and centers (0, 0) at (0.5, 0.5)
void vertex() {
	vec2 center_dist = vec2(0.5, 0.5) - VERTEX;
	center_dist *= boarder_zoom;
	VERTEX = vec2(0.5, 0.5) - center_dist;
}


// As it says
bool is_uv_outofbounds(vec2 a_uv){
	return a_uv.x > 1.0 || a_uv.x < 0.0 || a_uv.y > 1.0 || a_uv.y < 0.0;
}


// Adjust UV per new scale and pivot
vec2 adj_uv(vec2 a_uv, float a_scale, vec2 a_pivot){
	return a_pivot + (a_uv - a_pivot) * pow(a_scale, -1.0);
}


// Color of <sample> at given coord, scale, and pivot point
vec4 layer_color(vec2 a_uv, float a_scale, vec2 a_pivot){
	vec2 t_uv = adj_uv(a_uv, a_scale, a_pivot);
	if (is_uv_outofbounds(t_uv)){
		return vec4(0.0);
	}
	else{
		return texture(sample, adj_uv(a_uv, a_scale, a_pivot));
	}
}


// Returns [0, 0.999]
float normalized_float(float a_float){
	return float(int(10000.0 * a_float) % 10000) / 10000.0;
}


// Looping [0.0, 0.999] - used for building the different zoomed copies
float time_delta(){
	return normalized_float(TIME * speed);
}


// Looping [0.0, 0.999] - slower than <time_delta> by a factor of <layer_count>
// Used for keeping track of layer colors
float layer_time_delta(){
	return normalized_float(TIME * speed / float(layer_count));
}


// Converts <a_color> to greyscale, then tints
vec3 tinted_color(vec3 a_color, vec3 a_tint){
	float t_average = (a_color.r + a_color.g + a_color.b) / 3.0;
	t_average *= (1.0 - contrast_offset);
	t_average += contrast_offset;
	vec3 t_color = a_tint * t_average;
	return t_color;
}


// What? Is used as a bool to determin when layer and layer color match up
// Took ages of fiddling to get it to work and so it stays as is!
int color_index(int a_index){
	int t_layer_index = int(float(layer_count) * layer_time_delta());
	int t_index = abs(t_layer_index - a_index) % layer_count;
	return t_index;
}


// Here be all the goblins that get it all done
void fragment() {
	if (!is_show_original){
		COLOR = vec4(0.0);
	}
	else{
		COLOR = texture(TEXTURE, adj_uv(UV, pow(boarder_zoom, -1.0), vec2(0.5, 0.5)));
	}
	if (COLOR.a == 0.0){
		float scale_fraction;
		if (is_only_bigger){
			scale_fraction = (max_zoom - 1.0) * pow(float(layer_count), -1.0);
		}
		else{
			scale_fraction = max_zoom * pow(float(layer_count), -1.0);	// Used to calc zoom of a given layer
		}
		float size_scale;
		vec4 t_color;
		for (int i = layer_count - 1; i >= 0; i--) {
			size_scale = scale_fraction * (float(i) + time_delta());
			if (is_only_bigger){
				size_scale += 1.0;
			}
			vec2 adjusted_uv = adj_uv(UV, pow(boarder_zoom, -1.0), vec2(0.5, 0.5));
			adjusted_uv = adj_uv(adjusted_uv, size_scale, pivot);
			if (is_uv_outofbounds(adjusted_uv)){
				t_color = vec4(0.0);
			}
			else{
				t_color = texture(sample, adjusted_uv);
			}
			if (t_color.a > 0.0){
				t_color.a *= pow(1.0 - (size_scale / max_zoom), 0.7); // This value controls alpha falloff rate 
				COLOR = t_color;
				vec3 t_tint = COLOR.rgb;
				if (is_use_colors){
					float t_delta = float(layer_count) * (layer_time_delta());
					for(int j = 0; j < layer_count; j++){
						if (color_index(i + j) == 0){
							if (j < color_array.length()){
								t_tint = tinted_color(COLOR.rgb, color_array[j % color_count]);
							}
						}
					}
				}
				COLOR = vec4(vec3(t_tint), COLOR.a);
			}
		}
	}
}
Live Preview
Tags
2d, Sprite2D, zooming
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments