Is there a better way to create realistic fog?

I’ve been tinkering with fog for a while now in my game and I’ve noticed something less than desirable with a recent script I’ve designed.

[This] script is what I’m using in my game. It creates a large inverse sphere mesh and CFrames it to your camera every frame. Since the way it works is so simple, here’s the main function:

game:GetService("RunService").RenderStepped:Connect(function (Delta)
	if workspace.CurrentCamera then
		local FogDist = Lighting.FogEnd
		BallMesh.Scale = Vector3.new(FogDist, FogDist, FogDist) * -2
		BallPart.Color = Lighting.FogColor
		BallPart.CFrame = workspace.CurrentCamera.CFrame
	end
end)

This works great for all practical purposes and in testing it’s been more than great cosmetically. There’s just one issue: I’ve noticed an issue of rather harsh frame drops using this code (Framerate can go as low as 20FPS, I’m using a GTX 1070).

Given what I know about Roblox’s engine, I have two guesses for the cause of the lag. The first guess relates to the large part size. Since the ball scales to be accurate to the fog distance (to avoid making the ball visible), it can get quite large (well over 2048 studs) and may have adverse effects due to its size and how rendering is handled. The second guess relates to the constant CFraming. Although CFrame is generally fast, in this case it’s a huge render update. I make this guess because standing still for a short time (not moving my camera) will allow the game to return to 60 FPS, though it takes a couple of seconds.

Is there a better way to go about making realistic fog? What are other solutions people have had to this problem?

6 Likes

On line 4 of the function I gave, you can see that I am indeed using a mesh (BallMesh.Scale = Vector3.new(FogDist, FogDist, FogDist) * -2). I already factored in the lag caused by large parts. Additionally, parts cannot exceed 2048 on any axis for their size, so a mesh was absolutely necessary.

Oops, I misread that part. I apologize.

1 Like

Are you sure the fog is at fault? My GTX 1050 TI can run it fine. No lag drops, nor any noticable lag.

It sounds like you are generating a lot of physics overhead for the Roblox engine based on your described behavior. I would try several things:

  1. Set CanCollide property of the ball to false.
  2. Change the collision group of the ball using PhysicsService.
  3. Use the Offset property of FileMeshes instead of CFraming the ball part.

Point #3 sounds compelling.

I already have collision off (Given that it’s a small, anchored part, the collision shouldn’t be a problem anyway as there’s no interactions it has to do, and there’s no unanchored parts anywhere nearby with the exception of my avatar).

I’ll tinker with #2 and #3 to see what I can get to happen. I’ll edit my post after testing.

Update: Neither of those two solutions were successful. Good insight nonetheless.

You could try and decrease how fast your fog updates. Since you are updating on RenderStepped, you are updating every frame. Try decreasing the amount of time your fog updates (probably using a while loop)

That may be the one solution I have to take.

Through more testing I found that it doesn’t lag if the ball has Transparency=1, and it doesn’t lag if I simply don’t CFrame the ball.

This likely confirms my speculation that the lag stems from the render workload.

Update: Unfortunately the workload seems to be so heavy that even delay times of 0.2 result in lag.

I’m going to try something else where I use a flat plane situated in front of the camera to see how that ends up working.

Transparent parts can be incredibly expensive.

The transparency of the part is only >0 and <1 when transitioning from one state to another. Any other fog state will have the transparency at 0 or 1, nothing in between.

A bit of an update, using a plane in front of the camera has notable performance increases though they could use to be perfected.

In order to determine the size of the plane, I use this function:

function GetPlaneSize(Distance)
	local VPSize = workspace.CurrentCamera.ViewportSize
	local Aspect = VPSize.X / VPSize.Y
	local vFOV = math.rad(workspace.CurrentCamera.FieldOfView)
	local Y = 2 * math.tan(vFOV / 2) * math.abs(Distance)
	local X = Y * Aspect
	return X, Y
end

I should be able to make use of this by giving it some extra size to compensate for the slight delay from moving when it’s not bound to RenderStepped

You don’t need the whole sphere. You should only use the section of the sphere that the user can see. Roblox could be trying to render shadows or lighting from all around the sphere every time it is updated which could be the cause of your increased frame times.

Edit: combing this with your GetPlaneSize function could look just as good as using a whole sphere, although you may have to tweak it slightly to accommodate for the different shape

A bit of an update, I have come up with probably one of the single hackiest solutions I’ve made to-date.

I was able to get around this lag by attaching a BillboardGui to Terrain. The BillboardGui’s size is set to UDim2.new(W, 0, H, 0) where W and H are the return values from GetPlaneSize(Distance) (See response #12).

There is a Frame in this BillboardGui that has its color set to the fog’s color. The BillboardGui’s StudsOffsetWorldSpace property is set to the position of where the part would normally be, and the rotation is handled automatically by the UI element.

This isn’t really the best way in the aspect of long-term support (for instance, Roblox may deem it a good idea to not have fog affect BillboardGui, although I doubt this will be the case given that there was an update to add this a while ago), but it is most certainly functional and lagless.

5 Likes

I haven’t got the foggiest why you aren’t just using Roblox fog at this point tbh

2 Likes

This is what I’m currently doing, except I’m adjusting the container part’s position on RenderStep, instead of StudsOffsetWorldSpace. I think your method is better performance wise, so I think ill use that instead :slight_smile:

1 Like

I am using roblox fog – Roblox fog just doesn’t block out the sky, and that’s not realistic.

2 Likes

The Billboard gui is used to make the fog affect the skybox. (fog affects BillboardGuis)

It makes the fog much more effective at setting a lighting mood, you dont want evil black fog with a clearly visible pink skybox that has hearts on it :stuck_out_tongue:

nice pun lol

1 Like

This is a bit of a necro bump, but scaling up meshes or parts in Roblox to very large sizes always causes a lot of lag, instead scale the mesh inside blender or some other 3d modelling software before importing it. You won’t have any frame drops with it. It’s weird.

Does importing it as a large model really have a notable performance improvement? I may tinker with that to see how it works out and what it can do.

What do you mean ‘StudsOffsetWorldSpace property is set to the position of where the part would normally be’ ; I’m setting StudsOffsetWorldSpace to game.Workspace.CurrentCamera.CFrame.Position . Is that correct? It isn’t working correctly for me