Seamless Looping Notes
I just read “A Shader Trick” and it blew my mind. It took me a little while to understand and ultimately motivated me to write down all the various looping techniques I’ve used. I think the “trick” then naturally comes up as a solution to a deficiency in the more naive techniques. So, join me on a short survey of looping techniques.
Technique 1: Modular Arithmetic Loops
This approach is the simplest and works extremely well for discrete steps that loop. For example, with flip book animation.
The flipbook above has 9 frames. We can figure out the current frame by doing: currentFrame = totalPassedFrames % 9
. This always provides a value between 0 and 9 (inclusive). The code example is oversimplified because you’ll also want to hold frames for some length of time, but that’s basically modular arithmetic looping.
This works great for anything with discrete states from 0
to N
that need to loop. The loop is achieved by taking an incrementing frame counter and using modulus to constrain it within the [0, N]
bounds.
Technique 2: Duplicate overlapping offset systems
What if you have an artistic system that starts and ends, but you want to loop it forever? This is where duplicated overlapping offset systems (DOOS) come in.
Let’s say I have this spritesheet:
Which creates this flipbook animation:
This is a single firework starting, flying up, and exploding. How can I make this a seamless loop? You can create multiple systems and offset them. As long as the system fits within the video timeline, it can play to completion and then loop naturally. Below I’ve offset 3 of the same firework animation (and rotated the hue).
You won’t look at this and think “wow, that’s a sophisticated looping technique.” But it’s a foundation that can be used with other techniques to achieve looping. The main takeaway is that you start with a super simple animation like the single firework, and then duplicate that animation (or add other animations) to create a complex looping final composition. This final composition creates a feeling of chaos and causes the loop point to become hard to detect, but you maintain artistic control over the individual elements.
In the past I would see animations like that fireworks display and try to animate it all as a single flipbook. This was a massive fail because it was incredibly complex to manage all the independent loops.
Here’s a completely different scene that I made in Blender that utilizes DOOS.
The video is 12 seconds long and loops. Can you spot the loop point?
What is interesting about this video is that it contains a particle system that is 24 seconds long (twice as long as the video itself). To create a seamless loop, I needed to overlap the particle system with itself! Doing this is pretty easy. You basically chop the particle system timeline into two 12 second halves, then you overlap them. The end of the first half seamlessly loops into the start of the second half (at the loop point of the video).
I really love how this turned out and it’s really hard to spot the loop point as each particle system melds into the other. The power of DOOS is that it allows you to decompose individual elements, offset their timing in the timeline, and get a very complex scene without you needing to draw the complex scene frame by frame.
Technique 3: Naturally periodic functions
Often your game has a timer that increments – maybe the number of milliseconds (ms) since the
game started. In fact, you’ve been on this very article for
If you have this number that is incrementing, it’s natural to reach for
functions that are naturally periodic, like sin
or cos
.
Just pumping the time since the media started into a periodic function gives you really cool looping visuals like:
Except, this has flaws.
- This only works if you have a monotonically increasing counter. In this case if we ever reset the “time since you opened the page” there will be a jump-cut, so it’s not seamless.
- Because the time monotonically increases eventually the numbers get huge, and we run into floating point inaccuracies.
So basically, you shouldn’t really use this technique because there is a better technique, which is…
Technique 4: Mathematically Seamless Loops
This is the “trick” from the Witness blog post, and it’s amazing because it solves the problems with the previous technique. It enables resetting time to 0 after a chosen “fixed total time”. It provides a simple formula for providing “user editable tweak values” that allow an artist to tweak the loop timing without breaking the seamless nature.
Let me show you, have a play with this demo. Note that perfect loops are accomplished when the time is reset:
So the key idea is:
If you want to loop something N
times within your total duration, then N
must be an integer.
This sounds obvious, but it’s the foundation that makes this work. Basically, if you make the fixed loop point sufficiently long then you can define how many “complete” loops the animation makes before everything repeats.
So how can this look like in the code? Well, you divide your current time with
the total time giving you a normalized time between [0, 1)
. Then you can apply
integer tweak values that will never break seamlessness as long as the function
is periodic in the domain [0, 1)
.
For example sin(normalized_time * 2 * Math.PI)
is periodic about the domain
[0. 1)
. If you multiply normalized_time
by an integer that integer
represents the total number of loops the function can do in the total time. I.e.,
sin(normalized_time * tweak * 2 * Math.PI)
In the demo above, the sliders are modifying tweak values which provide artistic control over the loop. And since the sliders that multiply against the normalized time are integers they will always seamlessly loop.
The trick in “The Witness” blog post focuses on a special property of total
times that are powers of 10. Instead of normalizing the time, you can instead
allow the designer to provide a tweak value that has the number of decimals less
than or equal to the total loop times power of 10. As a single example, if your
total loop time is 100
, then the tweak value must not have more than 2 decimal
places. This makes sense because the smallest decimal you can represent is
0.01
where 0.01 * 100 = 1
! Oh, now the tweak value encodes the total time
normalization operation. Read “A Shader
Trick” for more details
around this technique, but I think I prefer working on a domain between [0, 1)
.
Conclusion
All of these can be mixed and matched and have their place. A fun thing to consider is how the mathematical approach can be combined with DOOS.
Go forth and loop!