4. XPBD и SmallSteps
Интро
Здесь я расскажу сразу про две статьи. XPBD - Extended Position Based Dynamics написана в 2016 году. Это улучшение метода PBD. SmallSteps - написана в 2019 году. Скорее не улучшение XPBD, а режим работы который работает лучше всего.
Идея
Большая проблема PBD - большое влияние количества итераций релаксации на результат симуляции. Чем больше итераций тем жестче тело. Вот так было в PBD:
XPBD предлагает решение этой проблемы.
XPBD
В статье улучшаются мягкие ограничения. Это ограничения, которые ведут себя как критически демпфированные пружины. Они постепенно сходятся к желаемому значению. Сложность в том, что они должны себя вести примерно одинаково вне зависимости от количества итераций. Для жестких ограничений ничего не поменялось.
Псевдокод симуляции
for (let point of points) {
// интегрируем позиции точек
point.p = dt * point.v + dt * dt * point.m_inv * point.fext;
}
// обнуляем лямбды
for (let c of constraints) {
c.lambda = 0;
}
while (i < max_iterations) {
for (let c of constraints) {
// вычисляем delta_lambda
let delta_lambda = c.compute_delta_lambda();
let delta_p = c.compute_delta_x(delta_lambda);
c.lambda += delta_lambda;
c.update_positions(delta_p);
}
i++;
}
// обновляем позиции и скорости
for (let point of points) {
point.v = (point.p - point.x) / dt;
point.x = point.p;
}
Дальше пойдет математика, которая не нужна для применения. Кому нужна конечная формула смело пропускайте следующий раздел
Математика XPBD
Базовая схема такая же как и в PBD. Сначала расчитываем позицию точки $x$ как будто она двигается без ограничений.
Потом применяем ограничения. Ограничения действуют силами $f$. У этих ограничений есть потенциальная энергия $U$. $$
f = -\frac{\partial U(x)}{\partial x}
$$ $$
M \ddot{x} = f
$$ M – это матрица массы. $$
M \ddot{x} = -\frac{\partial U(x)}{\partial x}
$$ Теперь используют неявную численную схему: $$
\begin{equation}
M \frac{x^{n+1} - 2x^n + x^{n-1}}{{\Delta t}^2} = -\frac{\partial U(x^{n+1})}{\partial x}
\end{equation}
$$ Эта схема неявная, так как $x^{n+1}$ зависит от U, которое зависит от $x^{n+1}$.
Для почти всех систем неявная схема абсолютно устойчива. Теперь нужно ввести $U$ для ограничений.
Мы считаем что уравнение ограничения $C(x) = 0$. зависит только от $x$. А еще мы считаем, что оно дифференцируемо.
Вообще можно в качестве потенциала использовать любую функцию, но нам полезно добиться хороших свойств от него. Для этого хорошо подходит квадратичный потенциал: $$
U(x) = \frac{1}{2} С(x)^T * \alpha^{-1} * C(x)
$$ Здесь $C(x)$ это вектор ограничений, а $\alpha$ это матрица compliance.
Вводится термин compliance $\alpha$: Это параметр обратный жесткости. Оно в пределах $(0; +\inf)$ Чем он больше тем мягче ограничение.
Эта матрица может быть не только диагональной. Благодаря этому можно комбинаю жесткости разных ограничений. У нас есть простое уравнение пружинки: $$
C(x) = x = 0
$$ И жесткость пружинки $k = \alpha^{-1}$. $$
U(x) = \frac{1}{2} (x^T * \alpha^{-1} * x) = \frac{1}{2} k x^2
$$ Тогда сила ограничения будет равна: $$
f = -\frac{\partial U(x)}{\partial x} = - \nabla C(x) * \alpha^{-1} * C(x)
$$ Дальше вводим переменную $\lambda$ которая будет множителем Лагранжа. $$
\lambda = -\tilde{\alpha} * C(x)
$$ Это уравнение можно переписать вот так: $$
C(x) + \tilde{\alpha} * \lambda = 0
$$ Тут $\tilde{\alpha}$ это $\frac{\alpha} {\Delta t^2}$. Подставляем уравнение силы и $\lambda$ в уравнение движения (1) и получаем финальную систему, которую нужно решить: $$
\begin{equation}
\begin{aligned}
M(x^{n+1} - \tilde{x}) - \Delta t^2 \nabla C(x^{n+1}) *\lambda^{n+1} = 0 \\
C(x^{n+1}) + \tilde{\alpha} * \lambda^{n+1} = 0
\end{aligned}
\end{equation}
$$ Где $\tilde{x} = x^n + \Delta t * v^n$ Получили нашу любимую нелинейную систему уравнений. Нам нужно найти $x^{n+1}$ и $\lambda^{n+1}$. В общем виде ее решить невозможно.
Поэтому в статье предлагают решать ее итерационным методом Ньютона.
Обозначим уравнения из (2) как $$
\begin{aligned}
g(x^{n+1}, \lambda^{n+1}) = 0 \\
h(x^{n+1}, \lambda^{n+1}) = 0
\end{aligned}
$$ Линеаризовываем их относительно $x^{n+1}$ и $\lambda^{n+1}$: Раньше мы писали ${n+1}$, и это означало значение на следующем шаге. Мы сейчас будем искать $x^{n+1}$ и $\lambda^{n+1}$ итеративно. И поэтому будем писать $x^{i}$ и $\lambda^{i}$, где $i$ – номер итерации. Т.е. $x^{i}$ это приближение к $x^{n+1}$ на $i$-й итерации. $$
\begin{bmatrix}
K, &\nabla C^{T}(x^{i}) \\
\nabla C{(x^{i})}, &\tilde{\alpha}
\end{bmatrix}
\begin{bmatrix}
\Delta x^{i} \\
\Delta \lambda^{i}
\end{bmatrix} =
\begin{bmatrix}
g(x^{i}, \lambda^{i}) \\
h(x^{i}, \lambda^{i})
\end{bmatrix}
$$ Где $$
\begin{aligned}
&K = \partial{g(x^{n+1}, \lambda^{n+1})} / \partial{x^{n+1}} = \\
&= M - \Delta t^2 \nabla C(x^{n+1}) \nabla C^{T}(x^{n+1}) \\
\end{aligned}
$$ Из этого уравнения мы получаем $\Delta x^{i}$ и $\Delta \lambda^{i}$, которые мы используем для улучшения наших приближений: $$
\begin{aligned}
x^{i+1} = x^{i} + \Delta x^{i} \\
\lambda^{i+1} = \lambda^{i} + \Delta \lambda^{i}
\end{aligned}
$$ И так делаем пока не сойдемся.
В качестве начального приближения $x^{0} = \tilde{x}$ и $\lambda^{0} = 0$. Проблема этого метода в том, что его не всегда просто реализовать. Нужно расчитывать матрицу $K$
у которой второе слагаемое – Гессиан функции $C(x)$. Это сложно. И вот мы получаем простую систему уравнений: $$
\begin{bmatrix}
M, &\nabla C^{T}(x^{i}) \\
\nabla C{(x^{i})}, &\tilde{\alpha}
\end{bmatrix}
\begin{bmatrix}
\Delta x^{i} \\
\Delta \lambda^{i}
\end{bmatrix} =
\begin{bmatrix}
0 \\
h(x^{i}, \lambda^{i})
\end{bmatrix}
$$ Переписав уравнения в удобной форме получаем: $$
\begin{equation}
\begin{bmatrix}
\nabla C(x^{i}) M^{-1} \nabla C^{T}(x^{i}) + \tilde{\alpha}
\end{bmatrix}
\Delta \lambda = -C(x^{i}) - \tilde{\alpha} \lambda^{i}
\end{equation}
$$ $$
\begin{equation}
\Delta x = M^{-1} \nabla C^{T}(x^{i}) \Delta \lambda
\end{equation}
$$Подробнее
Получаем уравнения
Вводим потенциал
Пример
Уравнения которые нужно решить
Решаем
Упрощаем
Конец уже близок
В уравнении (3) мы имеем систему линейных уравнений относительно $\Delta \lambda$. Последнее что нужно сделать это решить ее. Для этого можно использовать Gauss-Seidel итерации, который уде использовали в PBD. Если у нас матрица complience диагональная, то каждую $\Delta \lambda$ можно искать вот так:
$$ \begin{equation} \Delta \lambda_{j} = -\frac{C_{j}(x^{i}) - \tilde{\alpha_{j}} \lambda_{j}^{i}}{\nabla C_{j} M^{-1} \nabla C_{j}^{T} + \tilde{\alpha_{j}}} \end{equation} $$
$$ \begin{equation} \Delta x = M^{-1} \nabla C^{T}(x^{i}) \Delta \lambda \end{equation} $$
Где $\tilde{\alpha_{j}}$ это коэффициент compliance(обратная жесткость) для $j$-го ограничения.
И вот наконец-то мы приходим к финальной схеме: Расчитываем $\Delta \lambda$ по формуле (5), а потом по формуле (4) находим $\Delta x$ И смещаем нужные точки. И так для каждого ограничения. И повторяем нужное количество итераций. Что самое крутое, так это то, что для абсолютно жесткого ограничения где $\alpha = 0$ мы получаем PBD если занулим $\alpha$ в формуле (5)
После 10 итераций зависимости жесткости от количества итераций практически нет. И мы можем спокойно настраивать жесткость ткани.
<p>Коэффициент compliance $\alpha$ для обычной симуляции достаточно маленький. В этой симуляции я делю пользовательское число на 5000. Так что если у вас что-то не работает, то попробуйте уменьшить это число.</p>
Еще из приятностей. У нас есть $\lambda$, с помощью которой можно найти силу ограничения. И это позволяет делать ограничения, которые можно отключать если силы ограничения становятся слишком большими.
Дампинг
Дампинг
Дампинг здесь вводится через потенциал диссипации энергии.
$$ D(x, v) = \frac{1}{2} \dot{C}(x)^T \beta \dot{C}(x) = \frac{1}{2} v^T*\nabla C(x)^T \beta \nabla C(x) * v $$
Где $\beta$ это матрица дампинга.
Сила дампинга равна:
$$ f = -\frac{\partial D(x, v)^T}{\partial v} = -\nabla C(x)^T \beta \nabla C(x) * v $$
Точно также здесь можно ввести переменную $\lambda_{damp}$:
$$ \lambda_{damp} = -\tilde{\beta} \nabla C(x) * v $$
Где $\tilde{\beta} = \frac{\beta}{\Delta t^2}$
Дальше мы делаем очень крутой переход. Сила дампинга должна быть сонаправлена с силой самого ограничения. Поэтому мы можем просто сложить $\lambda_{elastic}$ и $\lambda_{damp}$ и получить общую силу ограничения.
$$ \lambda = \lambda_{elastic} + \lambda_{damp} = -\tilde{\alpha} \nabla C(x) - \tilde{\beta} \nabla C(x) * v $$
$$ h(x, \lambda) = C(x) + \tilde{\alpha} \lambda + \tilde{\alpha}\tilde{\beta} \nabla C(x) * v = 0 $$
Подставляя это в уравнение (3) и линеаризуя получаем:
$$ \left[\mathbf{I} + \frac{\tilde{\alpha} \tilde{\beta}}{\Delta t} \nabla \mathbf{C}(\mathbf{x}_i) \mathbf{M}^{-1} \nabla \mathbf{C}(\mathbf{x}_i)^{\mathrm{T}} + \tilde{\alpha}\right] \Delta \boldsymbol{\lambda} = -\mathbf{h}(\mathbf{x}_i, \boldsymbol{\lambda}_i) $$
И в итоге получаем вот такое решение для $\Delta \lambda$:
Если матрица compliance и дампинга диагональная, то получаем вот такую формулу:
$$ \begin{equation} \Delta \lambda_j = \frac{-C_j(\mathbf{x}_i) - \tilde{\alpha}_j \lambda_j - \gamma_j \nabla C_j (\mathbf{x}_i - \mathbf{x}^n)}{(1 + \gamma_j) \nabla C_j \mathbf{M}^{-1} \nabla C_j^{\mathrm{T}} + \tilde{\alpha}_j} \end{equation} $$
Где
$$ \gamma = \frac{\tilde{\alpha} \tilde{\beta}}{\Delta t} $$
Выглядит суперстремно, но тут все очень легко считается.
SmallSteps
SmallSteps это режим работы XPBD, который был описан в статье в 2019 году.
Используется тут warp NVIDIA
По дефолту используется в PhysX.
Я не знаю почему, но разработчики решили назвать этот солвер Temporal Gauss-Seidel (TGS).
Источники
- Оригинальная статья XPBD
- Статья в которой рассказывают почему квадратичный потенциал хорошо подходит для ограничений
- Статья в которой рассказывают почему можно обнулять $g(x^{i}, \lambda^{i})$ в XPBD и как это влияет на результат
- Про потенциал диссипации энергии тут
- Оригинальная статья SmallSteps