Thursday 12 August 2010

Light PrePass: Neon Lights !

A couple of days ago I was wondering what kind of new light types I would be able to model with the Light Prepass renderer that I wouldn't be able to implement easily with a standard forward renderer...

I came up with the simple idea of neon lights I could model with a line segment and a radius of influence. The main algorithm would be to find for each fragment the closest point on the neon segment which is easily done in the pixel shader :

float4 findClosestPointOnSegemntFromPoint(
    float4 vA,        // start of the neon segment
    float4 vB,        // end of the neon segment
    float vP          // point to light    )
{
    float4 dir = normalize( vB - vA );      // direction vector of the segment
    float len = distance( vA, vB );         // length of the segment

    float t = dot( (P - vA), dir );         // distance of the closest point P on the seg from the origin
    float normalized_t = t / len;           // points belonging to the segment will be in [0;1]
    normalized_t = saturate( normalized_t );// discatding points not belonging to the segment

    float4 vClosestPoint = vA + dir * len * normalized_t;        // Closest point !
   
    return vClosestPoint;
}

 Then I simply use this point as if it was the source of a point light. The results are actually quite nice:



The main issue will be to cast shadows from this type of lights but I can probably live without.

In conclusion, deferred rendering is definitely cool!

Tuesday 10 August 2010

Light PrePass: Light Pass done.

A screenshot that shows the different type of lights supported in the light pass for now:


- A directional light (purple) with an ambient component (dark blue)
- A red point light, with an inner radius
- A blue point light
- A yellow spotlight, with an inner angle
- A green spotlight

The next step is now the material pass - combining this light information with every object's material info.

Saturday 7 August 2010

Light PrePass: Spot Lights !

This one will be a brief post, just wanted to share the first 2 spotlights of the renderer :)
Here they are:


Just need to add a nice angular falloff and this is good to go ... (and I should maybe make a nicer test scene because this one is really ugly)

-m

Friday 6 August 2010

Light PrePass Renderer update

I resumed working on the light prepass renderer, trying to solve a few issues before adding more features to it.

The first thing was to use a sphere mesh for point lights instead of a fullscreen pass, in order to dramatically cut down the fillrate requirements. A fullscreen pass would still be faster if the camera is inside the light but on a general case, I assume that this case will be rare and didn't implement a special case for it (I might add that as an optim if needed later on).

For that to work I had to change the shaders a bit because the view space position reconstruction code only worked for fullscreen quads. I currently use the VPOS HLSL semantic to get the screen position of the current pixel and from there compute proper UV and ray vector. This works well but I would prefer not to rely on VPOS so I'll fix the shaders later on to generate the screen positions myself in the vertex shader.


Next thing I noticed is that it makes things simpler to only draw the backface triangles of my sphere as this works even if the camera is inside the light (all that is kinda similar to the old shadow volumes issues, Carmack's reverse, etc ...). Another issue may rise when the sphere gets clipped by the far plane. To fix that, I added the following line to the vertex shader that transforms his geometry:

    VS_OUTPUT main_LPP_PointLight( VS_INPUT _Input )
    {
        float4 vScreenPosition = mul( float4(_Input.vPosition, 1), g_mWorldViewProj );
   
    vScreenPosition.w = vScreenPosition.z;
        output.vPos = vScreenPosition;
        return output;
    }

This projects any vertex that is behind the far plane on the farplane and caps the hole that would have resulted in far clipping parts of the mesh. This is achievd by making sure that z/w = 1 all the time. Again, this used to be a shadow volume trick I think ...

Then, I found another artefact that I had to fix, cf the image below:



This time the culprit was the depth target generated during the geometry pass. Basically, the buffer was cleared to 0, which would map all the pixels to the far plane value ... creating a sort of solid wall at the end of the view frustum. And if a light got close to the far plane, it would start to light these pixels. The fix for that was to be able to discard pixels that were not written during the geometry pass. Ideally clearing the target with a value of -1 would be ideal but this is not supported by the Clear() method available in DX9 as the value gets clamped in the range [0;1], To simulate that a simple trick is:
  • Clear the depth buffer to 0
     Device::GetInstance().Clear( ClearFlag_All, 0x00000000, 1.0f, 0 );
  • store the depth in [1;2] instead of [0;1] in the geometry pass output
     PS_OUTPUT main_LPP_GeometryPass( VS_OUTPUT _Input )
     {
          ...
          output.RT0_Color = -_Input.vPosition.z / g_vProjectionAttributes.w + 1;     
          ...
     }
  • subtract 1 to the value we read back from the depth target. if this pixel was written, we get a nice depth in [0;1]. If not, we get -1 and we can clip this pixel 
     float4 main_LPP_PointLight( PS_INPUT _Input ) : COLOR0
     {
          ...
          float    depth = tex2D( g_tDepthBuffer, uv ).x;              
         depth = depth - 1;
         clip( depth );
          ...
     }

With these fixes, I thing I'm good with the basics for the point lights and most of it should be applicable to spot lights as well ... Back to work !

Sunday 1 August 2010

Memory Leaks

Yesterday, I spent some time trying to fix any memory leak I could find. This is the kind of things that I like to check regularly as it is a massive pain when you do it after it's not been done in a while. More importantly, leaks are often a good indicator that areas of code require more design and code quality attention so that's always worth keeping an eye on that.

There are several ways to track memory leaks and there is already a lot of tools available and web articles on this subject. I will probably have a look at more advanced solutions in the future but for this session I used a really basic yet powerful method. The CRT library that ships with Visual Studio has some debug functionality to help track leaks down. Basically the allocator can track all the allocations that were made and that were not deallocated before exiting the application. This is done by adding a single line at the very beginning of your entry point function (you can put it anywhere really but you really want that call to happen as early as possible so you can start tracking allocations):

_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

If you happen to have memory leaks, after closing the app, you should see in the Output window of Visual Studio something that looks like that:

Detected memory leaks!
Dumping objects ->
{2221} normal block at 0x08FE63D0, 164 bytes long.
 Data: <                > D4 88 1A 10 FF FF FF FF FF FF FF FF 00 00 00 00
{2191} normal block at 0x090771C8, 164 bytes long.
 Data: <                > D4 88 1A 10 FF FF FF FF FF FF FF FF 00 00 00 00
{2079} normal block at 0x09077590, 164 bytes long.
 Data: <                > D4 88 1A 10 FF FF FF FF FF FF FF FF 00 00 00 00
{2047} normal block at 0x08FD9918, 164 bytes long.
 Data: <                > D4 88 1A 10 FF FF FF FF FF FF FF FF 00 00 00 00
{469} normal block at 0x08FE5F98, 164 bytes long.
 Data: <                > D4 88 1A 10 FF FF FF FF FF FF FF FF 00 00 00 00
Object dump complete.

This is the list of all the allocated memory blocks that were not deallocated. Notice the number in the brackets at the beginning of every block - that's where things get interesting :) This number is an identifier for this particular memory allocation. The good thing is that there is another function you can use at the very beginning of your app to break into the debugger whe
n that specific allocation happens, so you can work out why there is no deallocation and fix the issue :

_CrtSetBreakAlloc( 2221 ); // Breaks in the debugger when alloc id=2221 is made !

This is not as lean as using some tools like BoundsChecker or more advanced debug functionnality you can embed in your software but it comes out of the box - on windows at least. I find that if you have it turned on all the time and regularly check your log output, then you can fix the leaks as soon as they appear and keep an application that is stable memorywise.