Pull to refresh

Симуляция физического мира

Reading time 9 min
Views 29K
Original author: Jamie Wong


Как бы вы подошли к симуляции дождя, ну или любого другого продолжительного физического процесса?

Симуляцию, будь это дождь, поток воздуха над крылом самолёта или же падающий по ступенькам слинки (помните игрушку-пружинку радугу из детства?), можно представить, если знать следующее:

  1. Состояние всего в момент начала симуляции.
  2. Как это состояние меняется из одного момента времени в другой?

Под «состоянием всего» я подразумеваю любые переменные данные, которые либо определяют как выглядит окружение, либо же изменения, происходящие с течением времени. Как пример состояния можно привести положение капли дождя, направление ветра, либо же скорость каждой части пружинки слинки.

Если положить, что всё наше состояние окружения это один большой вектор $\vec{y}$, то можно сформулировать нужные нам данные, указанные выше, в следующее:

  1. Найти значение $y_0$, удовлетворяющее $y(t_0) = y_0$?
  2. Найти функцию $f$ такую, что $\frac{dy(t)}{dt} = f(t, y(t))$.

Зачем нам надо хранить состояние всего в одном векторе, я объясню чуть позже. Это один из тех случаев, когда кажется что мы перебарщиваем с обобщением задачи, но я обещаю, в таком подходе есть свои интересности.

Теперь взглянем как можно хранить всю информацию об окружении в одном векторе на простом примере. Допустим, у нас есть 2 объекта в 2D симуляции. Каждый объект определяется своим положением $\vec x$ и скоростью $\vec v$.


Таким образом, чтобы получить вектор $\vec y$, нам надо объединить вместе вектора $\vec x_1, \vec v_1, \vec x_2 ~и~ \vec y_2$ в один, состоящий из 8 компонент, вот так:

$\vec y = \begin{bmatrix} \vec x_1 \\ \vec v_1 \\ \vec x_2 \\ \vec v_2 \end{bmatrix} = \begin{bmatrix} x_{1x} \\ x_{1y} \\ v_{1x} \\ v_{1y} \\ x_{2x} \\ x_{2y} \\ v_{2x} \\ v_{2y} \end{bmatrix}$


Если вас смущает, почему мы хотим найти $f(t, y(t))$, а не начальное определение $\frac{dy(t)}{dt}$, то смысл в том, что производная нам нужна как выражение, зависящее только от текущего состояния, $y(t)$, констант и самого времени. Если же это невозможно, то наверняка какая либо информация о состоянии не была учтена.

Начальное состояние


Чтобы определить начальное состояние симуляции, нужно определить вектор $\vec y_0$. Таким образом, если начальное состояние нашего примера с двумя объектами выглядит приблизительно так:


То в векторном виде это можно представить следующим образом:

$\vec x_1(t_0) = \begin{bmatrix} 2 \\ 3 \end{bmatrix}, \vec v_1(t_0) = \begin{bmatrix} 1 \\ 0 \end{bmatrix}, \vec x_2(t_0) = \begin{bmatrix} 4 \\ 1 \end{bmatrix}, \vec v_2(t_0) = \begin{bmatrix} -1 \\ 0 \end{bmatrix}$


Если объединить это всё в один вектор, мы получим нужный нам $\vec y_0$:

$\vec y_0 = \vec y(t_0) = \begin{bmatrix} x_{1x} \\ x_{1y} \\ v_{1x} \\ v_{1y} \\ x_{2x} \\ x_{2y} \\ v_{2x} \\ v_{2y} \end{bmatrix} = \begin{bmatrix} 2 \\ 3 \\ 1 \\ 0 \\ 4 \\ 1 \\ -1 \\ 0 \end{bmatrix}$


Производная функция


$\vec y_0$ определяет начальное состояние и теперь всё что нам нужно это найти способ перехода из начального состояния в состояние происходящее мгновение после него, а с полученного состояния ещё чуть дальше во времени и так далее.

Для этого, решим уравнение $\frac{dy(t)}{dt} = f(t, y(t))$ для $f$. Найдём производную от $y(t)$:

$\frac{dy(t)}{dt} = \frac{d}{dt} \begin{bmatrix} x_{1x} \\ x_{1y} \\ v_{1x} \\ v_{1y} \\ x_{2x} \\ x_{2y} \\ v_{2x} \\ v_{2y} \end{bmatrix} = \begin{bmatrix} \frac{dx_{1x}}{dt} \\ \\ \frac{dx_{1y}}{dt} \\ \\ \frac{dv_{1x}}{dt} \\ \\ \frac{dv_{1y}}{dt} \\ \\ \frac{dx_{2x}}{dt} \\ \\ \frac{dx_{2y}}{dt} \\ \\ \frac{dv_{2x}}{dt} \\ \\ \frac{dv_{2y}}{dt} \end{bmatrix}$


