What Goes Into a Lighting10 Apr 2016
So, it seems the perfectionist in me didn't let me sleep, before the lighting system was ridded of fakery and mere illusion-based shenanigans!
After finishing a couple of iterations of improvements, including a revamp from a sort of 2.5D to fully 3D representation of the global illumination, I'm quite happy with the results and no longer haunted by ray marching related ideas at nights.
It's time to recap some of the techniques put into use in the current lighting model of Payback Time.
Below is a short sequence of screenshots representing each major step of the lighting computations:
The rendering pipeline is completely deferred, for now. This means that the scene geometry is rendered independently of lights and the final lighting is produced per-pixel in a deferred shading pass computing the lighting equation for a fullscreen quad.
The current pipeline could be summarized with the following general steps: geometry, SSAO, lighting, bloom, color grading, anti-aliasing. Here's what each of the steps involve, in short:
Geometry: Taking all of the visible geometry (vertices, indices) and producing a g-buffer consisting of buffers with depth, normal, albedo and lightmap data.
SSAO: Screen-space ambient occlusion step, working on the previously produced g-buffer data and producing object-local / high-frequency shadows.
Lighting: The main lighting pass, producing all global and directional light shading.
Bloom: Bloom pass, extracting high-intensity areas from the HDR color buffer and producing glowing thingies.
Color grading: Producing LDR out of HDR color values, using a tone mapping function. Filmic in this case. Also, computes luminosity values required by the next anti-aliasing step.
Anti-aliasing: Smoothes the frame by looking for aliased edges. FXAA is used, for now.
The global illumination (GI) part is what I have been spending most time with lately. I am planning to make lighting a significant part of the gameplay itself, so having something that both looks alright and is controllable is quite important.
While SSAO handles local shadows nicely, I wanted to have a smooth / low-frequency shadows produced by the GI. Although there are various ways this can be achieved, I'll describe the approach I am using here.
First of all, the scene (i.e. the entire playable area) is sub-divided to cells. The cell size can be adjusted - I am using 8x8x32 sized cells. This means the XY-plane is higher resolution and the Z-axis (from ground towards skies) is less accurate. To give some kind of indication of this resolution, the floor object seen in the screenshots is 16x16 in XY, so it contains 2x2 cells for GI. Having a low resolution for GI makes having smooth shadows easier but on the other hand makes having very sharp shadows unobtainable. In addition to using a low-resolution lightmap for GI, I am sampling it with a cubic sampler for extra smoothness...
To produce even remotely realistic GI, a couple of factors should be taken into account:
- Line-of-sight (LOS). Objects should occlude the path of light from an emitter to a receiver object.
- Distance / falloff. Lights should illuminate the scene objects according to their distance from the light.
- Light direction. For producing correct diffuse and specular light components, the light direction in relation to the receiver object is required.
To tackle the three requirements, I settled for an approach that uses two 3D-textures as the output of the GI computation phase: a HDR RGB lightmap and an incidence map. The latter being a term I coined up for my purposes (there might be something similar out there, who knows).
The HDR lightmap is merely an RGB value which represents each scene cell's light intensity and color at that specific location. The incidence map is quite a bit more interesting: it represents the 3D light incidence vector of the cell, i.e. answering the question "from which direction was most of the light incoming to the cell?" The incidence vector is accumulated to contain a attenuation-weighted vector from each of the nearby emitting cells affecting the receiver. While this approach works very nicely, it has the edge case of multiple light sources creating a boundary area where the incidence vector is cancelled out - this can be worked around by examining the resultant vector length and scaling the incidence effect accordingly.
To add some actual algorithmic meat to this dev post, here's the outline of the GI computation:
- Pre-compute density for objects, i.e. a value describing per-cell blocking of light. 1 = no light gets through, 0 = all lights is allowed. Only done once per object modification (e.g. on destruction).
- Pre-compute emission for objects, i.e. an RGB-triplet describing per-cell emission of light. Again, only done per object modification (e.g. on destruction). The emission data is originally from the lightmap images which the engine uses while creating its 3D meshes.
- Pre-compute the above two properties for the scene where objects are placed in. Only done per scene modification (object addition / removal).
Compute the lightmap and incidence map for the scene. The algorithm in high level:
For each cell:
- Find cells with emissive light and within distance of falloff.
- Compute visibility from light source cell to current cell by using density pre-calculated before. This done by computing line-of-sight in cell-resolution, starting with visibility of 1 and subtracting each cells density on path.
- Determine attenuation based on light source distance. Computing a constant+linear+quadratic falloff equation will do.
- Determine incidence vector by summing all emissive cell direction vectors weighted with attenuation together.
- Store computed lightmap and indicent vector values to their respective
low-resolution 3D textures.
After the above steps the two 3D textures are ready to be consumed by the deferred lighting shader.
It's worth noticing that using this system has the advantage of light/emitter sources being very cheap - in fact, after the pre-computation, each light source has zero cost to the engine.
If you are interested in the minute details of the actual shading process, don't hesitate to ask about them!