Skip to content

Commit 0166fee

Browse files
committed
Physics interpolation docs
1 parent ba10a47 commit 0166fee

9 files changed

+348
-0
lines changed

tutorials/physics/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ Physics
1414
kinematic_character_2d
1515
soft_body
1616
collision_shapes_3d
17+
interpolation/index
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
.. _doc_advanced_physics_interpolation:
2+
3+
Advanced physics interpolation
4+
==============================
5+
6+
Although the previous instructions will give satisfactory results in a lot of games, in some cases you will want to go a stage further to get the best possible results and the smoothest possible experience.
7+
8+
Exceptions to automatic physics interpolation
9+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10+
11+
Even with physics interpolation active, there may be some local situations where you would benefit from disabling automatic interpolation for a :ref:`Node<class_Node>` (or branch of the :ref:`SceneTree<class_SceneTree>`), and have the finer control of performing interpolation manually.
12+
13+
This is possible using the :ref:`Node.set_physics_interpolated<class_Node_method_set_physics_interpolated>` function which is present in all Nodes. If you for example, set this interpolated flag to false for a Node, the children will recursively also be affected. This means you can easily disable interpolation for an entire subscene.
14+
15+
The most common situation where you may want to perform your own interpolation is Cameras.
16+
17+
Cameras
18+
^^^^^^^
19+
20+
In many cases, a :ref:`Camera<class_Camera>` can use automatic interpolation just like any other node. However, for best results, especially at low physics tick rates, it is recommended that you take a manual approach to Camera interpolation.
21+
22+
This is because viewers are very sensitive to Camera movement. For instance, a Camera that realigns slightly every 1/10th of a second (at 10tps tick rate) will often be noticeable. You can get a much smoother result by moving the Camera each frame in ``_process``, and following an interpolated target manually.
23+
24+
Manual Camera interpolation
25+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
27+
**Ensure the Camera is using global coordinate space**
28+
29+
The very first step when performing manual Camera interpolation is to make sure the Camera transform is specified in *global space* rather than inheriting the transform of a moving parent. This is because feedback can occur between the movement of a parent node of a Camera and the movement of the Camera Node itself, which can mess up the interpolation.
30+
31+
There are two ways of doing this:
32+
33+
1) Move the Camera so it is independent on its own branch, rather than being a child of a moving object.
34+
35+
.. image:: img/fti_camera_worldspace.png
36+
37+
2) Call :ref:`Spatial.set_as_toplevel<class_Spatial_method_set_as_toplevel>` and set this to ``true``, which will make the Camera ignore the transform of its parent.
38+
39+
Typical example
40+
^^^^^^^^^^^^^^^
41+
42+
A typical example of a custom approach is to use the ``look_at`` function in the Camera every frame in ``_process()`` to look at a target node (such as the player).
43+
44+
But there is a problem. If we use the traditional ``get_global_transform()`` on a Camera "target" Node, this transform will only focus the Camera on the target *at the current physics tick*. This is *not* what we want, as the Camera will jump about on each physics tick as the target moves. Even though the Camera may be updated each frame, this does not help give smooth motion if the *target* is only changing each physics tick.
45+
46+
get_global_transform_interpolated()
47+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48+
49+
What we really want to focus the Camera on, is not the position of the target on the physics tick, but the *interpolated* position, i.e. the position at which the target will be rendered.
50+
51+
We can do this using the :ref:`Spatial.get_global_transform_interpolated<class_Spatial_method_get_global_transform_interpolated>` function. This acts exactly like getting :ref:`Spatial.global_transform<class_Spatial_property_global_transform>` but it gives you the *interpolated* transform (during a ``_process()`` call).
52+
53+
.. important:: ``get_global_transform_interpolated()`` should only be used once or twice for special cases such as Cameras. It should **not** be used all over the place in your code (both for performance reasons, and to give correct gameplay).
54+
55+
.. note:: Aside from exceptions like the Camera, in most cases, your game logic should be in ``_physics_process()``. In game logic you should be calling ``get_global_transform()`` or ``get_transform()``, which will give the current physics transform (in global or local space respectively), which is usually what you will want for gameplay code.
56+
57+
Example manual Camera script
58+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59+
60+
Here is an example of a simple fixed Camera which follows an interpolated target:
61+
62+
.. code-block:: python
63+
64+
extends Camera
65+
66+
# Node that the camera will follow
67+
var _target
68+
69+
# We will smoothly lerp to follow the target
70+
# rather than follow exactly
71+
var _target_pos : Vector3 = Vector3()
72+
73+
func _ready() -> void:
74+
# Find the target node
75+
_target = get_node("../Player")
76+
77+
# Turn off automatic physics interpolation for the Camera,
78+
# we will be doing this manually
79+
set_physics_interpolated(false)
80+
81+
func _process(delta: float) -> void:
82+
# Find the current interpolated transform of the target
83+
var tr : Transform = _target.get_global_transform_interpolated()
84+
85+
# Provide some delayed smoothed lerping towards the target position
86+
_target_pos = lerp(_target_pos, tr.origin, min(delta, 1.0))
87+
88+
# Fixed camera position, but it will follow the target
89+
look_at(_target_pos, Vector3(0, 1, 0))
90+
91+
Mouse look
92+
^^^^^^^^^^
93+
94+
Mouse look is a very common way of controlling Cameras. But there is a problem. Unlike keyboard input which can be sampled periodically on the physics tick, mouse move events can come in continuously. The Camera will be expected to react and follow these mouse movements on the next frame, rather than waiting until the next physics tick.
95+
96+
In this situation, it can be better to disable physics interpolation for the Camera node (using :ref:`Node.set_physics_interpolated<class_Node_method_set_physics_interpolated>`) and directly apply the mouse input to the Camera rotation, rather than apply it in ``_physics_process``.
97+
98+
Sometimes, especially with Cameras, you will want to use a combination of interpolation and non-interpolation:
99+
100+
* A first person camera may position the camera at a player location (perhaps using :ref:`Spatial.get_global_transform_interpolated<class_Spatial_method_get_global_transform_interpolated>`), but control the Camera rotation from mouse look *without* interpolation.
101+
* A third person camera may similarly determine the look at (target location) of the camera using :ref:`Spatial.get_global_transform_interpolated<class_Spatial_method_get_global_transform_interpolated>`, but position the camera using mouse look *without* interpolation.
102+
103+
There are many permutations and variations of Camera types, but it should be clear that in many cases, disabling automatic physics interpolation and handling this yourself can give a better result.
104+
105+
Disabling interpolation on other nodes
106+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
107+
108+
Although Cameras are the most common example, there are a number of cases when you may wish other nodes to control their own interpolation, or be non-interpolated. Consider for example, a player in a top view game whose rotation is controlled by mouse look. Disabling physics rotation allows the player rotation to match the mouse in real-time.
109+
110+
111+
MultiMeshes
112+
^^^^^^^^^^^
113+
114+
Although most visual Nodes follow the single Node single visual instance paradigm, MultiMeshes can control several instances from the same Node. Therefore, they have some extra functions for controlling interpolation functionality on a *per-instance* basis. You should explore these functions if you are using interpolated MultiMeshes.
115+
116+
- :ref:`MultiMesh.reset_instance_physics_interpolation<class_MultiMesh_method_reset_instance_physics_interpolation>`
117+
- :ref:`MultiMesh.set_as_bulk_array_interpolated<class_MultiMesh_method_set_as_bulk_array_interpolated>`
118+
119+
Full details are in the :ref:`MultiMesh<class_MultiMesh>` documentation.
7.5 KB
Loading
10.5 KB
Loading
27.3 KB
Loading
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.. _doc_physics_interpolation:
2+
3+
Physics Interpolation
4+
=====================
5+
6+
.. toctree::
7+
:maxdepth: 1
8+
:name: toc-physics-interpolation
9+
10+
physics_interpolation_quick_start_guide
11+
physics_interpolation_introduction
12+
using_physics_interpolation
13+
advanced_physics_interpolation
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
.. _doc_physics_interpolation_introduction:
2+
3+
Introduction
4+
============
5+
6+
Physics ticks and rendered frames
7+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8+
9+
One key concept to understand in Godot is the distinction between physics ticks (sometimes referred to as iterations or physics frames), and rendered frames. The physics proceeds at a fixed tick rate (set in :ref:`ProjectSettings.physics/common/physics_fps<class_ProjectSettings_property_physics/common/physics_fps>`), which defaults to 60 ticks per second.
10+
11+
However, the engine does not necessarily **render** at the same rate. Although many monitors refresh at 60 Hz (cycles per second), many refresh at completely different frequencies (e.g. 75 Hz, 144 Hz, 240 Hz or more). Even though a monitor may be able to show a new frame e.g. 60 times a second, there is no guarantee that the CPU and GPU will be able to *supply* frames at this rate. For instance, when running with V-Sync, the computer may be too slow for 60 and only reach the deadlines for 30 FPS, in which case the frames you see will change at 30 FPS (resulting in stuttering).
12+
13+
But there is a problem here. What happens if the physics ticks do not coincide with frames? What happens if the physics tick rate is out of phase with the frame rate? Or worse, what happens if the physics tick rate is *lower* than the rendered frame rate?
14+
15+
This problem is easier to understand if we consider an extreme scenario. If you set the physics tick rate to 10 ticks per second, in a simple game with a rendered frame rate of 60 FPS. If we plot a graph of the positions of an object against the rendered frames, you can see that the positions will appear to "jump" every 1/10th of a second, rather than giving a smooth motion. When the physics calculates a new position for a new object, it is not rendered in this position for just one frame, but for 6 frames.
16+
17+
.. image:: img/fti_graph_fixed_ticks.png
18+
19+
This jump can be seen in other combinations of tick / frame rate as glitches, or jitter, caused by this staircasing effect due to the discrepancy between physics tick time and rendered frame time.
20+
21+
What can we do about frames and ticks being out of sync?
22+
--------------------------------------------------------
23+
24+
Lock the tick / frame rate together?
25+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
27+
The most obvious solution is to get rid of the problem, by ensuring there is a physics tick that coincides with every frame. This used to be the approach on old consoles and fixed hardware computers. If you know that every player will be using the same hardware, you can ensure it is fast enough to calculate ticks and frames at e.g. 50 FPS, and you will be sure it will work great for everybody.
28+
29+
However, modern games are often no longer made for fixed hardware. You will often be planning to release on desktop computers, mobiles and more, all of which have huge variations in performance, as well as different monitor refresh rates. We need to come up with a better way of dealing with the problem.
30+
31+
Adapt the tick rate?
32+
^^^^^^^^^^^^^^^^^^^^
33+
34+
Instead of designing the game at a fixed physics tick rate, we could allow the tick rate to scale according to the end users hardware. We could for example use a fixed tick rate that works for that hardware, or even vary the duration of each physics tick to match a particular frame duration.
35+
36+
This works, but there is a problem. Physics (*and game logic*, which is often also run in the ``_physics_process``) work best and most consistently when run at a **fixed**, predetermined tick rate. If you attempt to run a racing game physics that has been designed for 60 TPS (ticks per second) at e.g. 10 TPS, the physics will behave completely differently. Controls may be less responsive, collisions / trajectories can be completely different. You may test your game thoroughly at 60 TPS, then find it breaks on end users machines when it runs at a different tick rate.
37+
38+
This can make quality assurance difficult with hard to reproduce bugs, especially in AAA games where problems of this sort can be very costly. This can also be problematic for multiplayer games for competitive integrity, as running the game at certain tick rates may be more advantageous than others.
39+
40+
Lock the tick rate, but use interpolation to smooth frames in between physics ticks
41+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42+
43+
This has become one of the most popular approaches to dealing with the problem. It is supported by Godot 3.5 and later in 3D (although it is optional and disabled by default).
44+
45+
We have established that the most desirable physics/game logic arrangement for consistency and predictability is a physics tick rate that is fixed at design-time. The problem is the discrepancy between the physics position recorded, and where we "want" a physics object to be shown on a frame to give smooth motion.
46+
47+
The answer turns out to be simple, but can be a little hard to get your head around at first.
48+
49+
Instead of keeping track of just the current position of a physics object in the engine, we keep track of *both the current position of the object, and the previous position* on the previous physics tick.
50+
51+
Why do we need the previous position *(in fact the entire transform, including rotation and scaling)*? By using a little math magic, we can use **interpolation** to calculate what the transform of the object would be between those two points, in our ideal world of smooth continuous movement.
52+
53+
.. image:: img/fti_graph_interpolated.png
54+
55+
Linear interpolation
56+
^^^^^^^^^^^^^^^^^^^^
57+
58+
The simplest way to achieve this is linear interpolation, or lerping, which you may have used before.
59+
60+
Let us consider only the position, and a situation where we know that the previous physics tick X coordinate was 10 units, and the current physics tick X coordinate is 30 units.
61+
62+
.. note:: Although the maths is explained here, you do not have to worry about the details, as this step will be performed for you. Under the hood, Godot may use more complex forms of interpolation, but linear interpolation is the easiest in terms of explanation.
63+
64+
The physics interpolation fraction
65+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66+
67+
If our physics ticks are happening 10 times per second (for this example), what happens if our rendered frame takes place at time 0.12 seconds? We can do some math to figure out where the object would be to obtain a smooth motion between the two ticks.
68+
69+
First of all, we have to calculate how far through the physics tick we want the object to be. If the last physics tick took place at 0.1 seconds, we are 0.02 seconds *(0.12 - 0.1)* through a tick that we know will take 0.1 seconds (10 ticks per second). The fraction through the tick is thus:
70+
71+
.. code-block:: python
72+
73+
fraction = 0.02 / 0.10
74+
fraction = 0.2
75+
76+
This is called the **physics interpolation fraction**, and is handily calculated for you by Godot. It can be retrieved on any frame by calling :ref:`Engine.get_physics_interpolation_fraction<class_Engine_method_get_physics_interpolation_fraction>`.
77+
78+
Calculating the interpolated position
79+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
80+
81+
Once we have the interpolation fraction, we can insert it into a standard linear interpolation equation. The X coordinate would thus be:
82+
83+
.. code-block:: python
84+
85+
x_interpolated = x_prev + ((x_curr - x_prev) * 0.2)
86+
87+
So substituting our ``x_prev`` as 10, and ``x_curr`` as 30:
88+
89+
.. code-block:: python
90+
91+
x_interpolated = 10 + ((30 - 10) * 0.2)
92+
x_interpolated = 10 + 4
93+
x_interpolated = 14
94+
95+
Let's break that down:
96+
97+
- We know the X starts from the coordinate on the previous tick (``x_prev``) which is 10 units.
98+
- We know that after the full tick, the difference between the current tick and the previous tick will have been added (``x_curr - x_prev``) (which is 20 units).
99+
- The only thing we need to vary is the proportion of this difference we add, according to how far we are through the physics tick.
100+
101+
.. note:: Although this example interpolates the position, the same thing can be done with the rotation and scale of objects. It is not necessary to know the details as Godot will do all this for you.
102+
103+
Smoothed transformations between physics ticks?
104+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
105+
106+
Putting all this together shows that it should be possible to have a nice smooth estimation of the transform of objects between the current and previous physics tick.
107+
108+
But wait, you may have noticed something. If we are interpolating between the current and previous ticks, we are not estimating the position of the object *now*, we are estimating the position of the object in the past. To be exact, we are estimating the position of the object *between 1 and 2 ticks* into the past.
109+
110+
In the past
111+
^^^^^^^^^^^
112+
113+
What does this mean? This scheme does work, but it does mean we are effectively introducing a delay between what we see on the screen, and where the objects *should* be.
114+
115+
In practice, most people won't notice this delay, or rather, it is typically not *objectionable*. There are already significant delays involved in games, we just don't typically notice them. The most significant effect is there can be a slight delay to input, which can be a factor in fast twitch games. In some of these fast input situations, you may wish to turn off physics interpolation and use a different scheme, or use a high tick rate, which mitigates these delays.
116+
117+
Why look into the past? Why not predict the future?
118+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
119+
120+
There is an alternative to this scheme, which is: instead of interpolating between the previous and current tick, we use maths to *extrapolate* into the future. We try to predict where the object *will be*, rather than show it where it was. This can be done and may be offered as an option in future, but there are some significant downsides:
121+
122+
- The prediction may not be correct, especially when an object collides with another object during the physics tick.
123+
- Where a prediction was incorrect, the object may extrapolate into an "impossible" position, like inside a wall.
124+
- Providing the movement speed is slow, these incorrect predictions may not be too much of a problem.
125+
- When a prediction was incorrect, the object may have to jump or snap back onto the corrected path. This can be visually jarring.
126+
127+
Fixed timestep interpolation
128+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
129+
130+
In Godot this whole system is referred to as physics interpolation, but you may also hear it referred to as **"fixed timestep interpolation"**, as it is interpolating between objects moved with a fixed timestep (physics ticks per second). In some ways the second term is more accurate, because it can also be used to interpolate objects that are not driven by physics.
131+
132+
.. tip:: Although physics interpolation is usually a good choice, there are exceptions where you may choose not to use Godot's built-in physics interpolation (or use it in a limited fashion). An example category is internet multiplayer games. Multiplayer games often receive tick or timing based information from other players or a server and these may not coincide with local physics ticks, so a custom interpolation technique can often be a better fit.

0 commit comments

Comments
 (0)