Skip to content

Commit

Permalink
Add physics interpolation docs to 4.3
Browse files Browse the repository at this point in the history
  • Loading branch information
skyace65 committed Aug 13, 2024
1 parent 0447cfc commit 8c305dc
Show file tree
Hide file tree
Showing 10 changed files with 533 additions and 0 deletions.
1 change: 1 addition & 0 deletions tutorials/physics/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Physics
collision_shapes_3d
large_world_coordinates
troubleshooting_physics_issues
interpolation/index
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.. _doc_2d_and_3d_physics_interpolation:

2D and 3D physics interpolation
===============================

Generally 2D and 3D physics interpolation work in very similar ways. However,
there are a few differences, which will be described here.

.. note:: currently only 2D physics interpolation works in Godot.
3D interpolation is expected to come in a future update.

Global versus local interpolation
---------------------------------

- In 3D, physics interpolation is performed *independently* on the **global
transform** of each 3D instance.
- In 2D by contrast, physics interpolation is performed on the **local
transform** of each 2D instance.

This has some implications:

- In 3D, it is easy to turn interpolation on and off at the level of each
``Node``, via the ``physics_interpolation_mode`` property in the Inspector,
which can be set to ``On``, ``Off``, or ``Inherited``.

.. figure:: img/physics_interpolation_mode.webp
:align: center

- However this means that in 3D, pivots that occur in the ``SceneTree``
(due to parent child relationships) can only be interpolated
**approximately** over the physics tick. In most cases this will not
matter, but in some situations the interpolation can look slightly *off*.
- In 2D, interpolated local transforms are passed down to children during
rendering. This means that if a parent is set to
``physics_interpolation_mode`` ``On``, but the child is set to ``Off``,
the child will still be interpolated if the parent is moving. *Only the
child's local transform is uninterpolated.* Controlling the on / off
behaviour of 2D nodes therefore requires a little more thought and planning.
- On the positive side, pivot behaviour in the scene tree is perfectly
preserved during interpolation in 2D, which gives super smooth behaviour.

reset_physics_interpolation()
-----------------------------

Whenever objects are moved to a completely new position, and interpolation is
not desired (so as to prevent a "streaking" artefact), it is the
responsibility of the user to call ``reset_physics_interpolation()``.

The good news is that in 2D, this is automatically done for you when nodes
first enter the tree. This reduces boiler plate, and reduces the effort
required to get an existing project working.

.. note:: If you move objects *after* adding to the scene tree, you will still
need to call ``reset_physics_interpolation()`` as with 3D.

2D Particles
------------

Currently ``CPUParticles2D`` and ``Particles2D`` are not supported for physics
interpolation in 2D.
37 changes: 37 additions & 0 deletions tutorials/physics/interpolation/advanced_physics_interpolation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.. _doc_advanced_physics_interpolation:

Advanced physics interpolation
==============================

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.

.. note:: currently only 2D physics interpolation works in Godot.
3D interpolation is expected to come in a future update.

Exceptions to automatic physics interpolation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.

This is possible using the :ref:`Node.physics_interpolation_mode<class_Node_property_physics_interpolation_mode>`
property which is present in all Nodes. If you for example, turn off
interpolation for a Node, the children will recursively also be affected (as
they default to inheriting the parent setting). This means you can easily
disable interpolation for an entire subscene.

The most common situation where you may want to perform your own
interpolation is Cameras.

Disabling interpolation on other nodes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
14 changes: 14 additions & 0 deletions tutorials/physics/interpolation/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.. _doc_physics_interpolation:

Physics Interpolation
=====================

.. toctree::
:maxdepth: 1
:name: toc-physics-interpolation

physics_interpolation_quick_start_guide
physics_interpolation_introduction
using_physics_interpolation
advanced_physics_interpolation
2d_and_3d_physics_interpolation
244 changes: 244 additions & 0 deletions tutorials/physics/interpolation/physics_interpolation_introduction.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
.. _doc_physics_interpolation_introduction:

Introduction
============

Physics ticks and rendered frames
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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_ticks_per_second<class_ProjectSettings_property_physics/common/physics_ticks_per_second>`),
which defaults to 60 ticks per second.

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).

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?

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.

.. image:: img/fti_graph_fixed_ticks.webp

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.

What can we do about frames and ticks being out of sync?
--------------------------------------------------------

Lock the tick / frame rate together?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.

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.

Adapt the tick rate?
^^^^^^^^^^^^^^^^^^^^

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.

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.

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.

Lock the tick rate, but use interpolation to smooth frames in between physics ticks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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).

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.

The answer turns out to be simple, but can be a little hard to get your head
around at first.

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.

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.

.. image:: img/fti_graph_interpolated.webp

Linear interpolation
^^^^^^^^^^^^^^^^^^^^

The simplest way to achieve this is linear interpolation, or lerping, which
you may have used before.

.. note:: currently only 2D physics interpolation works in Godot.
3D interpolation is expected to come in a future update.

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.

.. 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.

The physics interpolation fraction
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.

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:

.. code-block:: python
fraction = 0.02 / 0.10
fraction = 0.2
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>`.

Calculating the interpolated position
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Once we have the interpolation fraction, we can insert it into a standard
linear interpolation equation. The X coordinate would thus be:

.. code-block:: python
x_interpolated = x_prev + ((x_curr - x_prev) * 0.2)
So substituting our ``x_prev`` as 10, and ``x_curr`` as 30:

.. code-block:: python
x_interpolated = 10 + ((30 - 10) * 0.2)
x_interpolated = 10 + 4
x_interpolated = 14
Let's break that down:

- We know the X starts from the coordinate on the previous tick (``x_prev``)
which is 10 units.
- 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).
- 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.

.. 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.

Smoothed transformations between physics ticks?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.

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.

In the past
^^^^^^^^^^^

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.

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.

Why look into the past? Why not predict the future?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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:

- The prediction may not be correct, especially when an object collides with
another object during the physics tick.
- Where a prediction was incorrect, the object may extrapolate into an
"impossible" position, like inside a wall.
- Providing the movement speed is slow, these incorrect predictions may not
be too much of a problem.
- When a prediction was incorrect, the object may have to jump or snap back
onto the corrected path. This can be visually jarring.

Fixed timestep interpolation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.

.. 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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.. _doc_physics_interpolation_quick_start_guide:

Quick start guide
=================

.. note:: currently only 2D physics interpolation works in Godot.
3D interpolation is expected to come in a future update.

- Turn on physics interpolation: :ref:`ProjectSettings.physics/common/physics_interpolation<class_ProjectSettings_property_physics/common/physics_interpolation>`
- Make sure you move objects and run your game logic in
``_physics_process()`` rather than ``_process()``. This includes moving
objects directly *and indirectly* (by e.g. moving a parent, or using
another mechanism to automatically move nodes).
- Be sure to call :ref:`Node.reset_physics_interpolation<class_Node_method_reset_physics_interpolation>`
on nodes *after* you first position or teleport them, to prevent
"streaking"
- Temporarily try setting :ref:`ProjectSettings.physics/common/physics_ticks_per_second<class_ProjectSettings_property_physics/common/physics_ticks_per_second>`
to 10 to see the difference with and without interpolation.
Loading

0 comments on commit 8c305dc

Please sign in to comment.