Giving Personality to Procedural Animations using Math

2.54M views2378 WordsCopy TextShare
t3ssel8r
It's been a while since the last video hasn't it? I've made quite a bit of progress since the last u...
Video Transcript:
In traditional vector animation, there is a concept called interpolation curves, which describes the way we move between one animation keyframe and the next Depending on the shape of this curve, we can control motion to slowly ease in, to slowly ease out, or even to anticipate and overshoot the keyframe The choice of animation curves can greatly affect the feeling of inertia and energy in the motion This grants the animator a good deal of artistic expression in describing motion while keeping the number of keyframes economical In procedural animation, however, it can be very powerful to do away
with the concept of keyframes entirely, freeing the system to move however the situation demands This adaptability is one of the main reasons to incorporate procedural animation in games For example, in the game I’m working on, I have this little robot character which moves its body and legs using inverse kinematics in response to realtime requests to move and change orientation I’m controlling it with my mouse here, and you can find tutorials for how to and you can find tutorials for how to implement this sort of thing online In game, this same flexibility allows the character to
immediately respond to changing gameplay situations This is pretty nice, but because we don’t have keyframes for these inputs, we can't really use interpolation curves to add that sense of inertia What we’d really like is the ability to achieve something like this, using an intuitive abstraction similar to interpolation curves, but without losing out on the dynamic responsiveness of a keyframe-free system Let’s start off by describing the problem we want to solve a bit more precisely Given some dynamically changing input x from our game world, we want to produce some dynamic output y which tracks the input
x, but is imbued with some sort of characteristic that conveys the way that we want the motion to feel In the simplest case, y=x, and our system rigidly conforms to whatever input we give it This isn’t very useful on its own, but it is a good starting point Whatever changes we make to this equation, we’ll want to ensure that in the long term, when x isn’t changing, y should eventually settle at x To introduce the possibility for interesting dynamics, let’s add some velocity and acceleration terms, represented here as first and second order derivatives with respect
to time, scaled by some values k1, k2, and k3 The dot notation here is just a shorthand way of writing time derivatives, which you may have also seen written like this, or this This type of relationship involving up to a second order time derivative is known as a second order system Mechanical motions in the real world tend to be second order, which you can think of as being related to the fact that forces act on acceleration, the second derivative of position, as described by Newton’s second law These forces usually arise out of stiffness, where they
are a function of displacement, or out of friction, where they are a function of velocity, hence our choice of terms For example, this is the equation of motion of a rigid body being dragged along one axis through a linear damped spring And this is the equation of motion for the SmoothDamp function in Unity By playing around with these values k1, k2, and k3, we can generate a whole host of different interesting behaviors, but as an artist, it might not be very intuitive how these values map to those different behaviors First, let’s simplify the visualization to
look at the response to a single sudden change in the input The resulting output is known as the step response, which is a standard way of visualizing the dynamics of a system, and captures the essence of the system’s behavior Next, using k1 k2 and k3, we’ll define 3 new variables: f, zeta, and r We’ll use f, zeta, and r (which I’ll explain in a moment) as the design parameters for the system’s motion Under the hood, we can use them to compute k1, k2, and k3 In this new parameterization of f, zeta, and r, each term
controls something that we can develop some reasonable intuition about f is the natural frequency of the system measured in Hz, or cycles per second It describes the speed at which the system will respond to changes in the input, as well as the frequency the system will tend to vibrate at, but does not affect the shape of the resulting motion zeta is known as the damping coefficient It describes how the system comes to settle at the target When zeta is 0, vibration never dies down, and the system is undamped Between values of 0 and 1, the
system is underdamped, and will vibrate more, or less, depending on the magnitude of zeta When zeta is greater than 1, the system will not vibrate, and instead slowly settle toward the target x, depending on the magnitude of zeta The SmoothDamp function in Unity uses a zeta equal to exactly 1 which is known as critical damping r is a value which controls the initial response of the system When r is 0, the system takes time to begin accelerating from rest When r is positive, the system reacts immediately to movement in x When r is greater than
1, the system will overshoot the target When r is negative, the system will anticipate the motion When modeling a mechanical connection, a value of 2 for r is typical Now, as artists, we have all we need to start playing with the characteristics of the motion of our procedurally animated object If we want to control the settling behavior of the system, we can play with zeta If we want to control the initial response of the system, we can play with r If we want to control how fast or slowly the system behaves, we can play with
f Coming back to this robot character, you can see I used an underdamped movement for the body with zeta of 0.5, and r of -2 to give it some anticipation before fast movements For a bit of extra flavor, I tilt the body towards the target position, to further hint at the intent behind the motion I want the turning to be smooth, so for the body orientation I use a zeta of 1 and r of 0 I do the same for the head orientation, but with a smaller f, to make it lag behind the body as
a sort of secondary motion The cables are animated with a little trick that I explained over on twitter a while ago Let’s now look at an approach for solving this equation in real-time, so that we can begin using it in practice There are many ways of numerically solving differential equations such as our second order one, but a particularly straightforward one is called Euler’s method Today, I will present a slight variation known as the semi-implicit Euler method, which, for our system, happens to have the same accuracy as the more complex Verlet integration First, we need to
allocate some state variables: position and velocity These are our estimates of y and its first derivative At timestep 0, they need to be initialized to some initial values Now, when the game is running, we’ll want to iteratively update these variables each frame Let’s call the amount of time that passes between frames T First, we update the position estimate by taking the previous iteration’s position and adding T times the velocity Next, we update the velocity by taking the previous iteration’s velocity and adding T times the acceleration To compute this acceleration, recall our equation of motion We
can solve for acceleration here And substitute in our most recent estimates of position and velocity Note that in semi-implicit Euler, we are using the updated position to compute velocity, which is slightly different from the regular Euler method where we use the previous frame’s position In the case that the input velocity, x dot, is unknown, we can also estimate it using historical measurements, for example, by approximating it as the average velocity since the previous sample This algorithm naturally translates almost line by line into code If we were to implement this in-game, we would see that it
does work as designed I hooked up the code to allow for f, zeta, and r to be changed in real time, and the system responds appropriately There is one big issue that we’ll run into after a bit of experimentation though Set the resonant frequency f too high relative to the frame rate, and the system becomes completely unstable, launching itself to infinity While it’s possible to simply keep f low enough such that this type of glitch is unlikely, in a game engine, we would really like to have a stronger guarantee that something like this doesn’t happen
in corner cases, such as a lag spike causing the time step to be much larger than anticipated This is a problem that can be solved, but to understand it properly, we’ll have to get a bit more technical with our analysis The reason why physics solvers like Euler’s method can become unstable is that fundamentally, they are feedback systems whose outputs from one iteration, here y and its derivative, are fed back into later iterations of the computation When the time step between frames is too large compared to the parameters, it will start accumulating errors over time Beyond
a critical threshold, these errors will start to compound on themselves, quickly leading to catastrophic failure To compute this threshold, we’ll need to organize the problem into a more standard form What we want to do is write the values of our state variables at frame n+1 in terms of our state variables at frame n We can do this by substituting the first equation into the second, so now both our equations have y and its derivative at frame n on the right-hand side Now let’s expand this and collect like terms This is a system of linear equations,
which we can write in matrix notation The system, written this way, is known as the state-space representation This matrix A, known as the state transition matrix, describes how each iteration of the state variables influence the next iteration Intuitively, you can imagine that the feedback will be stable If A doesn’t cause the state variables to grow if A was a number instead of a matrix, it would be easier to reason about the effect that it has on the system For example, in this simpler system with only a single state variable and no external forces, each iteration
is equal to the previous iteration scaled by A When the magnitude of A is less than 1, the magnitude of y at each iteration will be smaller than the previous iteration, causing the system to stabilize over time When the magnitude of A is greater than 1, the magnitude of y at each iteration will be larger than the previous iteration, so the system will quickly grow beyond control This is what we’d like to avoid In our system, A is a 2x2 matrix, which doesn’t have a magnitude that can directly be compared to 1 Instead, it has
what we can think of as two separate magnitudes, called eigenvalues We won’t get into it here, but suffice to say that both of these eigenvalues z1 and z2, which are computed by finding the two solutions to this equation, must have a magnitude less than 1 for the system to settle over time This is computed like this Which expands to this quadratic polynomial of z Which we can solve using the quadratic formula Our system will be stable when the magnitude of this expression is less than 1 Solving this inequality gives us the following constraint, that the
time step T must be less than the square root of 4 times k2 plus k1 squared, subtract k1 In our code, we can compute this maximum stable time step, T critical, and when running in-game, compare the time step against it If the time step is too large, we can divide it up into smaller steps that are below the stability threshold Taking smaller steps requires performing more computations per frame To avoid this, we might be able to get away with slowing down the dynamics instead In this case, rather than constraining T, we can solve for k2
and constrain that Clamping k2 to be above this value is not physically correct, but recall that our goal here was just to prevent catastrophic failure, not to produce an accurate physics simulation There are, of course, much more esoteric places you can take this analysis, and in a previous draft of this video, I did go a bit further For example, this frame-to-frame jittering behavior that sneaks in when the frequency is sufficiently high, is caused by negative eigenvalues, so if we also take that into account during our analysis, we can impose additional restrictions to ensure it doesn’t
happen In my own implementation, in case accuracy is needed, I opt to use a method known as pole-zero matching to compute values for k1 and k2 per-frame based on the time step This produces generally more accurate results for very fast movement, but comes with this additional computation cost that might not suit every application The idea behind using simple parametric motion models like our f zeta r one extends some interesting possibilities Just as an example, we could modulate the values of these parameters to convey gameplay information, so characters could move with different parameters depending on their
state of awareness, health, status effects, and so on This would help communicate the situation to players through motion in a way that’s cheap and easy to iterate on In any case, I’ve spent long enough working on this video instead of my game, so hopefully this has been an approachable but substantial introduction to some interesting concepts, or maybe a presentation of some applications of mathematics in an unusual but interesting context As always, thanks for watching
Copyright © 2024. Made with ♥ in London by YTScribe.com