Muzzle flash Z-axis billboard
Simple billboard shader that rotates the mesh around its local Z axis towards the camera, applies emission and hides it when the camera direction gets close to Z axis.
2 shaders are included:
- First one is for side view of the muzzle flash. It rotates the mesh around local Z axis to face the camera. “Face threshold” parameter is used to hide the mesh when the camera faces Z axis.
- Second shader is used for front/back view of the muzzle flash. “Face threshold” is instead used to reveal the texture when camera is facing the Z axis. I recommend using the same “face threshold” value selected for first shader.
Basic scene setup:
- Add MeshInstance3D node with “Quad Mesh” or AnimatedSprite3D node, depending if you want to use animation. This node will be used for side view of muzzle flash.
- Duplicate the created node. This second node will be used for front view.
- Create two “Shader Material” resources and assign “muzzle_flash_billboard_side.gdshader” to the first material and “muzzle_flash_billboard_face.gdshader” to the second one.
- Add textures to materials using “Main Tex” parameter.
- Assign created materials to nodes from steps 1 and 2.
Once the scene is set up, you should see the mesh rotate towards the camera.
If the rotation is wrong, you can rotate the meshes to change the billboard axis. Billboard axis follows the mesh local Z axis.
Feel free to copy, modify and use the code.
Shader code
/////////////////////// muzzle_flash_shader_side.gdshader /////////////////////////////
// Billboard muzzle flash shader (side view).
// Made by shadecore_dev, 2025.
shader_type spatial;
uniform float emission = 1.0; // Color multiplier
uniform sampler2D main_tex : filter_nearest; // Main texture
uniform float face_threshold = 0.1; // 0.0-1.0 Angle threshold for hiding the billboard when the camera gets close to Z axis.
void vertex() {
vec3 side_vec3 = -MODEL_MATRIX[0].xyz;
vec4 side_vec4 = -MODEL_MATRIX[0];
float vec_dif = abs(dot(MODEL_MATRIX[0].xyz, CAMERA_DIRECTION_WORLD));
MODELVIEW_MATRIX = VIEW_MATRIX * (
mat4(
vec4(normalize(cross(side_vec3, INV_VIEW_MATRIX[2].xyz)), 0.0),
side_vec4,
vec4(normalize(cross(INV_VIEW_MATRIX[0].xyz, side_vec3)), 0.0),
MODEL_MATRIX[3]
)
);
}
void fragment() {
float vec_dif = abs(dot(MODEL_MATRIX[0].xyz, CAMERA_DIRECTION_WORLD));
vec4 color = texture(main_tex, UV);
ALBEDO = color.rgb;
EMISSION = color.rgb * emission;
ALPHA = color.a * max(0.0, 1.0 - vec_dif - face_threshold);
}
///////////////////////////////////////////////////////////////////////////////////////
////////////////////////// muzzle_flash_shader_face.gdshader /////////////////////////
// Billboard muzzle flash shader (front/back view).
// Made by shadecore_dev, 2025.
shader_type spatial;
uniform float emission = 1.0; // Color multiplier
uniform sampler2D main_tex : filter_nearest; // Main texture
uniform float face_threshold = 0.1; // 0.0-1.0 Angle threshold for revealing the billboard when the camera gets close to Z axis.
void vertex() {
vec3 face_vec3 = -MODEL_MATRIX[1].xyz;
vec4 face_vec4 = -MODEL_MATRIX[1];
float vec_dif = abs(dot(MODEL_MATRIX[0].xyz, CAMERA_DIRECTION_WORLD));
MODELVIEW_MATRIX = VIEW_MATRIX *
mat4(
vec4(normalize(cross(face_vec3, INV_VIEW_MATRIX[2].xyz)), 0.0),
face_vec4,
vec4(normalize(cross(INV_VIEW_MATRIX[0].xyz, face_vec3)), 0.0),
MODEL_MATRIX[3]
);
}
void fragment() {
float vec_dif = abs(dot(MODEL_MATRIX[0].xyz, CAMERA_DIRECTION_WORLD));
vec4 color = texture(main_tex, UV);
ALBEDO = color.rgb;
EMISSION = color.rgb * emission;
ALPHA = color.a * max(0.0, 0.5 - max(0.0, 1.0 - vec_dif - face_threshold));
}
///////////////////////////////////////////////////////////////////////////////////////
Please give us the project file so we know how to set it up