Nebulas & Gas Clouds : Experiment 1

Back to… Nebulas & Gas Clouds

Having seen the technique of ‘mega-particles’ on the web a long while ago, I thought it would be worth a shot to see how effective they really are from Dom’s perspective, bearing in mind its requirement to actually fly towards, around, and inside clouds of interstellar gas.

As mega-particles (for the purposes of this experiment) are simply 3D objects, with 2D post-processing effects to distort them (make them fluffier) they should stand up to that list.

Here’s a video of the overall effect!

There are 5 stages to the process I implemented in this prototype, so let’s get on with it…

1) The Colour Map : Mega-particles

The basic idea is to represent the larger cloud with lots of smaller 3D objects – all lit and coloured in a manner to suit the desired outcome. In a nebula for instance, you can see the following basic patterns;

1) Gas colours change based on density, temperature, distance from the star remnant, angle of reflected light to the viewer
2) A typical Nebula can be thought of as one or more concentric spherical clouds of gas
3) To the observer, the middle of the Nebula is less noticable than the outer edges – so a ‘ring’ of illuminated cloud forms around it.

Of course, there are toroid Nebulas, towers, dispersed and so on – but we can emulate them all with mega-particles of suitable shapes and sizes.

So – for this prototype, the first stage is to create several shells of spheres, with varying sizes and colours, and render them into a texture with an orthographic projection (for now – it should be perspective, but restricting the frustum to maximise it around the cloud).

1 - Raw Colour MapDepth Map

No magic here – just a simple 3D scene. Ok maybe a little magic… spheres obscuring the centre of the cloud are more transparent, to simulate the observer effect I mentioned earlier. But, no alpha/z-sort is being done so some appear as ‘holes’, which is fine for the purposes of this experiment. The depth map on the left there in grayscale, and the colour map on the right. I randomly place spheres about a shell’s radius, and randomly alter the sphere radius to fit within that shells ‘thickness’, along with assigning a pseudo-random colour (in this case chosen from red, orange, yellow).

2) The First Blur Pass

Gas clouds are ‘fluffy’ – so those harsh surfaces and edges aren’t really desirable. We need to soften them up. Queue blur!

This is a simple blur pass – which allows me to tweak the blur radius. It’s a two stage blur; horizontal, and then vertical. The horizontal blur takes samples to the left and right of a given pixel, then averages the total colour value. The vertical takes samples above and below. The number of samples (distance from the pixel being blurred) is tweakable.

2 - Blur - First Pass

The OpenGLSL shaders for this are quite simple, both the horizontal and vertical shaders share the same vertex shader code;

blur.vs

#version 120
void main(void)
{
  gl_TexCoord[0] = gl_Vertex;
  gl_Position = gl_Vertex * 2.0 - 1.0;
}

 

Then a fragment shader for each of the horizontal and vertical blurs;

blurh.fs

#version 120
uniform sampler2D Texture;
uniform int Width;
uniform float odw;
void main()
{
  vec3 Color = vec3(0.0);
  int wp1 = Width + 1;
  float Sum = 0.0;
  for(int x = -Width; x <= Width; x++)
  {
    float width = (wp1 - abs(float(x)));
    Color += texture2D(Texture, gl_TexCoord[0].st + vec2(odw * x, 0.0)).rgb * width;
    Sum += width;
  }
  gl_FragColor = vec4(Color / Sum, 1.0);
}

 

blurv.fs

#version 120
uniform sampler2D Texture;
uniform int Width;
uniform float odh;
void main()
{
  vec3 Color = vec3(0.0);
  int wp1 = Width + 1;
  float Sum = 0.0;
  for(int y = -Width; y <= Width; y++)
  {
    float width = (wp1 - abs(float(y)));
    Color += texture2D(Texture, gl_TexCoord[0].st + vec2(0.0, odh * y)).rgb * width;
    Sum += width;
  }

  gl_FragColor = vec4(Color / Sum, 1.0);
}

 

I’ll get around to tidying those up at some point, I’m sure!

3) Distortion / Perturbance / Noise

The meat and gravy of the effect. This is a 2D effect, distortion/noise/perturbance is covered in great detail on the web, but the application for this purpose isn’t so much.

