CullingService - Custom Client Sided Culling/Streaming Module

Hi there and happy middle of the summer!

CullingService (CS) is a tool that I developed for my current project. I realized that there wasn’t anything like it on the DevForum and decided to open-source it for large scale use. Feel free to use it in your games or as a learning resource.

Check out the GitHub page to view the source code (in Rojo format): GitHub Repository
Download the model here: Roblox Library Model
Download the plugin here: CullingService Plugin

Example place (open sourced) with CullingService in action: Example Place

Read below for set-up information


What is Culling and Why Should I Care?

Culling, by definition, is a way of controlling rendering and typically handled by the engine. This is a necessity in games that have large maps, high detail, or a combination of both. If you look closely while playing many open-world games (Skyrim, Assassin’s Creed, etc.), you might notice that things at the edge of your screen load in or out depending on your distance). This is to reduce the performance costs of large and detailed maps. Conversely, you can flip the definition and reason that using culling also allows you to increase the detail of your map, and this is true too.

Not making sense? Think of it as a custom StreamingEnabled.

How Does it Work?

Using CS, you (the developer) assign anchor points to models that you want to be culled in and out. CS subdivides the map into cube regions (the size of these are controllable). As the player enters a region, CS checks to see if anchor parts in the regions adjacent to the one the player is currently in are within a distance to be culled. If they are, the model is culled in. Doing this increases performance by reducing the amount of distance calculations performed.

Here are some pictures to visualize what’s going

Map is divided into regions

Adjacent regions are checked (they do go away after you leave them, I just forgot to add that to the visualization)

Based on your region and distance, instances are culled in and out accordingly (modify your settings to make it look less choppy - the current settings are in place to clearly demonstrate the long, medium, and short ranges which can be culled)

CullingService now supports moving parts - read more about this in Advantages + Limitations)

More Information on…

Advantages + Why Not Use StreamingEnabled?

Great question - why not use a tool built into the engine? CS offers more control than StreamingEnabled and allows you to dodge problems that have plagued StreamingEnabled. Here are a couple advantages/issues solved:

  • Create specific render distances for various types of instances. For example: in a forest, cull in trees at a further distance (because they are big) and only cull in small details, like flowers, when the player comes to a much shorter distance.
  • Customize everything. Customize culling distances (as mentioned above) and other settings. Because these are customizable, this allows you to provide players options, such as a render setting of “low, medium, high, ultra high, etc.” if you wish. StreamingEnabled does not allow it or related properties to be modified via code, whereas CS allows you to make immediate changes.
  • If you use duplicate models (such as trees), you’re in luck! Once you set up the distance folders for one model, all of them will be set up. Only one model is stored, which enhances performance and expedites set-up.
  • Only cull what you want to cull. If you have certain parts that absolutely should not be culled, CS allows you to simply, easily, and quickly exclude them from being affected. This tackles the problem of all parts being uniformly streamed in and out by StreamingEnabled, which can be a massive issue for programmers.
  • Control CS via plugin to set things up. This means less work for the individual client and more work for you! Kidding, the plugin is very easy to use and makes set-up as simple as selecting the models in the Explorer menu that you want to cull and hitting a button
  • Cull out parts of the map (or the entire map) as you develop, reducing the overhead costs - especially for lower-end PCs. Cull it back in as desired.
  • Quickly integrate existing games with this. Anchor points can be automatically created through the plugin - just select your models and click a button.
  • Simple. Call CullingService.Initialize(). Afterwards, you can always pause or resume CS using CullingService:Pause() or CullingService:Resume()
  • New: CullingService allows support for moving models. Rather than move the entire model, all you need to do is move the associated anchor point (this makes CullingService minimally intrusive). CullingService features an optional built-in welding to support this (however, this only happens for moving parts)