Ого! Высокая получилась формула! Но можно привести её в более читаемый вид, если разобьём наш вектор $\vec y$ обратно на составные части.

$\begin{aligned} \frac{d \vec x_1(t)}{dt} = \begin{bmatrix} \frac{dx_{1x}}{dt} \\ \\ \frac{dx_{1y}}{dt} \end{bmatrix}, \frac{d \vec v_1(t)}{dt} = \begin{bmatrix} \frac{dv_{1x}}{dt} \\ \\ \frac{dv_{1y}}{dt} \end{bmatrix} \\ \\ \frac{d \vec x_2(t)}{dt} = \begin{bmatrix} \frac{dx_{2x}}{dt} \\ \\ \frac{dx_{2y}}{dt} \end{bmatrix}, \frac{d \vec v_2(t)}{dt} = \begin{bmatrix} \frac{dv_{2x}}{dt} \\ \\ \frac{dv_{2y}}{dt} \end{bmatrix} \end{aligned}$


$\vec x_1$ и $\vec x_2$ связаны аналогичными правилами, равно как и $\vec v_1$ с $\vec v_2$, поэтому несмотря на кучу выражений сверху, всё что нам действительно нужно найти, это следующие 2 вещи:

$\frac{d \vec x}{dt}~~\text{и}~\frac{d \vec v}{dt}$


От определения этих двух производных и зависит качество симуляции, именно в них вся сила. И чтобы симуляция не походила на программу где всё случается хаотичным образом, можно обратиться к физике за вдохновением.

Кинематика и динамика


Кинематика и динамика — необходимые ингредиенты для создания интересной симуляции. Начнём с самых основ на нашем примере.

За положение в пространстве отвечает $\vec x$, и первая производная положения точки по времени это его скорость $\vec v$. В свою очередь, производная от скорости по времени это ускорение, $\vec a$.

Может показаться, что мы уже нашли нужную нам функцию $f$, т.к. мы уже знаем следующее:

$\frac{d \vec x}{dt} = \vec v ~~\text{ и } ~\frac{d \vec v}{dt} = \vec a$


И в самом деле мы блестяще справились с $\frac{d \vec x}{dt}$, т.к. $\vec v$ это часть нашего вектора состояния $\vec y(t)$, но нам нужно ещё чуточку разобрать вторую формулу, потому что с $\vec a$ не всё так просто.

Тут нам поможет второй закон Ньютона: $\vec F = m \vec a$. Если предположить что масса наших объектов известна, то переставив переменные в выражении, мы получим:

$\frac{d \vec v}{dt} = \vec a = \frac{\vec F}{m}$


Так, погодите ка, $\vec a$ и $\vec F$ не являются частью $\vec y(t)$, поэтому это сложно назвать продвижением (помните, нам нужна производная функция зависящая только от $\vec y(t)$ и $t$). Но тем не менее мы продвинулись вперёд, потому что мы нашли все полезные формулы которые определяют поведение объектов в нашем физическом мире.

Предположим, что в нашем простом примере, единственной силой, которая воздействует на объекты является гравитационное притяжение. В таком случае, мы можем определить $\vec F$, используя закон всемирного тяготения Ньютона:

$F = G \frac{m_1 m_2}{r^2}$


Где $G$ это гравитационная постоянная $6.67 \times 10^{-11} \frac{Нм^2}{кг^2}$, а $m_1$ и $m_2$ это массы наших объектов (которые, мы предположим, являются константами).

Для создания самой симуляции, также нам понадобится направление и как то указать $r$ через компоненты вектора $\vec y(t)$. Если предположить что $\vec F_1​​$ это сила действующая на первый объект, то можно сделать это следующим образом:

$\begin{aligned} \vec F_1 &= G \frac{m_1 m_2}{|\vec x_2 - \vec x_1|^2} \left[ \frac{\vec x_2 - \vec x_1}{|\vec x_2 - \vec x_1|} \right] = G \frac{m_1 m_2(\vec x_2 - \vec x_1)}{|\vec x_2 - \vec x_1|^3} \\ \\ \vec F_2 &= G \frac{m_2 m_1}{|\vec x_1 - \vec x_2|^2} \left[ \frac{\vec x_1 - \vec x_2}{|\vec x_1 - \vec x_2|} \right] = G \frac{m_2 m_1(\vec x_1 - \vec x_2)}{|\vec x_1 - \vec x_2|^3} \end{aligned}$


