CodePoke
Propogating Knowledge Engineering
  • facebook
  • twitter
  • Home
  • Games
  • Supported Projects
  • Blog
Select Page ...

Blog

OpenGL (LibGDX) Laser FX

Gijs-Jan December 27, 2011 Uncategorized 28 Comments

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:

  1. Start Cap background; The initial blast coming from the nozzle
  2. Middle Section:  The repeatable part of your laser
  3. End Cap: The end part of your laser. (E.g. A fadeout)
  4. 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:

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

Middle Section

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

Middle Background

Middle Overlay

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

Start Background

Start Overlay

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

End Background

End Overlay

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

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:

  1. Set Blending to addition; {GL10.GL_SRC_ALPHA, GL10.GL_ONE}
  2. Adjust alpha of beam && glow color (decay effect)
  3. For {x = start, mid, end sprite}
    1. Set color to glow color
    2. Draw x-background sprite
    3. Set color to beam color
    4. Draw x-overlay sprite
  4. Set color to default
  5. 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:

Blending Off

Blending On

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:

  1. Cliffski’s Blog: The complexities of drawing laser beams
    Gave me a basic outline of how to do lasers without delving into any specifics
  2. Gratuitous Space Battles
    Inspiration!
  3. Ikuraga
    Inspiration!
← Map Editor
Machine Learning and Game Balancing pt. 1 →
Gijs-Jan

