New property: PVInstance.Visible

As a Roblox developer, it is currently frustrating, difficult, and badly performing to “unrender” PVInstances (Models and BaseParts). In order to create systems for LOD, or temporary object cleanup, developers must either unnecessarily delete the PVInstance, iterate over all of its descendants and explicitly set LocalTransparencyModifier (also recording their original values and hoping nothing else touches it in the meantime), or move the object very far from the camera. All of these methods come at a significant cost in terms of performance.

In my game, Aftermath, I very frequently move models and base parts in bulk to a very far away location to prevent rendering them from unnecessary distances. I use a similar system for zombies which aren’t within a reasonable distance. And I use this technique for building interiors that don’t need to be rendered 95% of the time. This is a common technique employed by many games, since reparenting objects tends to be far more expensive, and destroying objects often invokes a large cleanup job (I would often see upwards of 60ms), and LocalTransparencyModifier is too unreliable and slow in large models.

In cases where collisions aren’t a concern, and all we need to do is make it so a PVInstance is not rendered it would be ideal to do this quickly and efficiently. This idea has been proposed many times in the past, but having a property such as Visible is commonly used in the engine, and it behaves exactly the same as I’d expect it to in this case every where else its being used.

Just speculating, but it seems as though something like this already exists in the engine with the new culling system, so a similar property being exposed to developers would be awesome.

46 Likes

How would this work for LOD? Don’t you care about disabling collision also in that case so you don’t have multiple pieces of collision geometry stacked on top of eachother?

Also, moving the objects to a distant location is not as bad a solution as you make it out to be.

The Roblox engine is designed around a physics simulation, where moving objects is designed to be a fast-path, as simulated objects will be moving every frame. Taking advantage of that fast-path for swapping things out is actually just a reasonable use case.

1 Like

In the case of LOD, I don’t ever need collisions for my other LOD levels, or at least that’s a sacrifice I’m willing to make, although I’m sure the engine itself could do a far better job of handling LOD for us. Having control over visibility just seems like a simpler solution that we’re more likely to get, and has other uses as well.

I don’t mean that moving objects is a bad solution, it’s really the only solution we have. But moving objects, especially if those objects involve a humanoid, has always been a bottleneck for me.

Take my use-case on building interiors for example, if I want to move an interior to, say, 0, 10e8, 0 and I use streaming in my game, I have to move the interiors into the camera on the server (so I can validate bullet collisions and other server-sided simulations), recreate the interiors on the client and move them around as needed. If I don’t do this, then the objects will be streamed out and incur a large deferred delete job.

Moving an interior takes roughly 0.15-0.2ms on my PC (which isn’t a slow computer) since there are a lot of objects involved in that move. Presumably I could improve this performance by welding everything together (just a hunch, I haven’t tested this). The collisions are not at all a bottleneck for me, render time is, so moving them really isn’t necessary, and incurs a lot of extra work for the client and far more complex validation for bullets on the server side. I would also imagine it causes a lot of updates in the physics BVH or KD-tree, whatever it is that’s used internally.

Another good example I can think of in my game are my ground items. I have thousands of items scattered around the map, they’re not rendered on the server, but the information used in generating the models is streamed to the client using the built in streaming. These items never have any collisions at all, and some of them have a very good amount of detail… which we could reduce, sure. But if I could just not render these at all until players are within their desired visibility radius for them then that seems like a great option to me. Currently, I “cache” these models to the extent that I am able to and I move them far away and that works, but it begs the question in my mind “why can’t I just not render these?”

I certainly don’t mean to dog on moving objects at all as a solution. You definitely know more about the internals than I do, but given my use-case here does it not make sense to just be able to flick a switch and it doesn’t render at all anymore? Perhaps the LOD use-case isn’t a great one, I was trying to make this more broadly applicable. Most games don’t need to go to the trouble that my game needs to in order to run well on the client. Perhaps all of this could be solved with more granular streaming capabilities though. In any case, seems like a great capability for the engine to support in my mind.

5 Likes

Are you moving it as just a bunch of anchored parts in a Model?

If you weld it together into an assembly with only the root part being anchored that will fully take advantage of simulation and make it much faster to move.

4 Likes

This should really be documented. I was under the assumption that using PivotTo (and maybe in certain cases BulkMoveTo) was always the best option for giant models.

5 Likes

