OccluderInstance3D in Godot – Complete Guide

Immersing yourself in the wondrous world of 3D game development can sometimes overwhelm even the most dedicated creators, especially when it comes to optimizing performance. This tutorial shines a spotlight on one of the essential performance enhancements in Godot 4 – the OccluderInstance3D class. By mastering this tool, you can significantly improve the performance of your 3D games, especially in enclosed spaces where clear visibility strategies can chop down unnecessary rendering overhead.

Imagine you’re creating a labyrinthine dungeon filled with winding passages and closed doors. Would it make sense for your game engine to render the fire-breathing dragon waiting in a chamber hissing behind a massive stone wall? In reality, the dragon shouldn’t factor into performance until the brave knight (aka the player) actually sees it. This is where occlusion culling comes in, and it’s a game-changer for both performance and immersion in 3D environments.

What is OccluderInstance3D?

OccluderInstance3D is a class in the Godot game engine that provides occlusion culling for 3D nodes. Occlusion culling is a technique that prevents the rendering of objects that are not visible to the camera because they are obscured by other objects. In simpler terms, it’s like covering your eyes; if you can’t see it, it’s not there. This greatly enhances performance by ensuring that the game only spends precious resources on the parts of the scene the player can actually see.

What is OccluderInstance3D Used For?

OccluderInstance3D is utilized to smartly manage which parts of a 3D environment need to be rendered based on player perspective and environmental geometry, resulting in better overall performance. In interior spaces, where rooms, hallways, and barriers naturally block off areas from view, occlusion culling can slash the workload on your GPU, ensuring a smooth player experience.

Why Should I Learn About OccluderInstance3D?

Understanding how to implement OccluderInstance3D in your Godot projects can empower your games to run more efficiently, especially as your scenes become more complex. Any developer, aspiring or experienced, who wants to optimize their 3D projects and create polished, performance-friendly games should learn about occlusion culling. Plus, learning how to use the tools Godot provides will help you become a more adept game developer with a keen understanding of how to bring your intricate game worlds to life without sacrificing performance.

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Setting Up Your Scene for Occlusion Culling

To start using OccluderInstance3D in your Godot project, you need to have a 3D scene where occlusion culling would be beneficial. Let’s say you have a simple 3D scene with a few rooms and hallways. You’ll want to add occluders in strategic places where they can effectively limit rendering to visible objects only.

var occluder = OccluderInstance3D.new()
var mesh = OccluderPolygon3D.new()
mesh.set_closed(true)
occluder.set_occluder(mesh)
add_child(occluder)

First, we create a new `OccluderInstance3D` instance. Then, we create an `OccluderPolygon3D` which represents the shape of the occluder. The line `mesh.set_closed(true)` tells Godot that this occluder forms a closed shape, essential for proper occlusion in enclosed areas.

Creating Occluder Polygons

Now that you’ve placed your occluders, you have to define their shapes. Occluder polygons define the occlusion area in 3D space, and they are what actually determine what gets rendered or not. For a hallway, you may want to create occluder polygons that cover the front of each entrance to the hallway.

var occluder_polygon = OccluderPolygon3D.new()
occluder_polygon.set_shape(Vector3Array([Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0), Vector3(0, 1, 0)]))
occluder_polygon.set_closed(true)
occluder.set_occluder(occluder_polygon)

In this snippet, we’re defining a rectangular occluder polygon. The `Vector3Array` holds the corners of a rectangle directly in front of where a doorway would be.

Adjusting Occluder to the Environment

Positioning and scaling the occluder appropriately is crucial for it to work effectively. Make sure the occluder polygons are placed in a way that they cover the areas where you want to prevent rendering.

occluder.global_transform = Transform(Basis(), Vector3(5, 0, 10))
occluder.scale = Vector3(3, 4, 1)

This sets the occluder’s position and scale in the world. In this example, we’ve positioned the occluder so that it’s on the wall at coordinates (5, 0, 10) and scaled it to be three times wider and four times taller, without affecting its depth.

Testing Occlusion in Your Scene

Once your occluders are in place, you’ll want to test how effective they are. You could add some print statements to your code that output the number of draw calls being made with and without the occlusion culling active.

print("Draw calls without occlusion:", Performance.get_monitor(Performance.RENDER_OBJECTS_IN_FRAME))

occluder.visible = false
# Wait one frame or update your scene accordingly

print("Draw calls with occlusion:", Performance.get_monitor(Performance.RENDER_OBJECTS_IN_FRAME))

By toggling the visibility of your occluders and checking the `RENDER_OBJECTS_IN_FRAME` performance monitor, you can get a rough idea of how much your occluders are affecting performance. This practical test gives you insight and allows you to adjust your occluders for optimal efficiency. Keep in mind that the exact impact will vary depending on the complexity and specific setup of your scene.