28 Responses to OpenGL (LibGDX) Laser FX

  • Aron Bleifus
    1 / 3 / 2012

    Loving the information on this internet site, you have done great job on the blog posts.

    Aron Bleifus 1 / 3 / 2012
    Reply
    • Gijs-Jan
      3 / 7 / 2012

      Thanks!

      Gijs-Jan 3 / 7 / 2012
  • Alex
    1 / 24 / 2012

    Hi! Thank you for great explanation! Please, if you can – post a simple example code for use a constructEndingMesh function for libgdx! I try use it, but i dont understand how i can draw a sprites with returned float arrays, i mean – how it’s used with lot of sprites. By the way – why are you using a AtlasRegion and not the TextureRegion? Thank you!
    P.S. Sorry for my english =)

    Alex 1 / 24 / 2012
    Reply
    • Gijs-Jan
      3 / 7 / 2012

      The AtlasRegion gave us more necessary information on the truncated image (as we removed whitespace), which was needed for the drawing.

      I’ll try to post the simple example a.s.a.p.

      Gijs-Jan 3 / 7 / 2012
    • Alex
      4 / 2 / 2012

      Thank you for info! I’ll remember this! =)

      Alex 4 / 2 / 2012
  • Joakim
    2 / 19 / 2012

    Great blog. I agree with Alex. It would be great to see a simple example on how this all is implemented. I could not find any information about this on the web, so it would be quite valuable.

    Joakim 2 / 19 / 2012
    Reply
    • Gijs-Jan
      3 / 7 / 2012

      Hey,

      Thanks for the feedback. I’m sorry about the delay (we’re all students here).
      I’ll try to get a new post on the subject up a.s.a.p.

      Gijs-Jan 3 / 7 / 2012
  • David
    3 / 3 / 2012

    Thanks for the article, it’s very informative. A sample code of how to render the mesh would be awesome. I can’t find GL_QUAD in libgdx.

    David 3 / 3 / 2012
    Reply
    • Alex
      4 / 2 / 2012

      Hi! I think it’s because it’s GLES. Use GL_TRIANGLE_FAN. Here some useful info (i hope links is allowed) – http://pandorawiki.org/Porting_to_GLES_from_GL

      Alex 4 / 2 / 2012
  • D.Winter
    4 / 25 / 2012

    I’am an OpenGL (LibGDX) newbie and my English is so poor.I can’t understand all.
    If you can send me the project file.
    Thank you very much.
    My email is wudong61228@yahoo.com.cn

    D.Winter 4 / 25 / 2012
    Reply
  • Orest
    8 / 26 / 2012

    Shit! How does anyone do gradient mesh. It’s super confusing!! And it doesnt even blend well at parts.

    Orest 8 / 26 / 2012
    Reply
    • Gijs-Jan
      8 / 26 / 2012

      What do you mean with gradient mesh?

      Gijs-Jan 8 / 26 / 2012
  • marcin
    9 / 26 / 2012

    Hi, thx for toutorial..

    but the code is cutted.. I will be gratful if You post complete code, or sending my full code email.

    thank You

    Regards:)

    marcin 9 / 26 / 2012
    Reply
    • junius
      4 / 18 / 2013

      I’d like the code too, could you update the post, please… :'(

      junius 4 / 18 / 2013
    • Gijs-Jan
      4 / 18 / 2013

      Could you be specific on what part of the code you would like?

      Gijs-Jan 4 / 18 / 2013
    • junius
      4 / 19 / 2013

      the code is cut at line 117, it ends on a for loop
      for (int i = 1; i and that’s it…

      junius 4 / 19 / 2013
    • Gijs-Jan
      5 / 7 / 2013

      Ah, apologies; I’ll update the post!

      Gijs-Jan 5 / 7 / 2013
  • Joey Kirkland
    4 / 28 / 2013

    Hi, can you explain the animation a bit more? As it has been stated it makes sense though, when watching the demo on the website, it seems thought this animation continues in a repeating form, multiple times, as a repetition in some way. Perhaps this can be explained more.

    Joey Kirkland 4 / 28 / 2013
    Reply
    • Gijs-Jan
      5 / 10 / 2013

      Ah, yes.

      There are two animations going on:
      – The flicker, which is done by each frame setting the alpha of the background layer to a random value.
      – The movement of the texture, which is done by offsetting the texture U/V coordinates a bit every frame.

      Gijs-Jan 5 / 10 / 2013
  • nexen
    5 / 13 / 2013

    Could you explain a little bit more this line:

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

    wich are the values of lifeTimeOfLaser and totalTimeOfLaser?, why 1

    Thanks

    nexen 5 / 13 / 2013
    Reply
    • Gijs-Jan
      5 / 13 / 2013

      So; two variables:
      – lifeTimeOfLaser: The cumulative time in the animation; this is normally update with the delta time between frames. Per example, if 10ms passed between this frame and the previous, 0.01f is added to this variable. It is set back to 0 if it becomes bigger than totalTimeOfLaser.
      – totalTimeOfLaser: The duration of the animation; per example 2.0f for 2 seconds.

      The range of the alpha value in Color is [0.0 .. 1.0]. Because I wanted a “dying”-out effect, I used 1.0 – x^2.
      So what happens now is that the laser will be fully visible at the start of the animation; and then fade out using the following function:

      If the “1.0 -” wasn’t there, the function would fade-in, instead of fade-out.

      Gijs-Jan 5 / 13 / 2013
  • nexen
    5 / 13 / 2013

    WOW thanks for that great explanation, I’m taking notes 🙂

    nexen 5 / 13 / 2013
    Reply
  • nexen
    5 / 16 / 2013

    I’m trying to use the formula

    1 – (lifeTimeOfLaser / totalTimeOfLaser) ^ 2

    but I get an error (invalid operands to binary expression float and float), for what I understand I cant use (^) when lifeTimeOfLaser and totalTimeOfLaser are floats, how could you use it in that way?

    I’m working on Xcode, objective C, in a Cocos2d for iphone project.

    In my case opacity of the sprite is 255 so it’s ok in this way?

    255 – (lifeTimeOfLaser / totalTimeOfLaser) ^ 2

    nexen 5 / 16 / 2013
    Reply
  • Martin Ng
    7 / 30 / 2014

    Nice tutorial. Thank you!

    But I am wondering how the fade in/out work with the alpha, since this is additive blending, which alpha value is ignored.

    Martin Ng 7 / 30 / 2014
    Reply
    • Gijs-Jan
      7 / 31 / 2014

      It depends; you can either switch from Additive Blending to one that also incorporates the DST_ALPHA (e.g.: (GL_ONE, DST_ALPHA)); or premultiply the alpha which IIRC is done in the tutorial.
      So, strictly speaking, if the tutorial implies just using Additive Blending is enough, it’s incorrect.

      Gijs-Jan 7 / 31 / 2014
  • Ludum Dare 30 – Hello, Alien! – Post Mortem | Accidently Awesome Blog
    9 / 5 / 2014

    […] LAZORZS! The laser effects turned out to be pretty neat, and it’s really fun just playing around with the lasers. (Thanks to this laser creation guide.) […]

    Ludum Dare 30 – Hello, Alien! – Post Mortem | Accidently Awesome Blog 9 / 5 / 2014
    Reply
    • Gijs-Jan
      9 / 5 / 2014

      The game looks great, good luck with finishing it up!

      Gijs-Jan 9 / 5 / 2014
  • Simon
    10 / 28 / 2014

    Newbie here trying to follow your guide.

    I have implemented the lasers and they are fading in and out really nicely. But I’m having problem rotating the laser. I just can’t get it to work. I want that my complete laser, with the 6 sprites be drawn from a point to a point.. How do I do that?

    Simon 10 / 28 / 2014
    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  • Categories

  • Archives

    • March 2015
    • November 2014
    • September 2014
    • May 2013
    • August 2012
    • June 2012
    • December 2011
    • October 2011
    • September 2011
    • August 2011
    • September 2010
    • August 2010
    • July 2010
    • June 2010
  • Copyright © 2016 CodePoKE.net. All Rights Reserved