News

News

Technologic

LovePosted by Eskil Steenberg Fri, June 26, 2009 19:09:00

This is a very technical post, but for people who are interested, I will now give you an overview of the rendering engine powering LOVE.

The rendring engine used for LOVE is a custom built highly optimized OpenGL engine that tops out at around 200FPS running in 1920 by 1080 on a 100USD graphics card. While performance have always been important so has visual quality, compatibility, and most of all dynamic content. I try to use as few GL extensions as possible and beyond GL 1.2 the only ones i use are Cubemapps, FBOs, GLSL_100 and VBOs (Optional) giving me a very high degree of compatibility and portability.

Writing a good graphics engine is as much an art as it is a science, often there are many conflicting goals and optimization advice to follow. Sort front to back or state sort? CPU PVS or brute force GPU? Few state changes while avoiding uber shaders? Geometry or texturing? Looks vs Performance? Here are some of the choices i have made.

Terrain engine

The terrain engine is the by far biggest part of the engine and it also spends almost all graphics cycles. The world is build up out grids of blocks, and 8 by 8 blocks form a group. Each groups polygon mesh is generated by the engine and put in to a very large Vertex Buffer Object (VBO). By keeping all groups in a single large VBO I can bind the buffer once, then set up my vertex pointers (I use a interleaved vertex format for size and cashing reasons) at the beginning of the array once and then make a number of ranged draw calls for the groups that are inside the view frustum. Reducing state changes like this gives a massive boost to performance (especialy not having to reset the vertex pointers for each draw call), and i try to do it across the engines. The polygon array for each group starts with all walls, then objects, then floors and then finally edges and grass. This way I can draw different ranges depending on how far away the group is. If the group is far enough away i only draw walls (the ground wont be visible due to the curvature of the planet) as it gets closer a draw more and more of the array until i draw the smallest details like the grass. Keeping everything in a single VBO does force me to do my own memory management to defragmentate the VBO. Given the size of the world I only store groups that are close to the camera in the VBO, so when the player moves new groups have to be added and old ones removed. All this however forces me to draw all terrain with a single shader, and the little texture mapping I do (i mainly flat map polygons with single colors for artistic and art budget reasons), is done by UV mapping around one very large texture. 

To make my geometry more organic i add a quad that protrudes out from each polygon edge. This quad is then given a rounded profile using a alpha test texture. By lighting it and shading it the same way as the polygon it nicely blends and extends the surface. Normal engines have a lot of detail on the surface, with little edge detail where as I go in the opposite direction. The majority of the "Look" of LOVE comes from these very simple tricks rather then and complicated screen space computations. 

Shaders

The surface shader is a fairly simple one that uses normal texturing and lighting for local light sources. The sun light and ambient light are look-up in to two cube maps. These cube maps allows me to precisely control the color of each angle, and lately I have added a bit of colored noise in them to give some life to the environment. I refrain from filtering any of the qubemaps to create some extra banding that help establish the painterly look. The shadows are done with a simple shadow map. The complexity of the terrain unfortunately requires me to draw it in its entirety during the shadow pass, making cascading shadow maps a little too expensive. Finally I add a four color fog, with a near and far color, for the direction towards and away from the sun. This fog algorithm is then replicated in the sky box to match.

FX

The game employs a lot of FX and they are all based on two primitives, sprites and quads expanded along a axis (lines). Both these are expanded and aligned towards the camera inside the vertex shader. A major problem with FX work is that you end up with very many draw calls, sometimes only containing a single polygon. Therefor i consolidate all sprites and lines in to two draw calls by accumulating them in to large arrays. Again this means they need to map around large textures rather then giving each sprite and line type its own texture. Objects are collected in a similar way so that they can be drawn all at once. The only FX not done using these arrays is the mist. The mist is made up by large sprites that travel over the surface of the terrain. To get thick enough  mist I end up with a lot of overdraw, especially if many mist sprites get so close to the camera that they cover the entire screen. To combat this i have special shader that fades out the sprites as they get closer to the camera, and if they are close enough, it simply doesn't expansion the sprite, and the rasterizer ends up rasterizing an infinitely small polygon. 

Post

The entire frame is drawn in to a off screen buffer (Using FBOs) and then drawn with a color correcting and filtering shader to the screen. To improve the performance and soften the image i draw the off buffer at slightly lower resolution then the screen resolution, this has however very little impact on performance since I'm almost completely vertex bandwidth bound.

When writing a custom engine like this, and especially when you do non-photorealistic rendering you need to spend a lot of time experimenting and trying things out. Some parts of the graphics pipeline has been totally rewritten from scratch four or five times. As for the future there are some things i am still considering adding is a image space distortion map, but there are too few reasons to use it and the cost of switching FBO state is unfortunately a bit high. Other possible additions is to use a float buffer for the off screen rendering but again the bandwidth cost is far to high for the added quality. I would consider having these things optional, but it has been along standing goal of mine to present good visual quality even on low end rigs, and avoid the feeling that you are not experiencing the game it was meant to be.

  • Comments(31)http://news.quelsolaar.com/#post51