Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (499)
Games in Android Showcase (118)
games submitted by our members
Games in WIP (568)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Laser FX [OpenGL/LibGDX]  (Read 10492 times)
0 Members and 1 Guest are viewing this topic.
Offline methius

Junior Newbie





« Posted 2011-12-28 15:44:25 »

    Hey Guys,

    First post here, and thought I'd immediately start by contributing, the following is my blogpost about how I added Laser FX using OpenGL to our game:
    http://www.codepoke.net/2011/12/27/opengl-libgdx-laser-fx/


    And the youtube link: http://www.youtube.com/embed/qzmW9TuLRRU

    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.

     

    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
    [/list]

    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:
    [ul]
       
    • 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)
    [/ul]

    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
    1  
    openGlContext.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);

    LibGDX
    1  
    2  
    3  
    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:

    VS.

    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

    1  
    2  
    3  
    4  
    5  
    vertices[idx++] = x;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v;


    LibGDX

    1  
    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:

    1  
    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.
    1  
    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]

    1  
    2  
    3  
    4  
    5  
    6  
    7  
    8  
    9  
    10  
    11  
    12  
    13  
    14  
    15  
    16  
    17  
    18  
    19  
    20  
    21  
    22  
    23  
    24  
    25  
    26  
    27  
    28  
    29  
    30  
    31  
    32  
    33  
    34  
    35  
    36  
    37  
    38  
    39  
    40  
    41  
    42  
    43  
    44  
    45  
    46  
    47  
    48  
    49  
    50  
    51  
    52  
    53  
    54  
    55  
    56  
    57  
    58  
    59  
    60  
    61  
    62  
    63  
    64  
    65  
    66  
    67  
    68  
    69  
    70  
    71  
    72  
    73  
    74  
    75  
    76  
    77  
    78  
    79  
    80  
    81  
    82  
    83  
    84  
    85  
    86  
    87  
    88  
    89  
    90  
    91  
    92  
    93  
    94  
    95  
    96  
    97  
    98  
    99  
    100  
    101  
    102  
    103  
    104  
    105  
    106  
    107  
    108  
    109  
    110  
    111  
    112  
    113  
    114  
    115  
    116  
    117  
    118  
    119  
    120  
    121  
    122  
    123  
    124  
    125  
    126  
    127  
    128  
    129  
    130  
    131  
    132  
    133  
    134  
    135  
    136  
    137  
    138  
    139  
    140  
    141  
    142  
    143  
    144  
    145  
    146  
    147  
    148  
    149  
    150  
    151  
    152  
    153  
    154  
    155  
    156  
    157  
    158  
    159  
    160  
       /**
       * 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:
    Offline gimbal

    JGO Knight


    Medals: 25



    « Reply #1 - Posted 2011-12-29 16:54:04 »

    Wow. Incredibly well written and complete, well done! And pictures, it has lots of pictures! Smiley

    Incidentally, the graphics of that upper screenshot remind me a lot of old Amiga games like Cytron and Universal Warrior. Which is a good thing, me being a retro geek.
    Offline Nate

    JGO Kernel


    Medals: 149
    Projects: 4
    Exp: 14 years


    Esoteric Software


    « Reply #2 - Posted 2011-12-30 01:43:03 »

    Very nice. Thanks for the article!

    Any chance we can see a video?

    By using many pieces for the beam, you draw many blended images. Could it still look good with the start, start overlay, middle, middle overlay, end, and end overlay?

    Photoshop tip: to get a grayscale version of a color image so you can tint it with OpenGL, go Image -> Mode -> Grayscale, then Image -> Adjustments -> Levels, choose the "white level" dropped, and click on the brightest color in your image.

    Games published by our own members! Check 'em out!
    Legends of Yore - The Casual Retro Roguelike
    Offline roland
    « Reply #3 - Posted 2011-12-30 01:57:00 »

    I agree with Nate. Can you post a video?  Grin
    Offline methius

    Junior Newbie





    « Reply #4 - Posted 2011-12-30 03:20:31 »

    I'll try to make a video this afternoon! :-)
    Offline methius

    Junior Newbie





    « Reply #5 - Posted 2011-12-31 01:42:29 »

    Video up:

    http://www.youtube.com/watch?v=qzmW9TuLRRU&feature=youtu.be
    Offline Nate

    JGO Kernel


    Medals: 149
    Projects: 4
    Exp: 14 years


    Esoteric Software


    « Reply #6 - Posted 2012-01-01 05:46:58 »

    Thanks for making the video! The lasers really do look awesome! Cheesy

    Offline gimbal

    JGO Knight


    Medals: 25



    « Reply #7 - Posted 2012-01-02 15:50:30 »

    Both the game and the lasers look awesome. If you ever need to convince anyone to use OpenGL over plain Java2D to do a 2D game, this video is all you need as ammunition. I'm certainly convinced now Smiley
    Pages: [1]
      ignore  |  Print  
     
     
    You cannot reply to this message, because it is very, very old.

     

    Add your game by posting it in the WIP section,
    or publish it in Showcase.

    The first screenshot will be displayed as a thumbnail.

    Riven (10 views)
    2014-10-02 14:36:20

    Pippogeek (41 views)
    2014-09-24 16:13:29

    Pippogeek (32 views)
    2014-09-24 16:12:22

    Pippogeek (22 views)
    2014-09-24 16:12:06

    Grunnt (48 views)
    2014-09-23 14:38:19

    radar3301 (30 views)
    2014-09-21 23:33:17

    BurntPizza (65 views)
    2014-09-21 02:42:18

    BurntPizza (37 views)
    2014-09-21 01:30:30

    moogie (44 views)
    2014-09-21 00:26:15

    UprightPath (53 views)
    2014-09-20 20:14:06
    List of Learning Resources
    by Longor1996
    2014-08-16 10:40:00

    List of Learning Resources
    by SilverTiger
    2014-08-05 19:33:27

    Resources for WIP games
    by CogWheelz
    2014-08-01 16:20:17

    Resources for WIP games
    by CogWheelz
    2014-08-01 16:19:50

    List of Learning Resources
    by SilverTiger
    2014-07-31 16:29:50

    List of Learning Resources
    by SilverTiger
    2014-07-31 16:26:06

    List of Learning Resources
    by SilverTiger
    2014-07-31 11:54:12

    HotSpot Options
    by dleskov
    2014-07-08 01:59:08
    java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
    Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!