These are the foundational steps for incorporating OccluderInstance3D into your Godot projects. Remember to iterate and test continuously as you refine the positions and shapes of your occluders. There’s a balance to strike between performance gain and the time it takes to set up these occlusion polygons, so focus on areas where you expect the biggest performance boost. Stay tuned for the next part of this tutorial, where we’ll delve into more advanced uses and best practices for occlusion culling in Godot 4.As we refine our use of `OccluderInstance3D` in Godot, let’s delve into more complex scenarios. Consider a sizeable open-world scene where occlusion culling is not just beneficial but necessary for performance. Here, you may be dealing with natural terrain, buildings, and various other objects that could serve as natural occluders.

Handling Dynamic Occluders

In a dynamic environment with moving objects, static occlusion won’t suffice. Godot makes it possible to have occluders that adjust to the environment in real-time. Imagine a large roaming beast in your game that can block the line of sight to other entities. You could make it an occluder as it moves:

var moving_occluder = OccluderInstance3D.new()
# Apply the necessary shape and properties to the moving occluder here.

func _physics_process(delta):
    # Update the occluder position to match the position of the moving entity.
    moving_occluder.global_transform = $MovingEntity.global_transform

This script attaches the occluder to a moving entity. As the entity moves, the occluder’s transform is updated, making sure it accurately represents the occluded area at every frame. This dynamic adjustment is crucial in real-time scenarios where the game world is not static.

Complex Occluder Shapes

While simple shapes are faster to compute, sometimes complex scenes require more detailed occluders. You can define a more complex `OccluderPolygon3D` by providing additional points:

var complex_occluder = OccluderPolygon3D.new()
var complex_shape = Vector3Array([
    Vector3(0, 0, 0),
    Vector3(2, 0, 0),
    Vector3(2, 0, 2),
    Vector3(1, 1, 2),
    Vector3(0, 0, 2),
])
complex_occluder.set_shape(complex_shape)
complex_occluder.set_closed(true)
your_occluder_instance.set_occluder(complex_occluder)

In this case, we’ve outlined an L-shaped occluder, which could be used around a corner or any area where an L-shaped blockage is in place.

Using Multiple Occluders

Complicated scenes might require a series of occluders. You can manage multiple `OccluderInstance3D`s to optimize various parts of your scene:

foreach var position in occluder_positions:
    var occluder = OccluderInstance3D.new()
    var polygon = OccluderPolygon3D.new()
    # Configure your polygon properties here.
    occluder.set_occluder(polygon)
    
    occluder.global_transform = Transform(Basis(), position)
    add_child(occluder)

This loop creates an occluder for every position in an array, which is useful when setting up a series of similar occluders, like columns in a hallway or trees in a forest.

Visibility Notifiers and Occluders

Occluders work best when paired with visibility notifiers. `VisibilityNotifier3D` nodes can tell you when they’re actually visible. Combining this with occluders can provide a powerful system for managing the rendering workload.

var visibility_notifier = VisibilityNotifier3D.new()
visibility_notifier.set_aabb(AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2)))  # Set the bounding box to your liking
add_child(visibility_notifier)

visibility_notifier.connect("camera_entered", self, "_on_camera_entered")
visibility_notifier.connect("camera_exited", self, "_on_camera_exited")

func _on_camera_entered(camera):
    print("The object is now visible.")

func _on_camera_exited(camera):
    print("The object is no longer visible.")

By hooking up these callbacks to your visual nodes, you can manage not only when they are rendered based on occlusion but also carry out specific actions when they enter or exit the camera’s view. This can help to further optimize your game’s performance where necessary.

Profiling with Occlusion Culling

Finally, always profile your scene with Godot’s built-in profiler to observe the performance benefits of your occlusion culling setup.

# To start profiling
Performance.start_monitoring(Performance.PROFILER_FRAME_TIME)

# Do some gameplay here

var profiler_frames = Performance.get_monitor(Performance.PROFILER_FRAME_TIME)
print("Average frame time with occlusion culling:", profiler_frames.mean())
Performance.stop_monitoring(Performance.PROFILER_FRAME_TIME)

This measures the average frame time while occlusion culling is active. Assessing this data against the frame time without occlusion culling can give you a clear view of its impact on your game’s performance.

By following these examples and integrating `OccluderInstance3D` where it makes the most sense for your game, you stand to enhance performance significantly. Remember, though, that occlusion culling has an associated computational cost, so always look to strike a balance based on your scene’s complexity and the devices you’re targeting. It’s about finding the perfect middle-ground where your game looks great and runs smoothly.Integrating occlusion culling into your Godot 4 projects comes with several nuances. As you get deeper into the development, you’ll encounter situations where nuanced approaches can further optimize your game’s performance. Let’s explore some advanced code examples that may come in handy.

Combining Static and Dynamic Occluders

Some scenarios call for a combination of static and dynamic occluders. For example, imagine a scene with a revolving door (dynamic) and surrounding walls (static). Here is how you would set up a dynamic occluder for the revolving door while keeping static walls as occluders.

