Spatial View-Depending Directional Billboard
Wanna create the old Pokémon series with a 3D rotable camera? This shader is useful to make a simple and animatable 2.5d object or character in a 3D space, with a variable number of directions rendered depending on the view’s rotation. Here there are both the code for Godot 4 and (commented) for Godot 3.
The differences between the versions are only in the syntax and in the matrix rotation order (YXZ in Godot 4, XYZ in Godot 3).
To setup the shader:
- Create a MeshInstance and set a Quad mesh. The shader works with other meshes too, but the result would not be what you expect.
- Put the shader in the material of the mesh or in the material override.
- Choose a texture as sprite sheet, put it in the texture parameter and set the other parameters. It’s explained what they are used for below.
Talking about the sprite sheet, it should respect those rules:
- It’s divided as an uniform matrix. We will call rows the horizontal lines of frames, and column the vertical lines. If you want to invert this order remember to set Directions On Horizontal Axis true, as we will see in the parameters section.
- Each row represents the same animation of the other rows in a specific direction, represented only by the rotation around the vertical axis of the object.
NOTE: the shader works despite to the mesh transform, so to edit the direction of the object, you have to change a virtual rotation around the vertical axis, that is a parameter that we’ll see below. - Each column shows a frame of the animation in all the directions.
- The directions change uniformly counterclockwise along the rows, is you do it clockwise remember to set the parameter Clockwise true. It’s sugested to set the first direction toward east, but we will see that it’s recommended and not necessary.
- You can put different animations sheet in a single sprite sheet, as long as the rows count doesn’t change.
One of the screenshots shows how an 8 direction character (without animation, so there is just one frame column) should be deplaced in the sprite sheet.
The other shader parameters are:
- Albedo: the color multiplied to the sprite. If it’s set to white the sprite is rendered naturally, if red it looks like a Nintendo Virtual Boy character, and so on. The alpha channel is taken in consideration too.
- Directions Count: the number of rows in which the sprite sheet is divided.
- Frames Count: the number of column in which the sprite sheet is divided.
- Frame: it’s refered to the column of the sprite sheet, you can manipulate this integer by GDSpript to make the animations play. No need to say that GDScript can access to every shader parameter.
- Y Angle: the euler global rotation around the vertical axis, measured in radians. The resulting direction vector is
(cos y_angle, 0, -sin y_angle)
. - Starting Angle: the expected Y Angle of the direction of the first row, it should be set to solve an eventual rotation phase displacement.
Pratically, if your sprite sheet’s first row looks toward east, it must be set to 0, if it looks toward north it must be set to PI/2 and so on. - Directions On Horizontal Axis: if you displayed the frames along the vertical axis and the directions along the horizontal one, set this parameter true in order to fit everything.
- Clockwise: if you have drawn the sprite sheet so as to make the object rotate clockwise instead of counter-clockwise, set this parameter true.
Other notes:
- In Godot 4, the graphic result is a bit different to Godot 4 when WorldEnvironment is used.
- The image can be scaled in the mesh settings, instead of the scale of the Mesh Instance.
- To set (or get) a parameter in Godot 3 by GDScript follow this example:
mesh.surface_get_material(0).set("shader_param/frame",3.142)
- The link to the sprites i used:
https://it.pinterest.com/pin/351912455260252/
https://axulart.itch.io/small-8-direction-characters
I don’t own them, and I’m using them only for demonstrative reasons.
Shader code
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,unshaded;
uniform vec4 albedo : source_color = vec4(1.0,1.0,1.0,1.0);
uniform sampler2D sprite_sheet : source_color;
/// the count of v_frames, used for the directions
uniform int directions_count = 1;
/// the count of h_frames, used for the animations
uniform int frames_count = 1;
/// the current h_frame
uniform int frame = 1;
/// the angle around vertical axis; the resulting direction looks to (cos y_angle, 0, -sin y_angle)
uniform float y_angle = 0.0;
/// this parameter below specifies which angle is the first frame pointed to.
/// example: if your character has the first frame turned toward north, the starting angle should be pi/2, if it looks to east it can be let to 0.0
uniform float starting_angle = 0.0;
/// this option specify if the direction changes along the vertical axis (default, false) or use the horizontal one instead (true)
uniform bool directions_on_horizontal_axis = false;
/// if you have drawn the sprite sheet so as to make the object rotate clockwise instead of counter-clockwise, set this parameter below true
uniform bool clockwise = false;
void vertex() {
MODELVIEW_MATRIX = VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0],INV_VIEW_MATRIX[1],INV_VIEW_MATRIX[2],MODEL_MATRIX[3]);
}
void fragment() {
//euler angle around the vertical axis
float vert_axis_angle = atan(-VIEW_MATRIX[0][2]/VIEW_MATRIX[2][2]) + (VIEW_MATRIX[0][0] < 0.0?PI:0.0);
//difference
vert_axis_angle += y_angle - starting_angle;
//pinning
vert_axis_angle += PI / float(directions_count);
float direction = floor(vert_axis_angle * float(directions_count) / TAU);
if (clockwise) direction = 1.0 - direction;
float uv_y = direction / float(directions_count);
//texture UV
vec2 texture_uv = directions_on_horizontal_axis?
vec2(
(UV.x / float(directions_count)) + uv_y,
(UV.y + float(frame))/float(frames_count)
):
vec2(
(UV.x + float(frame))/float(frames_count),
(UV.y / float(directions_count)) + uv_y
);
vec4 albedo_tex = texture(sprite_sheet,texture_uv);
//albedo
ALBEDO = albedo.rgb * albedo_tex.rgb;
ALPHA = albedo.a * albedo_tex.a;
}
/************************ FOR GODOT 3 ***************************
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,unshaded;
uniform vec4 albedo: hint_color = vec4(1.0,1.0,1.0,1.0);
uniform sampler2D sprite_sheet : hint_albedo;
/// the count of v_frames, used for the directions
uniform int directions_count = 1;
/// the count of h_frames, used for the animations
uniform int frames_count = 1;
/// the current h_frame
uniform int frame = 1;
/// the angle around vertical axis; the resulting direction looks to (cos y_angle, 0, -sin y_angle)
uniform float y_angle = 0.0;
/// this parameter below specifies which angle is the first frame pointed to.
/// example: if your character has the first frame turned toward north, the starting angle should be pi/2,
///if it looks to east it can be let to 0.0
uniform float starting_angle = 0.0;
/// this option specify if the direction changes along the vertical axis (default, false) or use the horizontal one instead (true)
uniform bool directions_on_horizontal_axis = false;
/// if you have drawn the sprite sheet so as to make the object rotate clockwise instead of counter-clockwise, set this parameter below true
uniform bool clockwise = false;
void vertex() {
MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);
}
void fragment() {
//euler angle around the vertical axis
float vert_axis_angle = atan(CAMERA_MATRIX[0][2]/CAMERA_MATRIX[0][0]) + (CAMERA_MATRIX[0][0] < 0.0?3.141593:0.0);
//difference
vert_axis_angle += y_angle - starting_angle;
//pinning
vert_axis_angle += 3.141593 / float(directions_count);
float direction = floor(vert_axis_angle * float(directions_count) / 6.283185);
if (clockwise) direction = 1.0 - direction;
float uv_y = direction / float(directions_count);
//texture UV
vec2 texture_uv = directions_on_horizontal_axis?
vec2(
(UV.x / float(directions_count)) + uv_y,
(UV.y + float(frame))/float(frames_count)
):
vec2(
(UV.x + float(frame))/float(frames_count),
(UV.y / float(directions_count)) + uv_y
);
vec4 albedo_tex = texture(sprite_sheet,texture_uv);
//albedo
ALBEDO = albedo.rgb * albedo_tex.rgb;
ALPHA = albedo.a * albedo_tex.a;
}
****************************************************************/
Can you make a video explaining how to setup this shader?
i’m prettry confused by your instrution
Yep, you’re right. I’ll make a video to explain me better and I’ll load it in this page asap.
Done, it’s focused on how the texture should be setted and which is the purpose of any parameter… It’s my first edited video, so if you still have some doubts don’t hesitate to ask.