The Blue Trojan Horse That Snuck into A Total War Saga: Troy
August 2 2021 | Games
How Creative Assembly* Used IntelⓇ CPU to Inject More Realism into Troy
By Sergey Miloykov, Technical Director at Creative Assembly, Sofia, with support from Steve Hughes, Intel Application Engineer.
In 2019, during development of A Total War Saga: Troy, Intel approached us to collaborate on optimizing the game for desktop systems. The aim was to find ways to make better use of the CPU to maximize the visual impact of the game and deliver a more immersive, realistic experience.
Intel was interested to see how we could use CPU capacity to push the realism of the environments, while at the same time ensuring that the game was equally playable on a wide range of systems. Their plan fit well with ours, as our work to make 4K our rendering standard had pretty much saturated the GPU, and, since we had a few inherently single-threaded algorithms running on the CPU side, we had some idle cores to utilize.
We had a few brainstorming sessions with Intel, and one subject that kept coming up was the grass. If we could have grass that swayed realistically in a simulated wind system, and that interacted with dynamic objects in the game like the armed units and chariots, then we would have a significant increase in visual quality. This was the kind of simulation we could run on the CPU, and by either reducing the range or lowering the sim quality, we could make it scalable too.
Another idea we had was to improve the water. Originally, we were planning to have minimal interaction with the water since it didn’t play an important part in battles — it was just there to make the landscape complete. However, we started to think about how we could make the water more interactive so that it didn’t look out of place in the fields of swaying, interactive grass. Water surface simulation is a well-established algorithm, but you can always make it more interactive by adding CPU power to it, and that’s exactly what we had to spare.
The limiting factor when it comes to creating realistic grass is the number of grass blades. We didn’t want to simulate every blade of grass in the game world — that would be a bad idea. The world is divided into small square patches, each of which is rendered using data generated from a template patch, a square area filled with evenly-distributed grass blades. This means that the number of blades rendered is effectively a density function. The actual number of blades rendered in each square is dependent upon an artist-designed mask, and the distance from the camera. This gives us a way to maintain artistic freedom, while avoiding unnecessary grass simulation and rendering. Once a group of squares has been generated in this way, we simulate them and upload them for drawing.
The simulation itself is the sum of a number of Perlin noise functions for the regular wind, and a very high-frequency "dephasing" function that makes the blades out of phase from their near neighbours. This produces the natural, noised movement you see when the wind is not very strong.
The interaction is done using a particle-per-blade simulation where we pass an impulse from the colliding entities to the intersecting grass blades. All the entities are scattered across that same grid of patches, so each patch can be independently simulated.
We suppose circular and rectangular entity shapes. We also support grass tramping, where heavier entities and vehicles can trample the grass to the ground and it will remain that way for a while. The grass will recover and return to its vertical state, but the trails from moving entities will stay for some time on the grass fields, providing a hint to the other player that someone has passed by.
We produce a task for each grass patch and assign it to our internal task system. We used VTune extensively to profile our frame for "bubbles" inside our threads — i.e. threads that do nothing while other threads are working — since we had quite a few tasks which could nicely fill any number of concurrent threads and cores with work.
The grass rendering process is relatively simple — each blade is facing the camera and is subdivided into several joints vertically so it can bend properly. A big chunk of these blades is packed into a "rendering patch" and is drawn with a single GPU command, which does a good job of optimizing our grass rendering.
Since all the water in the game consists of shallow rivers and is mostly slow-moving, we didn’t need to delve deeply into the more complex wave-generation functions that are required for modern sea simulations. Instead, we just created a camera space grid which animates using the classic pressure function:
Pressure(x,y) = (WaterHeight(x+1,y) + WaterHeight(x-1,y) + WaterHeight(x,y+1) + WaterHeight(x,y-1))/4 - WaterHeight(x,y)
The available CPU power allows us to make this from quite a fine grid, which gives a good background simulation, and lets us introduce fairly realistic interaction between moving entities in the game and the water surface. For added effect, we also introduce occasional rain and falling particles into the simulation.
As simulating water in this way is an iterative process, we maintain a persistent set of patches that tile the surface. Each of these tiles can be processed in parallel which gives us a good density of task execution on the available CPU cores.
After the simulation we take the height map, generate a normal map, make some mipmaps, then fire off the water shader and we’re done. The key to this water solution is speed — all the calculation and updating is done in fairly dense SSE2 code so the execution time is very fast.
One thing we realised during our optimisation work with Intel was that this kind of enhancement can’t go on forever. There’s always a trade-off between CPU cost and GPU cost — everything you add on the CPU has a GPU toll. Eventually you add too much content for the GPU to render, however you optimize it! The key is to think smart, use the CPU threads wisely, and focus on what makes a good game great for the user.
By taking advantage of the unused cores we managed to add some significant improvements in visual quality to A Total War Saga: Troy which heightens the user experience and immersion. The design flexibility allows us to maximize quality on high-end CPUs and effortlessly throttle back these effects to accommodate lower-powered CPUs. This keeps the game enjoyable for a wide range of players, and we think we have built an environment worthy of the epic sagas of Troy. Intel was a great help in developing these effects and making a great game for our players. We look forward to working with them again in the future.
Creative Assembly is currently seeking experienced programmers (graphics, tools, core systems and more) to work on the Total War franchise and our new Sci Fi FPS IP.