3. SPH
Интро
SPH (Smoothed Particle Hydrodynamics) – метод сглаженных частиц для гидродинамики. Супер древний метод, который придумали в 1977 году для астрофизики. Астрофизика нас не особо интересует, но метод оказался очень крутым и его стали использовать для симуляции жидкостей в компьютерной графике.
Основная идея заключается в том, что мы моделируем жидкость с помощью частиц. Каждая частица представляет собой небольшую порцию жидкости и взаимодействует с другими частицами в своем окружении. Для того чтобы придать им ощущение жидкости, нужно вычислить плотность и давление в каждой частице, а затем использовать эти значения для вычисления сил, действующих на частицы. Проблема в том, что эти величины не определены для отдельных частиц. Они должны быть непрерывными. Основная идея заключается в использовании сглаживающей функции (kernel function), которая определяет, как частицы влияют на плотность и давление в своей окрестности.
Каждая частица образует вокруг себя “облако” влияния, и чем ближе к частице, тем оно сильнее.

Функция распространения влияния называется kernel function. Она должна удовлетворять нескольким свойствам:
- Влияние должно быть ограничено радиусом сглаживания. Вообще, влияние частицы может быть и бесконечным, просто это будет выглядеть странно, если частица будет влиять на все.
- Быть убывающей с расстоянием: влияние частицы должно уменьшаться с увеличением расстояния от нее. Нам ничего не мешает сделать так, чтобы частица влияла одинаково на все расстояния, но тогда мы получим проблемы на границе радиуса сглаживания.
- Быть нормированной: интеграл по всему пространству должен быть равен 1. Это нужно чтобы увеличение радиуса сглаживания не влияло на вычисляемое значение.
Математическое обоснование
Если у нас есть некоторая величина $A(\mathbf{r})$, определенная в пространстве, то мы можем записать ее с помощью дельта-функции: $$ A(\mathbf{r}) = (A * \delta)(\mathbf{r}) = \int A(\mathbf{r’}) \delta(\mathbf{r} - \mathbf{r’}) d\mathbf{r'}
Где $\delta(\mathbf{r})$ – дельта-функция Дирака. Тк с дельта-функцией работать неудобно, мы заменяем ее на kernel function $W(\mathbf{r}, h)$, которая приближает дельта-функцию при $h \to 0$:
И тогда мы можем записать аппроксимацию величины $A(\mathbf{r})$ как свертку с kernel function:
$$ A(\mathbf{r}) \approx (A * W)(\mathbf{r}) = \int A(\mathbf{r’}) W(\mathbf{r} - \mathbf{r’}, h) d\mathbf{v’} $$ где $\mathbf{v’}$ – объемный элемент в точке $\mathbf{r’}$.
Отсюда вытекают требования к kernel function:
- $\lim_{h \to 0} W(\mathbf{r}, h) = \delta(\mathbf{r})$ (приближение дельта-функцию)
- $\int W(\mathbf{r}, h) d\mathbf{r} = 1$ (нормировка)
- $W(\mathbf{r}, h) \ge 0$ (неотрицательность)
- $W(\mathbf{r}, h) = W(-\mathbf{r}, h)$ (симметрия)
- $W(\mathbf{r}, h) = 0$ при $\lVert \mathbf{r} \rVert > kh$ для некоторого $k$ (ограниченность влияния)
Ну в общем она должна выглядеть как-то так:

Например типичная kernel function – кубический сплайн:
$$ W(\mathbf{r},h)=\sigma_{d} \begin{cases} 6\bigl(q^{3}-q^{2}\bigr)+1, & 0 \le q \le \tfrac{1}{2},\\ 2(1-q)^{3}, & \tfrac{1}{2} < q \le 1,\\ 0, & \text{otherwise}, \end{cases} $$
где $h$ – радиус сглаживания, $r$ – расстояние от частицы до точки, в которой мы вычисляем значение, $q=\dfrac{\lVert\mathbf{r}\rVert}{h}$, а $\sigma_{d}$ – нормировочный коэффициент, зависящий от размерности пространства:
$$ \sigma_{d}= \begin{cases} \frac{8}{\pi h^{3}}, & d=3,\\ \frac{40}{7 \pi h^{2}}, & d=2,\\ \frac{4}{3 h}, & d=1. \end{cases} $$
Вычисление значения
Математическое обоснование
Теперь мы можем вычислить значение любой величины $A$ в точке $\mathbf{r}$ с помощью следующей формулы:
$$ (A * W)(\mathbf{r}) = \int A(\mathbf{r’}) W(\mathbf{r} - \mathbf{r’}, h) d\mathbf{v’} $$
где $\mathbf{v’}$ – объемный элемент в точке $\mathbf{r’}$. И для точечной массы $m$ и плотности $\rho$ мы можем записать $d\mathbf{v’} = \frac{m}{\rho} d\mathbf{r’}$.
Если мы моделируем жидкость с помощью частиц, то мы можем аппроксимировать интеграл суммой по всем частицам:
$$ A(\mathbf{r}) = \sum_{j} A_{j} \frac{m_{j}}{\rho_{j}} W(\mathbf{r} - \mathbf{r_{j}}, h) $$
Получается формула для вычисления плотности в точке $\mathbf{r}$:
$$ \rho(\mathbf{r}) = \sum_{j} m_{j} W(\mathbf{r} - \mathbf{r_{j}}, h) $$
В итоге очень простая идея: суммируем вклад всех частиц, взвешенный по их массе и kernel function.
Пора визуализировать:
Здесь я для каждой ячейки сетки вычисляю плотность; чем выше плотность, тем ярче цвет. Kernel function – кубический сплайн. Зная плотность, мы можем вычислить давление. А зная давление, можно вычислить силы. В итоге можно симулировать движение жидкости.
Получаем псевдокод алгоритма SPH:
function SPH_Simulation(particles) {
for (let i = 0; i < particles.length; i++) {
// Вычисляем плотность в частице i
rho_i = calculateDensity(i, particles)
}
for (let i = 0; i < particles.length; i++) {
// Вычисляем давление в частице i
P_i = calculatePressure(rho_i)
// Вычисляем силы на частицу i
F_i = calculateForces(i, particles, P_i, rho_i)
}
for (let i = 0; i < particles.length; i++) {
// Обновляем положение и скорость частицы i
updateParticle(i, F_i)
}
}В основном дальше каждая статья по SPH отличается определением функций для вычисления давления и сил и их использование