Radial Progress Shader

A shader used to display a ratio of two images in a circular pattern, similar to the ProgressBar node, but much easier to work with when working outside of UI. Based off of Demindiro’s Health Circle shader. The texture that the shader is applied to is used as the fill texture, and the empty texture can be set in the shader.

sampler2D Fill Texture Overlay: An image that is displayed over the texture the shader is applied to. Gradients are typically put here over white images.

sampler2D BG Texture: The texture that is displayed in the portion of the radial progress that is not filled.

float Fill Ratio: A number between 0 and 1 representing the amount of the radial progress that is filled.

float Start Angle: An angle (in degrees) that the radial progress starts at.

float Max Angle: The maximum angle (in degrees) that the radial progress can fill to.

bool Invert Fill: When true, the shader will use the base texture as the background, and bg_texture as the fill.

bool Reflect X: When true, the radial overlay will be flipped along the X axis.

bool Reflect Y: When true, the radial overlay will be flipped along the Y axis.

vec2 Offset: The UV offset of the radial overlay. Each value goes from -1.0 to 1.0, which will put it at each edge of the image.

Shader code
shader_type canvas_item;

const float PI = 3.141592656;

uniform sampler2D fill_texture_overlay;
uniform sampler2D bg_texture;
uniform float fill_ratio:hint_range(0., 1.) = 1.;
uniform float start_angle:hint_range(0., 360.) = 0.;
uniform float max_angle:hint_range(0., 360.) = 360.;
uniform bool invert_fill = false;
uniform bool reflect_x = false;
uniform bool reflect_y = false;
uniform vec2 offset = vec2(0., 0.);

mat2 tex_rotate(float _angle){
	return mat2(vec2(cos(_angle), -sin(_angle)), vec2(sin(_angle), cos(_angle)));

void fragment() {
	float fill_angle = radians(fill_ratio * max_angle);
	vec2 uv = ((UV * 2. - 1.) + offset) * tex_rotate(-radians(start_angle));
	if (reflect_x) {
		uv *= mat2(vec2(-1., 0.), vec2(0., 1.));
	if (reflect_y) {
		uv *= mat2(vec2(1., 0.), vec2(0., -1.));
	if ((!invert_fill && atan(uv.x, uv.y) + PI < fill_angle) || (invert_fill && atan(uv.x, uv.y) + PI > fill_angle)) {
		COLOR = texture(TEXTURE, UV) * texture(fill_texture_overlay, UV);
	} else {
		COLOR = texture(bg_texture, UV);
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

Simple Radial Progress Bar

Wave Progress Shader

another wave Progress Shader

Notify of

Newest Most Voted
Inline Feedbacks
View all comments
2 years ago

I was trying to figure out how to pull this shader out of the textureProgress node. This is exactly what I was looking for, thanks!

I found a little error in there where invert X doesn’t invert anything. You need a -1.0 in the if statement.

Last edited 2 years ago by MightyMochiGames
3 months ago

Such an amazing shader, very good job!