Since the game is based on fast rail movement, from the very beginning we had clear the rails were going to be one of the biggest technical challenges of the project. Thanks to the Spline and Spline Mesh components provided by UE4, we could build the first prototype and start iterating in the fun part of the game early on the development. But, these components did not solve all our problems…
The Problem – Spline Mesh Rendering
Once we had a “big” testing level built, the game started to run a bit laggy in some of the computers we where testing the game on. I found out that the spline mesh rendering was taking a big percentage of the frame time. This makes sense because every spline is rendered by using multiple small objects. The longer the spline, the more objects we will need to represent it:
The problem here is that we have a lot of objects to render in order to visualize the rails that the spline are representing. Although the rail meshes are using LODs, the mesh used when the object is far away contains just 35 triangles, is not enough to make the game run over 60 FPS. On the other hand, rendering is not only the problem, all these objects need to perform the occlusion test, which will also take some time (I am not sure how UE4 implements it, but is good to have that into account).
The Solution – Custom LOD Algorithm
Before explaining the solution I applied, it is important to mention that the most common and efficient solution to improve rendering time for a big amount of objects would be to use Instance Rendering. Unfortunately, we cannot benefit from this technique in our problem because UE4 does not support instanced rendering for Spline Mesh Components. I guess the reason is that most spline meshes render with a different shape (depends on the shape of the spline). This means that each mesh we render needs more information, apart from the model to world matrix transformation.
The solution that came to my mind was to make a similar approach to the LOD algorithm used with meshes, which is, when an object is far away from the camera we can use less triangles to represent the object without having an actual impact in how the object looks in screen.
We can use a similar approach for the meshes that we use to represent our spline.
This simple solution was the first approach I tried and gave so good results that decided to keep it for the moment. If in the future this part gives us more problems the algorithm can be improved. The improvements in the performance are the following:
- Playing in editor
- Pre-optimization: 24 ms, 41 FPS
- Post-optimization: 18 ms, 55 FPS
- Playing in standalone build
- Pre-optimization: 19 ms, 53 FSP
- Post-optimization: 14 ms, 71 FSP
These are some visual results of how the level looks with the optimizations turned on. For clarity, only splines are shown.
We could trade a bit of performance in order to get a better quality when splines are very far (i.e. using a low number of segments to be represented), by only optimizing parts of the curve we know will not change too much the shape of the spline.
For example, in the image above, we could make the straight parts use less number of meshes and use more in the loops.