Provide a way to check if some rendered content is culled

As a Roblox developer, it is near impossible to tell if some rendered content has been culled. It’s possible to do, but it’s very slow, hard to test, and hard to perfect.

What is rendered content?
  • Texture
  • BasePart
  • GuiObject
  • LayerCollector

Culling exists to stop the rendering of content the user cannot even see. This is a revolutionary set of improvements for performance! However, your code still wastes time updating the content’s appearance or acting on it every frame, even when it is not visible. What a waste!

In the following example, each part is doing a lot of expensive work. The engine automatically stops rendering the parts you cannot see, but they are still doing redundant working in the background… wasting CPU time.

The game stays at 30 FPS or worse no matter how many parts are on screen.

Now in the following video, the parts only work when they are on screen.

The FPS starts at 30 and rises to 100! Keep in mind I added artificial lag for each part to mock a lot of work being done. Changing color and orientation isn’t very expensive. If you had more complicated rendering needs, it would have a similar impact.

It’s not getting the maximum FPS because frustum culling in Luau is expensive. The internal calculations have already been done… so exposing some sort of property, method, or event to tell if the part has been culled wouldn’t be expensive at all. Keep in mind that you’ll also be benefiting from Occlusion and Distance culling.

Bonus

Yes, the parts are actually not working when they aren’t on screen. No fakery here, other than the artificial work for each part.

Here’s the place file if you want to experiment or view the code! You can use anything here with no attribution and for free. If I make changes, I’ll add a new file instead of replacing this one.

CullingProblem.rbxl (70.9 KB)

3/23/2025

In conclusion, having a way to tell if some rendered content has been culled would help developers optimize their games.

If you update something every frame that the user may not see all of the time, you need this!

:heart:

15 Likes

They should add this to their roadmap fr

1 Like

Currently overhauling ParallaxWindow, which uses similar technology that’s in the game portals for THE HUNT, THE CLASSIC, THE HAUNT, THE GAMES, and Winter Spotlight. Calculating the parallax for all of those frames gets expensive, and this feature would help optimize that relatively easily. Using your own frustum culling solution is slow!

This would be amazing, though it should definitely be locked behind a function call because we don’t want tons of signals being fired. Or they could just make the property not fire a changed signal… honestly either way is fine.
I would also hope for thread safety.

Not to nitpick all the work you did to setup these examples, but what is suppose to block the view of those objects in your example? They all seem to be out in the open? I figured anything happening to the objects would still process, just not render. Never actually tried to experiment on that part myself as I assumed it worked like you show in your examples.

[edit]
Did my own experiment via your example file. I turned off your own cull detection so I could get the brute of the example. I did notice that even when culled by the game engine, it’s still showing those surfacegui.

So, I set a short range limit and that did help a little bit.

In this experiment, I had it remove the surfacegui from the part. So compared to my first test, just removing the surface gui alone gives a +30% frame rate boost. Not that this invalidates your point, but it does show that surfacegui are basically ignored by culling, which I did not know before. :thinking:

1 Like

Nothing! They are only there to appear and disappear from view. They rotate and have SurfaceGuis showing the faces because that made it easier to test my frustum culling module.

1 Like

What prevents you from using Camera:WorldToScreenPoint()? It performs really well as an alternative.

but yes I agree that roblox should give us a better way because resorting to hacky ways for basic things like these is getting really annoying

That is inaccurate at many camera angles facing away from the part. You need to frustum cull, and it becomes very slow. I’m still trying to optimize my solution.

I didn’t read through all of the code to mark when an object was culled, but I do like the idea and utility it has. Is your current code trying to be a copy of how Roblox does it or can you take some shortcuts to figure out if an object is being culled and not worry about perfect accuracy? Objects not in the camera field of view seemed to be a good shortcut, but then objects that are actually in the camera view, how accurate are you trying to emulate? For example, complex object shapes versus simple logic like “well, the whole thing is covered by this degree of margin, marked it as obviously culled”, etc? Very good work what you have so far. My old system was radius based, but then I had to change it because the Roblox culling has no effect on objects with the humanoid attached to it, but a more thorough system would be a big benefit for everyone. :+1:

1 Like

I want it it to be accurate, but with some forgiveness to not overcomplicate it. If it at least doesn’t incorrectly cull and culls at most other camera angles, it’s good for me. That’s why I’m rewriting it already, so it uses cone frustum a instead of rectangular ones. Less calculations, roughly the same result!

1 Like

I don’t plan on even attempting occlusion culling, if that’s what you mean here. It’s impossible!

2 Likes

Hello, I finished my optimized culling module! I’m using cone intersection and simplified math to get roughly the same result.

We still need this feature because it will be blazingly fast to just read a single property compared to anything we can write in Luau.

Keep in mind that this solution only frustum culls one face of your choosing, instead of culling the entire part. That’s because I’m transitioning it to ParallaxWindow.

CullingProblem.rbxl (81.6 KB)

3/27/2025
You may use all of the content in this file for your projects! No attribution needed.

There’s no artificial lag in this video. Changing the color is lagging the game, so culling has helped to optimize. Culling is still slow, so you don’t get the max FPS of 240. For a real use case, there wouldn’t be hundreds of surfaces your checking.

This file also includes a way to debug. Press F2 to view the rays and the cones. Debugging works better when width = 1 inside of Parts.Script, which is the code that spawns all of the tests.

2 Likes