How to create optimal levels for Crystal Space

Written by Jorrit Tyberghein, <jorrit.tyberghein@uz.kuleuven.ac.be>.

Note: Building optimal levels is not very easy as there are a lot of factors to consider. Crystal Space has a lot of tools to offer (like sectors, portals, visibility cullers, ...) but using those effectively is an art. In this chapter we will not talk about how to create levels. For that you use external tools like Blender, Quark, or other. This chapter focuses on how you should partition your level in sectors, the kind of mesh objects you should use, the visibility cullers, ....

For a good discussion about sectors and visibility cullers you should have a look at See Visibility Culling.

LEV sectors and portals     Sectors and Portals
LEV Visibility Cullers     Visibility Cullers
LEV Object Types     Object types
LEV Helping Renderer     Helping the Renderer
LEV Lighting Considerations     Lighting Considerations

Sectors and Portals

As explained in See Visibility Culling a sector is the basic building block in a level. When you create a level you should decide on how to partition your level in sectors. The easiest solution is to use a single sector. In many cases that may even be acceptible. Here are some points to think about when deciding on how to partition your level into sectors:

Visibility Cullers

Every sector has its own visibility culler. Crystal Space currently supports two kinds of visibility cullers: Frustvis and Dynavis. Dynavis attempts to work more on culling but it also has more overhead. So you should use Frustvis in case you have the following kinds of levels:

So if you have a complex level with lots of large objects then you should consider using Dynavis.

So if you decided on using Dynavis for a sector you should follow the following guidelines for that sector:

Here is an example on how you can replace the occlusion mesh of some mesh object in a map file:

<meshobj name="complexWall"> <plugin>thing</plugin> <params> ... </params> <polymesh> <mesh> <v x="-1" y="-1" z="-1" /> <v x="1" y="-1" z="-1" /> <v x="1" y="4" z="-1" /> <v x="-1" y="4" z="-1" /> <t v1="0" v2="1" v3="2" /> <t v1="0" v2="2" v3="3" /> </mesh> <viscull /> </polymesh> </meshobj>

And here is an example of how you can disable the occlusion mesh for an object:

<meshobj name="wallSegment"> <plugin>thing</plugin> <params> ... </params> <polymesh> <viscull /> </polymesh> </meshobj>

Object types

Regardless of sector partitioning and visibility culling requirements the choice of objects you use can also be important for performance. Crystal Space supports many mesh objects but the most important ones are:

Here are some guidelines on using these objects:

Helping the Renderer

When considering on how to design your objects you should keep in mind what the renderer prefers to get. For the renderer a mesh is defined as a polygon or triangle mesh with a single material and/or shader. So if you are using a thing mesh that uses multiple materials then this is actually a set of different meshes for the renderer. To avoid confusion we will call the single-material mesh that the renderer uses a render-mesh.

With OpenGL and especially if you have a 3D card that supports the VBO (Vertex Buffer Objects) extension the renderer prefers render-meshes that have a lot of polygons. So for the renderer it is better to use 10 render-meshes with 10000 polygons as opposed to 100 render-meshes with 1000 polygons even though the total number of polygons is the same.

On the other hand, this requirement conflicts with some of the guidelines for the visibility culler. Getting an optimal setup depends on the minimum hardware you want to support. If you are writing a game for the future and decide to require VBO support in the 3D card then you should use fewer but larger objects. For the current crop of 3D cards that are in use right now finding a good compromize is best.

One other technique you can use to help increase the size of render-meshes is to try to fit several materials on one material. For example, if you have a house with three textures: wall, roof, and doorway then you can create a new texture that contains those three textures. The end result of this is that every house will be a single render-mesh instead of three which is more optimal for OpenGL. There are four disadvantages to this technique:

  1. You have to be able to fit the smaller textures in the big texture without too much waste. Fitting four 64x64 textures in one 128x128 texture is easy but fitting three 64x64 textures in one 128x128 texture is going to waste one some precious texture memory. Of course you could try to use the remaining 64x64 space for textures on other objects.
  2. It is possible that you have lower quality textures because combining them on a bigger texture may otherwise overflow hardware limitations.
  3. It is harder for the artist to create models with this technique.
  4. This technique is not possible if you have a tiling texture. i.e. a wall texture that is repeated accross a large surface.

Lighting Considerations

When designing a level you also have to think about where to place lights. If you plan to use stencil based lighting or hardware based vertex lighting then you must be very careful not to exagerate in the number of lights. Runtime performance with those two techniques depends on the number of lights and their influence radius. For this reason you are probably better off using lightmapped lighting in case you have a big level with lots of lights.

With lightmapped lighting there is no runtime cost associated with having multiple lights (there is a slight memory cost associated with having many pseudo-dynamic lightmaps). A higher number of lights simply means that recalculating lighting will take longer.


This document was generated using GNU Makeinfo version 1.68