Modeling and texturing for optimal performance

From FSDeveloper Wiki
Revision as of 04:31, 11 March 2010 by Mjahn (talk | contribs) (Basic modelling rules)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search



When developing an object, the designer is always seeking a compromise between the amount of detail to put into the object and the performance achieved when using it in Flight Simulator. On one hand we would like to put in a lot of detail, either as polygons or as high resolution textures, because that will give the most realistic result. On the other hand we also want the objects we create to perform reasonably well when we load them in FS, otherwise the users of our object (either aircraft or scenery) will not have an enjoyable experience. So the developer has to find a compromise here.

On his blog Adrian Woods has an interesting series of posts about how to optimize the performance of the art you are making. I would like to encourage everybody to have a look at those posts (part 1, part 2, part 3 and part 4). But because these posts might be a bit technical, this article aims to provide some more background information and examples to express his suggestions more clearly.

Background

Before we start with the actual discussion on how to model and texture your object for the best performance, it is needed to start with a little bit of background information on what determines the performance of your object once it is loaded in FS. That is the aim of this section and we will discuss vertices and drawcalls.

Vertices

A simple cube
At first you might think that the amount of polygons in your object are the most important for the performance. A simple cube seems to have 6 polygons for example, but is that true?

A simple cube with triangles
The graphics card that is drawing your object on screen does not work with polygons, all it can do is draw triangles on screen. So to get a better understanding of the amount of work the graphical card has to do when drawing your object you should not count the polygons, but the triangles. For the simple cube that means we have 12 triangles. Especially when you have polygons with a lot of vertices, like the roof of a complex building, you will see that one polygon can consist of a lot of triangles. So the next time you determine how complex your object is count the triangles.

Going back to the graphics card, it now has the task to draw all the triangles on screen. To do this the engine basically provides the card with a list of vertices and then tells it to draw the triangles using these vertices. But these so called texture vertices do not only contain the position of the point that should be drawn, they also contain the texture mapping and the direction of the normal vector at that position.

A simple cube with the vertex normals shown
So if we look at the cube example again, we have those 12 triangles. But how many vertices are needed to draw these triangles? At first you might think the cube has 8 vertices, but think again. Each of the sides of our cube has a different normal vector. So that means that the vertices at the corners of our cube can not be used for all three triangles that meet at that corner. So instead of 8 vertices, you see the graphical card needs 24 texture vertices to draw the 12 triangles of the cube.

And when we add textures to our cube things can become even more complex. In the case that we would give each of the triangles an unique texture mapping, so that the UV coordinates do not match with the UV coordinates of the other triangles that the triangle aligns with, you will see that the total vertex count of the cube has gone up even further to 36 texture vertices.

Drawcalls

Now we know how the graphics card draws the objects and that we should actually count the number of texture vertices to see how complex an object is. But how does it work when we apply a certain material to our object?

First we need to clarify what a material means in this case. Does it mean the texture you apply, maybe the diffuse colour you select, or even something else? When the graphical card is drawing the triangles it is in a certain state, that means that a certain texture is active, a certain diffuse, ambient and emissive colour have been set, etc. When this state has been set correctly it is rather straight-forward for the graphics card to draw all triangles that use these properties. That is what happens when the program issues a drawcall to the graphical card, it then draws all triangles with the same properties. But changing the state for the next triangles that have to be drawn is a rather expensive operation performance wise.

To put it simply, any attribute you change in the FSX Material settings of your object will cause a change of the drawing state and thus a new drawcall. For something like the texture or diffuse colour this is probably obvious, but also when you only change the specular power or some other minor setting this will have the same result. All these material attributes together are called the shader constants.

So besides the amount of texture vertices, we now know that also the amount of drawcalls is very important when we want to keep the performance as good as possible.

Basic modelling rules

Before we take a look at how to model and texture your object as efficiently as possible, with the texture vertices and drawcalls in mind, we will first take a look at some simple “rules” for the modelling of objects.

