What Is Occlusion Culling and Why Does It Improve FPS?
Imagine standing in front of a massive castle wall in an open-world RPG. You only see the stone wall. However, behind it sits a bustling marketplace full of stalls, props, and characters. Without optimization, the engine still processes and sends all that hidden geometry to the GPU. This wasted effort creates rendering overhead, leading to low frame rates on both mobile devices and PCs.
This is where Occlusion Culling comes in. It is a rendering optimization technique that identifies which objects are completely hidden behind closer, opaque structures (like walls, hills, or buildings) and prevents the engine from drawing them. By skipping these hidden meshes, you reduce the workload on both the CPU and the GPU, yielding significant FPS improvements.
1. Understanding the Rendering Pipeline: Frustum vs. Occlusion Culling
Camera rendering relies on two visibility tests. The camera sees through a pyramid-like field of view called the camera frustum. Frustum Culling checks if an object is inside this shape; if it is behind the camera or off-screen, the engine discards it. This check is automatic and extremely cheap on the CPU.
However, frustum culling does not check if objects block each other. If you stand in a dense forest, frustum culling attempts to render every tree in your line of sight, even if they are stacked deep behind the first row. Occlusion Culling solves this by analyzing depth to discard meshes hidden behind closer objects.
| Optimization Metric | Frustum Culling | Occlusion Culling |
|---|---|---|
| Scope of Check | Is the object inside the camera's viewing field? | Is the object blocked from view by closer geometry? |
| Engine Setup | Automatic (no developer setup) | Manual configuration (requires baking or setting volumes) |
| CPU Cost | Extremely low (fast bounding box checks) | Moderate (evaluates depth buffers or grid cells) |
| GPU Savings | High (stops drawing off-screen models) | Extreme (stops drawing overlapping geometry) |
| Ideal Environment | All scenes (open worlds, space) | Dense scenes (corridors, cities, forests, interiors) |
2. How Occlusion Culling Works
Occlusion culling categorizes objects into two roles:
- Occluders: Solid, opaque blocking structures (terrain, walls, buildings).
- Occludees: Props that can be hidden behind occluders (furniture, debris, loot chests). Some large objects can act as both.
Frustum culling checks if both are in front of you. If they are, it tells the engine to draw the apple and then paint the cardboard over it. Occlusion culling checks depth first, realizes the apple is blocked, and discards it before drawing begins.
3. Types of Occlusion Culling
Engines use three primary strategies to evaluate visibility:
Hardware Occlusion Queries
The CPU queries the GPU: "If I render this bounding box, will any pixels pass the depth test?" If zero pixels pass, the object is occluded.
Pros: Dynamic and highly accurate; no pre-computation needed.
Cons: Waiting for the GPU to return results causes latency (CPU-GPU sync stalls), which can hurt frame rates on older hardware.
Software Occlusion Culling
To avoid GPU sync stalls, the CPU rasterizes a low-resolution depth buffer containing only the largest occluders, and checks bounding boxes against it.
Pros: Bypasses GPU query overhead; ideal for mobile platforms where GPU queries are slow.
Cons: Uses CPU threads that could otherwise handle physics or AI.
Precomputed / Baked Occlusion Culling
The engine divides the level into 3D cells and pre-calculates visibility data offline. At runtime, the engine checks which cell the camera is in and loads its visibility list.
Pros: Extremely cheap runtime CPU check (simple array lookup).
Cons: Only works for static objects; increases game build sizes and memory footprint.
4. Why It Improves FPS: The Math of Rendering Savings
Pairing occlusion culling with standard rendering reduces bottlenecks across the entire pipeline:
- Draw Calls (CPU): If a scene has 1,200 props and a wall blocks 1,000 of them, the CPU only prepares 200 draw calls instead of 1,200, saving valuable frame time.
- Vertex Processing (GPU): Drawing only visible props cuts the vertex shader workload from millions of polygons to a fraction, saving GPU memory bandwidth.
- Overdraw (GPU): Skipping hidden objects avoids rendering overlapping layers of pixels, reducing fill-rate limitations, particularly on mobile chipsets.
| Pipeline Phase | Without Culling | With Culling | Performance Gain Type |
|---|---|---|---|
| CPU Main Thread | Processes visibility for all 1,200 meshes | Processes visibility for 200 meshes | Saves CPU time for game logic and scripts |
| Graphics API | Submits 1,200 draw calls to driver | Submits 200 draw calls to driver | Saves driver translation time (prevents stalls) |
| GPU Vertex Stage | Computes math for 2.4M polygons | Computes math for 400K polygons | Saves vertex shading and pipeline capacity |
| GPU Pixel Stage | High overdraw and depth sorting waste | Low overdraw (renders visible surfaces) | Reduces VRAM bandwidth and thermals |
5. Setting Up Occlusion Culling in Unity
Unity uses Umbra for baked occlusion culling. To configure it:
- Select static blocking meshes in the hierarchy. In the Inspector static dropdown, check **Occluder Static** and **Occludee Static**. For smaller props, check **Occludee Static** only.
- Open the panel: Window > Rendering > Occlusion Culling.
- In the **Bake** tab, adjust parameters:
- Smallest Occluder: Min size of an object that can block others. Smaller values increase bake file size.
- Smallest Hole: Min gap the camera can see through.
- Click **Bake**.
- Switch the Scene View dropdown to **Occlusion Culling** and select your Main Camera to visualize objects disappearing as they are blocked.
6. Setting Up Occlusion Culling in Unreal Engine
Unreal Engine uses dynamic hardware occlusion queries by default. To optimize visibility checks:
Precomputed Visibility Volume
For mobile and low-spec hardware, bake visibility cells:
- Place a **Precomputed Visibility Volume** covering the player's movement area.
- Ensure **Precomputed Visibility** is checked in World Settings.
- Click **Build Lighting** to bake cells.
Verification Console Commands
Open the in-editor console (tilde key `~`) to profile culling:
r.VisualiseOccludedPrimitives 1: Outlines occluded meshes in green.stat InitViews: Displays counters, including **Occluded Primitives**, showing how many actors are successfully culled.
7. Common Pitfalls and Performance Costs
Be aware of these potential performance bottlenecks and setup issues:
- CPU Baking Overload: Setting "Smallest Occluder" too low generates excessive cells, making visibility lookups cost more CPU time than drawing the meshes.
- Baking Size Bloat: Large worlds baked with detailed culling parameters can add hundreds of megabytes of data to your build size.
- Pop-in Artifacts: Hardware occlusion delays can cause hidden meshes to visibly snap into view when you turn corners quickly. Use larger occluders to mask transitions.
- Dynamic Environments: Baked occlusion is static. An open door marked "Occluder Static" will continue to cull the room behind it, creating empty rendering voids.
8. Best Practices for Game Developers
Follow these rules to maximize rendering optimization:
- Design Winding Paths: Create levels with corners, curves, or large walls to maximize occlusion opportunities. Long, straight corridors offer no culling benefits.
- Mark Sparingly: Only mark large, structural meshes (walls, hills, houses) as occluders. Small objects (cups, rocks) should be occludees only.
- Pair with LODs: Occlusion culling hides close objects behind walls, while LODs optimize visible objects in the distance. Use both together.
Comments
Post a Comment
Feedback