FLEX is the name of the new GPU physics solver I have been working on at NVIDIA. It was announced at the Montreal editor's day a few weeks ago, and today we have released some more information in the form of a video trailer and a Q&A with the PhysX fan site.

The solver builds on my Position Based Fluids work, but adds many new features such as granular materials, clothing, pressure constraints, lift + drag model, rigid bodies with plastic deformation, and more. Check out the video below and see the article for more details on what FLEX can do.

Position Based Fluids (PBF) is the title of our paper that has been accepted for presentation at SIGGRAPH 2013. I've set up a project page where you can download the paper and all the related content here:

I have continued working on the technique since the submission, mainly improving the rendering, and adding features like spray and foam (based on the excellent paper from the University of Freiburg: Unified Spray, Foam and Bubbles for Particle-Based Fluids). You can see the results in action below, but I recommend checking out the project page and downloading the videos, they look great at full resolution and 60hz.

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.

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

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;
}

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.

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!

## Recent Comments