Advertisement Jump to content
  • Advertisement
  • 08/24/17 10:15 AM

    Fluid Rendering with Box2D

    General and Gameplay Programming

    dmatter
    • Posted By dmatter

    This is a technical article about how I implemented the fluid in my game “Invasion of the Liquid Snatchers!” which was my entry for the fifth annual "Week of Awesome " game development competition here at GameDev.net.

    One of the biggest compliments I’ve received about the game is when people think the fluid simulation is some kind of soft-body physics or a true fluid simulation. But it isn’t!

    The simulation is achieved using Box2D doing regular hard-body collisions using lots of little (non-rotating) circle-shaped bodies. The illusion of soft-body particles is achieved in the rendering.

     

    The Rendering Process

    Each particle is drawn using a texture of a white circle that is opaque in the center but fades to fully transparent at the circumference:

    particle-150x150.png

    These are drawn to a RGBA8888 off-screen texture (using a ‘framebuffer’ in OpenGL parlance) and I ‘tint’ to the intended color of the particle (tinting is something that LibGDX can do out-of-the-box with its default shader).

    It is crucial to draw each ball larger than it is represented in Box2D. Physically speaking these balls will not overlap (because it’s a hard-body simulation after all!) yet in the rendering, we do need these balls to overlap and blend together.

    The blending is non-trivial as there are a few requirements we have to take into account:

     - The RGB color channels should blend together when particles of different colors overlap.
       -- … but we don’t want colors to saturate towards white.
       -- … and we don’t want them to darken when we blend with the initially black background color.
     - The alpha channel should accumulate additively to indicate the ‘strength’ of the liquid at each pixel.

    All of that can be achieved in GLES2.0 using this blending technique:

    glClearColor(0, 0, 0, 0);
    glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE);

    Putting all that together gets us a texture of lots of blurry colored balls:

    blurryparticles.png

     

    Next up, is to contribute this to the main backbuffer as a full-screen quad using a custom shader.

    The shader treats the alpha channel of the texture as a ‘potential field’, the higher the value the stronger the field is at that fragment.

    The shader compares the strength of the field to a threshold:

    • Where this field strength is strong enough then we will snap the alpha to 1.0 to manifest some liquid.
    • Where the field strength is too weak then we will snap the alpha to 0.0 (or we could just discard the fragment) to avoid drawing anything.


    For the final game I went a little further and also included a small window around that threshold to smoothly blend between 0 and 1 in the alpha channel, this softens and effectively anti-aliases the fluid boundary.

    Here’s the shader:

    varying vec2 v_texCoords;
    uniform sampler2D u_texture;
    
    // field values above this are 'inside' the fluid, otherwise we are 'outside'.
    const float threshold = 0.6;
    
    // +/- this window around the threshold for a smooth transition around the boundary.
    const float window = 0.1;
    
    void main() {
        vec4 col = texture2D(u_texture, v_texCoords);
        float fieldStrength = col.a;
        col.a = smoothstep(threshold - window, threshold + window, fieldStrength);
        gl_FragColor = col;
    }

     

    This gives us a solid edge boundary where pixels are either lit or not lit by the fluid.
    Here is the result after we apply the shader:

    aftershader.png

    Things are looking a lot more liquid-like now!

    The way this works is that when particles come within close proximity of each other their potential fields start to add up; once the field strength is high enough the shader will start lighting up pixels between the two particles. This gives us the ‘globbing together’ effect which really makes it look like a fluid.

    Since the fluid is comprised of thousands of rounded shapes it tends to leave gaps against the straight-edged tilemap. So the full-screen quad is, in fact, scaled-up to be just a little bit larger than the screen and is draw behind the main scene elements. This helps to ensure that the liquid really fills up any corners and crevices.

    Here is the final result:

    fullycompositedfluid.png

    And that’s all there is for the basic technique behind it!

     

    Extra Niceties

    I do a few other subtle tricks which help to make the fluids feel more believable…

    • Each particle has an age and a current speed. I weight these together into a ‘froth-factor’ value between 0 and 1 that is used to lighten the color of a particle. This means that younger or faster-moving particles are whiter than older or stationary parts of the fluid. The idea is to allow us to see particles mixing into a larger body of fluid.
    • The stationary ‘wells’ where fluid collects are always a slightly darker shade compared to the fluid particles. This guarantees that we can see the particles ‘mixing’ when they drop into the wells.
    • Magma particles are all different shades of dark red selected randomly at spawn time. This started out as a bug where magma and oil particles were being accidentally mixed together but it looked so cool that I decided to make it happen deliberately!
    • When I remove a particle from the simulation it doesn’t just pop out of existence, instead, I fade it away. This gets further disguised by the ‘potential field’ shader which makes it look like the fluid drains or shrinks away more naturally. So, on the whole, the fading is not directly observable.

     

    Performance Optimisations

    As mentioned in my post-mortem of the game I had to dedicate some time to make the simulation CPU and Memory performant:

    • The ‘wells’ that receive the fluid are really just colored rectangles that “fill up”. They are not simulated. It means I can remove particles from the simulation once they are captured by the wells and just increment the fill-level of the well.
    • If particles slow down below a threshold then they are turned into non-moving static bodies. Statics are not exactly very fluid-like but they perform much better in Box2D than thousands of dynamic bodies because they don’t respond to forces. I also trigger their decay at that point too, so they don’t hang around in this state for long enough for the player to notice.
    • All particles will eventually decay. I set a max lifetime of 20-seconds. This is also to prevent the player from just flooding the level and cheating their way through the game.
    • To keep Java’s Garbage Collector from stalling the gameplay I had to avoid doing memory allocations per-particle where possible. Mainly this is for things like allocating temporary Vector2 objects or Color objects. So I factored these out into singular long-lived instances and just (re)set their state per-particle.

    Note: This article was originally published on the author's blog, and is reproduced here with the author's kind permission.



      Report Article


    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

    There are no reviews to display.


  • Advertisement
  • Advertisement
  • What is your GameDev Story?

    In 2019 we are celebrating 20 years of GameDev.net! Share your GameDev Story with us.

    (You must login to your GameDev.net account.)

  • Latest Featured Articles

  • Featured Blogs

  • Advertisement
  • Popular Now

  • Similar Content

    • By carlos gallegos
      Hi everyone
      Im working along side some friends of mine and we are looking for a 2D artist to work with us
      we are a team of 2 coders and a 3D artist
      if you are interested to join our project please message me and we can work something up
      the ultimate goal of our project its to go into kickstarter and get funding for the game
      basics from the game twin-stick shooter wit 2.5D for exploration of towns and cities in the world of the dead
      base in the mexican culture of "El dia de los muertos" ithe project name its 
      Mictlan
      2D game art 
      sprites
      UI elements (inventory and Icons and menu)
      if anyone its interested please message me so we can chat abour the project
      I let you some images of the game and some assets we have done
      (sorry for that we are not artists)
      feel free to contact me carlos_gallegos2095@outlook.com
       
       









    • By _kasm_
      Hello! I'm brand new to these forums, and I am looking for a hobbyist artist to work with me on a game I have a working prototype and documentation for. The game is in Java, and it is a Roguelite game that allows the player to select a class that has unique abilities before facing the procedurally generated dungeon before them. I do have plans for the future of the game in later stages of development, but will omit those unless interested, as I want the artist who's working on this project with me to have a good amount of creative control.
      If you are interested, email me at jlars789@gmail.com, where I can link the Dropbox to the playable prototype demo and the documentation I have so far. 
      Thanks in advance! 
    • By Tara Louise
      This idea came to me in a dream. It's a choose your own adventure game. 2D and VR optional. Basically, you are set up in a big town with other live players. You are free to do what you'd prefer. For example, in the game you could stay at home and do any mundane activity that you choose. Or, explore and find quests. These quests are no dragons and knights. This setting is taken place in a regular town in a modern day time frame. You could find a quest from a programmed avatar who would need help trying to capture a criminal, etc. You could be like Grand Theft Auto and be the one people try to capture. You could get a job. You could meet people (being a live game) and go to a restaurant, smoke, drink, play darts/pool, get a hotel room with someone etc. The game is what you want to make of it. Either it's a simple story line (for someone like me) who likes to explore different places, do extreme sports and play mini games with other live players. Or. You can make it something completely different and fierce with a whole lot of action. It's your choice.
      All people would love this game because they are almost designing it as it goes. What will be the adventure today? Or what will the regular day bring?
      Thoughts? Comments? An e-mail is fine, too. Taraloumccarthy@gmail.com. 
      So far this is an idea. And the post is would you play it? Are there interests in coming up with a game plan on this.
    • By SIr Pep
      I have been making my game 2d in Unity and almost immediately came upon this 2D tile rendering issue.
      All I've found online, and am using, is a hack to fix it which includes separating them all, and in some cases drawing lines in between every single tile to match colors.
      I'm wondering if anyone else has this issue and how to fix it. 
      Also, @CrazyCdn here's what I meant. You see streaks across the tiles that shoudn't be there.


      Also, here's a youtube video of that exact problem
      https://www.youtube.com/watch?v=QW53YIjhQsA
       
    • By datboi
      Hello everyone. I have been trying to make a font renderer that uses freetype for the past couple of days but am currently stuck with getting uv tex values when rendering.
      Here is the current code I use
      struct SVertex { SVector4f        pos;     Color32           col;     SVector2f        tex; }; // D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 float CDisplayFont::DrawTextA( const char * szText, int textCount, const SVector2f& pos, const SColor & color, const SRectf * pClipRect ) {     SVector2f tmp = ( pos ); IRender * pRender = g_pCore->GetGraphics()->GetRender();          for ( int i = 0; i < textCount; ++i )     {         Codepoint_t cp = static_cast< Codepoint_t >( szText[ i ] );         if ( GlyphInfo_t * info = GetGlyphInfo( cp ) )         {             if ( szText[ i ] != ' ' )             {                 float sx = tmp.x + info->offsetX * m_fScaleHoriz;                 float sy = tmp.y - ( info->height - info->offsetY ) * m_fScaleVert;                 float w = info->width * m_fScaleHoriz;                 float h = info->height * m_fScaleVert;                                                      // column (u) and row (v) number                 // -- heres where i'm stuck                 float u = 0;                 float v = 0;             SVertex vtx[ ] =                 {                     { sx,     sy + h, 0.0f, 1.f, color, u, v },                     { sx,     sy,     0.0f, 1.f, color, u, v },                     { sx + w, sy + h, 0.0f, 1.f, color, u, v },                     { sx + w, sy,     0.0f, 1.f, color, u, v },                     { sx + w, sy + h, 0.0f, 1.f, color, u, v },                     { sx,     sy,     0.0f, 1.f, color, u, v }                 };             //   arguments ->    ( rl, vtx data, vtx count, topology, texture (IDirect3DTexture9) )                 pRender->PushVertices( NULL, vtx, 6, D3DPT_TRIANGLELIST, info->texture->GetInternalPtr() );                 }                 tmp.x += ( float )( info->advance >> 6 ) * m_fScaleHoriz;                 }     }          return tmp.x; } This obviously prints nothing because I am stuck on how exactly I should be getting the correct u & v coords.
      I have confirmed that I am getting the correct texture by saving the IDirect3DTexture9 to a file, getting stuff like this - https://imgur.com/a/Lwl2Xws

      I appreciate any advice/pointers in the right direction, thank you.
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!