The first and most obvious rule is to use only what you need. If you are making a generic autogen it does not make much sense to use ten different texture sheets and thousands of vertices on this object. That is more something you would do for an important landmark on your airport which the user will look at from close distance. So try to keep things as simple as possible and don't overdo it when that is not needed.

Related to this is another area where you would have to make a choice, the textures versus polygons battle. You could model all the details of your object with polygons, but often using fewer polygons and putting the detail in the texture makes more sense.

A nice example of this is a GMax tutorial I once saw on how to model a house (not the MS tutorial that comes with FS2004, but one for another game). In that tutorial it was explained in detail how to model each of the tiles of the roof in 3D. That might be a good approach if you are modelling for a first person shooter game, but for Flight Simulator using one or two polygons for the whole roof and a nice roof texture is a lot more sensible.

For the usage of textures the basic rule is to choose the right resolution for the object you are making and to use a similar resolution on all parts of your object. When you are making a generic autogen object you in general would use a lower resolution for the textures. On the other hand for that big airport terminal building where the user comes very close, it makes sense to use a higher resolution. And once you have selected your resolution, stick to that for the total object. Having one side with a much higher resolution than another side will give a very weird looking effect.

But even if you follow these basic rules of modelling, you can still create an object that will not display optimal performance in FS. The rest of this article will focus on some tips and tricks to improve the performance even further, while they have no effect on the way your object looks, so no compromises on the amount of detail.

Optimize texture vertices

As was explained before the amount of texture vertices is something you should keep an eye on, as that is a good indication of how much work the graphics card has to do to draw your object. So how do we keep them as low as possible?

As the texture vertex contains both position, texture mapping and normal vector it means that these must all be the same for two vertices, before the same texture vertex can be reused in two triangles. So when mapping your texture it can be a benefit to make sure that the pieces of your texture align correct, like you wrap the texture around the object.

As the normal vector is also part of the texture vertex, this optimization works best for smooth objects. For a simple cube or other building you will always keep multiple vertices at the corners, because the normal vectors are different. But even for a simple cube applying your UV texture mapping correctly can make the difference between 36 and 24 texture vertices.

Optimize drawcalls

Besides optimizing the amount of texture vertices, it is also very important that you try to keep the amount of draw calls as low as possible. To do this try to use the following tips.

When using textures, try to pack everything on one texture sheet when possible. So it is better to use one 1024x1024 texture, than to use 4 512x512 textures. Every new texture file that is used will add an additional drawcall. And if your object does not fill the entire texture sheet, just add the textures for an objects that is geographically grouped with it to the same sheet.

Try to use a texture for everything. So instead of creating a new material with a diffuse colour when you want to give a polygon a certain solid colour, it is better to use a piece of your texture for this. And on most texture sheets there will be a little spot left where you can put an area of for example 4x4 pixels with the colour you want to use.

Try to keep all the shader constants the same. Even if you use one texture file, but change a small attribute like the specular power, that will already cause an extra drawcall.

Levels of detail

Another way to improve the performance of your object is to add levels of detail (LODs) to it. A LOD is a simpler version of your object that are shown from a distance*, where you can't see all the details anyway. But that does not mean that you need to add a lot of LOD levels to your object, because each additional level increases the memory usage. So you need to find a balance between the amount of memory used by the object and the reduction in texture vertices that you can achieve by adding the LODs. For most objects using 3 LODs is probably the best compromise. Then you would create a LOD 100, a LOD 040 and a LOD 010. You could make LOD 010 a dummy, so that the object is not drawn from a great distance*.

*Note that the way FS determines 'distance' here isnt as simple as a geographical distance from the object (I.e. LOD_100 activates at 100m, LOD_040 activates at 40m etc) In fact, thinking about it this way will cause confusion since the values are actually inverse to the distance (I.e. use a high LOD value for objects that will show when you are closer to them).

The LOD 'distance' in FS is in fact determined by the number of pixels covered by the object on screen. Therefore a small/simple object on a low resolution screen will switch to different LOD at a different geographical distance than a large/complex object on a higher resolution screen. There will be some experimentation needed to get these values set at acceptable levels.