Rendering a Nebula

Today I’m going to write about making a nebula shader to make awesome looking galaxies and nebulae. This is part of my masters thesis which involves writing a Renderman pipeline for RIT’s visualization software called Spiegel. The data that is visualized comes from the Center for Computational Relativity and Gravitation. For this article, the data doesn’t actually have to be real astrophysical data, it can be made up and it will still look good. The article is concerned more with the aesthetics than accurate visualization of the data.

The reference image I was using was
nebula reference image
Picture taken from

I’m going to talk about using Renderman shaders, but it can easily be translated into OpenGL or DirectX or whatever because it doesn’t use anything characteristic to Renderman. I started experimenting on a small file with three spheres. I’m basing the shader on the cloud shader in Advanced Renderman by Larry gritz and Anthony A. Apodaca. It’s a fantastic book and a great introduction to writing various shaders and working with Renderman.

The data was a point cloud of about 400k data points which just held location data. I was supposed to make this point cloud look like nebula. The simplest method was to make buckets based on the locations. Basically you take the first point and put it in a list. Now, if the next point lies within a certain radius of that point, you don’t add a new point, instead you increase the neighbor count of the point already in the list. The points are then used as locations of spheres which are shaded using a shader I will talk about below.

The first thing I started with was a Fractional Brownian Motion function, which sums up several noise() functions that have a different amplitude and frequency. This creates organic looking noise, but it wasn’t quite the noise I wanted. It looks too grainy for a nebula, which should be more wispy and gaseous and less cotton ball like. This is what the first results looked like below.
initial results

Results were very disappointing. It didn’t look anything like something one would find in space. The trick isn’t to do just one level of noise, it’s to offset the position being shaded by noise, and use that as input for another noise function, so if we have a noise (x,y,z) function we would do noise(x + noise(x,y,z), y + noise(x, y, z), z + noise(x, y, z)). Depending on the values you put into the function we get the results below.

better cloud

Playing around with the values can also yield interesting results such as these van Gogh looking patterns.

The next two are also interesting, but not really useful for nebulae.

Going back to the original concept, since we have all these data points and we know how many neighbors each sphere has, we can adjust the luminance of it by converting the color into HLS color space and increasing the L value as density increases. This results in the render below.

The final part was putting in the stars. The stars are shaded with a very simple shader that assigns the color based on the angle towards the camera. It takes the dot product and multiplies the color by the angle. We want some over saturation so the star appears white in the middle and multiplying it by the magic number 3 works well in this case.

angle = abs(normalize(N).normalize(I));
Ci = Color * angle * 3;
Oi = angle;

This results in the final image below.

One further thing that I haven’t tried yet which comes to mind while writing this would be to mix the color from purple to blue as the density changes along the edges.

This is the nebula shader. It is based on the cloud shader in the book Advanced Renderman. The modification I made was the noise function.

1:  surface nebula (float Kd = 0.5;  
2:      string shadingspace = "world";  
3:       /* Controls for turbulence on the sphere */  
4:       float freq = 1, octaves = 8, lacunarity = 2, gain = 0.5, wispyness = 2;  
5:       /* color */  
6:       color Color = color(.8, .5, .7);   
7:       /* Falloff control at edge of sphere */  
8:       float edgefalloff = 8;  
9:       /* Falloff controls for distance from camera */  
10:       float distfalloff = 1, mindistfalloff = 1000, maxdistfalloff = 2000;  
11:    )  
12:  {  
13:    point Pshad = freq * transform (shadingspace, P/15);  
14:    float dPshad = filterwidthp (Pshad);  
15:       float f = freq;  
16:       float weight;  
17:    if (N.I > 0) {  
18:       /* Back side of sphere... just make transparent */  
19:            Ci = 0;  
20:            Oi = 0;  
21:    } else { /* Front side: here's where all the action is */  
22:            float opac = 0;  
23:            opac += fBm(point(xcomp(Pshad) + wispyness * fBm(Pshad/10,dPshad/10, 3, 5, .5),   
24:                                 ycomp(Pshad) + wispyness * fBm(Pshad/10,dPshad/10, 3, 5, .5),   
25:                                 zcomp(Pshad) + wispyness * fBm(Pshad/10,dPshad/10, 3, 5, .5)),   
26:                           dPshad, octaves, lacunarity, gain);  
27:            opac = smoothstep (-1, 1, opac);  
28:            /* Falloff near edge of sphere */  
29:            opac *= pow (abs(normalize(N).normalize(I)), edgefalloff);  
30:            /* Falloff with distance */  
31:            float reldist = smoothstep(mindistfalloff, maxdistfalloff, length(I));  
32:            opac *= pow (1-reldist, distfalloff);  
33:            color Clight = 0;  
34:            illuminance (P) {  
35:                 Clight += Cl;  
36:            }  
37:            Oi = opac * Oi;  
38:            Ci = Kd * Oi * Cs * Clight * Color;  
39:            Oi -= 0.1;  
40:    }  
41:  }  
  1. No trackbacks yet.

Leave a Reply

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

You are commenting using your 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 )

Google+ photo

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

Connecting to %s

%d bloggers like this: