Detecting objects within the camera viewport

  1. What do I want to achieve?
    I want to have a performant system that checks if a part is visible to the camera or not.
  2. What is the issue?
    There’s no built-in function for it, only one that checks if a specific position (Vector3) is visible or not, while I want it to check for a whole part.
  3. What solutions have I tried so far?
    So I’ve tried going through the 4 corner verticies on a block (I am just using a block, no complicated sphere or such thankfully) and it worked and looked like this:
local function onScreen(object)
	if object:IsA("BasePart") then
		local bool = false
		
		for _, vector in pairs(verticies) do
			if not bool then
				local cornerPosition = (object.CFrame * CFrame.new(object.Size.X / 2 * vector[1], object.Size.Y / 2 * vector[2], object.Size.Z / 2 * vector[3])).Position
				
				local _, onScreen = MovementCamera:WorldToScreenPoint(cornerPosition)
				
				if onScreen then
					bool = true
				end
			end
		end
		
		return bool
	end
end

But even tho it worked, just checking the verticies is not enough. You see, if the part is big enough, the verticies might not be visible to the camera, but the part might still be.

In the example image below, I put on wireframe rendering, took a picture and put it in paint. Then I drew the outline of a cameras view and the 4 verticies of a part that is very big (The size I use so It’s a genuine problem).

Please let me know if you have any thoughts or perhaps solutions to this. I’m ready for a conversation.

If any edges cut any plane of the camera, the surface is visible.

Even for some positions of small objects, just point-in-box checks are not enough; you need to check for vertices being on different sides of any of sides of the viewport. In typical frustum culling code, the frustum has 6 planes to consider, but in your case the WorldToScreenPoint is aready doing the projection to 2D, so you only need to think about the 4 lines that are infinitely long extensions of the viewport rectangle sides.

Even with this separating plane check implemented, the code can still fail when the projection of your object is much larger than the viewport (the Part is huge, or simply very close to the camera). For this case, I refer you to the clever blog post below. TLDR is that you can check part vertices against viewport rect, but also viewport corners against the projected bounds of the Part. This is a bit trickier in 2D, since you need to know which vertices of the part bounding box are part of the convex hull of the projection, but there are lots of algorithms for this and they are not complicated. 2D Graham Scan is pretty simple.

1 Like

Thank you for taking your time to have a look at my issue!
(due to the fact that I often get hundreds of views but “no” replies at all)

You made a great and in-depth explanation, I’ll look more into it! Thanks again!

No problem! I should have noted also, if you don’t need this to be 100% accurate–and in fact for things like convex MeshParts you can’t even make it perfect–you can not bother with trying to figure out the actual polygonal projection and convex hull business and instead just use the 2D axis-aligned bounding box of the part’s projection. Once you project all the part vertices onto the screen with WorldToScreenPoint, you can just use math.min and math.max to get the bounding box extents, and then proceed with just the 4 corners of that box. It will err on the side of thinking the object is visible, which is generally the direction you want it to fail if you’re just trying to hide off-screen things, as it will make sure nothing that should actually be visible remains culled, so the user won’t see things popping on and off.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.