Frame rate independent lerp?

Hello, fellow enthusiasts. I am in the urge of enlightenment! How can delta time be tied with a function that is running every frame to make the function scale accordingly? Let me show You what I mean:

30 frames per second

240 frames per second

The higher the frame rate the stiffer it gets. I am aiming to make it as similar as possible no matter the frame rate. I can not figure out what to do with delta time to get the best results.

local RunService = game:GetService("RunService")

local camera = workspace.CurrentCamera
local cameraCharacter = camera:WaitForChild("CameraCharacter")

RunService.RenderStepped:Connect(function(deltaTime)
    -- Just +, -, *, / a by delta time does little to no difference
    local a = 0.5
    local lookVector0 = cameraCharacter.PrimaryPart.CFrame.LookVector
    local lookVector1 = camera.CFrame.LookVector

    local position = camera.CFrame.Position
    local lookVector = lookVector0:Lerp(lookVector1, a)

    local cFrame = CFrame.new(position, position + lookVector)
    cameraCharacter.PrimaryPart.CFrame = cFrame
end)
10 Likes

I think ^ (1/deltaTime) is what you’re looking for. You’ll also need to increase “a” to around .988 to keep your current behaviour (@60fps).

3 Likes

Thank You for suggesting. These are the results when a = 0.99 ^ (1 / deltaTime):
30 fps
240 fps
It is not perfect but it does look more accurate. There might be a better way.

Possibly try replacing the exponent sign (^) with multiplication (*)?

Thanks,
Unfortunately, a * (1 / deltaTime) is greater than 1, therefore the lerp just breaks:
60 fps

Ah, missed that - if you just multiply by the delta time rather than 1 / delta time it might work :slightly_smiling_face:

1 Like

I am a colleague of @LeikaZ. And since he is sleeping… Because it is 03:00… Anyways. The lerp is very slow when multiplying deltaTime by values from 0 to 1. So the variable would end up something like a = 2 * deltaTime. This would mean that a could get higher than 1, therefore mess up the lerp. I have adjusted the variable to a = 0.5 * (1 - deltaTime). But even then the results were far from perfect:

i had a similar problem but since im bad at math i just took a large number (like if i did .1 i’d do 100 or something) and then multiplied by delta time, it takes some experimenting but it might help??

I believe changing a to a / (1/deltaTime)/maxFPS should do the trick. If the game is running at 240 fps and deltatime is 1/240 this should yield 240/maxFPS so an a of 0.5/1. If the game is running at half speed (120/240) you’ll get an a of 0.5/0.5 so 1 (twice the default in twice the time). Now you can clamp a like this to make sure it doesn’t lerp too far: math.clamp(a, 0, 1)

1 Like

Thank You for replying, @Hexcede. Unfortunately, that does not seem to be the answer either… The lerp becomes too stiff on low FPS compared to mid/high FPS.

Here are the results when a = math.clamp(0.25 / ((1 / deltaTime) / 144), 0, 1):

not into maths but, how about a = 0.2 * deltaTime * 30
meaning a would be around 0.2 when fps is 30, would be 0.1 when fps is 60, and 0.4 when fps is 15, lower the fps goes it would go towards 1, and eventualy exceed it, not sure how you’d keep the same effect under 8-10 fps but i’m sure nobody will be playing the game at that framerate… you can probably clamp it so it doesn’t go over 0.8 or 0.9 with a = math.min(0.2*deltaTime*30, 0.8).
otherwise i think lerp wouldn’t work.

2 Likes

Thank You for replying. This method works pretty well with any frame rate over 30. Here are the results when a = math.min(0.8 * deltaTime * 30, 0.9):

30 FPS

60 FPS

240 FPS

We are still open for suggestions.

i found this on the internet, i suggest you read it, might help what you’re looking for, not in roblox LUA but it’s still understandable:

I don’t know the answer, but I think we’d all have a better chance of getting it if we discussed the issue, rather than just say “try X”.

How is DeltaTime usually used?

When moving a part to be frame rate independent, you define a Speed in StudPerSecond. Because you know how much time has passed, you know how many studs it’ll have moved between frames.

-- Typed on mobile

RS.Heartbeat:Connect(function(DeltaTime)
  Part.CFrame = Part.CFrame * CFrame.new(0,0,-Speed*DeltaTime)
end)

How can we apply this concept to lerps?

With OP’s current implementation, the lerps have no defined speed. DeltaTime therefore doesn’t give us useful information, because knowing how much time has passed doesn’t help if we don’t know how much it moves per time increment. They are therefore frame rate dependant.

The question is now how do we set a Speed for a lerp? OP clearly wants non-linear motion, so it gets even more complicated when you take that into account.
Let’s start with linear motion, and then we’ll figure out how we can implement an easing function.

1 Like

A very good article on this topic: USING LERP WITH DELTA-TIME,
and the answer is a = lerp(a, b, 1 - f ^ dt)

3 Likes

Hey, you might have linked a wrong link. What does a, b and f stand for? Thanks!

I will post the answer here.
a - start value
b - result value
f - time
a = lerp(a, b, 1 - f ^ dt)

It can be easily adjusted to a vector lerp:
local currentVector = currentVector:Lerp(targetVector, 1 - howFastItLerps ^ deltaTime)

PERFECT ACCURACY

The answer is not entirely obvious. It’s actually:

a = lerp(a, b, 1 - f ^ dt)

…where f is the factor between 0 and 1 deciding how quickly it catches up, e.g. 0.25. In fact it works out that this is the remaining factor per second, so if it’s 0.25 it means it covers 75% of the remaining distance every second - independent of the framerate!

So why does this work? Since we cover 1 - f^dt, the remaining distance is f^dt. Over n frames, the remaining distance is thus (f^dt)^n. Again we can assume a steady framerate and that dt is equal to 1 / n . This gives the remaining distance over one second at n frames per second as (f^(1 / n))^n. If you know your powers, you know that (x^a)^b = x^ab. So we can simplify and see that the remaining distance after one second is f^1 = f. So it’s covered the same distance in one second regardless of the framerate!

This has the nice quality of being able to easily decide the percentage of the distance it covers every second. As before using f = 0.25 means it covers 75% of the distance regardless of the framerate, whereas trying to figure out the multiplier when just using f * dt to get it to cover 75% of the distance per second is very tricky (actually mathematically impossible, since it depends on the framerate), and probably just means guessing a few numbers and eyeballing it. Also as long as 0 < f < 1 , it will never overshoot.

19 Likes

Completely off topic but how is your client surpassing 60fps? Unless you’ve done some sort of modifying the Roblox client to remove the frame cap, I can only suspect you are somehow hooked up to physics updates (which according to my recollection go up to 240hz)…

I’m using a Roblox FPS Unlocker by axstin. During the last RDC staff confirmed that it’s allowed.

3 Likes

Ah, I see. Good to know!

filler