After my last post, I decided to continue with the z-reconstruct approach and to use light-meshes where possible. I’ve implemented two more light types: directional and spot. You can get the source code here.
Here is a screenshot of the lights in action, in the game editor I’m working on:
The directional light is the easiest one: just render a full-screen quad and do the math. We don’t need to compute attenuation or light to pixel vectors (assuming our directional light only has parallel rays), so the shader is very simple. The only problem is the fill-rate: as directional lights usually don’t have attenuation, we always draw a full-screen quad for each directional light. To make it less expensive, I’m using depth test to reject fragments that only contains background values and thus don’t need to be lit. My fullscreen quad outputs z = 1, the farthest distance possible, and I set the depth test to Greater.
Here is a screenshot with a single directional light:
Spotlights are basically lights with a cone-shaped attenuation, like a desk lamp. In my code, I assume it behaves like a point light (has a world position) and also a direction. I don’t use the correct formulas to compute cone attenuation, falloff etc, like described here. Instead, I’ve developed my own (crazy) formula that works fine and doesn’t need pow or div and still gives a customizable falloff on cone’s border.
To render the spotlights I’ve created a cone mesh, with length = 1 and radius ~= 1. It is very coarse, about 30 triangles. I had to expand the radius to more than 1 unit, because the tessellation makes the distance between the edges and the center smaller than 1. With this mesh in hands, I could scale the length by light radius and the cone radius by light radius * tan(spot angle). All the math is in the source code, both the mesh scaling and the shading formula.
Here is a sequence of optimizations to reduce pixel processing:
- Final image, single spot light;
- Using a screen-aligned quad;
- Using a sphere mesh;
- Using a cone mesh (good);
- Using z-test (better!);
- Using clip() after attenuation: it saves the specular computing (two normalizes).
As you can see its a HUGE win to use the cone mesh, we save one order of magnitude of pixels in that case. We could even use a frustum-cone intersection to skip the light in the culling stage, or even a frustum-frustum (built-in on XNA) instead of the frustum-bounding sphere that I’m doing in the code.
I’m slowly importing my engine code into this project, so probably the next topic will be about skinned meshes and alpha masked objects, and then shadows or SSAO. Comments and replies are welcome.