Jump to content
• Advertisement
• # Fluid Rendering with Box2D

General and Gameplay Programming

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

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:

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:

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:

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

• 0
• 25
• 0
• 1
• 0
• ### Featured Blogs

• Advertisement

• 13
• 26
• 10
• 11
• 9
• ### Similar Content

• Hi everyone,
Me and my friend are working on an 2d action shooting game.
You can check techno demo of core mechanic here: https://spidamoo.itch.io/bunny
So, as you can see, we really need an artist here. (And probably for other projects after)

• Hello everyone, it's my first time posting on this forum. I'm very excited to meet you.
Anyways, my first question is about procedurally generating a world. I'm seeing how to implement a procedural generated world, and placing objects after that seem to be a big challenge.
Let's say I have a big object that doesn't fit inside a chunk or that it is spawned in the frontier of a chunk. How would you go about generating the whole thing using the nearby chunks? With terrain it's pretty easy because the terrain is not hardcoded, so the algorithm is entirely free. But when I want to place hardcoded objects like castles or trees, I can't just cut the castle in half - or can I?
What's a way to approach this problem?
I'm using LWJGL/OpenGL in Java for a 2D game.

• I am curious, if anyone would be interested in an RPG Adventure in a visual novel art style?
I loved Doki Doki and if I could create something in an RPG element, that would be THE BEST.
I am a complete noob however, that is the thing. Like I just started adventuring into coding two weeks ago. I love it so far.
I think I may be addicted. oof. Which is why I want to create something I have that itch. lol.
Basically if anyone who wants to pitch in for free, or not I'd be glad to include them in the credits section.
Also, I'd love to get the community involved in this, to create more fun RPG-esk things if that makes sense.
Where would I go for that?
• By TheMode
Before starting: I'm looking for a Java developer who want to help me improving my game engine and then, create a game that I will describe
My goal is to create a "fight arena" multiplayer game similar to xblaster (only how they managed arena, I do not want robot stuff)
For people who do not know this game, let me explain how I'm inspired by it
You log in the game, you can enter an arena at any time, in the arena, there are 4 portals where you can go and enter another arena, there can be a maximum of 4players in the same arena, they have to fight each other in order to get money to improve their characters.
I won't describe it any longer, I've much more ideas about the game.
I already done the server architecture, I have a Game Engine (the client side), but there are still things to do on it, that's why I'm looking for another developer to help me if the game also look interesting for you
Here the version of the engine:
https://github.com/TheMode911/ProneusEngineV2/blob/master/src/main/java/fr/proneus/engine/demo/DemoState.java
Discord: TheMode#3487

• In MonoGame I'm writing a shader for lighting and shadows in a 2D Platformer.

A shadow will be drawn for each character for each light that hits said character. Because shadows from different lights can overlap the shadows are drawn to a texture where each pixel is a bitfield where each bit tells you if the pixel was reflected by a given light. In the lighting shader for each light, it only applies light if the bit for that light is not set at the given pixel.

In order to not make for example 40 draw calls to draw 40 shadows if 40 lights overlapped a character, I batch shadows together into a VertexBuffer with the data specifying which light created the given shadow. In the shadow shader, it samples the render target I am drawing the shadows to and sets its own bit. My problem is that the changes from the previous shadows in the same batch aren't applied to the render target until after the draw call has completed. This results in the bitfield getting overwritten by shadows from other lights.

If I could somehow sample the back buffer this wouldn't be a problem. Is there any way I can fix this without making a draw call for each shadow?
×

## 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!