Unity Rendering & Performance
Optimization
Building Playable Games on Low-Spec Hardware
Written for Unity 2020.3 LTS, URP pipeline
Target hardware profile: Intel Core i3, integrated graphics, 8GB RAM
For Study Purposes Only
Table of Contents
● 1. Why Performance Optimization Matters on Low-Spec Hardware
● 2. Understanding the Render Pipeline (URP)
● 3. Lighting: Baked vs Realtime
● 4. Draw Calls and Batching
● 5. Level of Detail (LOD) and Occlusion Culling
● 6. Texture and Material Optimization
● 7. Fog and Distance-Based Culling Tricks
● 8. Script-Side Performance Pitfalls
● 9. Profiling Tools in Unity
● 10. Optimization Checklist and Practice Exercises
1. Why Performance Optimization Matters on Low-Spec
Hardware
On a machine with integrated graphics and a low-end CPU, every draw call, every dynamic light, and
every unnecessary script update directly competes for very limited resources. Unlike a dedicated
GPU setup, integrated graphics share memory bandwidth with the CPU, so inefficient rendering
choices hurt both frame rate and overall responsiveness.
The good news: most performance problems in small-to-mid scale indie horror games come from a
short list of repeat offenders — realtime lighting, excessive draw calls, and unbounded script logic
running every frame. Fixing these few categories usually solves the majority of frame rate issues,
even on weak hardware.
2. Understanding the Render Pipeline (URP)
The Universal Render Pipeline (URP) is designed to be lighter-weight and more configurable than
the older Built-in pipeline, making it a good fit for low-spec targets — but only if its settings are tuned
correctly, since URP defaults are often set for mid-range hardware.
• Render Scale: renders the frame at a fraction of native resolution, then upscales. Setting this
to 0.85 (or lower) significantly reduces pixel-shading workload with a relatively small visual
quality tradeoff.
• MSAA (anti-aliasing): smooths jagged edges but costs extra GPU work. On weak integrated
graphics, disabling MSAA or using a cheaper alternative (FXAA via post-processing) is usually
worth the performance gain.
• Shadow distance and resolution: reducing both lowers per-frame shadow rendering cost
substantially, especially in scenes with many objects.
Render Scale at 0.85 with shadow distance capped and MSAA disabled is a strong baseline starting point
for an i3 + integrated graphics target before any other optimization is applied.
3. Lighting: Baked vs Realtime
Lighting is one of the most expensive parts of rendering, and the single biggest lever for low-spec
performance. Unity offers two fundamentally different approaches:
Lighting Type Computed When Runtime Cost Flexibility
Baked (lightmaps) At edit-time / build-time Very low (just texture sampling) Static only — can't move lights
Realtime Every frame, every light High — scales with light + shadow count Fully dynamic
Mixed Combination of both Moderate Static base + limited dynamic lights
For static environments — like a forest level or a flooded sublevel that doesn't change — baked-only
lighting is the strongest choice for weak hardware: light and shadow information is precomputed
once and simply read from a lightmap texture at runtime, which is extremely cheap compared to
realtime shadow calculations.
Dynamic elements that genuinely need a moving light (like a player-held flashlight) should remain
realtime, but kept to as few simultaneous realtime lights as possible — ideally just one or two active at
a time.
4. Draw Calls and Batching
A draw call is an instruction sent from the CPU to the GPU telling it to render a specific set of
geometry. Each draw call has CPU overhead, and on a weak CPU, having hundreds of separate
draw calls per frame can bottleneck performance even if the GPU itself isn't under heavy load.
• Static batching: Unity automatically combines draw calls for static (non-moving) objects that
share the same material, as long as they're marked 'Static' in the Inspector.
• Dynamic batching: automatic batching for small moving objects sharing a material, subject to
stricter size/vertex limits.
• SRP Batcher (URP-specific): reduces the per-object CPU cost of switching between
materials, even when objects aren't batched into a single draw call.
Practical tip: reuse the same material across as many objects as possible (e.g., one rock material for
all rocks in a forest scene rather than unique materials per rock) — this directly increases how much
static batching can combine.
5. Level of Detail (LOD) and Occlusion Culling
Not every object needs to be rendered at full detail all the time. Two complementary systems reduce
wasted rendering work:
5.1 LOD (Level of Detail)
An LOD Group component swaps a model for a simpler, lower-poly version as the camera moves
farther away, since fine detail is imperceptible at distance anyway. This reduces vertex/triangle
processing cost for distant objects without any visible quality loss to the player.
5.2 Occlusion Culling
Occlusion culling prevents the renderer from processing objects that are completely hidden behind
other objects (e.g., a room the player hasn't entered, blocked by a wall). Unity's built-in Occlusion
Culling system (Window > Rendering > Occlusion Culling) precomputes visibility data for static
geometry, which is especially valuable in indoor or maze-like horror game levels.
Frustum culling (skipping anything outside the camera's view) happens automatically in Unity, but
occlusion culling — skipping things inside the view that are simply blocked from sight — must be explicitly
baked and is not automatic.
6. Texture and Material Optimization
On hardware with limited video memory (shared with system RAM on integrated graphics), texture
size and count matter just as much as polygon count.
• Texture compression: use compressed formats (e.g., DXT/BC formats on PC) instead of
uncompressed textures — drastically reduces memory footprint with minimal visible quality
loss.
• Texture resolution: a 4K texture on a small prop is almost always wasted detail; cap
resolution based on how large the object appears on screen, not how 'nice' the source texture
is.
• Texture atlasing: combining multiple small textures into one larger texture sheet allows more
objects to share a single material, supporting better batching.
• Mipmaps: enable mipmaps so distant objects sample a smaller pre-shrunk version of the
texture, improving both performance and reducing visual shimmer/aliasing at a distance.
7. Fog and Distance-Based Culling Tricks
Fog is not just an atmospheric effect for horror games — it's also a practical performance tool. By
setting the camera's far clip plane to roughly match where fog becomes fully opaque, objects beyond
that distance can be entirely excluded from rendering without the player noticing, since they'd be
invisible in the fog anyway.
This combination — dense fog + a matching reduced draw/far-clip distance — is one of the most effective
performance tricks available for a horror game on weak hardware, because it directly cuts down the
number of objects the renderer even attempts to process, while simultaneously reinforcing the horror
atmosphere of limited visibility.
Practical setup: set Camera Far Clip Plane to a value just beyond where Linear or Exponential fog
reaches full density (Window > Rendering > Lighting > Environment > Fog), so there's no visible
'pop-in' of objects suddenly appearing as fog density changes slightly with movement.
8. Script-Side Performance Pitfalls
Rendering isn't the only bottleneck — inefficient script logic running every frame can hurt the CPU just
as much, especially with weaker single-thread performance.
Pitfall Why It's Costly Fix
GetComponent() in Update() Searches component list every frame Cache the reference once in Awake()/
Instantiate/Destroy frequently Memory allocation + garbage collection spikes Use object pooling for frequently spaw
Find()/FindObjectOfType() repeatedly Scene-wide search, very slow at scale Cache references or use direct serializ
Heavy logic in Update() for all objects Cost scales linearly with object count Stagger checks via timers; only update
Garbage collection (GC) spikes are a frequent cause of sudden, brief stutters rather than a
consistently low frame rate. Reducing per-frame allocations (e.g., avoiding 'new' inside Update(),
reusing lists/arrays) helps smooth out these spikes considerably.
9. Profiling Tools in Unity
Optimization without measurement is guesswork. Unity provides built-in tools to identify exactly where
time is being spent each frame:
• Profiler (Window > Analysis > Profiler): shows CPU, GPU, memory, and rendering
breakdowns frame-by-frame — the primary tool for finding bottlenecks.
• Frame Debugger (Window > Analysis > Frame Debugger): steps through individual draw
calls within a single frame, useful for understanding exactly what's being rendered and why
batching isn't occurring as expected.
• Stats overlay (Game view 'Stats' button): a quick at-a-glance view of FPS, draw calls,
triangles, and batches without opening the full Profiler.
A practical workflow: build a development build (not just Play Mode in the editor, which has its own
overhead) and profile that, since editor performance doesn't always reflect what an actual player will
experience on the target hardware.
10. Optimization Checklist and Practice Exercises
• ■ Render Scale set to 0.85 or lower in URP asset settings.
• ■ Lighting set to baked-only wherever the scene is static.
• ■ Shared materials used wherever possible to support batching.
• ■ LOD Groups added to high-poly models visible at a distance.
• ■ Occlusion culling baked for indoor/enclosed levels.
• ■ Textures compressed and capped to a reasonable resolution.
• ■ Fog distance roughly matched to camera far clip plane.
• ■ No GetComponent()/Find() calls inside Update() loops.
Practice Exercises
• Open the Unity Profiler on one of your existing scenes and identify the single most expensive
item in the CPU and Rendering breakdowns.
• Convert one dynamic light in your scene to baked lighting (where appropriate) and compare
frame rate before and after using the Stats overlay.
• Add an LOD Group to a high-poly prop and verify the simplified mesh swaps in correctly at
distance.
• Identify any GetComponent() or Find() calls currently inside an Update() method in your
project, and refactor them to cache the reference instead.
• Tune fog density and camera far clip plane together so distant geometry pop-in becomes
unnoticeable.
End of Notes — Unity Rendering & Performance Optimization. Apply the checklist to one of your active
projects and measure frame rate before/after each change individually, rather than changing everything at
once — this makes it clear which optimization actually mattered.