Jump to content
  • Advertisement
  • Remove ads and support GameDev.net for only $3. Learn more: The New GDNet+: No Ads!

  • 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


    There are no comments to display.



    Create an account or sign in to comment

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

    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

  • Advertisement
  • Advertisement
  • Latest Featured Articles

  • Featured Blogs

  • Popular Now

  • Similar Content

    • By ggenije
      Important: I am trying to realize in scrtach which is performance very low due to it's "virutal level" scrtach->flashplayer->java...
      Also i'm new to this forum so i'm sorry if I missed group (like last time)
      Like a title is saying:
      I have project ,and I get negative feedback on it because some people need 30 min to complete it (what is the planned time)
      but problem is that some people need EVEN 5 hours…(game is incremental/idle/upgrade type so it's important to keep same time ...)
      ———————————————————————————————————————-
      Of course people with slower computer will have less fps so game will be slower for them,
      so I have created TimeDelta system for each frame to calculate something to do per second
      for example
        Update(){move(TimeDelta*speed)}  so that mean it will be moving speed number of pixels(or units) per second so it will be same for almost each user.

      But problem is next:
      I have to change ySpeed by jumpPower (#PlayerJump in my project)
      when any jump button is pressed
      then in each frame decrease ySpeed by gravity it is(-10 * TimeDelta)
      but when someone have lower fps it will have higher TimeDelta and will fall faster but with same jump it turns out to jump significantly lower that changes core of game
      BUT even worse if fps suddenly in moment of jump then timeDelta would be 1 so player will jump much much MUCH higher , then fall much slower because timeDelta changed in meanwhile…(and the point of my game is about upgrading jump not complete game in first fps drop)


      —————————————————————————————————————————————————————

      Then I got an idea to fix TimeDelta (like in unity for rigibody) so it will be rounded like
      if calculated TimeDelta is 0.01834 it will be 0.02 fixed
      if weaker computer is using it the TImeDelta will be 0.143 so runded to 0.14 and so on…

      I did not manage to realize it… i tried to calculate it before main initialization of game objects
      but I'm afraid to fps will drop in moment that is calculating so it will be much diffirent…
      I was trying with empty loop(400)(in scrtach even this is taking time) to calculate it but i'm not sure is it right

      So is there good way to realize this fixed TimeDelta
      I only have timer function to use and time difference between frames
       
      This_is_the_link_for_the_game
    • By Kamal Wafi
      Hi there,
      i recently start learning unity and im working in my first game ,
       
      I was wondering if unity had functions to support the motion control effect (tilting screen to move character) you see
      in doodle jump (which is 2d game) ? If it exists, what are they called? and how it works ?

      Thanks
    • By 3dmodelerguy
      For reference I am use Unity as my game engine and the A* Pathfinding Project for path finding as there is no chance I would be able to create anything close to as performant as that in any reasonable amount of time.
      So I am looking to build a game that is going to have a very similar style as Prison Architect / Rim World / SimAirport / etc. One of the things that I assume is going to effect performance is path finding. Decisions about the game I have already made that I think relate to this are:
      1. While I am going to be using Colliders, all of them will be trigger colliders so everything can pass through each other and I will not be use physics for anything else as it has no relevance for my game
      2. I am going to want to have a soft cap at the map size being 300x300 (90,000 tiles), I might allow bigger sizes but do something like Rim World does in warning the player about possible side effect (whether it be performance or gameplay)
      3. The map will be somewhat dynamic in that the user will be able to build / gather stuff from the map but outside of that, it should not change very much
      Now I am going to build my game around the idea that users would be in control of no more than 50 pawns at any given time (which is something I can probably enforce through the game play) but I am also going to want to have number other pawns that are AI controlled on the map (NPCs, animals, etc.) that would also need path finding enabled. Now I did a basic test in which I have X number of pawns pick a random location in the 300 x 300 map. move towards it, and then change the location every 3-5 seconds. My initial test was pretty slow (not surprising as I was calculating the path every frame for each pawn) so I decided to cache the calculated path results and only update it ever 2 seconds which got me:
      100 pawns: 250 - 450 FPS
      150 pawns: 160 - 300 FPS
      200 pawns: 90 - 150 FPS
      250 pawns: 50 - 100 FPS
      There is very little extra happening in the game outside of rendering the tilemap.
      I would imagine the most pawns on the map at a given time that need path finding might be a 1000 (and I would probably be able to make due with like 500 - 600). Now obviously I would not need all the pawn to be calculation paths every 2 seconds nor would they need to be calculating paths that are so long but even at a 5 second path refresh rate and paths that are up to 10 tiles long, I am still only able to get to about 400 pawns before I start to see some big performance issues. The issue with reducing the refresh rate is that there are going to be cases where maybe a wall is built before the pawns path is refreshed having them walk through the wall but not sure if there is a clean way to update the path only when needed.
      I am sure when I don't run the game in the Unity editor I will see increase performance but I am just trying to figure out what things I could be doing to make sure path finding is as smaller of a performance hit as possible as there is a lot of other simulation stuff I am going to want to run on top of the path finding.
    • By phil67rpg
      well I am able to get my sprites to rotate and move in all directions, I have drawn two plane sprites, I am also able to shoot a bullet in the up direction, I want to shoot bullets in all directions just like my plane rotates, I just need a hint on how to proceed, go easy on me this is new stuff to me. However I am making progress.
    • By Waaayoff
      I'm looking for an algorithm that I can use to remove vertices that are close to each other within a margin of error in a triangular mesh. Pretty much similar to Blender's "Remove Doubles" feature, if anyone is familiar with it.
      I think the issue isn't just removing the doubles, but also how would I handle the face indices once I remove "duplicate" vertices?
  • Advertisement
×

Important Information

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

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!