// Setup for static wall occluders
foreach var wall_position in wall_positions:
    var wall_occluder = OccluderInstance3D.new()
    var wall_polygon = OccluderPolygon3D.new()
    # Configure your wall polygon properties here.
    wall_occluder.set_occluder(wall_polygon)
    wall_occluder.global_transform = Transform(Basis(), wall_position)
    add_child(wall_occluder)

// Setup for the dynamic occluder of the revolving door
var door_occluder = OccluderInstance3D.new()
var door_polygon = OccluderPolygon3D.new()
# Configure your dynamic door polygon properties here.
door_occluder.set_occluder(door_polygon)
$RevolvingDoor.add_child(door_occluder)

func _physics_process(delta):
    door_occluder.global_transform = $RevolvingDoor.global_transform

While the wall occluders are set up once and remain the same throughout gameplay (since walls typically don’t move), the door occluder needs to be constantly updated to follow the door’s movement.

Adjusting Occluders at Runtime

Sometimes you need to modify occluders on the fly, such as opening a previously occluded passageway. Here’s how you might update an occluder when a door opens.

func open_door(door, door_occluder):
    door.occluder.visible = false  # Hide the occluder when the door is open.

func close_door(door, door_occluder):
    door.occluder.visible = true  # Show the occluder when the door is closed.

In these functions, the occluder’s visibility is toggled depending on whether the door is open or closed. This allows for the occlusion culling to respond to changes in the environment.

Optimizing Occluder Complexity Based on Distance

In some cases, the detail of an occluder can be reduced based on the camera’s distance from it. Less detailed occluders can be simpler to compute and may be sufficient when viewed from afar.

var camera_distance = camera.global_transform.origin.distance_to(occluder.global_transform.origin)
if camera_distance > some_threshold:
    occluder.set_occluder(simple_polygon)
else:
    occluder.set_occluder(complex_polygon)

Here you check the distance from the camera to the occluder and switch between a simple polygon and a complex one based on a predefined threshold. This can save resources when high detail is not necessary.

Level of Detail (LOD) and Occlusion

Level of Detail (LOD) techniques are often used in conjunction with occlusion culling to further improve performance. LOD involves decreasing the complexity of a model as it moves farther from the viewer. When an object is occluded, you might switch to an even lower LOD or skip rendering it entirely.

if object.is_visible_in_viewport() && !occluder_polygon.is_occluding(object.global_transform.origin):
    var distance_to_camera = camera.global_transform.origin.distance_to(object.global_transform.origin)
    object.set_lod(distance_to_camera)
else:
    object.hide()  # Or set lowest possible LOD

This code snippet uses a combination of visibility checks and the distance to the camera to determine the LOD to set for each object. If the object is occluded, the code either hides the object or sets the LOD to the lowest possible level.

As you can see, occlusion culling is not a one-size-fits-all solution. It requires careful planning and consideration of how it interacts with other optimization strategies in your game. Keep experimenting with the `OccluderInstance3D` node to fine-tune your game’s performance, and remember, the goal is always to strike the right balance between visual fidelity and a fluid gameplay experience.

Continue Your Game Development Journey

Congratulations on learning the ins and outs of `OccluderInstance3D` in Godot 4! Embarking on this path has hopefully opened up new avenues for optimizing your 3D games and enhancing player experience through smart rendering strategies. But this is only the beginning of your journey into game development with Godot.

To keep building your skills and dive deeper into the expansive world of game creation, we invite you to explore our Godot Game Development Mini-Degree. This comprehensive program is your gateway to mastering cross-platform game development using Godot 4, and it covers a variety of game genres – from platformers to RPGs. Whether you’re just starting out or looking to level up your existing knowledge, our courses can help guide you further down the path to becoming a confident game developer.

For an even broader exploration of what Godot has to offer, visit our collection of Godot courses at Zenva Academy. Take advantage of our extensive library to sharpen your expertise in areas that interest you most, and build a strong portfolio of real projects. Keep learning at your own pace, create engaging games, and seize the opportunities in the ever-growing games market. With Zenva, your next game development breakthrough is just a course away.

Conclusion

As you step back and survey your newfound knowledge of `OccluderInstance3D` in Godot 4, remember that each tool and technique you master edges you closer to bringing your most ambitious game visions to life. By embracing occlusion culling, you’re not just improving your game’s performance; you’re crafting experiences that captivate and run seamlessly on a variety of platforms. This isn’t only about creating games—it’s about infusing your projects with professionalism and technical prowess that sets them apart in the bustling interactive media landscape.

We at Zenva understand the dedication it takes to level up as a game developer, and we’re here to support you every step of the way. Harness the full potential of your creativity and our resources—whether you’re developing your next indie hit or honing your skills for a career in the industry. So, why not continue this journey with us? Take the leap and enrich your skillset with our Godot Game Development Mini-Degree. Trust us, your future games—and their many future fans—will thank you!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.