Ideally, this should use 3D noise to preserve as much ‘3D’ of the original scene  (in the Colour Map) as possible. But, as this is a prototype, cheap is cheerful, so 2D will suffice. The distortion effect is very simple, and simply takes the colour map as an input texture, along with a noise map, and then uses the current texture uv-coordinate plus an offset derived from the noise texture to generate the fragment.

2 - Blur - First Passnoise4

3 - Distorted Blur(First Pass)

The offset is derived by using the input uv-coordinate to get the red and green component from the noise texture at that location. These values are used to give us a new u and v in the range of 0.0 -> 1.0. I offset their values to be -0.5 -> +0.5 and then scale it based on a tweaker value to let me adjust it in real time. This is then added to the incoming uv, to ‘distort’ it which means the final fragment colour is now coming from ‘somewhere else’ in the colour map, instead of the originally intended location. As this is predictable noise, we get the same result for the same input values every time, giving the final image temporal coherence (i.e. it stays put over time 😉 )

The noise shader uses the same vertex shader given above for blur.vs, and then the following fragment shader…

noise-distort.fs

#version 120
uniform sampler2D texNoise;
uniform sampler2D texInput;
uniform float fScaling;
void main()
{
  vec2 uvNoise = texture2D(texNoise, gl_TexCoord[0].st).rg;
 
  uvNoise += vec2(-0.5, -0.5);
 
  uvNoise *= 2.0 ;
 
  // displace texture coordinates
  vec2 uvDistorted = gl_TexCoord[0].st + (uvNoise * fScaling);
  gl_FragColor = texture2D(texInput, uvDistorted);
}

 

Different noise textures give different cloud effects. The noise texture can also be animated over time – or we could just use a noise function (like Maggy) and just ditch the noise texture entirely.

So now we have a much messier scene in our texture! It’s beginning to look more cloud-like, but still not there yet…

4) The Second Blur Pass

We need to soften those distorted edges up a little – but not too much, otherwise the blur effect will darken our final image far too much and require gamma adjustments or other post-effects, and we don’t want to over do it (though I might later on!).

Again, we re-use the same horizontal/vertical blur pass from earlier, but with smaller blur widths for a more subtle effect.

4 - Blur - Second Pass

5) Final Composition

All that blurring has cost us a lot of detail, so this final pass simply renders the blurred results from step 4) over the distorted texture from step 3) with an alpha value of 0.75. This increases the brightness a tiny bit, but also allows a little of the original distortion to show through.

5 - Composite Blend with Raw Colour Map

Et voila! A nebulous, animatable(ish) gaseous cloud effect, with full 3D lighting!

In the videos, I’ve shown the techniques and tweakers at work, and the final result has the original spheres coloured based on their distance from the centre of the cloud to give a more nebula/explosion-like look.

Post Mortem

It’s both a great effect, but also slightly disappointing. I can see many applications for this – atmospheric clouds, explosions and perhaps nebula’s. It’s animatable – both the 3D scene can be manipulated (scaled up/down, the geometry can be animated and so on) and also the noise texture can be animated.

It can also be refined by multiple render passes of different detail levels and blurring, all being combined into one final result which would show varying details across the cloud.

But, ultimately, I think the amount of work to get it to work with 3D noise won’t pay off. It’s not much effort to be fair, but I can see from this prototype that the end result won’t be what I’m after.

That said, this technique can support fly-throughs and viewing the nebula/cloud from inside if some clever plane intersections are made with the frustum. But, the lack of spatial coherence in the 2D distortion would mean that the ‘fluffiness’ will ripple as you fly around it (you can see this already in the videos). The distortion is also locked to the 2D plane being used, so after a time you can see it’s not really ‘3D’. 3D noise will help, but I doubt the fidelity will stand up to it.

So, although I am happy with this prototype, and can see a use for it in Dom, it’s onto Experiment 2 – which (if it works as I expect) is going to be far more interesting… I hope… 😉


 

References

http://www.inframez.com/events_volclouds_slide01.htm

http://www.whiteflashwhitehit.com/content/webgl/PP/mega_particle.html

http://graphicsrunner.blogspot.co.uk/2008/03/volumetric-clouds.html


 

Back to… Nebulas & Gas Clouds

Subscribe to The Dominium Observer Newsletter!
Subscribe