After mapping out the starting stage of this project, I analysed the project and all of it's settings and made a couple conclusion based on some experiments. From these conclusions I made a technical plan with a few steps to optimise my entire project.
Before I started analysing, I set up my profiling tools to analyse builds of my project so the editor interferes as little as possible with the performance of my game.
Before building:
After building (EditorLoop is gone)
Besides using the stander Unity profiler, I also installed the memory profiler, which is still a preview package in Unity 2020.3.30f1. This allows me to take a look at how much certain assets/textures is occupying the memory in a specific frame.
Besides using the stander Unity profiler, I also installed the memory profiler, which is still a preview package in Unity 2020.3.30f1. This allows me to take a look at how much certain assets/textures is occupying the memory in a specific frame.
At last, I also installed the Frame Debugger, which shows me how a specific frame is rendered and how many draw calls are being used. Using this I can see if objects with the same material are rendered in the same frame, and if they're not I could maybe tweak the material's settings to enable static batching so there less draw calls are needed.
Before starting to analyse my entire project, I saw that my FPS was limited to about 144. This is due to Vsync and my 144hz monitor, and can be seen in this screenshot I took off a build of my project. Notice how WaitForTargetFPS is clearly limiting my FPS.
Ok, so after turned VSync I noticed that my FPS increased to about 270 (or at least under the same lighting circumstances, but I will get into that later), without there being any screen tears. I also noticed however that there is still a large chunk labeled as "VSync", even though I just turned it off.
Apparently Unity labels . WaitForLastPresent processes under VSync, which basically means my project is GPU-bound. This is something which I should not be worried about at this moment, since this is pretty standard.
Conclusion: VSync should be disabled.
I have about 10 lights in my scene which are not turned on when starting the game. I made a simple script to turn them on by pressing 1-4 on my keyboard. By turning on these lights in groups I can see that my FPS decreases and starts to fluctuate a lot.
4 Spot lights: 265 - 270 FPS
6 Spot Lights, 2 Point Lights: 240 - 270 FPS
6 Spot Lights, 5 Point Lights: 230 - 260 FPS
6 Spot Lights, 8 Point Lights: 225 - 250 FPS
The lights seen on the right are used to brighten up the room and reveal the clues which the player has to use to solve the puzzle. This is why I can't just bake the entire scene. I could however bake the lights which are on by default (about 4 spot lights) and bake the 3 torches which are only turned on after the player solves the puzzle (which reveals the set of stairs seen below.
So to be clear, the lights seen on the top image aren't on when you enter the scene, but are turned on while the player completes the puzzle.
After some searching I came across a few methods for swapping between different lightmaps at runtime. For this I would have to bake three different versions of my scene, and make a script swap between the different lightmaps so I can reduce the amount of realtime light sources and set most objects go static.
Conclusion: The amount of realtime lights should be decreased, either by baking or removing some light sources entirely.
A lot of the meshes I'm using in this project are quite high-res, which can be seen in both the memory usage and by just looking at the wireframe for some of the statues. Allthough the image on the right could maybe pass as a pretentious piece of modern art, I think my game would benefit a lot from decreasing the amount of vertices in the statues. For this I could use Maya or Zbrush.
To be sure that my project actually benefits from decreasing the amount of vertices, I made a scene without the two Shiva statues. This increased my maximum FPS by about ten. I also made a scene in which I removed all of the statues, which increased my maximum FPS by ten more. The amount of memory used for mesh data is decreased a lot, which can be seen using the Memory Profiler window.
Conclusion: by remeshing objects with a high amount of vertices the FPS can be increased.
Besides remeshing I could use occlusion culling to cull the lower or upper half of the scene at runtime, since the player will almost never see everything at once.
Another interesting insight made using the memory profiler is the fact that some of my normal textures take up a lot of memory. After compressing the normal map of the rocks (so the max size is 1024x1024 pixels, which is one step lower than the rock's albedo: 2048x2048), you can see a difference in the texture's memory usage, as well as in the environment. I think in this case that it hurts the look of the environment a bit too much.
Normal map without compression (2048x2048)
After compressing normal map (to 1024x1024)
Besides the textures used by the rocks I noticed that a lot of textures start with Armature_, which are textures used by the player model. After compressing each of these textures to a maximum of 512, I noticed another difference in memory usage. The FPS did not increase however. Besides compressing textures, I could also look into channel packing since all of the normal and roughness textures I'm using are loose files and mostly actually use one channel only.
Conclusion: Although it isn't sure if it will increase the game's overall FPS, compressing normal maps can decrease the amount of memory used.
In the image above in the lower left corner is a texture called UI_Circle_Faded, which is a texture which is standard for every Unity project and used for UI objects on the canvas. After some research I learned that Unity has to draw disabled canvas Game objects, even though you won't actually see them. Knowing I have a few disabled Canvas objects which I'm not using anymore, I tried to delete all of them. This decreased the memory usage by about 0.1GB.
Conclusion: unused canvases should be removed.
Camera settings and unused cameras
At the start of this level, the player slides down a rope and falls on the ground. This is done using a simple script which turns off the "Climbing Player" gameobject and turning on the "Movable Player" gameobject, which is a third person character controller.
Instead of turning off the unused camera, I could disable the camera at runtime after the player reaches the ground (since the player won't have to climb up the rope anymore). Besides this camera I also have an unused camera for a cinematic which I didn't use. To compare, I made a build with and without the unused camera objects. This increased my FPS by about 5.
Conclusion: removing unused cameras won't impact my FPS that much, but might be nice to clean up my scene hierarchy.
Particle systems
As I already stated on the previous page, I am using eight torches with particle systems (as seen on the images below). The sparks particles are so small however that they can't be seen by the player, so they can be removed.
Besides the torch particle systems, I have two particle systems for dust particles. The boundary box for these particles is sometimes outside of the level. If I were to make the boundary box smaller and fit the shape of the level better I could decrease the amount of particles by decreasing the emission.
Hierarchy & folder structure
While making this project I imported a lot of different assets, which I didn't sort into the correct folders afterwards. I also didn't rename these assets, which is something that I should definitely do.
Profiling & specs
I noticed during my profiling that my laptop is quite powerful and that some changes don't impact the framerate as much as it should. So from now on I will use my desktop to analyse, which means I have to connect the build on my desktop to the profiler in Unity on my laptop.
At first this was a bit cumbersome, but later on I realised that profiling my build in this way would give me more accurate results than using the same computer to profile and analyse. So now that I can profile my build in almost complete isolation, I analyse more accurately.
Laptop
CPU: Intel Core i5, 11300H, quad-core
Motherboard: CFL, Octavia_CFS V 1.28
Screen: 1920 x 1080 pixels (39.6cm), 144Hz
Memory: 16 GB
Graphics Card: Nvidia GeForce RTX 3050
Operating System: Windows 10
Desktop
CPU: AMD Athlon(tm) X4 845 Quad Core
Motherboard: ASRock FM2A68M-DG3+
Screen: 1920x1080, 60 hz
Memory: 8 GB
Graphics Card: GeForce GTX 1650 (MSI AERO ITX OC)
Operating system: Windows 10
Based on this small analysis and simple experiments, I made a technical plan including all of the next steps which I shall take to optimise this project even further:
Profiling Tools
In between each test I will build and profile versions before and after using another desktop.
Project Settings
Disable VSync.
Lighting
Bake the bottom half of the scene.
Decrease the amount of spot lights which are turned on at the start of the level.
Experiment with swapping between different lightmaps (so most objects should be static).
Meshes & culling
Remesh objects with a lot of vertices.
Bake occlusion culling so not every object is rendered.
Textures
Compress normal maps to be the albedo's texture /2, if it doesn't impact the look of the scene too much.
Unused objects
Remove unused canvases.
Remove unused cameras.
Write a script to delete unused player character (after swapping to other character).
Particle systems
Remove particle systems which don't impact the look of the game.
Make dust particle system smaller and reduce emission.
Hierarchy & Folders
Rename assets using a naming convention.
Reorder hierarchy and project folders.