Hypothetically, if there was a model with 2000+ BaseParts (MeshParts, Parts etc.), and I move it in these two ways:

  • all parts unanchored and with a WeldConstraint to one root part, and just setting the CFrame of the root part
  • all parts anchored and calling :BulkMoveTo after calculating the relative CFrames based on a root part

Would the first method with the welds be faster? Is it a significant difference?

Welded will be significantly faster than not welded.

Note that this doesn’t mean you shouldn’t use PivotTo: You should just also weld the object if you plan to be moving it a lot. PivotTo is smart enough to take advantage of you having welded the object to perform better.

Using PivotTo isn’t just about performance, it’s about being able to control object placement through the pivot without caring about the specific parts within the object.

9 Likes

In this case, I definitely agree with @Mauio64, this comes as a big surprise to me… So far, I’ve been under the assumption that the overhead of firing .Changed signals as well as processing all the physics was slower than :BulkMoveTo. Thank you for clarifying this!

2 Likes

Learned something new! This is interesting.

2 Likes

Does this not seem like a lot of “gotchas” in order to address the issue of the OP? It’s a pretty terrible workflow to have to have specialty knowledge of the backend of how assemblies are managed by Roblox.

4 Likes

If you think that’s a lot of gotchas, a “visible” property puts it to shame in complexity.

If you have an engine that’s not built from the ground up with a concept of visible things get very complicated trying to implement it.

  • Should visible impact physics?
  • Should visible impact selection?
  • But selection is based on physics (via raycasts), so these are somewhat coupled.
  • Should visible impact audio (simulated audio / what happens if I play a sound in a non-visible object?)
  • If I make a part of a welded model invisible, should the model get cut “cut in half” and fall into two pieces?
  • Should particle emitters and other effects in the invisible object be invisible?
  • Should it be possible to easily find out if an object is visible? (or do I have to manually walk up the tree looking if any parent is visible)

A lot of these questions have very different answers on what you would like in different scenarios so making a “visible” that actually does something people would be happy with is quite challenging.

4 Likes

Most of these can be developer controlled IMO.

It should not impact physics.
Unless I’m misunderstanding this, it should not impact selection, since there’s already a Locked property. It should behave the exact same as a transparent part but for a whole PVInstance instead.

No, this can be checked with a quick if-statement before playing the audio.

No.

Edit: This is subjective, but I believe they shouldn’t be automatically set to invisible. Developers can manually disable them when they disable visible, but if they were to be automantically hidden then it would be impossible to enable them whatsoever while an object isn’t visible.

Yes.


This feature request is also similar to New property: Model.Visible.

5 Likes

I think it should just make it invisible. And none of the above questions should have any different behavior than default.

4 Likes

I believe a better name like “CanRender” would solve all of your proposed issues.

This is extremely straight forward and clearly indicates that this dictates whether or not it is rendered and nothing else. The other “CanX” properties can be used to handle physics processing and what not.

As for your final issue - whether or not it should be possible to easily find out if an object is “visible”/rendered - yes it should be. Either through a function or a complimentary property such as “IsRendered”.

9 Likes

If you make a part’s Transparency 1, are physics, selection, audio, or welds affected? Generally, no, as far as I’m aware.
Thus, I think it stands to reason that setting a model to not render wouldn’t affect any of that either. @SoCalifornian made a good suggestion in naming it CanRender instead of Visible; I’d also expect this property to not render any “special effect” instances (Particles, Sparkles, Beams, Lights, etc)

For other cases, I think it’s reasonable to say developers can either move or reparent the model, as we do right now.

6 Likes

For anyone interested, ScaleTo(0.0001) can be a supplement for this so long as you do not rely on physics simulation. My biggest usecase for PVInstance.Visible is to hide items (custom items system) when theyre unequipped, but to keep them in workspace to work with streaming. ScaleTo() effectively solves this but is kinda scuffed.

Does that prevent them from being rendered? I can’t imagine it does. I suspect moving them far away from origin is still a much faster solution in both regards. And ultimately, them not being rendered at all is still a much smoother solution for us in general.

I don’t really agree with some of the other replies here about behavior, so here is my input since I have elevated this topic before.

Yes. It should turn off all collisions for parts and its descendants if it is a model. Collisions being on defeats the whole purpose of the simplicity of the property, since it will require the following for 99% of cases where you expect collision to be off:

local defaultCollisions = {}

