Occlusion Culling Universal
Key RequiredUniversal

Occlusion Culling Universal

Universal Scripthuezoangel57huezoangel57 675 views 75 executesUpdated Dec 20, 2025
5.0/ 5(2 votes)
20100% Positive
Lua Script•234 lines (5369 chars)
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

local CONFIG = {
	UpdateInterval = 0.15,
	MaxDistance = 180,
	NearDistance = 70,
	FOVAngle = 90,
	MinSize = 5,
	MaxObjectsPerFrame = 15,
	MaxTotalObjects = 300,
	RegisterDelay = 0.5,
}

local trackedObjects = {}
local objectLookup = {}
local lastUpdate = 0
local currentIndex = 1
local isRegistering = false

local excludedClasses = {
	"Terrain", "Camera", "Sound", "Script", "LocalScript", "ModuleScript",
	"Attachment", "Constraint", "WeldConstraint", "Motor6D", "Decal", "Texture"
}

local excludedNames = {
	["Baseplate"] = true,
	["Terrain"] = true,
}

local function shouldExclude(obj)
	if not obj:IsA("BasePart") then return true end
	
	for _, class in ipairs(excludedClasses) do
		if obj:IsA(class) then return true end
	end
	
	if excludedNames[obj.Name] then return true end
	if obj.Transparency >= 0.98 then return true end
	if obj.Size.Magnitude < CONFIG.MinSize then return true end
	
	local model = obj:FindFirstAncestorOfClass("Model")
	if model then
		if Players:GetPlayerFromCharacter(model) then return true end
		if model:FindFirstChildOfClass("Humanoid") then return true end
	end
	
	return false
end

local function registerObject(obj)
	if shouldExclude(obj) then return end
	if objectLookup[obj] then return end
	if #trackedObjects >= CONFIG.MaxTotalObjects then return end
	
	local tracked = {
		obj = obj,
		origTrans = obj.Transparency,
		origColl = obj.CanCollide,
		visible = true,
	}
	
	table.insert(trackedObjects, tracked)
	objectLookup[obj] = tracked
end

local function isInFOV(objPos, camPos, camLook)
	local toObject = (objPos - camPos)
	local distance = toObject.Magnitude
	if distance < 10 then return true end
	
	toObject = toObject.Unit
	local dot = camLook:Dot(toObject)
	
	return dot > 0.3
end

local function setVisibility(tracked, visible)
	if not tracked or not tracked.obj or not tracked.obj.Parent then return end
	if tracked.visible == visible then return end
	
	local obj = tracked.obj
	
	local success = pcall(function()
		if visible then
			obj.Transparency = tracked.origTrans
			obj.CanCollide = tracked.origColl
		else
			obj.Transparency = 1
			obj.CanCollide = false
		end
	end)
	
	if success then
		tracked.visible = visible
	end
end

local function performCulling()
	if not camera or not camera.Parent then return end
	
	local camPos = camera.CFrame.Position
	local camLook = camera.CFrame.LookVector
	
	local totalObjects = #trackedObjects
	if totalObjects == 0 then return end
	
	for i = 1, CONFIG.MaxObjectsPerFrame do
		if currentIndex > totalObjects then
			currentIndex = 1
			break
		end
		
		local tracked = trackedObjects[currentIndex]
		
		if tracked and tracked.obj and tracked.obj.Parent then
			local objPos = tracked.obj.Position
			local distance = (camPos - objPos).Magnitude
			
			local shouldBeVisible = distance <= CONFIG.MaxDistance
			
			if shouldBeVisible and distance > CONFIG.NearDistance then
				shouldBeVisible = isInFOV(objPos, camPos, camLook)
			end
			
			setVisibility(tracked, shouldBeVisible)
		end
		
		currentIndex = currentIndex + 1
	end
end

local function cleanupInvalidObjects()
	for i = #trackedObjects, 1, -1 do
		local tracked = trackedObjects[i]
		if not tracked.obj or not tracked.obj.Parent then
			objectLookup[tracked.obj] = nil
			table.remove(trackedObjects, i)
		end
	end
end

local function batchRegister()
	if isRegistering then return end
	isRegistering = true
	
	local count = 0
	
	for _, obj in ipairs(workspace:GetDescendants()) do
		if count >= CONFIG.MaxTotalObjects then break end
		registerObject(obj)
		count = count + 1
		
		if count % 50 == 0 then
			task.wait(0.2)
		end
	end
	
	isRegistering = false
	print("✓ Occlusion: " .. #trackedObjects .. " objetos registrados")
end

local pendingObjects = {}
local lastRegisterTime = 0

workspace.DescendantAdded:Connect(function(obj)
	if isRegistering then return end
	if #trackedObjects >= CONFIG.MaxTotalObjects then return end
	
	table.insert(pendingObjects, obj)
end)

task.spawn(function()
	while task.wait(CONFIG.RegisterDelay) do
		if #pendingObjects > 0 and not isRegistering then
			local currentTime = tick()
			if currentTime - lastRegisterTime >= CONFIG.RegisterDelay then
				local toRegister = math.min(#pendingObjects, 10)
				
				for i = 1, toRegister do
					local obj = table.remove(pendingObjects, 1)
					if obj and obj.Parent then
						registerObject(obj)
					end
				end
				
				lastRegisterTime = currentTime
			end
		end
	end
end)

workspace.DescendantRemoving:Connect(function(obj)
	if objectLookup[obj] then
		objectLookup[obj] = nil
	end
end)

task.spawn(function()
	while task.wait(15) do
		if not isRegistering then
			cleanupInvalidObjects()
		end
	end
end)

RunService.RenderStepped:Connect(function()
	local currentTime = tick()
	if currentTime - lastUpdate >= CONFIG.UpdateInterval then
		performCulling()
		lastUpdate = currentTime
	end
end)

task.spawn(batchRegister)

_G.OcclusionStats = function()
	local visible = 0
	for _, tracked in ipairs(trackedObjects) do
		if tracked.visible then
			visible = visible + 1
		end
	end
	return {
		Total = #trackedObjects,
		Visible = visible,
		Hidden = #trackedObjects - visible,
		Pending = #pendingObjects,
	}
end

print("✓ Occlusion Culling Optimizado")

Features & Information

This scripts make roblox to not render things that the player dont see

Community Comments (1)

LeumasLeumasMay 1, 2026

Tuff

More Scripts for Universal Scripts