Fixing Jittery CFrame Lerping

The first-person arm rig in my FPS project uses CFrame’s lerp method to create a swaying effect (lerp is called every frame in a RenderStepped function). It works nicely and I like its simplicity, but it always appears really jittery.

Here’s a gif displaying the jittery-ness I’m talking about. Hopefully you can see how the rig stutters across the screen.
https://i.gyazo.com/f65ef13c11a6c34d71006850f9801de9.mp4

Is there a good way to alleviate this jittering by lerping “better” or some other way?

1 Like

It seems like it only jitters when you are moving. How exactly are you going about the CFraming? Are you using a weld/constraint, or are you editing the actual CFrame?

It only jitters when the player moves because the CFrame of the arm rig is set with a line that looks like this (again, called every frame):

armrig:SetPrimaryPartCFrame(armrig.PrimaryPart.CFrame:Lerp(cameraCF, smallNum))

(“armrig” is the arm rig, “cameraCF” is the camera’s CFrame, and “smallNum” is a tiny base two number that I can’t remember off the top of my head)

If the position of the arm rig’s primary part is less than .001 studs away from cameraCF, my code sets the arm rig’s CFrame to be exactly cameraCF. Thus, the lerping only really happens when the player is walking or moving their camera.

Even in RenderStepped you need to make sure the values you are lerping are offsets relative to the camera. It looks like you’re lerping the resulting world position, pulling the weapon toward where you were standing the frame (or 2, or 3…) before.

EDIT: Now that I see your code, which was not what I was replying to, the question becomes what is cameraCF… the previous renderstep’s camera location (bad), or the next frame’s (good). Renderstep bind priority matters here. But my point above still applies 100%. You need to move the arm to the new (next frame) camera location, then offset it, otherwise it’s going to lag behind when you move. You want the sway to be independent from camera motion through the world. What you have it doing is chasing the camera by a fixed amount (fixed fraction of the position change, that is, not fixed distance) that doesn’t take camera velocity into consideration.

6 Likes

It’s bound to render step here: Enum.RenderPriority.Camera.Value + 1, which I thought would solve the problem.

Although I have a second function bound to render step with the RenderStepped event which updates a few other values that may have an effect on the lerping. I may try combining the two render step functions into one bound right after the camera update and see if that helps.

EDIT: I typed this before seeing your note on camera velocity. How might I take camera velocity into consideration?

That part is good. Binding before the camera updated would have added another source of jitter (it could look quite similar, lag proportional to camera speed).

What I mean is calculate your sway as an offset from the camera, as if the camera were stationary. Then, add this offset to the camera position on each renderstep. What you have now is the arm always lerping to approach the camera’s world position, which is like having the camera pull the arm along on bungie cord, not what you want.

8 Likes

Here’s what I changed my above line of code to:

-- Called every render step at Enum.RenderPriority.Camera.Value + 1
armrig:SetPrimaryPartCFrame(prevCameraCF:Lerp(cameraCF, smallNum))
prevCameraCF = camera.CFrame

I’m not sure if this is exactly what you’re talking about, and there is still a slight amount of jitter with this, but it’s reduced enough that I don’t notice it unless I’m looking for it, which is better.

1 Like

I was a bit worried you were doing the calculation in world coordinates. What AllYourBlox meant is that you should store the sway as a relative CFrame, say ‘offsetCFrame’, then set armrig’s CFrame to camera.CFrame:ToWorldSpace(offsetCFrame).

1 Like

This moves the arm to some point between old and new camera locations. I’m unclear now as to what the “sway” is you’re talking about. “Sway” generally means a periodic back-and-forth or side-to-side motion, but this code suggests what you might actually be looking for is for the arms to offset in the opposite the direction of character travel? Lerping with a small fixed value will not generate (or more accurately, introduce) any kind of rhythmic sway. Can you clarify or show us an example of the effect you are trying to achieve?

By “sway” in this case, I meant a slight drag. As in, if you quickly move the camera, the gun won’t immediately point toward where you are now looking, but arrives there a few frames later. The other kind of “sway,” the rhythmic one, is done with Lissajous curves and works quite well.

1 Like

Slightly off topic, but please don’t use SetPrimaryPartCFrame for this. Over repeated usages you’ll accumulate rounding errors and the parts will start to fly away from each other.

I can understand where animating something like a door using SetPrimaryPartCFrame would eventually run into rounding errors, but the parts in my rig are connected with Motor6Ds for animation with the plugin. Does the rounding error problem still apply for Motor6Ds? If so, what should I use instead?

If they’re held together by those constraints, then why do you bother using setprimarypartcframe? you may as well directly set the cframe of the primary part.

2 Likes

The last little bit of jitter you see is just the consequence of the variable length of time between rendersteps. Animation you do by setting position or CFrame on renderstep is never going to be perfectly smooth if it doesn’t take into account the actual amount of time that has passed. Your update uses a lerp with a fixed second argument, it’s not a function of tick() or of the time delta passed to the function bound to RenderStepped. Because of this, you can see slight variations in framerate as jitter. We’re really sensitive to this too.

I don’t want to deprive you of the joy and reward of working this out, but I made an example just so that you can see sort of what the possible smoothness looks like, so that expectations are reasonable. And if you can’t get something working that you like, I’ll share this code example in the tutorial area.

3 Likes

I’ll probably figure out some tick()/delta based solution sometime in the future. But my current solution works well enough for now.