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;vScreenPosition.w = vScreenPosition.z;
return output;
}
}
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 );
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