Подытожим. Изменение состояний в нашей системе из двух объектов полностью выражено через переменные $\vec x_1, \vec v_1, \vec x_2, ~и~ \vec v_2$. И изменения такие:

$\begin{aligned} \frac{d \vec x_1}{dt} &= \vec v_1 \\ \\ \frac{d \vec x_2}{dt} &= \vec v_2 \\ \\ \frac{d \vec v_1}{dt} &= \vec a_1 = \frac{\vec F_1}{m_1} = G \frac{m_2 (\vec x_2 - \vec x_1)}{|\vec x_2 - \vec x_1|^3} \\ \\ \frac{d \vec v_2}{dt} &= \vec a_1 = \frac{\vec F_1}{m_1} = G \frac{m_1 (\vec x_1 - \vec x_2)}{|\vec x_1 - \vec x_2|^3} \end{aligned}$


Теперь у нас есть всё, что отличает нашу симуляцию от всех других симуляций: $\vec y_0$ и $f$.

$\begin{aligned} \vec y_0 &= \vec y(0) &= \begin{bmatrix} \vec x_1(0) \\ \vec v_1(0) \\ \vec x_2(0) \\ \vec v_2(0) \end{bmatrix} &= \begin{bmatrix} (2, 3) \\ (1, 0) \\ (4, 1) \\ (-1, 0) \end{bmatrix} \\ \\ f(t, y(t)) &= \frac{d\vec y}{dt}(t) &= \begin{bmatrix} \frac{d\vec x_1}{dt}(t) \\ \\ \frac{d\vec v_1}{dt}(t) \\ \\ \frac{d\vec x_2}{dt}(t) \\ \\ \frac{d\vec v_2}{dt}(t) \end{bmatrix} &= \begin{bmatrix} \vec v_1(t) \\ \\ G \frac{m_2 \big(\vec x_2(t) - \vec x_1(t)\big)}{|\vec x_2(t) - \vec x_1(t)|^2} \\ \\ \vec v_2(t) \\ \\ G \frac{m_1 \big(\vec x_1(t) - \vec x_2(t)\big)}{|\vec x_1(t) - \vec x_2(t)|^2} \end{bmatrix} \end{aligned}$


Но как, имея строго определённую симуляцию, превратить её в красивую анимацию?

Если у вас был опыт написания симуляции или игры, то возможно вы предложите нечто такое:

x += v * delta_t
v += F/m * delta_t

Но давайте чуть остановимся и разберём почему это сработает.

Дифференциальные уравнения


Прежде чем мы приступим к реализации, нужно определиться, какая информация у нас уже имеется и что нам нужно. У нас есть значение $y_0$, которое удовлетворяет $y(t_0) = y_0$, так же есть $f$, удовлетворяющее $\frac{dy}{dt}(t) = f(t, y(t))$. А нужна нам функция, которая даст нам состояние системы в любой момент времени. Математически, нам нужна функция $y(t)$.

Имея это ввиду и приглядевшись к $\frac{dy}{dt}(t) = f(t, y(t))$, можно заметить, что это уравнение связывает $y$ со её производной $\frac{dy}{dt}$. Это означает что наше уравнение дифференциальное! Обыкновенное дифференциальное уравнение первого порядка, если быть точнее. Если его решить, то мы найдём функцию $y(t)$.

Задача нахождения $y(t)$ по данным $y_0$ и $f$ называется задачей Коши.

Численное интегрирование


Для некоторых примеров задач Коши можно легко найти ответ аналитическим методом, но в сложных симуляциях аналитический подход может оказаться очень сложным. Поэтому попробуем найти способ поиска аппроксимированного решения задачи.

Для примера возьмём простую задачу Коши.
Дано: $y(0) = 1$ и $\frac{dy}{dt}(t) = f(t, y(t)) = y(t)$. Найти аппроксимированное решение для $y(t)$.

Рассмотрим задачу с геометрической точки зрения и посмотрим на значение и касательную в точке $t = 0$. Из того, что нам дано, имеем $y(0) = 1$ и $\frac{dy}{dt}(t) = y(t) = 1$


