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 !

No comments:

Post a Comment