This page provides a framework for considering performance when creating new models. Blindly making a model without considering performance is likely to lead to poor results, both in terms of frame rate, and in terms of visual quality. People often assume that better-looking objects will be inherently poor performers. This is not a good general-case summary- in practice, the better that objects perform, the more detail can be used in a scene. With this in mind, we can see how an object built with performance in mind can actually provide significantly better visual quality than an object built for visuals alone.
It's also worth considering that polygon count and performance are not the only trade-offs involved when modeling. Modeling techniques and context often prove a significantly larger effect on frame rate than the raw polygon count numbers.
Where is the object used?
Before creating a new model in Trainz, there are certain usage parameters that need to be defined:
- What is the closest that the player-camera will come to the object in a regular play session?
- What is the farthest that the player-camera will be able to view the object in a regular play session?
- At what range will the player-camera typically sit from the object in a regular play session?
- Will the object regularly be viewed from multiple angles, or primarily a single angle?
- What is the expected object density of the scene surrounding the object? Is it urban or rural, jungle or plains?
- How custom is the object? Is it specific to a particular area in a particular layout, or is it likely to be reused heavily?
- Will the object typically be reused heavily in a single scene?
Regardless of the object type to be modeled, answering these questions provides a framework for making decisions about modeling techniques.
Each mesh and each texture used in Trainz impacts performance at various stages in its life cycle:
- Mesh loading performance.
- Texture loading performance.
- Mesh memory cost.
- Texture memory cost.
- Mesh parametric performance.
- Mesh stitching performance.
- Per-polygon render cost.
- Per-material render cost.
- LOD change cost.
- Per-mesh culling cost.
- Per-object memory cost.
The following techniques are exceedingly useful in allowing higher-detail scenes.
Level Of Detail
Level-Of-Detail (LOD) is a rendering term for swapping between various 'detail levels' as an object changes viewing distance. Close objects are rendered in high detail, whereas far objects use a lower detail variant. Each LOD should look identical, with the exception of the performance vs quality trade-off. It is important that the overall coloring (including apparent coloring that stems from the lighting model) and silhouette of the object are preserved, since changes to these are the most visible to an observer. Changes to the fine details within the object are far less noticeable.
As an object moves away from the observer, each doubling of the distance results in a halving of both the horizontal and vertical size of the object in screen space. Roughly speaking, this means that we can afford to lose 75% of the object detail at each double distance. In practice, it can be hard to lose that much detail without compromising the silhouette, so a slightly more gradual approach is recommended. If an object is at full detail at 10m, consider halving the detail at 30m, and again at 100m. By the time you reach 1000m, you need to have shed the vast majority of the detail and be left with a box or billboard type object. Smaller objects should vanish completely well before they reach this distance.
LOD operates for both the textures and the models.
TS2009 will take care of performing texture LOD by falling back to lower mip levels as the object moves into the distance. The unused higher mip levels will be unloaded as necessary, to reduce the game's memory footprint. When the object approaches the observer, the higher mip levels will be reloaded. When several of the same object are in the scene, the game will load up to the highest mip level currently necessary for any of the objects, but the GPU will still perform texture LOD per object.
It is up to the content creator to correctly configure LOD for the models. It is very important to include LODs for any object that has more than ~20 polygons which is capable of mesh stitching, to prevent the polygon count blowing out if the model is used heavily and the draw distance is set high. For objects which are definitely not candidates for mesh stitching, LODs are important where the polygon count exceeds ~300 polygons.
The number of LOD models to make is dependent on the the object type and how it is used, but it is recommended that roughly four models are used. Too few LODs, and the object will either drop detail too visibly or not drop soon enough. Too many, and the game will waste CPU time swapping the models in and out.
Where mesh stitching is not possible (for example, on Train vehicles), LM.txt files should be used to provide LOD support. For stitched meshes, the "mesh-table" Container 'lod-level' tag should be used instead.
Creating a texture page which is shared between multiple meshes can be a significant performance win if the meshes are likely to be visible in a scene at around the same time. For example, several related tree meshes could be made with a single shared texture. Each tree mesh may use a common part of the texture and some object-specific part of the texture, or each tree mesh could use a completely separate space on the texture page. This is also true for different LODs of a single object - since the various LODs will be swapped between on a regular basis, having all the LODs use the same texture (again, either the same space on the texture, or separate spaces as required) is going to be a performance win.
To enable texture sharing, the following conditions should be met:
- Multiple meshes exist in the same directory.
- The meshes use identical material names.
- The meshes are exported with up-to-date TS2009 exporters. Older exporters will prevent texture sharing.
- The materials should have identical setup, including referencing the same *.texture.txt files.
A mesh library is an asset which is not placeable in itself, but rather provides a number of mesh files which are used by other assets. The advantages of using assets from the shared library are as follows:
- If multiple assets use the same mesh, there is a corresponding reduction in disk usage and memory usage (as compared to each asset containing a duplicate copy of the mesh and textures.)
- If multiple assets use the same mesh, they are candidates of mesh stitching together.
- If multiple assets use different meshes from the same library, but the meshes share materials, they are candidates for mesh stitching together.
- If multiple assets use different meshes from the same library, but the meshes share textures, there is a corresponding reduction in disk usage and memory usage (as compared to each asset containing a duplicate copy of the textures.)
- If many assets use the same library, an update to the library will affect all assets rather than requiring an update to each asset.
- Textures from meshes that are regularly used together can be combined into a unified texture page to enable mesh stitching where it would not otherwise be available.
An asset may source meshes from multiple libraries as well as its own local folder.
It is recommended that asset libraries are not made too large on disk - while combining meshes and textures in this manner improves efficiency, the bandwidth cost of providing an update to the entire library can become prohibitive if it becomes too large.
Alpha masking refers to a special case of alpha blending where all pixels are rendered as either fully opaque (100% alpha) or fully transparent (0% alpha) with no partial transparency. This can be treated as a special case because it does not require alpha sorting in order to produce visually correct results. This provides a significant performance win over an equivalent alpha sorted material.
When an alpha masked texture is recognized, Trainz will also prefer DXT1a compression over the DXT3 required for normal alpha blending. This provides a 50% reduction in texture memory usage.
The following techniques generally lead to poor performance and reduce available scene detail.
Prior to TS2009, all materials which made use of alpha blending, with a very few exceptions, were passed through a very slow alpha sorting process. This process is necessary to achieve some semblance of correct transparency ordering, however it is a burden on the CPU and results in the geometry being broken up in a manner that gives a very inefficient input to the GPU. There is no real solution to this problem - current rendering technology based on polygon rasterization is fundamentally inefficient when dealing with blended alpha sorting.
The current recommendation is to simply avoid using alpha blending. Most objects which have traditionally used alpha blending can be constructed using an increased polygon count and alpha masking. This leads to a slightly different graphical result - object edges are sharp, rather than blurred - which may be considered better or worse than the old approach, depending on the circumstances and personal opinion. When close to the observer, this approach is generally considered to give superior results due to the increased polygon count.
The increase in polygon count does bring a performance cost of its own, but when applied sensibly and in conjunction with LOD, the polygon cost is negligible when compared with the old alpha sorting approach.
In summary - if your diffuse map uses an alpha channel for opacity control, ensure that the entire channel consists of pure black and pure white pixels - do not use even a single pixel of gray. It is recommended that you additionally flag your texture.txt file with the "AlphaHint=masked" attribute.
Use of multiple materials
When rendering a model, each material used results in a single request to the GPU, regardless of the number of polygons drawn with that material. This is known as a "draw call". Due to driver and communication overheads, each draw call has a fixed performance cost, in addition to any pixel cost and polygon costs. This cost varies between hardware platforms, but it is reasonable to say that the fixed overhead becomes a significant penalty in any draw call below 500 polygons.
Because of this fixed cost, it is more efficient to use a single texture page rather than several separate textures when rendering a given model. This is especially true at lower LODs since the polygon count and pixel count are lower - the ratio of wasted setup time to actual geometry rendered will increase substantially.
As an example, let's assume that the hardware can handle around 400 calls in a frame. This means that you can safely render 400 objects if each uses a single material. If each uses 4 materials, then you are limited to around 100 objects in view. It's easy to see how reducing material count becomes important. Mesh stitching can also help here, since it may reduce the number of draw calls necessary if the same object is used repeatedly.
High mesh subdivision in distant objects