OpenGL (LibGDX) Laser FX

A couple of weeks ago I started thinking on how to integrate some nice lasers fx in our game, Prototype Defense.

After searching on the internet for some guidelines on the subject, I found a single post on a dev’s blog; Cliffski’s.

Sadly however, he merely gave an outline on the subject, without any real implementation or specifics.

After implementing his idea, I started trying to enhance and expand it; and this is how I did it.

End Result

Ingame

Pro Tip #1: Whenever you are working with OpenGL, and designing effects; learn to use the Hot Plug feature of your desired IDE. E.g.; code is integrated into a running program whenever you save it; without having to restart the entire program. This makes it easy to try different blending techniques, etc..

Laser FX 101

To create laser fx in your game, you essentially need 3 sprites:

Start Cap background; The initial blast coming from the nozzle Middle Section: The repeatable part of your laser End Cap: The end part of your laser. (E.g. A fadeout) Interference Overlay: A repeatable overlay animation showing “interference” which will give direction and realism.

For our purpose, we will expand this by another 3 sprites, and adjust the other 4.

Essentially I divided the Start, End and Middle section into 2 sprites; the background and the overlay.

The background indicates the characteristic “glow” of the laser, while the overlay indicates the “white, bright” beam.

I did this so I can programmatically set the color on the background (glow) of the sprite enabling me to reduce the amount of textures required. (and enable me to change the color during gameplay for even more wackier effects)

So now we have 7 sprites:

Start Cap Background: The blast & glow coming from the nozzle Overlay: The blast and beam coming from the nozzle End Cap Background: The ending part glow; a fade out of the glow Overlay: The ending part beam; a fade out of the beam Middle Section Background: The glow of the beam Overlay: The beam itself

Middle Section

(Author note: the px measurements are for an image of 64x64)

As the middle section will define the actual look’n’feel of the laser, we’ll start with this. Open your favorite artwork program and create a white line in the middle of your image, from top to bottom, 2px wide on a seperate layer we’ll call “mid-overlay”. Now add a outer glow effect to this layer, white, which as a non-linear fade-off so that it ends at around 5px. Copy “overlay” into a new layer, and call this “mid-background”. Now adjust the outer glow effect such that its fade-off is much larger (~18px) and it has a transparency of around 50%. Now save the overlay and background layers into seperate sprites. (E.g; laser-mid-o.png & laser-mid-b.png)Don’t forget to save the whole image to a seperate file, as we will re-use it for the start & end section.

Pro Tip #2: You can add overlay / background layers with varying outer glow effects to create different types of laser. Just keep in mind that the background layers should represent a “glow”, while the overlay layers should represent the beam itself.

Start Section

Copy the contents of “mid-overlay” and “mid-background” into 2 new layers called “start-overlay” and “start-background”.

On the “mid-overlay” layer, disable the outer glow and draw a white circle in the center of your image (or wherever you think the nozzle will be), slightly larger than the “mid-overlay” line. Remove the part of the line that would be behind the nozzle, so you end up with something that resembles a ball and a stick portruding from it.

Then re-enable the glow for this layer.

Now repeat this process for the “mid-background”; so the glow incorporates the circle.

Save the layers into seperate sprites.

(E.g; laser-start-o.png & laser-start-b.png)

End Section

Copy the contents of “mid-overlay” and “mid-background” into 2 new layers called “end-overlay” and “end-background”.

Create a temporary layer and fill it with a gradient going from white to transparent from top to bottom. Select the resulting fill, and use the selection to delete the contents in the “end-*” layers.

Essentially you should now end up with a fade effect going from opaque to transparant, from bottom to top.

Again, save the layers into seperate sprites.

(E.g; laser-end-o.png & laser-end-b.png)

Pro Tip #3: This can obviously be done in different ways, try and experiment!

Overlay Animation

For this, I simply used random noise, blurred, of the length of my beam, and width of around half the glow. I then animated it by moving the texture upwards, ensuring that it repeated seamless at both bottom and top.

(e.g.: copy the portion that moves outside at the top to the bottom)

Now onto the actual code!

Rendering

Essentially we want to render as such:

Set Blending to addition; {GL10.GL_SRC_ALPHA, GL10.GL_ONE} Adjust alpha of beam && glow color (decay effect) For {x = start, mid, end sprite} Set color to glow color Draw x-background sprite Set color to beam color Draw x-overlay sprite Set color to default Draw overlay animation from the center of the start sprite to the center of the end sprite.

Additions would be:

Add randomness to the alpha

Add a fade to the laser; E.g.: The end of the midpoint mesh (and endcap mesh) could have targetAlpha.(see code at the bottom)

Blending

First off, the essential part of the laser look’n’feel is a specific blending technique call addition.

The openGL command for this is:

OpenGL

openGlContext.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);

LibGDX

spriteBatch.end(); // actual drawing is done on end(); if we do not end, we contaminate previous rendering. spriteBatch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE); spriteBatch.start();

The difference? Well look:

Color

Now, choose a color for your beam and your glow, and ensure that the mesh you use to draw the background sprites has the glow color as it’s vertex color; and the overlay sprites have the beam color.

To get a traditional laser effect, use red for the glow, and white for the beam.

OpenGL

vertices[idx++] = x; vertices[idx++] = y; vertices[idx++] = color; vertices[idx++] = u; vertices[idx++] = v;

LibGDX

spriteBatch.setColor(color);

To make the laser look more realistic, we will add a simple decay effect by setting the alpha of both glow and beam to something like:

color.alpha = 1 - (lifeTimeOfLaser / totalTimeOfLaser) ^ 2

