Optimizing Assets for Cities Skylines

Hello there! I'm “ComradeIntense”, and I'm here to share some insights and best practices for enhancing the performance of your Cities Skylines assets.

The subsequent sections will unveil the wisdom I've gathered during my time exploring this domain, lessons I've learned from experts (particularly from my teacher, Ronyx69), and from my professional experience. My intention is to pass on this accumulated knowledge. Should you find any discrepancies or have suggestions, please reach out; I'm always on the learning curve.

While this guide is tailored for those who have a foundational understanding of asset creation (a helpful starting point would be: https://cslmodding.info), it will delve deeper into the implications of your design choices on the game's performance and how to mitigate any negative impacts.

While I aim to be concise, there'll be instances where we'll delve into the technicalities. These are crucial for a comprehensive understanding, so I'd recommend thorough reading without skimming over any part.

Part I

1) Definition of Draw Calls

A draw call is a command where the (CPU) directs the (GPU) to render an object on the screen. The process begins when a mesh is loaded. The CPU then calculates the positions of each vertex in the mesh relative to the world space, as well as the associated materials.

It then instructs the GPU to display the mesh on the screen by providing the location of each vertex and the materials to be applied. The efficiency of this process is influenced by the number of vertices and draw calls.

A discrepancy in the performance capacities of the CPU and GPU can result in a bottleneck. If a CPU lags in processing the instructions, the GPU may remain idle, waiting for further directives.

Image descrbing GPU draw call bottleneck

2) The Role of Triangles (Tris) in Graphics

Understanding the relevance of draw calls provides context to the significance of triangle count in gaming graphics. A triangle is formed by three vertices. The GPU calculates the position of these vertices.

Therefore, a reduction in the number of triangles corresponds to fewer vertices for the GPU to process, leading to enhanced performance. Modern GPUs can handle a large number of triangles effectively.

Nonetheless, it's essential for developers to be judicious in the use of triangles. Using more triangles than needed can impact overall game performance, especially when multiple assets are displayed on the screen simultaneously.

3) Comparative Analysis: Cities Skylines vs. Other Games

Different games employ various techniques to optimize graphics rendering. A central query in optimization is whether it's more efficient to render one large mesh or multiple smaller meshes. Typically, rendering a single large mesh requires fewer draw calls than multiple smaller meshes.

Many game developers combine smaller meshes to create a single, larger mesh to boost performance. Additionally, developers often employ strategies like using higher triangle counts for objects near the player or camera and fewer triangles for distant objects. Some even use detailed images on flat surfaces to simulate intricate details without additional triangles.

In the game "Cities Skylines", the optimization process is distinct. Players have the autonomy to add numerous objects to their game scenes, which can result in non-optimized graphics rendering due to the vast array of assets. Each added mesh requires the CPU to execute draw calls, consuming more processing power.

While it's crucial to be conscious of triangle and vertex counts, achieving a balance between detail and performance is equally essential. Developers should discern what requires intricate modeling and what can be represented using simpler graphics tools, such as normal maps or diffuse techniques.

Part II

1) Understanding Vertex Precision Loss

In games, there exists a central reference point, known as the world origin, identified by coordinates x,y,z = 0,0,0. All vertices of meshes are computed relative to this world origin. Some game developers might calculate vertex positions in relation to the camera or might shift the world origin.

Image descrbing GPU draw call bottleneck

When a draw call is initiated for a mesh, each vertex's location in world space, or its relative position to the world origin (0,0,0), is determined and relayed to the GPU for rendering on the screen.

Vertex position data is stored in a format known as a 32-bit floating point. Floating points provide precision up to a certain extent. The greater the distance a vertex is from the world origin, the higher the chance for computational errors. Vertices distant from the world origin can begin to display inaccuracies in their calculated positions.

In cases where a vertex in the middle of a mesh isn't linked to another vertex, inaccuracies in position calculations become apparent. This can lead to visible glitches in rendering:

Single vertex not linked to another vertexVisible glitch caused by improper mesh geometry

To address this, it's essential that a mesh remains "closed." For instance, the problematic vertex should be connected as displayed:

Vertex connected to another vertex

Alternatively, if connecting isn't feasible, the vertex can slightly overlap an adjacent face:

Vertex slightly overlapping adjacent face

This approach isn't perfect and should be utilized if the textures match, preventing Z-fighting or display flickering.

Vertices should ideally remain connected, even if it necessitates more triangles. Additionally, merging vertices can be beneficial. An illustrative video that demonstrates the importance of maintaining vertex precision can be found here: Minecraft video

2) Overdraw and Its Impact

Overdraw in gaming can have a significant impact on performance. Essentially, overdraw occurs when the GPU renders pixels that overlap, which can be resource-intensive.

