2.5d sprite shader

Reproduces the behaviour of 2.5D/Doom-style sprites inside a shader.

The shader is designed to minimise the work done in scripting that’s usually required with this sort of thing in godot.

The other benefit is you can see the effect in-editor.

The shader is set up so the angular sprites are stacked horizontally, with the animation frames stacked vertically

The “frame” instance uniform can be used to change the animation from by offsetting the UV Y coordinate. Because you can animate everything in godot, you can animate your sprite by changing the frame value.

Updates provided in part thanks to MrTriPie’s comments.

Updates:

  • Y billboard option bool
  • alpha option bool
  • scale uniform
Shader code
//By Kyra July 2023. Updated December 2023
//Tested in Godot 4.2
//Made with help from Blackle Mori at suricrasia.online and MrTriPie's commnets on GDshaders

shader_type spatial;

uniform sampler2D albedo;

uniform int directions = 8;

uniform int frame_count = 5;

uniform int frame = 0;

uniform float scale = 1.0;

uniform bool y_billboard;

uniform bool use_alpha;

void vertex() {
	if(y_billboard){
		MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
        vec4(normalize(cross(vec3(0.0, 1.0, 0.0), INV_VIEW_MATRIX[2].xyz)), 0.0),
        vec4(0.0, 1.0, 0.0, 0.0),
        vec4(normalize(cross(INV_VIEW_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))), 0.0),
        MODEL_MATRIX[3]);
	}else{
		MODELVIEW_MATRIX = VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0], INV_VIEW_MATRIX[1], INV_VIEW_MATRIX[2], MODEL_MATRIX[3]);
		MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);
	}
	VERTEX *= scale;
	vec3 direction_to_camera = normalize(CAMERA_POSITION_WORLD-NODE_POSITION_WORLD);
	float angle_to_camera = atan(direction_to_camera.x,direction_to_camera.z);
	vec4 model_direction = MODEL_MATRIX*vec4(1.0,0.0,0.0,0.0);
	float y_angle = atan(model_direction.x,model_direction.z)-PI;
	float final_angle = angle_to_camera-y_angle;
	float int_y_angle = round((final_angle / TAU) * float(directions)) / float(directions);
	UV.y += float(frame);
	UV /= vec2(float(directions), float(frame_count));
	UV.x += int_y_angle;
}

void fragment() {
	vec4 albedo_tex = texture(albedo,UV);
	if(use_alpha){
		ALPHA_SCISSOR_THRESHOLD = 0.5;
		ALPHA = albedo_tex.a;
	}
    ALBEDO = albedo_tex.rgb;
}
Tags
2.5d, 2d, 3d, doom, retro
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

Outline and Glow Shader Sprite 3D

2d sprite based vfx gradient shader

Animated Noise and Sprite Mixer

Subscribe
Notify of
guest

6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
MrTriPie
MrTriPie
11 months ago

I had problems with it working correctly on a different amount of directions than 8

I think changing this line:

float int_y_angle = float(2+int(((final_angle_y)/TAU)*9.0))/8.0;

to this fixes it:

float int_y_angle = round((final_angle_y/TAU)*float(directions))/float(directions);
MrTriPie
MrTriPie
11 months ago

If you want the billboarding to be constrained to the Y axis, you can replace these lines:

 MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
        INV_VIEW_MATRIX[0], INV_VIEW_MATRIX[1], INV_VIEW_MATRIX[2], MODEL_MATRIX[3]);

With these (copied from Godot’s StandardMaterial converted to ShaderMaterial):

    MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
        vec4(normalize(cross(vec3(0.0, 1.0, 0.0), INV_VIEW_MATRIX[2].xyz)), 0.0),
        vec4(0.0, 1.0, 0.0, 0.0),
        vec4(normalize(cross(INV_VIEW_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))), 0.0),
        MODEL_MATRIX[3]
    );
AncientStoneStudios
11 months ago

is there a way to chnage the size?

CoolDotty
CoolDotty
5 months ago

I was getting some funky colors but adding an export hint to the albedo fixed it.

uniform sampler2D albedo: source_color;

Last edited 5 months ago by CoolDotty