local function setInvisible(pvInstance)
	pvInstance.Visible = false
	
	for _, descendant in pairs(pvInstance:GetDescendants()) do
		if descendant:IsA("BasePart") then
			defaultCollisions[descendant] = descendant.CanCollide --This works for unmodified parts, but what if another script changes its CanCollide property?
			descendant.CanCollide = false
		end
	end
end

local function setVisible(pvInstance)
	pvInstance.Visible = true

	for _, descendant in pairs(pvInstance:GetDescendants()) do
		if descendant:IsA("BasePart") then
			descendant.CanCollide = defaultCollisions[descendant]
		end
	end

	--And deal with memory management and so on...
end

Overall, not ideal as it causes unnecessary complexity. If physics are simulated for invisible parts, that is still okay. Any edge cases can be dealt by the current method of manually setting its transparency to 1.

Yes. Invisible instances should only be selectable via the explorer and not the viewport.

Yes. Audio that is a descendant of an invisible PVInstance should have its volume set to zero. The point of this property should be to provide a one-and-done solution that simplifies the process of making instances completely absent from the scene while invisible.

No. Welds should be retained. When an unanchored “half” of the assembly becomes visible again, assuming that half is clipping into the ground, I’d rather deal with the entire assembly popping up out of the ground again than it breaking the entire assembly.

Yes, similar to how audio is also “absent” from the scene. This again prevents you from having to loop through descendants and do extra dirty work.

A simple read-only property would be great for this, but it is not needed as code for this would be pretty simple:

local function isTrulyVisible(instance)
	if not instance or instance == game then
		return true
	end

	if instance:IsA("PVInstance") and instance.Visible == false then
		return false
	end

	return isTrulyVisible(instance.Parent)
end

TL;DR:

It makes the most sense for it to perform the exact same as setting the transparency of its descendants to 1 (including particles and other non-BaseParts) and setting all collisions off.

2 Likes

Also wanted to include my argument for why the current methods of setting instances to be invisible are insufficient right now.

Right now to make an object invisible, you are limited to these options:

1. Iteration

This is done by looping through an object’s descendants and manually setting Transparency = 1 and CanCollide = false for each descendant. Example:

local function setInvisible(instance)
	if instance:IsA("BasePart") then
		instance.Transparency = 1
		instance.CanCollide = false
	elseif instance:IsA("Decal") or instance:IsA("Texture") then
		instance.Transparency = 1
	elseif instance:IsA("ParticleEmitter") or instance:IsA("Beam") or instance:IsA("Trail") then
		instance.Transparency = NumberSequence.new(1)
	elseif instance:IsA("Highlight") or instance:IsA("SurfaceGui") or instance:IsA("Light") then
		instance.Enabled = false
	end --etc...
end

This brings a multitude of issues:

  1. This function can get pretty lengthy when accounting for every type of instance. There are hundreds of different instances with different methods to make them invisible, meaning you will end up with dozens of ugly if-else statements.
  2. Code is never future-proof this way. For example, what if another instance like “Glow” is added one day? You would have to add another if statement to account for it or it may be visible still.
  3. If you need a setVisible() function, you have to store values of properties before setting them invisible (for example, part:SetAttribute("DefaultTransparency", part.Transparency)). This seems simple, but what if another script wants to change those properties while it’s invisible? Then you would have to either directly set its property like normal if its visible, or update the attribute instead if its invisible. Basically, a simple part.Transparency = 0.5 statement becomes 5x longer:
if isInvisible(part) then
	part:SetAttribute("DefaultTransparency", 0.5)
else
	part.Transparency = 0.5
end

2. Setting an object’s parent to nil or ReplicatedStorage

Now that the object’s parent isn’t where it should be, your scripts are going to either error or become more complex for no reason. For example, now you can’t rely on workspace.Tree running without errors because Tree may be under ReplicatedStorage if it’s hidden. Writing local tree = workspace:FindFirstChild("Tree") or ReplicatedStorage:FindFirstChild("Tree") is the only way around this and is complex for something so simple.

3. Using Model:ScaleTo(0.001)

Not only is this extremely hacky, but it likely still renders which can cause performance issues since objects are just resized small. Plus, assemblies that small probably cause a multitude of physics issues.

3 Likes

To those who mainly want to use this feature to hide objects in Studio, I figured I’d add this great plugin here to those who are interested:

Unfortunately it still uses one of the hacky solutions from above, and the complications that come with it make it best only as a plugin. I’d still love to see built-in functionality from the properties panel and in the explorer from Studio so you do not have to use a separate plugin window for a basic feature like this.