Optimizing Roblox without StreamingEnabled - The Mystery Of Duvall Drive

If you have not seen it, Roblox’s Alpha Strike Group released an official experience “The Mystery Of Duvall Drive”
You can play it here:

And Alpha Strike Group also produced a great set of documentation on how they built it here:

It boasts future lighting and advanced materials and streaming enabled, and it looks absolutely fantastic.

But, frankly, it just doesn’t run very well, especially at max graphics settings…

So in this breakdown, I’m going to run through some things I did to take this from a 20fps experience on max gfx on my modest nvidia 1080ti, to 60fps, and hopefully explain why in a useful way for optimizing your own projects.

First up - my ground rules:

  1. "max graphics" settings only! Even games like this can be optimized to run well under those conditions, which is the whole point of this article.
  2. I’m not going to replace/remake any of the art assets or take away any features - for the most part it should stay visually identical
  3. StreamingEnabled will be turned off, we won’t need it.
  4. I’ll leave suggestions for things that can be further improved to the appendix.

Let’s start with the base experience:

Sitting at this area near the car and looking towards the house is going to be my test spot, it’s about the worst performing place in the whole map.

It’s got about 7.2 million in-scene triangles, and runs at about 20fps at max settings. Look at how many shadow tris its rendering - almost 12.5 million! Turning the quality settings down disables shadows, so you get a lot of performance back by turning shadows off.

If you just wanted to “make it run better” for no extra effort, turn shadows off and call it a day. But we can keep the shadows AND make it run a lot better, which is what we’ll do.

Key Observation 1: players don’t want to turn the settings down just to make your experience run nicely. You should get used to optimizing your experience with max graphics settings turned on, and learn how to make that run well. By the same token, having an experience that barely works at max graphics settings should be considered a broken experience.

[Warning, the rest of this post is just an outline, I plan on continuing this at a later point, but the highlights are here]

Observations:

Some authoring mistakes:

  • Unanchored Physics objects everywhere (7ms cpu just for that…)
  • Lack of primary parts on most models (cant distance cull them with no position…)
  • Server scripts to do rotations on objects is not a good idea eg: cloud rings, these days you can simply use a localscript for this and save bandwidth (goes from 15kb/s → ~0.3 kb/s)
  • Terrain is crudely put together, can at least slice the bottom flat and get rid of a lot of visible caves and cliffs

Observations:

  • Most of the outside map is detail objects like trees plants and rocks
  • You really can’t see very far which we can use to our advantage
  • The map is already broken up into some useful regions such as the motel, the house interior, the grounds, the solarium, the treehouse etc.
  • Can’t really see inside the house very well from outside the house until you get close

So first, up, I went and made a few minor changes:

  1. I turned streaming enabled off.
  2. I allowed 3rd person view again, just because I don’t like the fps controls when developing in studio :slight_smile:
  3. I went and rearranged a bunch of the folders so that windows belonged to the exterior of the house.
  4. I edited the contents of some folders for interior sections so that the bounds made more sense (some large volumes that could be moved to exterior etc)

Then I used an existing module I made to tag every single rock, bush and detail object out in the world and set it as a big detail (400 unit culling radius) or small detail (200 units and it culls).

These are quite conservative values, and you shouldn’t ever be able to spot an object being popped out by this. Using tighter values saves a lot more triangles but reduced it from about 7 million in the starting scene to 4.4 million. You could argue that streamingenabled is meant to do this but honestly, streamingenabled is expensive and unreliable, as opposed to just handling it yourself on the client.

Finally I made it so the interior regions, motel, and treehouse only appear if you’re within 50 studs or entirely within them, otherwise they get deparented on the client.

This took the starting area triangle count down to 2.5 million(!)

Link to the optimized project:

The remaining things to do:

  • Properly tidy up the authoring boo-boos like the floating physics objects.

  • Clean up a lot of the moving/flickering lights that cast shadows - one solution for this project would be to possibly turn shadow casting off on lights until they’re within nearby range like < 100 units.

  • Add Tree LOD models, especially important for the gigantic trees - they can’t just be deparented because their popping is noticeable. 1/2 the scenes triangles in trees, so effort here is worthwhile.

  • Add house exterior /solarium LOD models - replace them with simpler models when the player is further away.

  • Possibly wire up a full portal visibility system to reduce rendering when inside/outside the house, although wasn’t really required for this project

  • write more of a breakdown on what you can do to region-ize your own games, and how to hide/show various things like terrain or models.

45 Likes

I’m interested in what kind of module is this? And can you open the game to the public to see in more detail what you have done.

3 Likes

This is a great tutorial, I love this. Definitely going to be saving this to show others! I see too many developers just slapping StreamingEnabled on to make their game run well. Awesome post.

FYI, Roblox already does this. It’s more expensive to enable/disable the property.

Neat! I actually not too long ago tried something similar to the “Beyond the Dark” VisTech, and while I never got around to fixing up some collisions in the game to get it playable, I was able to get the game to run buttery smooth even when looking back at the bridge.

What I did to the absurd number of unanchored parts was basically run a script at runtime to anchor all the parts after they’ve settled in and copied them back in place in the editor. I had to split up the process a bit since it started to lag after selecting 1K+ parts, but other than that it was pretty smooth sailing.

I also went overkill and basically disabled all collision-based properties (CanTouch, CanCollide, CanQuery), set all MeshParts’ CollisionFidelity to Box, and set as many RenderFidelity to Performance. Of course, that means you can’t actually walk through it but it’s still pretty neat.

I also tried to do something similar to you’re culling method, but the cost of reparenting/CFraming or StreamingEnabled made it obsolete.

You can try it here

BTD Optimized - Roblox

3 Likes

It’s worth noting that Duvall has a lot of small parts with collisions and shadows enabled, such as piano keys or some objects inside of boxes.

Any object that is too small compared to the character, or an object that can’t physically be touched due to barriers, overlapping collisions, etc, should have all their collisions remove. This means disabling CanTouch, CanCollide and CanQuery.

Also most likely if the part is small or untouchable, it’s a safe bet to just remove CastShadow as well, as these objects won’t cast meaningful shadows. Also any objects inside existing shadows should have their CastShadow property disabled.

1 Like

Am I reading this incorrectly or are you saying that a model with a primarypart performs better than a model without one?

3 Likes

Wait… Does the engine do not actually cull things without the Primary Part? or Is it just needed for the module to work?

2 Likes

Primaryparts are just just needed for distance culling and bounds checks to work. Otherwise the engine chooses and it can pick any old random orientation.

Regarding collision:

Any object that is too small compared to the character, or an object that can’t physically be touched due to barriers, overlapping collisions, etc, should have all their collisions remove. This means disabling CanTouch, CanCollide and CanQuery.

Hmm I can’t agree with this one. For shadows, sure, but roblox’s broadphase collision is very good. From experience I’ve never run into an problem unless it was some worst case scenario like a giant pile of unanchored parts or something.

Ahh! Thanks, I will try to apply this to my super-detailed Castle that has a bunch of models without any primary parts. I’ll just write a macro plugin code to do this automatically.

There’s this weird bit of culling that reveals a red coloured part beneath the roof;

Stepping closer;

The colour of the part beneath the roof should match it better, but otherwise this is giving me wayyy better perf! From 5 FPS to a stable 30.

Also, is this still progressable?

Edit: also plox uncopylock

1 Like

What you’ve done sounds very interesting, and pretty impressive. I’m a bit curious about how you’ve made your own culling system, that sounds like the most impactful change, but it’s only slightly glossed over in your explanation.

1 Like