Мы пока не знаем как выглядит $y(t)$, но мы знаем что возле точки $t=0$, значение близко к касательной. Теперь постараемся вычислить $y(0 + h)$ для маленького значения $h$, воспользовавшись касательной. Для начала попробуем $h = 0.5$.


Если расписать, то мы приближаем значение $y(h)$ следующим образом:

$\begin{aligned} y(h) \approx y(0) + h \frac{dy}{dt}(0) &= y(0) + h f(0, y(0)) \\ &= y(0) + h y(0) \\ &= 1 + h \end{aligned}$


Так, для $h = 0.5, y(h) \approx 1.5 $.
​​
Теперь мы можем продолжить вычислять для других точек. Хотя, конечно, мы нашли не точное значение $y(h)$, но если наше приближённое значение очень близко к точному, то аппроксимированная касательная тоже будет очень близка к действительной!

$\begin{aligned}​f(t,y(t))&​=y(t)\\​f(0.5,1.5)​&=1.5​​\end{aligned}$



Далее, продвинемся ещё на $h$ единиц вправо по касательной.


Повторим процесс и получим угловой коэффициент касательной $f(t,y(t))=f(1,2.25)=2.25$:


Процедуру можно проводить рекурсивно и для этого выведем формулу:

$\begin{aligned} t_{i+1} &= t_i + h \\ y_{i+1} &= y_i + h f(t_i, y_i) \end{aligned}$


Данный численный метод решения дифференциальных уравнений называется методом Эйлера. Для общего случая шаг x += v * delta_t.

В нашем конкретном случае, пошаговое решение выглядит так:

$\begin{aligned} t_{i+1} &= t_i + h \\ y_{i+1} &= y_i + h y_i \end{aligned}$


Используя данный метод, результаты удобно представлять в виде таблицы:

$\begin{aligned} t_0 &= 0, & y_0 &= 1 & & &\\ t_1 &= 0.5, & y_1 &= y_0 + h y_0 &=& 1 + 0.5 (1) &=& 1.5 \\ t_2 &= 1, & y_2 &= y_1 + h y_1 &=& 1.5 + 0.5 (1.5) &=& 2.25 \\ t_3 &= 1.5, & y_3 &= y_2 + h y_2 &=& 2.25 + 0.5 (2.25) &=& 3.375 \\ \end{aligned}$


Оказывается, у нашей задачи есть красивое аналитическое решение $y(t) = e^{​t} ​​$:

График функции y(t) = e^​t

Как вы думаете, что произойдёт, если в методе Эйлера уменьшить шаг?


Разница между аппроксимированным и точным решениями уменьшается с уменьшением $h$! К тому же, вдобавок к уменьшению шага, можно использовать и другие методы численного интегрирования, которые могут привести к лучшему результату, такие как метод средних прямоугольников, метод Рунге-Кутты и метода Адамса.

Настало время кодить!


С таким же успехом как мы вывели математическое представление описания симуляции, мы можем написать реализацию симуляции программно.

Т.к. я больше всего знаком с JavaScript, и мне нравится ясность, которую добавляют в код аннотации, все примеры будут написаны на TypeScript.

А начнём мы с версии, в которой подразумевали, что $y$ это одномерный массив чисел, прямо как в нашей математической модели.