Image describing what overdraw is

To elucidate, consider a rendered scene featuring two characters in the background and glasses in the foreground. The GPU would have to render both the characters and the overlapping area of the glasses' lens, consuming additional resources.

A building comprised of two intersecting shapes illustrates this concept further:

Image showing a mesh of a building with window extrusionsImage showing overdraw faces inside a mesh that never are visible

In the above example, the areas marked in red and blue denote overdraw. To optimize performance, these areas should be trimmed:

Image showing removed faces which are never seen to avoid overdrawExploded view of a mesh to showcase overdraw

Another example involves a sword image with transparent areas:

Vertex slightly overlapping adjacent face

Between Fig1 and Fig2, the latter is preferred due to reduced overdraw, even with a higher triangle count. While transparent pixels might seem negligible, the GPU processes them, using unnecessary resources.

3) The Concept of Overshading

Overshading, akin to Overdraw, affects game performance. It results from the presence of slender triangles in a rendered image. The GPU processes pixels in 2x2 blocks. If a tiny section of a triangle touches a single pixel within such a block, the GPU processes the entire block. This leads to wastage as up to 75% of the processed data might be discarded.

Image showing overshade calculation by GPU

For instance, consider a building with an intricate design:

Image showing a mesh with bad overshading

To improve the design and reduce overshading, a few additional cuts and connections can be introduced:

Image showing a mesh with good overshading

While contemporary GPUs are adept at handling such computations, optimizing topology to reduce long, thin triangles is recommended for efficient performance.

Part III

1) Texture Ratios and Power of 2

The topic of texture sizes and the significance of sticking to powers of 2, namely, 32 (smallest), 64, 128, 256, 512, 1024, 2048, and 4096 (largest), has been under constant discussion. While it's true that textures can come in any ratio and aren't confined to being square (for instance, 256x2048 is entirely acceptable), one cardinal rule is not to utilize textures that deviate from the powers of 2.

You might argue, “I've never encountered issues without adhering to this.” However, two aspects warrant consideration:

A) Compression:

Upon saving your texture, launching the asset editor will trigger the game engine to transform your texture to the DXT format, a compressed texture format, DXT being the compression methodology. This compression cannot transpire if your texture isn't divisible by 32. Hence, it's imperative to adhere to the previously mentioned dimensions.

B) MipMaps:

Mip maps come into play when the game engine is loading the textures. They serve as the equivalent of LODs for textures. These are lower resolution duplicates of your primary texture that activate based on the mesh's distance from the camera. As the mesh recedes, a lower-resolution texture is applied, with this resolution diminishing progressively as you pan out. As you zoom in, the resolution incrementally reverts to its original size.

Here's an illustrative chain of a mip map: Representation of how mipmaps work

This mechanism is automated and doesn't require any manual adjustments.

Now, aligning this with the power of 2 concept: Deviating from power of 2 textures can lead to compression-induced texture corruption, most noticeable from a distance or at certain angles. While the texture will still function and display on your model, underlying problems exist.

Furthermore, some game engines, like Unreal Engine, don't even support mip mapping without power of 2 textures. However, Unity (employed by Cities Skylines) offers an alternative approach. While Unity allows non-power of 2 textures, they consume more memory and exhibit slower GPU read times. This is due to the engine enforcing mip maps, scaling, and padding your texture to the succeeding power of 2. Therefore, for optimal performance, consistently use power of 2 resolutions.

2) Texture Size: A Critical Aspect

Concluding this segment, let's delve into texture sizes. Larger image resolutions demand more memory (specifically VRAM in your GPU) during rendering.

Here's a revelation: The file size of textures saved as PNGs on your device is not the prime concern, as in-game importing compresses them via DXT. Notably, memory consumption isn't a reflection of texture intricacy but is solely dependent on resolution size. Therefore, it's crucial to maintain a balance between resolution and detail retention.

In Cities Skylines, DXT compression manifests in the following sequence:

_diffuse
_alpha + _color + _illumination collectively (ACI)
_normal + _specular in tandem (XYS)

Ronyx69 crafted a comprehensive chart detailing the sizes of these compressed textures:

Image of a table showing sizes of compressed textures

(Keep in mind that the final asset size isn't solely due to textures, but they constitute a significant portion.)

What's the next step? Simply put, avoid superfluous texture sizes. Although it might seem evident, achieving this requires experience. Regrettably, many asset creators take shortcuts, leading to oversized textures.The industry needs a shift from the mundane cycle of replicating these flawed methods. The focus should be on maximizing texture efficiency, reusing sections, and reducing resolution size without compromising on quality.

While this guide emphasizes performance-oriented aspects, remember: not everything has to be intricately modeled, and most assets don't necessitate a texture resolution exceeding 2048.