FAIL (the browser should render some flash content, not this).

Search the site

FAIL (the browser should render some flash content, not this).

Main Menu

Crepuscular rays in XNA (God Rays) PDF Print E-mail
Written by DrMistry   
Tuesday, 16 August 2011 15:28

Right now then, I don't normally share code, but it's my wedding anniversary today and I'm feeling happy and generous and I've just finished prototyping the shader which achives this oft-hunted effect.  Crepuscular rays are those lovely plays of light you see busting through tree-lined lanes on a sunny day, with great "volumes" being illuminated.  Lots of folk have asked for help with this effect all over the web, but once you get to thinking like a pixel shader it's not too hard to get it going on.  The rough basis of the code is from GPU Gems 3 and I'd advise anyone serious about getting in to shaders to get familiar with all of the GPU Gems series.  Chances are the effect your after has been worked on at some point!  I wanted a chader for this effect so I could have sun rays in interesting, non-static, evolving patterns.  Here's where I'm at right now:

starshader

See the way that the rays emerge from the bright spots, particularly in the lower-right area of the sun.  That's exactly what I'm talking about.  The whole process takes 4 passes, which I'll now describe quickly.  There are plenty of other applications for the shader which don't need the first 3 parts of the process - indeed you can apply the shader to any Texture2D you like.  Try to hold back from over-using the technique though, there's no gain in guilding the lilly :)

Firstly, when the program creates  a star it also makes 2 textures, both based on a 3D perlin noise routine.  Using spherical co-ordinates we take a "globe" of perlin noise - one for the normal surface texture of the sun.  This looks kind of like a lavafield, and is pretty much just white-yellow-red noise.  Since it's been sampled using spherical coords we don't even have to worry about wrapping because that's inherant in the geometry used to generate the texture.  Nice!  The second texture is a "turbulance map", again a perlin sphere which is used to create a rolling and boiling effect over the surface of the star.  The idea for this comes from here if you're interested in a more in-depth explaination of the what and why.  Suffice to say, once we have our 2 textures we can start messing around with them.  I use the turbulance map pretty much as suggested in the link, rotating 2 spheres slightly differently and combining the resulatant image, only drawing bright pixels to the screen, thus:

nonrayedstar
Now first impressions may be "wow, that's way too dark to be a star!" but hold on to your pants because this is going to get lightblasted by the creps shader.  The main surface map is as it was generated by the perlin routine, and the turbulance layer is the result of rendering 2 same-sized spheres with the same texture, rotated by different amounts, and then passed through a "compressing" pixel shader which only draws pixels which are pretty bright.  OK, so far so dull.  But now we have this image in a rendertarget (and thus a texture2d) we can run this through the creps shader, specifying the screen-space co-ordinates of the center of the star.  This is important for the shader, and the co-ordinates MUST be within the range of 0..1 and is achieved thusly:

screenPos = new Vector2(graphics.GraphicsDevice.Viewport.Project(Vector3.Zero, Projection, View, albedoWorld).X, graphics.GraphicsDevice.Viewport.Project(Vector3.Zero, Projection, View, albedoWorld).Y);
screenPos.X /= (graphics.GraphicsDevice.PresentationParameters.BackBufferWidth);
screenPos.Y /= (graphics.GraphicsDevice.PresentationParameters.BackBufferHeight);
crepsEffect.Parameters["ScreenLightPos"].SetValue(screenPos);

The star is always positioned at Vector3.Zero in my universe, but it works for any Vector3 you care to use, provided it's on the screen when projected in to screen space.

But what exactly does the creps shader do?  It's pretty simple.  For each pixel, we take a series of samples along a ray cast from the current pixel to the screen position of the light source.  We then smear those samples outward along the ray, including a decay coefficient so that we're not just blinded by infinite-length shafts of simulated sunlight.  It's really that simple.  Here's the code for the shader:

float4x4 MatrixTransform : register(vs, c0);
float2 ScreenLightPos;
int numSamples;
float Density;
float Weight;
float Decay;
sampler DryImage : register(s0)
{
    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;  
    AddressU  = Wrap;
    AddressV  = Wrap;
};

void SpriteVertexShader(inout float4 color    : COLOR0, 
inout float2 texCoord : TEXCOORD0, 
inout float4 position : SV_Position) 

    position = mul(position, MatrixTransform); 
}

float4 PixelShaderFunction(float2 texCoord : TEXCOORD0) : COLOR0
{
    float2 deltaTexCoord = (texCoord - ScreenLightPos.xy);
    deltaTexCoord *= 1.0f / numSamples * Density;
    float4 color = tex2D(DryImage, texCoord);
    float illuminationDecay = 1.0f
    for (int i = 0; i < numSamples; i++)
   {
       texCoord -= deltaTexCoord;
       float4 sample = tex2D(DryImage, texCoord);
       sample *= illuminationDecay * Weight;
       color += sample;
       illuminationDecay *= Decay;
   }
return color;
}

technique Technique1
{
    pass Pass1
    {
 
      VertexShader = compile vs_3_0 SpriteVertexShader();
       PixelShader = compile ps_3_0 PixelShaderFunction();
    }
}

Right now I'm drawing with SpriteBatch, so my draw line provides the "dry" (unprocessed) texture with no need to set parameters.  The numSamples, Weight, Decay and Density variables are set at create-time, when the 2 texture maps are created.  Sometimes these parameters need some experimentation to get the exact desired result, and you can achieve a broad range of effects - from sun rays to a kind of visual feedback which has hundreds of uses.  You know that bit at the end of Raiders Of The Lost Arc where the nazis melt and start spewing light?  Kind of like that.  Nicey. Anything that involves melting nazis is something I'm going to be in favor of ;0)

So, after the creps shader has chewed over the boring, dim, dark star we get something very much like this:

rayedstar

The artifacts in the rays are the result of a low sample frequency (50 per ray).  Tweaking all the parameters leads to a wide range of effects.

The MatrixTransform parameter is just used to handle the requirement to have a vertex shader which is forced in shader model 3 under XNA4.  Google it if you need more info.  So there we are - very very few lines of code to generate an effect which is attractive, computationally cheap and very flexible.  I expect I'll also be using it for things like photon torpedos, large explosions, hyperspace transits and all manner of lovely things.  I think this is a great example of how "thinking like a shader" pays dividends - it's not massively complex once you realise that you need a function which works on a pixel-by-pixel basis.  The only pain in the butt is that it provides a Texture2D which obviously is devoid of depth data, but we all know there are ways around that.  Right now I'm depth-testing planets and moons against the star position and drawing things in depth order (furthest away first) and this is a cheap and simple way to do it for the purposes of prototyping.  So there we are, my first shared shader.  Hope you like it, and if you have any questions, just get in tough on Twitter, Facebook or here.  Chin chin, and happy aniversary to me and MrsMistry :0)



Add this page to your favorite Social Bookmarking websites