Limitations

  • Only models can be culled
  • Models must have (kinda) unique names. Models with the same name will be presumed to be the same model. Therefore, duplicate models that are the same in every aspect (minus position and orientation which will automatically be handled once they are assigned an anchor point) can (and should) have the same name, whereas models that are different should be assigned a different name. Basically, don’t name all of your models “Model”
  • Models which are being culled will have their PrimaryParts overwritten (to utilize movement controls)
  • Only what you specify to be culled in your game design (i.e. Studio) will be culled. This can be problematic for things created mid-game. CS can be thought of as ‘semi-automatic’. At this stage, it can’t do it all.
  • This has all purpose-use; however, it does not have an extreme control (used loosely, there are still lots of settings to be modified) set that allows it to be useful in every game. It may be more useful, in certain situations, to use StreamingEnabled or to fork this.
  • Moving parts must have the same render distance (i.e. put them all in either Long, Medium, or Short) and only one distance folder in the model. Having multiple may break it.

How To Use It

  1. Install the plugin (linked above) and open Studio
  2. Create the following folders as children of workspace: AnchorPoints, CulledObjects, CullingRegions
  3. Create the following folders as children of ReplicatedStorage: ModelStorage, NonCulledObjects
  4. Install the CullingService model via Roblox (or import directly from GitHub)
  5. If installing from Roblox, put UtilityModules and CullingService in ReplicatedStorage
  6. Create a LocalScript on the client, require CullingService, and call CullingService.Initialize()
  7. Open the plugin and hit the initialize button (this will check that all folders in step 2 are created)
  8. Create folders in your model with the names: Short, Medium, Long (these will decide which parts are culled in and out at which ranges). Place the parts within these relevant folders to control which culling range they fall into.
  9. Select (i.e. click on/highlight in your explorer menu) the model (or all models which have been set up) and generate anchor points for your selection via the plugin
  10. With the same selection, click add selection model storage [Note: duplicate models (with the same name) are automatically handled, so feel free to mass select many models]
  11. You are now free to delete those models from your workspace - they will be automatically culled in once the player enters their culling radius
  12. If you wish to adjust the model, simply delete the anchor point associated with that specific model (or adjust the position of the anchor point)
  13. Adjust settings in CullingService.Settings to create a smooth experience for your players. In it’s most ideal state, CS will run and players will not even realize it. If you’re able to tweak the settings to give the illusion of a fully loaded map while things are actually being efficiently culled in and out, you’ve hit adjusted your Settings perfectly. Don’t stress if you aren’t able to do that (since you’ll also want to factor in performance costs of culling more instances in), but I would advise this to be the goal.

I highly recommend saving a copy of the open-sourced test game (linked above) and playing around with the plugin there - it should make a lot more sense

Settings Overview

  • Distances: Customize the various culling distances. These are measure in studs, so these are the distances that a player must enter to have instances in the relevant distance folder of the model culled in. If they exceed this distance, those instances will be culled out. Search radius is the distance
  • Paused: boolean value that is set via CullingService:Resume() and CullingService:Pause()
  • Region Length: the length (in studs) of the invisible regions that will divide up your map. Make sure that the length of this is at least 1/3rd of the Long Distance (although I would recommend making sure it’s around half of that distance)
  • Wait Time: this is the time that CullingService checks for adjustments (updating ranges, culling whole models in, culling models out)
  • Welded Anchor Points: table of strings for anchor points that should be welded to their respective models. Use this to make models moveable and make changes to the anchor point’s CFrame value

Closing Thoughts

This was a headache to work on and a very niche subject in programming (since engines typically handle this), so I wanted to open source it to give others a stepping stone in their creations. Feel free to use it as a development tool, learning resource, or whatever else you think of. If you think of improvements, feel free to submit a pull request to the GitHub repo. Unfortunately, I don’t have a ton of time right now to make a tutorial video, but it is relatively self explanatory. If anyone is able to do so, let me know and I will link it in the thread.

Special thanks to (in no particular order besides alphabetical) @Ahlvie, @boatbomber, and @DevBuckette who were immensely useful in helping me understand the concept and how to approach the topic. Huge thanks to @XAXA for her BoundingBox formula, which allowed me to create a more accurate BoundingBox of the workspace and avoid problems caused by the Terrain instance. Regions are handled using @VerdommeManDevAcc 's Object Tracker & Area Manager module (which I always find extremely useful).

