← Back to Blog 3d-modeling

Occlusion Culling for Game Assets: Unity, Unreal Engine 5, and Godot 4 Complete Guide

By BitSoul Team6/22/2026Updated 8/1/20268 min read44 views
Occlusion Culling for Game Assets: Unity, Unreal Engine 5, and Godot 4 Complete Guide

Every game developer hits the same wall: your scene looks great in a small test level, then frame rate collapses the moment you fill it with props, buildings, and foliage. The culprit is almost always overdraw — your GPU is rendering geometry the player can never see. Occlusion culling is the solution, and getting it right can drop your draw call count by 60–80% on dense scenes.

This guide covers the full setup for Unity URP, Unreal Engine 5, and Godot 4, including how to prepare your assets to make culling actually work.

What Occlusion Culling Does (and What It Doesn't)

Occlusion culling prevents the CPU from submitting draw calls for objects blocked from the camera by other solid geometry. It is entirely a CPU-side optimization — objects are excluded before they ever reach the GPU's rasterizer. This is distinct from backface culling (which the GPU handles per-triangle) and frustum culling (which removes objects outside the camera's view frustum, and runs automatically in all three engines).

The key constraint: occlusion culling requires occluders — solid, opaque geometry that reliably blocks large screen areas. A glass wall, alpha-masked foliage mesh, or thin fence cannot occlude because the camera can see through it. Effective occluders are thick walls, terrain, large building interiors, and solid prop volumes. You often add simplified occluder proxy meshes specifically for culling — invisible at runtime, but present in the culling system.

