Skip to content

Low FPS Troubleshooting and Optimization Guide

This section explains how to diagnose and resolve performance (FPS) issues in your project. It also covers GC Alloc elimination and shader memory/build time optimization.


Low FPS Diagnostic Process

There can be many different causes for low performance, so always follow this process:

  1. Find what is actually slowing you down
  2. Optimize only that thing (do not make random edits)
  3. Recheck FPS
  4. If FPS increases with no side effects, keep the change and push to version control
  5. Go back to step 1

1. CPU or GPU Bound?

Lower the Game Window resolution and RenderScale to the lowest possible:

Result Cause
FPS stays the same Likely CPU Bound
FPS increases significantly Definitely GPU Bound

Note

Most concert projects are CPU Main Thread bound.


2. Number of Characters

1-5 characters is typical and should not slow down significantly due to NiloToon.

Each NiloToonPerCharacterRenderController has a fixed CPU Main Thread cost, and you can check the sum of time cost in the Profiler. If you use many NiloToonPerCharacterRenderController scripts for props/items, pay attention to the total CPU Main Thread time cost.

CPU cost in Profiler

4 characters + 16 props = 20 NiloToonPerCharacterRenderController scripts producing a total of 0.x ms CPU cost on Intel Core i7-14700 CPU


3. Hardware Requirements

Example PC for Unity Editor:

Component Specification
CPU Intel Core i7-14700
GPU NVIDIA RTX 4080 Super

This specification is suitable for running small to medium concert projects in the Editor.


4. Editor vs Build

For CPU, there can be significant CPU performance differences between Editor and Build. Editor Play Mode typically has more editor-only overhead and tends to be slower compared to a built executable. If possible, build and test CPU performance in a build.

For GPU, it is fine to test performance in the Editor. GPU performance in a build will not differ significantly from the Editor as long as the resolution stays the same.


5. Common CPU Bottlenecks

To solve CPU Bound problems, check the following:

Reduce or Disable

  • Planar Reflection (each reflection uses an extra camera)
  • More than 1 camera (using a single 3D Main Camera is highly recommended)
  • Too many characters with NiloToonPerCharacterRenderController (e.g., > 5)
  • Too many props/items with NiloToonPerCharacterRenderController (e.g., > 20)
  • Too heavy MagicaCloth 1/2, VRM Spring Bone
  • Too heavy Timeline / Director Update (usually caused by lighting animation)
  • Too many visible lights
  • Too many lights with unnecessary Shadowmap enabled
  • Non-SRP Batching materials in Frame Debugger
  • CPU particles with high spawn count (e.g., sea of penlights or confetti)
  • NiloToon's "Keep play mode mat edit?" - prevents SRP Batching in Editor
  • NiloToon's High Quality Culling (in NiloToon Renderer Feature's Char Self Shadow section) - costs a full 0.x ms Camera SRP Culling

Try Enabling

  • DX12 Graphics API (DX11 is generally slower in multi-thread)
  • DX12 Split Graphics Jobs - much better render loop CPU performance, no negative effect on visual results
  • DX12 Graphics Jobs for Editor - much better render loop CPU performance
  • GPU ResidentDrawer - may improve CPU performance only when rendering large amounts (>10K) of GameObjects with the same MeshRenderers for GPU instancing. It has a fixed CPU cost per frame, so enabling it will reduce performance if GPU instancing is not utilized heavily
  • GPU Skinning (Batched)
  • IL2CPP in Build

Debugging Tools

Profiler and Frame Debugger are excellent tools for finding CPU bottlenecks. These two tools will help you find most CPU performance problems.


6. Common GPU Bottlenecks

The causes of GPU bottlenecks are generally more straightforward.

Reduce or Disable

  • High Game Window resolution (e.g., >= 4K)
  • High RenderScale (e.g., >= 1)
  • MSAA enabled at high resolution/RenderScale (try disabling MSAA and using TAA/DLSS native AA instead)
  • Volumetric light beams / Volumetric fog overdraw - too many stacking together and covering many pixels
  • Too many visible lights in Forward+
  • High-resolution textures with no Mipmap
  • Too heavy Post-Processing (try disabling Volume and check FPS difference)
  • Realtime GI solutions (e.g., HTrace SSGI / WSGI)

