You are here

How to use GLSL shaders to create a damage flash effect

Body: 

You know the effect, seen in tons of classic games -- an enemy flashes white to indicate they have taken damage. An example from my own game Tiny Robot Justice Squad which is on Steam Greenlight can be seen below:

The general logic behind the effect is as follows: If the enemy takes damage, for some number of frames afterwards draw a white version of their sprite rather than the actual sprite. In this devblog I will explain how I accomplish this effect using OpenGL shaders. A Google search will bring up lots of posts on Game Development websites and Stack Overflow of different ways to accomplish the effect, but unfortunately some of the higher-ranked results are old, not very good and/or rely on deprecated OpenGL functionality. This muddies the waters, and makes it difficult to research how to accomplish such a seemingly simple effect, if your engine does not support it out-of-the-box.

This will be written in a very "Dummies Guide" kind of way, as GLSL shaders are not something I have a massive amount of experience with, and we are mainly concerned here with achieving a certain graphical behaviour. Feel free to mail me if you see any glaring mistakes. I use libGDX, however the code that follows is GLSL shader code, so will work on any engine that supports OpenGL shaders, the actual non-shader code logic is also very light so would be trivial to adapt to your own engine.

Some of the possible ways I found to achieve the effect were:

  • Make and export a version of the sprite that is filled white.
  • Edit the pixel array directly and set every pixel to rgb(255,255,255).
  • Set certain OpenGL rendering flags/blend functions before drawing the sprite.
  • Use a shader.

There are various problems with all of these. The first, to make and export an entirely white sprite, isn't ideal because it means you have to go back through all of your previous art or (write a script to do so -- sounds like a pain!) and generate specific images. If you have memory limitations on your software, this might not even be an option.

The second solution is very similar to the first -- to fill each pixel white -- on the upside, this seems like it would be easier to script and to generate just once for all of your sprites, however it relies on your engine giving you access to the pixel array. In my case, I wasn't sure how libGDX dealt with such things and what the best practices were in working with them. It seemed too messy and low-level for my liking.

The third option, to set certain OpenGL rendering flags, seems like the worst -- a lot of the functions recommended online are deprecated (many I found were in forum posts from 2006!), or don't produce the desired effect. One example I saw suggested setting the blending mode to additive and rendering the sprite a bunch of times, using glBlendFunc(GL_SRC_ALPHA, GL_ONE). This is obviously a bit hacky, and this kind of additive approach fails when a sprite contains dark or black colours, as these won't be correctly set to a lighter colour by additive blending (0 + 0 == 0). Another example I saw said to simply change the OpenGL rendering colour to tint the sprite during rendering using glColor3f(255,255,255) however, this doesn't work at all, as white is transparent in OpenGL, so we can't tint the sprite white this way. It works for other colours, however.

The final solution, to use a shader, seems to be the best. It's engine-agnostic, clean, fast and straightforward. Shaders have been part of the OpenGL standard since 2004, so any reasonably recent system should be able to cope with them. My personal problem was that, while I am a pretty competent coder, dealing with shaders and GLSL has always been a bit of a struggle. But I persevered and cobbled together a solution which I am now going to explain. A shader program has two components: a vertex shader and a fragment shader.

First up is our vertex shader. This actually doesn't do anything too interesting, it just takes a vertex and gives it a colour.

attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;
varying vec4 v_color;
varying vec2 v_texCoords;

void main() {
  v_color = a_color;
  v_texCoords = a_texCoord0;
  gl_Position = u_projTrans * a_position;
}

This just draws whatever colour it is told to, meaning that the sprite appears as normal if we do not alter the shader's input. The actual interesting code is next up here in the fragment shader:

#ifdef GL_ES
precision mediump float;
#endif

varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;

void main() {

  vec4 texColor = texture2D(u_texture, v_texCoords);
  vec3 white = texColor.rgb + vec3(255, 255, 255);
  texColor.rgb = white;
  gl_FragColor = v_color * texColor;
}

On line 12 and 13, we take the input colour vector (the variable texColor.rgb) and add 255 to each component using vector addition. So, no matter what the input colour, we are going to increase it to the maximum value, making it appear white.

All you need now is logic in your program to decide whether to activate the shader or not. What follows is libGDX specific, but the general workflow should be identical for any other engine. First, we load in the shader -- in libGDX this is accomplished by using the ShaderProgram class like so:

ShaderProgram flashShader = new ShaderProgram(Gdx.files.internal(
"shaders"+File.separator+"flash-vert.glsl").readString(), 
Gdx.files.internal("shaders"+File.separator+"flash-frag.glsl").readString());

In my code I use a boolean isFlashing that every game object has, which is attached to an external timer object in the update loop. So when an enemy takes damage, this timer ensures that isFlashing is set to true for a short period of time, and is then set to false. While isFlashing is true we want to render the sprite using our flash shader, and when it is false we want to render it normally. I do this as follows:

if(isFlashing) {
  batch.setShader(flashShader); // turn the shader on
  sprite.draw(batch) // draw the sprite
  batch.setShader(null); // turn the shader off
} else {
 // render normally
}

That's all there is to it, really. In libGDX to remove a shader we simply set the shader to null, but your engine might differ. One bit of advice I would give is to remember to have a fallback effect: People might play your game without shaders, since they might be playing on old machines, or they may just have shaders turned off. In my code, if shaders are turned off, I fall back to just tinting the sprite red using OpenGL functionality like this:

if(isFlashing) {
  if(shadersEnabled == false) {
    batch.setColor(new Color(1, 0, 0, 0.95f)); // render a flash without using shaders
  }
} else {
// render normally

It doesn't look as nice, but it's guaranteed to work on all systems, and ensures that people that do turn shaders off don't miss out on a vital bit of game feedback.

You can download the shaders here:

Fragment Shader
Vertex Shader

Thanks for reading, and I hope this was of some use to you -- I wrote it because I noticed there was no up-to-date central resource for achieving this effect that I could easily find online. If you have any questions or comments, or if you come up with any new cool effects based on this idea, feel free to get in touch.