43 Likes

I find this useful. One question: Does the bounding boxes affect performance? The first image actually scares me.

1 Like

In all my tests it has not. Any (potential) performance hits will only happen at the beginning of the player’s experience, and this is also offset by not having to load the entire map in.

If you use reasonable region sizes as well (ex: not chopping the entire map into 10x10 stud boxes) the amount of regions created will be fewer which results in less initial computations. If there’s an exception to this (such as a huge map that will require a large amount of regions by nature), adding a small wait to extend the calculations performed by the RegionHandling.GenerateInternalRegions() will help reduce the strain on the client.

2 Likes

I have a suggestion, instead of working with different folders you can use tags from CollectionService instead. This would be more flexible and easier to work with imo.

2 Likes

Thanks for the suggestion! The feedback is really greatly appreciated.

I originally opted for folders just for visual organization since it cleans up the Explorer display when lots of regions or anchor points are being created. Since part of this relies on being able to get a live picture of what this looks like in action while in Studio and manage the system via plugin, a clear and neat Explorer is handy.

You’re right that this would make it more flexible though, and I may make the switch over in a later update if I can find a way to manage to keep the Explorer tidy as well.

3 Likes

Will this work with both Streaming Enabled + This system at the same time without any complications?

1 Like

I haven’t tested it and can’t promise 0 complications, but using both seems redundant.

If StreamingEnabled works fine for you, your project has been set up to work with it, or you have no need of the additional features CullingService offers then you may be better off sticking to that.

CullingService was developed to offer more features/customization than StreamingEnabled and solve certain problems or complications that StreamingEnabled brings along, specifically coming from the development of my current project. If your project is at a point where you find both equally attractive, then it may be either a matter of personal preference or your project’s intended end state which decides which is best for you.

1 Like

Added an update (ability to cull moving models):

  • Remove the Search Radius setting, since that’s basically obsolete due to how regions are tracked
  • Added support for culling moving models. Read the Advantages + Limitations + Settings Overview sections for more information. TL;DR is that you can make changes to the anchor point’s CFrame, because it will be automatically welded; all instances within the model need to be in the same distance folder + only have one distance folder; and you need to add the name of the anchor point to the table in Settings called “Welded Anchor Points”
  • Speak of the devil! Added a Settings Overview section to this thread
  • Put the main loop in a coroutine so that you don’t have to wrap it in one yourself (or do other methods)
  • Standardized the formatting on settings
2 Likes

Bug Fix

  • Pushed a fix that restricted the size of the system (because I was working with position rather than CFrames + was using a part to visualize the CFrame and working with values associated with the BoundingBox rather than the true values)
  • CullingService should now scale as intended for maps of any size
1 Like

This is really helpful! I’ve always wanted the ability to be able to control more than what StreamingEnabled gave us.

2 Likes

Quick update

Added a new setting called UseParts which allows you to enable or disable the use of parts in CullingService.

Not sure if anyone was using the regions created by CullingService in other parts of their code and relied on the actual regions being created, hence why this is now a setting versus a switch in behavior. However, now this means that CullingService adds 0 parts to your game (something that I realized was a very ironic downside in a module that aims to reduce the amount of total parts loaded in the first place)!

There should be 0 issues updating any projects to this version, just remember to adjust the settings accordingly. As always, feel free to play around with this in the example place. The change should be reflected on the GitHub and Roblox Library as well.

1 Like

Just a quick suggestion, if you dont have the folder(s) created before enabling/installing the plugin it will give out this error


And looking through the plugin code I noticed as soon as the plugin is enabled it searches for the folders, so just small a suggestion to make it look for the folders either on pressing initialize button or whenever you click one of the required buttons

Its not really a big deal all you have to do is make the missing folders and re-open the file game might be confusing to new users that don’t know

1 Like

POV: Anchor points have collisions enabled
Good module though, definitely will be put to use in my project Feudal Japan.

1 Like

If so, I think it should be possible to choose between the two instead of having the original system completely replaced.