Try Enabling

  • Batched GPU Skinning - better GPU performance when models have many SkinnedMeshRenderers and Blendshapes
  • DLSS to lower the actual 3D resolution

Note

For detailed example settings for resolution and AA, see the AA/DLSS Guide.


Achieving 0 GC Alloc

Achieving 0 GC Alloc

Projects Using Terrain

In all NiloToon Renderer Features:

  • Disable Perfect Culling For Shadow Casters - prevents Terrain crashes
  • Disable Terrain Crash Safe Guard - removes GC Alloc

Settings for Terrain projects

Projects Not Using Terrain

In all NiloToon Renderer Features:

  • Enable Perfect Culling For Shadow Casters - improves shadow quality
  • Disable Terrain Crash Safe Guard - removes GC Alloc

Settings for non-Terrain projects


Reducing Shader Memory and Build Time

Similar to URP's Lit Shader and ComplexLit Shader, NiloToon_Character.shader uses many #pragma multi_compile and #pragma shader_feature_local to support different use cases.

Shader memory usage and build time scale at approximately 2^n for n required on/off features. This means the number of shader variants grows exponentially as more features are enabled.

Mobile Builds

Reducing shader memory for mobile builds is critical. Otherwise, out-of-memory crashes may occur.

Step 0: Use Unity6.1 or Newer

In Unity6.1, NiloToon's Fog multi_compile is optimized into a non-shader-variant method, which significantly reduces memory usage and build time in exchange for slightly more fragment calculation.

Step 1: Build Stripping Information

Set Project Settings > Graphics > Shader Variant Log Level = Only SRP Shaders.

NiloToon's stripping system will log more information when building the player/Asset Bundle.

  1. After building, paste NiloToon_Character in the Console window's search bar
  2. Check the ForwardLit Pass's Total Variant Count
  3. Edit NiloToon Stripping settings
  4. Build again and check the ForwardLit Pass's Total Variant Count to confirm shader variant changes

Step 2: URP Best Practices

For all URP Assets included in Quality Settings:

  • Disable a feature in all URP Assets in your build - URP keeps only variants where the feature is disabled
  • Enable a feature in all URP Assets in your build - URP keeps only variants where the feature is enabled

Mixed Settings Warning

If a feature is ON in one URP Asset and OFF in another, URP will not strip that feature and will keep both ON+OFF variants in the build, approximately doubling build time and shader memory for each such feature!

Step 3: NiloToonShaderStrippingSettingSO

To lower multi_compile memory usage of the NiloToon_Character shader, use NiloToonShaderStrippingSettingSO.

  1. Right-click in the Project window and create a NiloToonShaderStrippingSettingSO
  2. Drag it to every NiloToonAllInOne Renderer Feature included in the build
  3. Disable features that are unnecessary for the build per platform - turning each keyword off reduces runtime shader memory usage and build time by approximately 40-50%

Verification Method

To verify it is working, disable every feature for your target platform first and build, then check the NiloToon_Character shader memory usage in Memory Profiler. When working correctly, you should see it drop to approximately 10-50MB. Then add back the keywords you actually need and build again.

Step 4: Remove XR Packages

If XR is not needed, remove XR-related packages from the Package Manager. This helps Unity strip XR-related keywords.

Step 5: Material Optimization

NiloToon's shader contains many shader_feature directives, but if you do not enable them in any materials included in the build, they will never increase shader memory, performance cost, or build time.

The more unique shader keyword combinations across all your materials, the more runtime memory is needed and the longer the build time.

When Using Addressables

After setting up NiloToonShaderStrippingSettingSO, if you do not see any change in the next Addressables build:

  1. Clean all previously built Addressables data
  2. Build again

Addressables will not consider a NiloToonShaderStrippingSettingSO edit as a content asset change, so the last build's cache remains "valid" and clicking build will do nothing.

Step 6: Lower Build CPU Usage

When building, Unity spawns one Unity Shader Compiler for each CPU thread to maximize shader compile speed. To maintain PC responsiveness while slowing down shader compilation, use the following Editor Command Line Argument:

-job-worker-count 8

In UnityHub, select the target project and click Add command line arguments to enter the argument.

Choosing an Appropriate Value

Start at 8 and increase by 4 until the build makes your PC too slow for other tasks (e.g., playing games). If the PC is a dedicated build machine, do not use this argument.