Adventures in Fluid Simulation

November 1, 2010

I have to admit to being simultaneously fascinated and slightly intimidated by the fluid simulation crowd. I’ve been watching the videos on Ron Fedkiw’s page for years and am still in awe of his results, which sometimes seem little short of magic.

Recently I resolved to write my first fluid simulator and purchased a copy of Fluid Simulation for Computer Graphics by Robert Bridson.

Like a lot of developers my first exposure to the subject was Jos Stam’s stable fluids paper and his more accessible Fluid Dynamics for Games presentation, while the ideas are undeniable great I never came away feeling like I truly understood the concepts or the mathematics behind it.

I’m happy to report that Bridson’s book has helped change that. It includes a review of vector calculus in the appendix that is given in a wonderfully straight-foward and concise manner, Bridson takes almost nothing for granted and gives lots of real-world examples which helps for some of the less intuitive concepts.

I’m planning a bigger post on the subject but I thought I’d write a quick update with my progress so far.

I started out with a 2D simulation similar to Stam’s demos, having a 2D implementation that you’re confident in is really useful when you want to quickly try out different techniques and to sanity check results when things go wrong in 3D (and they will).

Before you write the 3D sim though, you need a way of visualising the data. I spent quite a while on this and implemented a single-scattering model using brute force ray-marching on the GPU.

I did some tests with a procedural pyroclastic cloud model which you can see below, this runs at around 25ms on my MacBook Pro (NVIDIA 320M) but you can dial the sample counts up and down to suit:

(videos don’t show in RSS feeds)

Here’s a simplified GLSL snippet of the volume rendering shader, it’s not at all optimised apart from some branches to skip over empty space and an assumption that absorption varies linearly with density:

uniform sampler3D g_densityTex;
uniform vec3 g_lightPos;
uniform vec3 g_lightIntensity;
uniform vec3 g_eyePos;
uniform float g_absorption;

void main()
{
	// diagonal of the cube
	const float maxDist = sqrt(3.0);

	const int numSamples = 128;
	const float scale = maxDist/float(numSamples);

	const int numLightSamples = 32;
	const float lscale = maxDist / float(numLightSamples);

	// assume all coordinates are in texture space
	vec3 pos = gl_TexCoord[0].xyz;
	vec3 eyeDir = normalize(pos-g_eyePos)*scale;

	// transmittance
	float T = 1.0;
	// in-scattered radiance
	vec3 Lo = vec3(0.0);

	for (int i=0; i < numSamples; ++i)
	{
		// sample density
		float density = texture3D(g_densityTex, pos).x;

		// skip empty space
		if (density > 0.0)
		{
			// attenuate ray-throughput
			T *= 1.0-density*scale*g_absorption;
			if (T <= 0.01)
				break;

			// point light dir in texture space
			vec3 lightDir = normalize(g_lightPos-pos)*lscale;

			// sample light
			float Tl = 1.0;	// transmittance along light ray
			vec3 lpos = pos + lightDir;

			for (int s=0; s < numLightSamples; ++s)
			{
				float ld = texture3D(g_densityTex, lpos).x;
				Tl *= 1.0-g_absorption*lscale*ld;

				if (Tl <= 0.01)
					break;

				lpos += lightDir;
			}

			vec3 Li = g_lightIntensity*Tl;

			Lo += Li*T*density*scale;
		}

		pos += eyeDir;
	}

	gl_FragColor.xyz = Lo;
	gl_FragColor.w = 1.0-T;
}

I’m pretty sure there’s a whole post on the ways this could be optimised but I’ll save that for next time. Also this example shader doesn’t have any wavelength dependent variation. Making your absorption coefficient different for each channel looks much more interesting and having a different coefficient for your primary and shadow rays also helps, you can see this effect in the videos.

To create the cloud like volume texture in OpenGL I use a displaced distance field like this (see the SIGGRAPH course for more details):

// create a volume texture with n^3 texels and base radius r
GLuint CreatePyroclasticVolume(int n, float r)
{
	GLuint texid;
	glGenTextures(1, &texid);

	GLenum target = GL_TEXTURE_3D;
	GLenum filter = GL_LINEAR;
	GLenum address = GL_CLAMP_TO_BORDER;

	glBindTexture(target, texid);

	glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
	glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);

	glTexParameteri(target, GL_TEXTURE_WRAP_S, address);
	glTexParameteri(target, GL_TEXTURE_WRAP_T, address);
	glTexParameteri(target, GL_TEXTURE_WRAP_R, address);

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	byte *data = new byte[n*n*n];
	byte *ptr = data;

	float frequency = 3.0f / n;
	float center = n / 2.0f + 0.5f;

	for(int x=0; x < n; x++)
	{
		for (int y=0; y < n; ++y)
		{
			for (int z=0; z < n; ++z)
			{
				float dx = center-x;
				float dy = center-y;
				float dz = center-z;

				float off = fabsf(Perlin3D(x*frequency,
										   y*frequency,
										   z*frequency,
										   5,
										   0.5f));

				float d = sqrtf(dx*dx+dy*dy+dz*dz)/(n);

				*ptr++ = ((d-off) < r)?255:0;
			}
		}
	}

	// upload
	glTexImage3D(target,
				 0,
				 GL_LUMINANCE,
				 n,
				 n,
				 n,
				 0,
				 GL_LUMINANCE,
				 GL_UNSIGNED_BYTE,
				 data);

	delete[] data;

	return texid;
}

An excellent introduction to volume rendering is the SIGGRAPH 2010 course, Volumetric Methods in Visual Effects and Kyle Hayward’s Volume Rendering 101 for some GPU specifics.

Once I had the visualisation in place, porting the fluid simulation to 3D was actually not too difficult. I spent most of my time tweaking the initial conditions to get the smoke to behave in a way that looks interesting, you can see one of my more successful simulations below:

Currently the simulation runs entirely on the CPU using a 128^3 grid with monotonic tri-cubic interpolation and vorticity confinement as described in Visual Simulation of Smoke by Fedkiw. I’m fairly happy with the result but perhaps I have the vorticity confinement cranked a little high.

Nothing is optimised so its running at about 1.2s a frame on my 2.66ghz Core 2 MacBook.

Future work is to port the simulation to OpenCL and implement some more advanced features. Specifically I’m interested in A Vortex Particle Method for Smoke, Water and Explosions which Kevin Beason describes on his fluid page (with some great videos).

On a personal note, I resigned from LucasArts a couple of weeks ago and am looking forward to some time off back in New Zealand with my family and friends. Just in time for the Kiwi summer!

Links

GPU Gems – Fluid Simulation on the GPU
GPU Gems 3 – Real-Time Rendering and Simulation of 3D Fluids
Fluid Simulation For Computer Graphics: A Tutorial in Grid Based and Particle Based Methods

Advertisement

7 Responses to “Adventures in Fluid Simulation”

  1. Sam Says:

    Fantastic stuff! Looks great!

  2. Marco Says:

    Simply awesome!
    Thanks for the interesting article and links.

  3. Jose Says:

    Very nice results, and thanks for the article and the links!

  4. Kyle Hayward Says:

    Very cool results. I’ve been wanting to get into fluid sim for awhile too and I’ve had that book on my Amazon list for ages. Looks like I’ll pick it up.

  5. eXile Says:

    Wow, stunning results!

  6. James Waples Says:

    This is amazing!

    The video showing the smoke cube exploding is realtime right?

  7. sasmaster Says:

    Can you expose here Perlin3D method? Or at least the parameters meanings you pass into it?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.