The engine on which Skullstone is based – JME 3.0 – uses forward rendering method of rendering light, meaning that each object within a specified range of a given light source is rendered according for specifics of that source. This method is good enough to be used in simple games. It’s easy to be implemented and it works great with limited amount of lights.
Skullstone uses lots of light sources, some of them have their positions fixed, others appear and disappear – this makes forward rendering suboptimal for our project. Frames per second counter would take a serious hit with each additional source used on a level. It would be noticeable for example whenever a magic bolt is thrown.
In order to simplify rendering process while keeping quality graphics at smooth framerate we introduced a series of modifications and customizations to the engine.
First of those customizations is dropping forward rendering in favour of deferred shading. This popular method of rendering 3d scenes is based on splitting the whole process into two steps. First, the so called G-buffer is created – we only gather the actually needed data. Full-screen textures with information on diffuse, specular, normal, and glow channels for each visible pixel are created. Only then the scene is drawn. We literally draw lights. They are “physically” represented as spheres or fullscreen quads (the choice of geometry depends on position of the light in relation to the camera’s position and its radius) which then receive textures from the G-buffer, as well as Z-buffer created in the deferred shading’s first pass, and a set of shadowmaps – these will be explained later.
The last rendered light comes from the player himself. It glows just like a torch as long as any character holds one in his or her hand. If no torch is being held – it glows a soft blue ambient light. Calculating colours in linear space results in the soft blue light being visible only in places with no other illumination. This last rendered light also enables the ambient occlusion effect. We assumed that at this stage of rendering we have all data required to add this effect and we don’t need any more passes to do it.
Shadows are a very important part of Skullstone’s visuals. Dark corridors create unique ambience. Unfortunately, creating them requires a lot of resources. Each visible light source needs information about its surroundings (within defined range). Six cameras are used to create six shadowmaps – textures used to decide whether a given pixel is illuminated or not – then used in the second stage of our deferred shading. Shadowmaps should be constantly updated – changing the scene’s composition (like moving an object around) should be followed by an instant change in shadowmaps to ensure the shadow will always be placed in the correct place.
JME 3.0 engine renders shadowmaps for each rendered frame. This is useful when used with a small amount of light sources and when there are frequent changes in what’s supposed to be shown to the player. This is suboptimal for Skullstone – the environment is mostly fixed, the only moving objects are monsters, items, doors and similar. This means it’s highly possible newly rendered shadowmap will be identical to the one rendered for the previous frame.
Our way of optimizing it is based on creating a checksum for each camera used in shadowmap rendering. This checksum includes information on transformations for each object visible by the camera. If the checksum is identical to calculated previously we assume nothing has changed and rendering it again is not required. A lot of time is saved that way.
Each underground level of Skullstone is full of torches, fires, glowing holes in the ground and other light sources. Deferred shading allows them to be drawn quickly, yet this can also be optimized further – there’s no reason to draw light which are not visible by the player. Because of this we use light culling. When applied in its most basic form it simply turns off all the light sources currently located outside of camera's frustum. The light is, of course, treated as a sphere, not a point. The torch located outside the camera’s frustum of view may still create a visible glow. Such approach allows turning most of the light sources in the scene off, yet, a number of them is still on even when invisible due to different reasons, like being hidden behind a wall.
Skullstone’s levels are based on a grid. Each cell of the grid is square-shaped and they can be opened for the player (corridors) or not (walls).The map, when looked at from above, brings a maze to mind. A labyrinth made of large pixels. Black pixels prevent movement, white do not. We used this for the second stage of light culling – Bresenham lines are created between the player’s position and position of light sources together with their surroundings. If at least one line related to a given light source is not running through a wall we assume it is visible by the player and in result is turned on.
Apart from what’s described above, a series of minor optimization methods is applied as well. These are mostly based on rewriting or overwriting those components of the JME 3.0 engine which touch the matter of scene rendering specifics. For example – the said glowmap rendering during the first step of deferred shading required certain modifications to the engine’s filter used for applying glow on rendered scenes. And we did even more with this modification – glowmaps’s glow and glow resulting from the scene’s lighting are drawn at the same time but with two different values. Normally, this would require JME to apply two separate glow filters.
These and all other optimizations result in allowing us to render high quality graphics while keeping frames per second high. The test scene containing a single animated object located right in front of the player, four active light sources (and around forty deactivated by the light culling algorithm) is rendered at steady 108 frames per second with resolution of 1600x900 pixels using GTX 750Ti graphics card and Core 2 Duo 3Ghz. You don’t need a powerful machine to run Skullstone smoothly and have a lot of fun playing it!