What culling does not fix: shader complexity, overdraw from transparent materials, or expensive post-processing. Profile first with a GPU profiler (RenderDoc, Unreal Insights, or Unity's GPU profiler) to confirm draw call count is your actual bottleneck before spending time on culling setup.

Profiling Before You Cull

Profiling Before You Cull — illustrated

Before configuring any culling system, establish a baseline. You need two numbers: total draw calls per frame and GPU frame time. If GPU frame time is high but draw calls are low, culling won't help — you have a shader or texture bandwidth problem.

Unity URP: Open the Frame Debugger (Window → Analysis → Frame Debugger) and count RenderForwardOpaque events. Enable the Stats overlay in the Game view to read draw calls live. The SRP Batcher reduces CPU overhead per draw call but doesn't eliminate them — high draw call counts still matter.

Unreal Engine 5: Run `stat SceneRendering` in the console. Watch `Visible Static Mesh Elements` — this is your pre-culling draw count. After enabling Hierarchical Z-Buffer (HZB) occlusion, you want this number to drop significantly when the camera faces occluded areas. Also check `stat GPU` for per-pass breakdown.

Godot 4: Use the built-in profiler (Debugger → Profiler) and enable Visible/Shadow Casters in the renderer stats. The `rendering/draw_calls` metric in the debugger overlay (`p` key in debug builds) gives you a live read.

Target draw call budgets vary by platform: mobile targets under 200 per frame, PC games typically stay under 2000, and consoles sit between 500–1500 depending on complexity. If you're over budget, proceed with culling setup.

```
# Unity console command to log draw calls each frame
Application.targetFrameRate = 60;
Debug.Log("Draw calls: " + UnityStats.drawCalls);

# Unreal console commands for culling diagnostics
r.HZBOcclusion 1 # Enable HZB occlusion (default on)
r.VisualizeOccludedPrimitives 1 # Show what's being culled (green = culled)
```

| Metric | Mobile Budget | PC Budget | Console Budget |
|--------|--------------|-----------|----------------|
| Draw calls | < 200 | < 2000 | < 1500 |
| Visible triangles | < 500K | < 2M | < 3M |
| GPU frame time | < 16ms | < 8ms | < 11ms |
| Texture memory | < 256MB | < 2GB | < 512MB |

Setting Up Occlusion Culling in Unity URP

Unity uses a baked occlusion culling system via the Occlusion Culling window (Window → Rendering → Occlusion Culling). This requires a bake step — it builds a PVS (Potentially Visible Set) data structure that the runtime queries each frame.

Marking objects: Select static geometry and enable the `Occluder Static` and `Occludee Static` flags in the Inspector's Static drop-down. Large solid meshes (walls, floors, terrain) should be Occluder Static. Everything that can be hidden should be Occludee Static. Dynamic objects (enemies, vehicles, NPCs) cannot participate in baked culling — use `OcclusionCulling.GetVisibility()` at runtime for dynamic occludees, or rely on Unity's built-in frustum culling for moving objects.

Bake settings: In the Occlusion Culling window, set Smallest Occluder to roughly the smallest wall thickness you want to treat as a solid blocker (default 5 units is often too large for indoor scenes — try 1–2). Smallest Hole controls how small a gap must be before it's treated as solid. Bake time scales with scene complexity; a dense urban level can take 10–30 minutes.

Occlusion portals: Add OcclusionPortal components to doorways, windows, and tunnel entrances. When closed, they act as solid occluders. When open, they allow visibility through. This is essential for building interiors — without portals, Unity may cull interior rooms even when the player is standing in a doorway.

After baking, use the Visualization mode in the Occlusion Culling window to scrub the camera through your scene and verify that distant rooms and objects are culled. Watch for holes where objects remain visible through thick walls — usually caused by Smallest Occluder being set too large.

Occlusion Culling in Unreal Engine 5

Unreal 5 uses Hierarchical Z-Buffer (HZB) occlusion by default — no bake step required. HZB works by rendering a low-resolution depth buffer each frame, then testing object bounding boxes against it to determine visibility. This is a dynamic, GPU-assisted approach that handles moving occluders naturally.

Occluder geometry: Mark static meshes as occluders by enabling `Affect Distance Field Ambient Occlusion` and confirming the mesh has `Cast Shadow` enabled (occluders must cast shadows to participate in HZB). For large solid props, also enable `Use as Occluder` in the mesh's LOD settings — this generates a simplified occluder mesh used in the depth prepass.

Nanite and occlusion: Nanite-enabled meshes are handled differently. Nanite performs its own GPU-driven visibility determination at the cluster level, so individual Nanite meshes don't go through the traditional HZB occluder path the same way. Non-Nanite meshes still benefit fully from HZB. Don't disable HZB thinking Nanite makes it redundant — non-Nanite assets (characters, some props, transparent objects) still rely on it.

Cull Distance Volumes: Add a Cull Distance Volume actor around areas where small props should disappear beyond a set range. This complements HZB by removing tiny objects (pebbles, debris, small foliage clumps) before they even reach the occlusion query step. Set cull distances conservatively — too aggressive and players will see objects pop in.

```ini
# Unreal Engine scalability config (Engine.ini) for aggressive culling
[SystemSettings]
r.HZBOcclusion=1
r.OcclusionQueryLocation=1
r.MinScreenRadiusForLights=0.03
r.MinScreenRadiusForDepthPrepass=0.03
```

Godot 4 Occlusion Culling

Godot 4 Occlusion Culling — illustrated

Godot 4 uses a portal-based occlusion system activated by adding `OccluderInstance3D` nodes to your scene. Unlike Unity's baked PVS, Godot's occluder system is configured per-mesh at edit time and queries dynamically at runtime.

OccluderInstance3D setup: Select your solid geometry (walls, floors, large props) and add an OccluderInstance3D as a child. Choose an OccluderShape — typically `ArrayOccluder3D` for custom shapes baked from the mesh, or `QuadOccluder3D` for flat surfaces. For complex meshes, use the "Bake Occluders" button in the editor toolbar (3D → Bake Occluders) to auto-generate simplified occluder shapes from marked MeshInstance3D nodes.

Portal and room system: For indoor scenes, Godot 4 no longer uses the explicit Room/Portal system from Godot 3 — it was removed. Instead, rely on OccluderInstance3D nodes placed on interior walls and use VisibilityNotifier3D on objects that should trigger culling callbacks. Combine this with the rendering LOD system (`GeometryInstance3D.visibility_range_begin` / `visibility_range_end`) to fade out small props by distance.

Baking workflow: Add OccluderInstance3D nodes to meshes you want to act as occluders. Set the occluder shape type to `ArrayOccluder3D`. Use 3D → Bake Occluders from the editor menu — Godot will generate simplified geometry from all marked MeshInstance3D nodes with `gi_mode` set to Static. Lower the `Occluder Polygon` simplification setting for faster runtime queries at the cost of precision.

```gdscript
# GDScript: check if a node is currently visible (culled or not)
func _process(delta):
var viewport = get_viewport()
# Use VisibleOnScreenNotifier3D for per-object visibility events
if $MyProp.visible:
# Object passed culling and frustum test
pass
```

Asset Preparation for Effective Culling

Culling systems are only as good as the occluder geometry you give them. Assets purchased or downloaded from https://bitsoulhosting.com/marketplace often come as detailed game-ready meshes — you'll need to decide which ones act as occluders and may need to add simplified proxy meshes.

Occluder proxies: For complex architectural props (ornate columns, detailed facades), create a simplified box or convex hull proxy in Blender and name it with `_occluder` suffix. In Unity, assign it as a custom occluder mesh. In Unreal, this proxy drives the depth prepass. The proxy is never rendered — only queried for visibility.

Minimum thickness rule: An occluder must be thick enough that a camera ray cannot pass through it at the resolution of your occlusion query. For Unity's baked system, this is controlled by Smallest Occluder. For Unreal HZB, the mesh's world-space bounding box depth is the effective measure. Thin walls (under 0.2 world units) often fail to occlude reliably — add interior geometry or thicken them.

LOD coordination: Occlusion culling and LODs are complementary. Objects at LOD2 or LOD3 (very low poly) have smaller bounding boxes, which means they're harder to occlude precisely. Ensure your LOD transitions happen *before* objects get small enough that bounding-box occlusion breaks down. A good rule: LOD cull distance should be shorter than your occlusion query effective range for that object size.

| Asset Type | Occluder? | Occludee? | Notes |
|------------|-----------|-----------|-------|
| Exterior walls (thick) | Yes | Yes | Primary occluder source |
| Terrain mesh | Yes | No | Mark as Occluder Static only |
| Interior props | No | Yes | Too thin/complex to occlude |
| Foliage (alpha) | No | Yes | Transparent — can't occlude |
| Vehicles (moving) | No | Runtime only | Dynamic; baked culling doesn't apply |
| Skybox / sky dome | No | No | Always visible |

Download optimized 3D assets with clean geometry and pre-built LODs from https://bitsoulhosting.com/marketplace to reduce the manual cleanup required before culling setup.

Closing: Measure the Gain

After setting up occlusion culling, re-profile with the same tools you used at baseline. In a well-configured urban or interior scene you should see a 40–70% reduction in draw calls when the camera faces dense geometry. If the reduction is under 20%, check that your occluder meshes are thick enough and that Occluder/Occludee Static flags are applied correctly.

Culling is not a one-time configuration — re-bake (Unity) or re-validate (Godot) after major level changes. In Unreal, monitor `stat SceneRendering` after adding large new asset groups.

For game-ready 3D assets with optimized topology, proper LODs, and clean geometry that works well with all three engines' culling systems, browse the collection at https://bitsoulhosting.com/marketplace.

Tags: occlusion culling game optimization Unity Unreal Engine 5 Godot 4 GPU performance 3D game assets draw calls

Skip the modelling — download it instead

A free BitSoul account gets you 2 game-ready models every month plus 25 AI Engine credits to generate one of your own, no card required. Clean topology, PBR textures, and GLB downloads that drop straight into Unreal, Unity, Godot or Blender — plus OBJ and 3D-printable STL export.

Create a free account → Browse 846 models