Rendering Viewport Frames with Region3


I keep seeing people making these limited viewport renderers using a for loop that loops through the workspace to detect if a part is on a camera’s screen, this is very bad for performance, so I went looking for a better solution and I found one. This thread is meant for helping people learn to efficiently render their viewports

Region3 is useful for rendering, its not nearly as expensive to get parts that are on a camera as looping through workspace, here is a video using this method:

as seen in the video, it runs just fine, now the video didn’t capture at 60 fps, but the game was running at it, I have also tested this on some mobile devices to ensure it runs good.

The methods used for rendering that i have seen all over here on the dev forums include looping through the workspace and testing if a part is on screen to render it, rather than using a Region3

Basic example of looping through the workspace:

local camera = workspace.CurrentCamera -- This would likely be a camera other than this
local viewportFrame = script.Parent.ViewportFrame
    for i,v in pairs(workspace:GetChildren()) do
        if v:IsA("BasePart") then
            local _,visible = camera:WorldToScreenPoint(v.Position)
            if visible then
                local c = v:Clone()
                c.Parent = viewportFrame

Example of using a region3:

local function renderDynamic(camera,viewport,characters,list,renderDistance)
	--Characters is a folder i parented all the player's characters to when they joined to make rendering easier
	--List is the whitelist used to render dynamic parts
    --I also put a folder in the viewportframe named "Dynamic"
	--Create the region3 to detect the parts
	local center = (camera.CFrame.LookVector*renderDistance)/2
	local corner =,renderDistance/4,renderDistance/2)
	local renderRegion = - corner, center + corner)
	--Get all parts within the region
	local parts = workspace:FindPartsInRegion3WithWhiteList(renderRegion, list, PartLimit)
	--Loop through the parts
	for i,v in pairs(parts) do
		--check if the part is part of a character
		if v.Name == "HumanoidRootPart" then
			local function renderCharacter(chr)
				if not chr.Archivable then chr.Archivable = true end
				local c = chr:Clone()
				for i,v in pairs(c:GetDescendants()) do
					if v:IsA("Script") or v:IsA("Sound") then
				c.Parent = viewport.Dynamic
			if not(viewport.Dynamic:FindFirstChild(v.Parent.Name)) then
				v.Parent.Archivable = true
				v.Parent.Archivable = false
		elseif not(v:IsDescendantOf(characters)) then
			local c = v:Clone()
			for i,v2 in pairs(c:GetDescendants()) do
				-- Remove things we dont want
				if v2:IsA("Script") or v2:IsA("Sound") then
			c.Parent = viewport.Dynamic

you would want an inverse of the function above that runs less often to render the static parts.

Rendering parts with the workspace loop often results in framerates below 30 fps, but so far from my testing with the region3, the frames havent dipped below 53 fps on any device i run it on, running at an average of 59 fps.

I will be testing this in more intense situations for better benchmarks soon.

Of course people have come up with better methods for rendering viewport frames with the workspace loop, as can be found here: [Release] ViewportFrame limited-rendering (I have not checked if their new version also uses a loop similar to this so I will not include it here)

you can explore the place I made for this here:
Region3Viewports.rbxl (562.4 KB)

or here

closed #4

opened #5



This is awesome!!!


Solid solution for limited rendering, though as many people stated the main purpose of these frames doesn’t seem to be large scale rendering. I thought about Region3s and it’s a very nice use of them, so kudos on making it and tossing it up open source for the community.


This is super cool, thanks for making and sharing!


Amazing! Thanks for sharing it


Try using a large anchored/invisible/nocollide sphere and using GetTouchingParts to test in a radius… I’m not sure what difference in performance that will have but it would be good to find out! Hopefully it’s fast if not faster. Also you would need to connect a .Touched event to the sphere for this to work but an empty function works.


yeah but using region3 doesnt even have any events, which means it causes less updates