[Open Sourced!] "Dual Render" Scope System

Disclaimer:

THIS IS NOT SUITABLE FOR A PRODUCTION GAME ENVIRONMENT. It is an interesting tech demo prototype, but is absolutely an abuse of ViewportFrames. Note that it slows exponentially- the more objects, the slower it gets. I’ve optimized plenty, but it is not enough.


Before we begin:
What is a Dual Render scope system?
It is a system that allows the game to make the scope a separate render, so the player doesn’t lose peripheral vision. It’s a bit hard to describe, so I’d rather just show you examples:
Arma 3 and Escape From Tarkov

The Roblox engine had no way to do this… until ViewportFrames! I created a ViewportFrame security camera system and open sourced it, and @SammySpicer suggested I try a dual render scope system! So I built the system up from scratch again, and created this new thread dedicated to it!

With that said, I present: my system!

As you can see in the top left corner, I’m getting a perfect 60FPS!
In addition, the script is running very efficiently:
Perf
(Special thank you to @UghLily for helping with the optimization process!)

Edit: There were some issues that have been resolved.

Rather than go redo all the files to support this change, go here for a step by step of how to patch your files.

Issues

Issue A: Scaling
My scaling assumed that the part wasn’t rotated, so it broke and distorted when tipped. I’ve redone it using proper A = bh by getting 3 corners and getting the magnitude without depth.

Issue B: Rotation
When the ScreenPart tips, the GUI wouldn’t. This is also fixed in the patch step-by-step link.

Issue C: Mapping
Another mistake is that the GUI mapped to the center of the ScreenPart, rather than to the face. This has been solved and is updated in the patch step-by-step.

How was this done?

Well, what does it need to do?
We need it to replicate the entire workspace into the ViewportFrame (and keep it refreshed), and keep the ViewportFrame’s CurrentCamera aligned with the scope.

A naive approach:
Every frame, clear the ViewportFrame, then reclone the workspace and update the CurrentCamera CFrame.
Now, obviously this is a terrible idea. So what will we do?

Our Approach:

  • The Camera:

The CurrentCamera is an easy solve! We can use a :GetPropertyChangedSignal(“CFrame”) to only update it when needed!

Now for the hard part:

  • The Objects.

First, we need to prioritize. Most important is definitely Humanoid objects- NPCs and Players. We want to render these with the maximum FPS we can get, using Heartbeat, updating them every frame.

Next up is your average part. We can use a simple wait() to make them update at a viable FPS; it will slow down its own refresh rate in order to keep the game running as fast as possible. (This can make it jittery, but moving parts aren’t that common so we can get away with it.) We don’t simply update the part at this FPS; instead, we check if it needs an update. If the CFrame has changed, then we update our ViewportFrame clone part. This way, we only do work if needed! But that’s not all we can do. We can choose to only update the part if it’s in front of us! This way, we don’t bother moving parts that are behind us! Now, I know what you’re thinking. In a previous post, I said:

But I’ve been taught a new trick for this, thanks to @denissini on Discord! We can do some angle checks to find out if the part is in front of us! (Note: This doesn’t check if it is visible, as it will update parts behind walls. It only check if we might be able to see it.) So now we only update parts when truly needed!

Now, we (as the creators of the game place) happen to know that the Map never moves. Ever. So why bother doing refresh checks? We render it once, and then never check it again. This means that the map (which is most of the workspace) is barely a performance hit at all!
This allows us to increase map size without a severe performance drop!

Using this implementation, I got it working at 60FPS!

Effects/Polish

Focus
We want to blur the peripheral vision, for this awesome “focus” effect.


My original version was a SurfaceGUI, and a BlurEffect would blur out the scope as well!
Instead, we’ll have to use a ScreenGui! We can use :WorldToScreenPoint() to find out where the face of the scope is, and put the GUI there!

Night Vision
It was suggested and prototyped by @grilme99 to create a night vision edition! Because ViewportFrames don’t render full lighting, it will always be well lit even when the world is dark. That means, all we need to do it put a green overlay and darken the world!


I will include this version in the final product section.


Special thanks:
The amazing @Ugh_Lily (Twitter) helped me optimize this even more, eliminating many function calls and table indexes! Thanks to her, it runs much more smoothly than before! (Remember how we made the refresh rate slow down to maintain 60 FPS? Well, thanks to her, it doesn’t need to slow down as much. It’s super smooth :drooling_face:)


Final Product

Here is the place file! Feel free to mess around and learn! If you find any bugs, please report them!
Dual Render Scope.rbxl (406.9 KB)
Here is the night vision edition! It’s pretty sweet!
Dual Render Scope NV.rbxl (406.9 KB)

Showcase:


Bugs (Report more if you find them!)

  • [FIXED] Render is slightly vertically stretched (This was due to the SurfaceGui CanvasSize not being a square)
  • [FIXED] Objects created during runtime don’t render (I’d forgotten to add a .ChildAdded)
  • [FIXED] Objects inside Objects get created twice (I cloned the whole object, and then clone each descendant itself, making two of each descendant)

The lack of bug reports is scaring me… there’s no way something I made actually works perfectly :sweat_smile:


Benchmarking

A lot of people have asked how this holds up in various environments, so I tested it.