function runSimulation(
    // y(0) = y0
    y0: number[],
    // dy/dt(t) = f(t, y(t))
    f: (t: number, y: number[]) => number[],
    // показывает текущее состояние симуляции
    render: (y: number[]) => void
) {
    // Шаг вперёд на 1/60 секунды за тик
    // Если анимация будет 60fps то это приведёт к симуляции в рельном времени
    const h = 1 / 60.0;

    function simulationStep(ti: number, yi: T) {
        render(yi)
        requestAnimationFrame(function() {
            const fi = f(ti, yi)

            // t_{i+1} = t_i + h
            const tNext = ti + h

            // y_{i+1} = y_i + h f(t_i, y_i)
            const yNext = []
            for (let j = 0; j < y.length; j++) {
                yNext.push(yi[j] + h * fi[j]);
            }

            simulationStep(tNext, yNext)
        }
    }
    simulationStep(0, y0)
}

Оперировать с одномерными массивами не всегда удобно, можно абстрагировать функции сложения и умножения процесса симуляции в интерфейс и получить краткую обобщённую реализацию симуляции используя TypeScript Generics.

interface Numeric<T> {
    plus(other: T): T
    times(scalar: number): T
}

function runSimulation<T extends Numeric<T>>(
  y0: T,
  f: (t: number, y: T) => T,
  render: (y: T) => void
) {
    const h = 1 / 60.0;

    function simulationStep(ti: number, yi: T) {
        render(yi)
        requestAnimationFrame(function() {
            // t_{i+1} = t_i + h
            const tNext = ti + h
            // y_{i+1} = y_i + h f(t_i, y_i)
            const yNext = yi.plus(f(ti, yi).times(h))
            simulationStep(yNext, tNext)
        })
    }
    simulationStep(y0, 0.0)
}

Положительной стороной данного подхода является возможность сконцентрироваться на основе симуляции: что именно эту симуляцию отличает от любой другой. Используем пример симуляции с двумя объектами, упомянутыми выше:

Код симуляция двух объектов
// Состояние симуляции двух объектов в один тик времени
class TwoParticles implements Numeric<TwoParticles> {
    constructor(
        readonly x1: Vec2, readonly v1: Vec2,
        readonly x2: Vec2, readonly v2: Vec2
    ) { }

    plus(other: TwoParticles) {
        return new TwoParticles(
            this.x1.plus(other.x1), this.v1.plus(other.v1),
            this.x2.plus(other.x2), this.v2.plus(other.v2)
        );
    }

    times(scalar: number) {
        return new TwoParticles(
            this.x1.times(scalar), this.v1.times(scalar),
            this.x2.times(scalar), this.v2.times(scalar)
        )
    }
}

// dy/dt (t) = f(t, y(t))
function f(t: number, y: TwoParticles) {
    const { x1, v1, x2, v2 } = y;
    return new TwoParticles(
        // dx1/dt = v1
        v1,
        // dv1/dt = G*m2*(x2-x1)/|x2-x1|^3
        x2.minus(x1).times(G * m2 / Math.pow(x2.minus(x1).length(), 3)),
        // dx2/dt = v2
        v2,
        // dv2/dt = G*m1*(x1-x1)/|x1-x2|^3
        x1.minus(x2).times(G * m1 / Math.pow(x1.minus(x2).length(), 3))
    )
}

// y(0) = y0
const y0 = new TwoParticles(
    /* x1 */ new Vec2(2, 3),
    /* v1 */ new Vec2(1, 0),
    /* x2 */ new Vec2(4, 1),
    /* v2 */ new Vec2(-1, 0)
)

const canvas = document.createElement("canvas")
canvas.width = 400;
canvas.height = 400;
const ctx = canvas.getContext("2d")!;
document.body.appendChild(canvas);

// Текущее состояние симуляции
function render(y: TwoParticles) {
    const { x1, x2 } = y;
    ctx.fillStyle = "white";
    ctx.fillRect(0, 0, 400, 400);

    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.ellipse(x1.x*50 + 200, x1.y*50 + 200, 15, 15, 0, 0, 2 * Math.PI);
    ctx.fill();

    ctx.fillStyle = "red";
    ctx.beginPath();
    ctx.ellipse(x2.x*50 + 200, x2.y*50 + 200, 30, 30, 0, 0, 2 * Math.PI);
    ctx.fill();
}

// Запускаем!
runSimulation(y0, f, render)


Если подшаманить с числами, то можно получить симуляцию орбиты Луны!

Симуляция орбиты Луны, 1 пикс. = 2500 км. 1 сек. симуляции равна 1 дню на Земле. Пропорция Луны к Земле увеличена в 10 раз

Столкновения и ограничения


Приведённая математическая модель и в самом деле симулирует физический мир, но в некоторых случаях метод численного интегрирования, к сожалению, ломается.

Представьте симуляцию прыгающего на поверхности мячика.

Состояние симуляции можно описать так:

$\vec y = \begin{bmatrix} x \\ v \end{bmatrix}$


Где $x$ это высота мяча над поверхностью, а $v$ его скорость. Если отпустить мяч с высоты 0.8 метра, то получим:

$\vec y_0 = \begin{bmatrix} 0.8 \\ 0 \end{bmatrix}$


Если изобразить график $x(t)$, то получим нечто следующее:


Во время падения мяча производная функции $f$ вычисляется достаточно легко:

$f(t,y(t)) = \frac{dy}{dt}(t) = \begin{bmatrix} \frac{dx}{dt}(t) \\ \\ \frac{dv}{dt}(t) \end{bmatrix} = \begin{bmatrix} v \\ a \end{bmatrix}$


С ускорением свободного падения, $a = -g = -9.8 \frac{m}{s^2}$.

Но что произойдёт, когда мяч коснётся поверхности? То, что мяч достиг поверхности мы можем узнать по $x=0$. Но при численном интегрировании, в один момент времени мяч может находиться над поверхностью, а уже в следующий под ней: $x_i > 0, x_{i+1} < 0$.

Можно было бы решить эту задачу путём определения момента столкновения $t_{​c} ~ (t_i < t_c < t_{i+1})$. Но даже если этот момент найти, как определить ускорение $ \frac{dv}{dt}$ так, чтобы оно менялось в противоположную сторону.

Можно, конечно, определить столкновение в ограниченном промежутке времени и применить другую силу $F$ на этот отрезок времени $\Delta t$, но гораздо легче определить дискретную константу ограничивающую симуляцию.

А чтобы уменьшить величину проницания мячом поверхности, можно за один тик вычислять сразу несколько шагов симуляции. В совокупности с этим, код нашей симуляции изменится так:


function runSimulation<T extends Numeric<T>>(
  y0: T,
  f: (t: number, y: T) => T,
  applyConstraints: (y: T) => T,
  iterationsPerFrame: number,
  render: (y: T) => void
) {
    const frameTime = 1 / 60.0
    const h = frameTime / iterationsPerFrame

    function simulationStep(yi: T, ti: number) {
        render(yi)
        requestAnimationFrame(function () {
            for (let i = 0; i < iterationsPerFrame; i++) {
                yi = yi.plus(f(ti, yi).times(h))
                yi = applyConstraints(yi)
                ti = ti + h
            }
            simulationStep(yi, ti)
        })
    }
    simulationStep(y0, 0.0)
}

И теперь уже можно написать код нашего прыгающего мячика:

Код прыгающего мячика

const g = -9.8; // m / s^2
const r = 0.2; // m

class Ball implements Numeric<Ball> {
    constructor(readonly x: number, readonly v: number) { }
    plus(other: Ball) { return new Ball(this.x + other.x, this.v + other.v) }
    times(scalar: number) { return new Ball(this.x * scalar, this.v * scalar) }
}

function f(t: number, y: Ball) {
    const { x, v } = y
    return new Ball(v, g)
}

function applyConstraints(y: Ball): Ball {
    const { x, v } = y
    if (x <= 0 && v < 0) {
        return new Ball(x, -v)
    }
    return y
}

const y0 = new Ball(
    /* x */ 0.8,
    /* v */ 0
)

function render(y: Ball) {
    ctx.clearRect(0, 0, 400, 400)
    ctx.fillStyle = '#EB5757'
    ctx.beginPath()
    ctx.ellipse(200, 400 - ((y.x + r) * 300), r * 300, r * 300, 0, 0, 2 * Math.PI)
    ctx.fill()
}

runSimulation(y0, f, applyConstraints, 30, render)




Внимание разработчикам!


Хоть у такой модели есть свои плюсы, она не всегда ведёт к производительным симуляциям. По мне, такой фреймворк полезен для представления поведения симуляции, даже если в ней происходит много чего лишнего.

Например, симуляция дождя в начале этой статьи [прим. В оригинальной статье, в начале вставлена красивая интерактивная симуляция дождя, рекомендую посмотреть воочию] не была создана с использованием, описанного в статье, шаблона. Это был эксперимент с использованием Entity–component–system. Исходники симуляции можно найти тут: симуляция дождя на GitHub.

До скорого!


Я нахожу пересечение математики, физики и программирования чем-то действительно впечатляющим. Создание работающей симуляции, её запуск и рендеринг это некий особенный вид чего-то из ничего.

На всё изложенное меня вдохновили материалы лекции SIGGRAPH, точно так же как и в симуляции жидкости. Если хотите найти более исчерпывающую информацию о вышеизложенном, то взгляните на материалы курса SIGGRAPH 2001 «Введение в физическое моделирование». Привожу ссылку на курс 1997 года, т.к. Pixar похоже удалила версию 2001.

Хочу поблагодарить Maggie Cai за чудесную иллюстрацию пары под зонтом и за терпение при кропотливом подборе цветов, в то время как я не могу отличить синее от серого.

А если вас интересует, то иллюстрации были созданы в Figma.
Only registered users can participate in poll. Log in, please.
Переводить ли статью «симуляция жидкости» (ссылка дана в заключении статьи)
97.11% Да 168
2.89% Нет 5
173 users voted. 22 users abstained.
Tags:
Hubs:
+31
Comments 31
Comments Comments 31

Articles