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
Edit: it should be noted the examples used in this thread clones and destroys parts every frame, I highly recommend not doing that, and instead updating the CFrames of dynamic parts every frame, this is just a quick example I threw together to showcase this.
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
game:GetService("RunService").RenderStepped:Connect(function()
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
end
end
end
end)
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 = Vector3.new(renderDistance/2,renderDistance/4,renderDistance/2)
local renderRegion = Region3.new(center - 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
v:Destroy()
end
end
c.Parent = viewport.Dynamic
end
if not(viewport.Dynamic:FindFirstChild(v.Parent.Name)) then
v.Parent.Archivable = true
renderCharacter(v.Parent)
v.Parent.Archivable = false
end
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
v2:Destroy()
end
end
c.Parent = viewport.Dynamic
end
end
end
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