My PC Specs:
Processor: Intel Core i7 CPU 920 @ 2.67GHz
Graphics: NVIDIA GeForce GTX 750 Ti
Memory: 8GB
OS: Windows 10 Home 64-bit

My PC is mid-range, perfect for average testing. Not a gaming PC, but not a toaster/laptop.

Environment A:
20 Humanoids, Empty baseplate, No other moving parts
Result- 60FPS, ~10.8% CPU Usage

Environment B:
20 Humanoids, Small map, 50 moving parts
Result- 60FPS, ~15.2% CPU Usage (Note: Moving parts are no longer being refreshed at 60FPS. They slowed themselves down as per our prioritized optimizing, but are still smooth.)

Environment C:
50 Humanoids, Full map, 300 moving parts
Result-
For this one, recording mattered, so here’s both results:
When recording: 14.2FPS, ~9.2% CPU Usage
When not recording: 18.9FPS, ~12.7% CPU Usage

The map, as we know, is not a performance hit. (We ran the full map at 60FPS in a previous video) Therefore, we need to determine which factor caused this drastic drop.

Env D:
50 Humanoids, Full map, No other moving parts
Result- 48FPS, ~26.0% CPU Usage
For this one, recording didn’t change it.

Env E:
No Humanoids, Full map, 300 moving parts
Result- 60FPS, ~5.7% CPU Usage
For this one, recording mattered. When not recording, the balls were refreshed a bit smoother.

Analysis

I stand by my original statement. This is not conducive to smooth gameplay.
An average match in Operation Scorpion will have about 12 humanoids, a full map, and probably about 100 moving parts at a time. This would leave the game at about 55FPS and the scope with a 40FPS refresh rate. This is not optimal, especially since other player might have slower PCs than I do.
The ViewportFrames themselves cannot render Terrain, particles, or lighting. This means that dark rooms will still appear well lit, any particle effects (muzzle flashes, breaking glass, blood) will not appear, and maps with terrain won’t work. This is not a viable method. We will simply have to hope Roblox adds an actual Dual Render system!

I hope you enjoy!

When I saw this, I literally screamed so loud my family came running over.

179 Likes

This is some neat stuff

7 Likes

@LordMerc and @TheRings0fSaturn, I thought you guys would like to see this!

6 Likes

Really great work!

4 Likes

Using ViewportFrames to do this. Is something that had never occured to me!
This is absolutely amazing. Bravo!

3 Likes

Honestly one of the best thing to come out of ViewPortFrames.

Really wish Roblox had a in-house option for a camera system similar to this except it’s not duplicating everything into a frame - like Portal or Half Life 2’s Portal / TV systems

6 Likes

Also, to fix the stretching problem, under the Surface GUI make the size 800 x 800 instead of 800 x 600. :slight_smile:

I also think changing the resolution to a lower number improves performance. I could be wrong however. :stuck_out_tongue:

5 Likes

It’s cool but unfortunately I am not a huge fan of using hacky methods or abusing features to accomplish something. In the long run the stability of the method is very low and support either becomes very hard to maintain appropriately or cannot be done at all.

Though I do praise your creation, I’m afraid that a proper dual rendering system will never come to be and I can never be content with an alternative. For example: I would really love to add blurring to the peripheral vision but keep everything within the scope crystal clean.

Nice to see you working your talents, though. Gave you a post like, it’s the most I can do.

8 Likes

Not on a PC RN, but I wonder, what would happen if you set the time to midnight?

5 Likes

Great job!
I thought of doing it when they announced viewportframe, but never actually thought of how to do it

4 Likes

I’ve seen ur tweet it’s nice, also good job @TheRings0fSaturn looking forward to this!

3 Likes

Absolutely impressive work. I’m astonished.

5 Likes

Really awesome, was wondering when someone would make this a real thing! Could I possibly use this for my game?

1 Like

It’s interesting, but it’s not really viable for actual use cases in production (as you stated). Still cool though!

Viewports are a great step, but Roblox need to pull a full Unity / Unreal and allow for multiple render windows. Sure, it’s expensive and people will create non-performant stuff, but they already do and that’s not a good reason to limit features for stronger developers. I’ll be a happy person when I can render a secondary camera in a smaller window.

5 Likes

Completely agree. Multiple render windows would be so useful…

4 Likes

What if, instead of cloning the whole workspace, you only cloned stuff that was in the scopes view?

Edit: Not sure how this would work, but maybe use rays to get an area that you can clone? :thinking:

1 Like

I don’t know if this has been asked before but could you please add your specs to your thread so we can see what achieves 60 FPS

1 Like

I actually made that already. Here’s something that might blow your mind:
It was slower. Why?

I used a combination of screen projection and raycasts to determine if an object was visible.
That’s expensive. It’s far faster to rely on the ViewportFrame internal system of deciding which parts to render!

3 Likes

Very mid-range PC. Nearly a decade old, and the only thing I upgraded since buying was the graphics card.

Intel Core i7 CPU 920 @2.67GHz
8GB of RAM
GTX 750 ti

1 Like

Absolutely. I stated very clearly in the post that this is not suitable for real work. However it is cool!

And what if I told you I might already be working on blurring peripherals while keeping the scope clear…

1 Like