By making this exponential instead of linear, we get a nice “afterglow” effect.

Texture repeat

For almost all texture we either don’t need to repeat {start, end} or just stretch them {middle}.

The main problem however is the Overlay animation. We need to repeat this texture vertically so we do not get any stretch artifacts.

Normally this could be easily solved by adjusting V in the texture coordinates of the mesh.

vertices[idx++] = v * scale; // Where factor by which we are stretching

However, this will only work if the overlay animation is in a single texture.

If you are (and I really hope so; if not make this priority number 1) working with texture atlases, then the U,V coordinates point to a region within the texture, which we can not simply repeat.

The solution to this is to create a single mesh consisting of multiple quads which point to this region.

The method to create such a mesh:

(Author note: This method also incorporates setting the color of said mesh, and a extension in which a alpha-gradient could be given. E.g.: the mesh interpolates from the colorT.alpha to colorT.alpha*alpha over its scale.)

[Based on LibGDX SpriteBatch, with minor adjustments]

/** * Creates a mesh which will draw a repeated texture. To be used whenever we are dealing with a region of a TextureAtlas * @param vertices For re-use, the vertices to use for the mesh. If insufficient are provided, a new array will be constructed * @param region The AtlasRegion to use * @param scale The factor by which we want to repeat our texture * @param x * @param y * @param originX * @param originY * @param width * @param height * @param scaleX Scale by which we want to expand the mesh on X * @param scaleY Scale by which we want to expand the mesh on Y * @param rotation Degrees of rotation for mesh * @param colorBase The initial base color * @param alpha The alpha by which to mult the colorBase by; resulting in the end interpolation target. * @return */ private static final float[] constructEndingMesh( float[] vertices, AtlasRegion region, int scale, float x, float y, float originX, float originY, float width, float height, float scaleX, float scaleY, float rotation, Color colorT, float alpha) { if(scale * 20 > vertices.length) vertices = new float[20*scale]; float color = colorT.toFloatBits(); float colorE; int idx = 0; // bottom left and top right corner points relative to origin final float worldOriginX = x + originX; final float worldOriginY = y + originY; float fx = -originX; float fy = -originY; float fx2 = width - originX; float fy2 = height - originY; // scale if (scaleX != 1 || scaleY != 1) { fx *= scaleX; fy *= scaleY; fx2 *= scaleX; fy2 *= scaleY; } // construct corner points, start from top left and go counter clockwise final float p1x = fx; final float p1y = fy; final float p2x = fx; final float p2y = fy2; final float p3x = fx2; final float p3y = fy2; final float p4x = fx2; final float p4y = fy; float Fx1; float Fy1; float Fx2; float Fy2; float Fx3; float Fy3; float Fx4; float Fy4; // rotate if (rotation != 0) { final float cos = MathUtils.cosDeg(rotation); final float sin = MathUtils.sinDeg(rotation); Fx1 = cos * p1x - sin * p1y; Fy1 = sin * p1x + cos * p1y; Fx2 = cos * p2x - sin * p2y; Fy2 = sin * p2x + cos * p2y; Fx3 = cos * p3x - sin * p3y; Fy3 = sin * p3x + cos * p3y; Fx4 = Fx1 + (Fx3 - Fx2); Fy4 = Fy3 - (Fy2 - Fy1); } else { Fx1 = p1x; Fy1 = p1y; Fx2 = p2x; Fy2 = p2y; Fx3 = p3x; Fy3 = p3y; Fx4 = p4x; Fy4 = p4y; } float x1 = Fx1 + worldOriginX; float y1 = Fy1 + worldOriginY; float x2 = Fx2 + worldOriginX; float y2 = Fy2 + worldOriginY; float scaleX2 = ((Fx2-Fx1) / scale); float scaleY2 = ((Fy2-Fy1) / scale); float scaleX3 = ((Fx3-Fx4) / scale); float scaleY3 = ((Fy3-Fy4) / scale); float scaleAlpha = (colorT.a - (colorT.a*alpha)) / scale; float x3 = x1; float y3 = y1; float x4 = x2; float y4 = y2; final float u = region.getU(); final float v = region.getV(); final float u2 = region.getU2(); final float v2 = region.getV2(); for (int i = 1; i <= scale; i++) { x1 = Fx1 + scaleX2*(i-1) + worldOriginX; y1 = Fy1 + scaleY2*(i-1) + worldOriginY; x2 = Fx1 + scaleX2*i + worldOriginX; y2 = Fy1 + scaleY2*i + worldOriginY; x3 = Fx4 + scaleX3*i + worldOriginX; y3 = Fy4 + scaleY3*i + worldOriginY; x4 = Fx4 + scaleX3*(i-1) + worldOriginX; y4 = Fy4 + scaleY3*(i-1) + worldOriginY; color = colorT.toFloatBits(); colorT.a-=scaleAlpha; colorE = colorT.toFloatBits(); vertices[idx++] = x1; vertices[idx++] = y1; vertices[idx++] = color; vertices[idx++] = u; vertices[idx++] = v; vertices[idx++] = x2; vertices[idx++] = y2; vertices[idx++] = colorE; vertices[idx++] = u; vertices[idx++] = v2; vertices[idx++] = x3; vertices[idx++] = y3; vertices[idx++] = colorE; vertices[idx++] = u2; vertices[idx++] = v2; vertices[idx++] = x4; vertices[idx++] = y4; vertices[idx++] = color; vertices[idx++] = u2; vertices[idx++] = v; } return vertices; }

Then feed this mesh, along with the texture to either OpenGL, or LibGDX.

And that’s about it. Have fun coding!

References: