Стаття
Фізика та симуляція · ⏱ ≈ 14 хв читання · Останнє оновлення: 23 червня 2026 р.

Симуляція тканини методом інтегрування Верле

Тканина виглядає оманливо просто — тонкий гнучкий лист — проте переконлива її симуляція потребує одночасного опрацювання тисяч зв'язаних зв'язків, гравітації, опору повітря, зіткнень і самоперетину. Модель пружина-маса з інтегруванням Верле, популяризована Томасом Якобсеном у його доповіді на GDC 2001 року, дає стабільну, візуально переконливу тканину завдяки напрочуд простому алгоритму, що працює в реальному часі.

1. Модель пружина-маса

Тканина дискретизується як сітка частинок (точкових мас), з'єднаних пружинами. Три типи пружин відтворюють механіку тканини:

Структурні

З'єднують кожну частинку з 4 горизонтальними/вертикальними сусідами. Протидіють розтягу й стисненню — задають форму спокою тканини.

Зсувні

З'єднують кожну частинку з 4 діагональними сусідами. Протидіють зсувній деформації, що дозволяє квадратним коміркам ставати паралелограмами без розтягу.

Згинальні

З'єднують кожну частинку із сусідами через один крок (пропускаючи одного). Протидіють згинанню / складанню — надають тканині жорсткості проти заломів.

Розрив (опційно)

Прибирають пружини, коли розтяг перевищує поріг. Уможливлюють ефекти розриву тканини за невеликої додаткової вартості.

2. Інтегрування Верле

Стандартне інтегрування Ейлера накопичує похибку й може ставати нестійким для жорстких пружин. Інтегрування Верле симплектичне — добре зберігає енергію й безумовно стійке за достатньо малих кроків:

Інтегрування Ейлера (нестійке для жорстких пружин): v = v + a·dt pos = pos + v·dt Позиційне інтегрування Верле: x(t+dt) = 2·x(t) − x(t−dt) + a·dt² Еквівалентна форма (зберігаємо поточну + попередню позицію): temp = pos pos = pos + (pos − prev)·damping + acc·dt² prev = temp Неявна швидкість: v = (pos − prev) / dt Згасання: помножити (pos − prev) на (1 − damping·dt) за крок

Ключова ідея в тому, що швидкість виводиться неявно з історії положень — немає окремої змінної швидкості, яка б накопичувала похибки. Це робить Верле унікально стійким для симуляцій на основі зв'язків.

3. Типи зв'язків

Зв'язок відстані між двома частинками p₁ та p₂ з довжиною спокою d₀ проєктує обидві частинки назовні/всередину, щоб відновити їхню відстань до d₀:

delta = p₂ − p₁ dist = |delta| diff = (dist − d₀) / dist Корекція: p₁ += delta · diff / 2 p₂ -= delta · diff / 2 Для закріплених частинок (нескінченна маса): застосувати всю корекцію до вільної частинки. Для відношення мас m₁, m₂: p₁ += delta · diff · (m₂/(m₁+m₂)) p₂ -= delta · diff · (m₁/(m₁+m₂))

Зв'язки — це зв'язки нерозтяжності: вони забезпечують максимальну відстань (тканина не розтягується понад довжину спокою). Стиснення зазвичай дозволене — тканина може зморщуватися, але не може мати від'ємну довжину.

4. Релаксація зв'язків (метод Якобсена)

За багатьох переплетених зв'язків задоволення одного порушує інші. Підхід Якобсена: ітерувати по всіх зв'язках кілька разів за кадр. Кожен прохід наближає систему до одночасного задоволення всіх зв'язків:

За кадр: 1. Проінтегрувати всі частинки методом Верле (застосувати гравітацію, згасання) 2. Повторити N разів: (N = 3–10 для стабільної тканини) для кожного зв'язку: satisfy(p₁, p₂, rest_length) 3. Застосувати реакції на зіткнення Більше ітерацій = жорсткіша тканина (менше розтягу) Менше ітерацій = швидше, але тягуче

Це еквівалентно ітеративному розв'язанню системи зв'язків методом Гаусса–Зейделя. Воно збігається квадратично поблизу розв'язку й стійке до перевизначених чи майже вироджених конфігурацій, які поклали б матричні розв'язувачі.

5. Зіткнення зі сферою та підлогою

Реакція на зіткнення частинки зі сферою з центром C та радіусом R: якщо частинка всередині сфери, спроєктувати її на поверхню:

Зіткнення зі сферою: d = pos − C len = |d| якщо len < R: pos = C + d · (R / len) Підлога (y = 0): якщо pos.y < 0: pos.y = 0 тертя: prev.x = pos.x + (prev.x − pos.x) · friction_k Обидва коригують pos, але НЕ prev → Верле автоматично обчислить швидкість відскоку з корекції положення (pos зрушено, prev незмінне)

У цьому витонченість Верле: реакція на зіткнення — це лише корекція положення. Жодної зміни швидкості не потрібно — інтегратор виводить нову швидкість зі скоригованих положень.

6. Самоперетин

Запобігання проходженню тканини крізь саму себе витратне. Наївний підхід (перевіряти кожну пару трикутників) має складність O(n² × m²). Промислові рішення:

Для інтерактивних демо сили відштовхування + збільшена кількість ітерацій зв'язків прийнятно опрацьовують 90% видимих артефактів самоперетину.

7. Повна реалізація на JavaScript

// Симуляція тканини — інтегрування Верле + релаксація зв'язків
class Particle {
  constructor(x, y, z) {
    this.pos  = {x, y, z};
    this.prev = {x, y, z};
    this.acc  = {x: 0, y: 0, z: 0};
    this.pinned = false;
  }
  integrate(dt, damping = 0.99) {
    if (this.pinned) return;
    const {pos, prev, acc} = this;
    const vx = (pos.x - prev.x) * damping;
    const vy = (pos.y - prev.y) * damping;
    const vz = (pos.z - prev.z) * damping;
    prev.x = pos.x; prev.y = pos.y; prev.z = pos.z;
    pos.x += vx + acc.x * dt * dt;
    pos.y += vy + acc.y * dt * dt;
    pos.z += vz + acc.z * dt * dt;
    acc.x = 0; acc.y = 0; acc.z = 0;
  }
}

class Constraint {
  constructor(a, b) {
    this.a = a; this.b = b;
    const dx = b.pos.x-a.pos.x, dy = b.pos.y-a.pos.y, dz = b.pos.z-a.pos.z;
    this.rest = Math.sqrt(dx*dx + dy*dy + dz*dz);
    this.torn  = false;
  }
  satisfy(tearThreshold = Infinity) {
    if (this.torn) return;
    const {a, b} = this;
    const dx = b.pos.x - a.pos.x;
    const dy = b.pos.y - a.pos.y;
    const dz = b.pos.z - a.pos.z;
    const dist = Math.sqrt(dx*dx + dy*dy + dz*dz) || 0.0001;
    if (dist > this.rest * tearThreshold) { this.torn = true; return; }
    const diff = (dist - this.rest) / dist;
    if (!a.pinned) { a.pos.x += dx * diff * 0.5; a.pos.y += dy * diff * 0.5; a.pos.z += dz * diff * 0.5; }
    if (!b.pinned) { b.pos.x -= dx * diff * 0.5; b.pos.y -= dy * diff * 0.5; b.pos.z -= dz * diff * 0.5; }
  }
}

class Cloth {
  constructor(cols, rows, spacing) {
    this.cols = cols; this.rows = rows;
    this.particles   = [];
    this.constraints  = [];
    for (let r = 0; r < rows; r++)
      for (let c = 0; c < cols; c++) {
        const p = new Particle(c * spacing, -r * spacing, 0);
        if (r === 0) p.pinned = true; // закріпити верхній ряд
        this.particles.push(p);
      }
    const at = (r, c) => this.particles[r * cols + c];
    for (let r = 0; r < rows; r++)
      for (let c = 0; c < cols; c++) {
        if (c < cols-1) this.constraints.push(new Constraint(at(r,c), at(r,c+1)));  // структ. гориз.
        if (r < rows-1) this.constraints.push(new Constraint(at(r,c), at(r+1,c)));  // структ. верт.
        if (c < cols-1 && r < rows-1) {                                         // зсувні
          this.constraints.push(new Constraint(at(r,c), at(r+1,c+1)));
          this.constraints.push(new Constraint(at(r,c+1), at(r+1,c)));
        }
        if (c < cols-2) this.constraints.push(new Constraint(at(r,c), at(r,c+2)));  // згинальні гориз.
        if (r < rows-2) this.constraints.push(new Constraint(at(r,c), at(r+2,c)));  // згинальні верт.
      }
  }

  update(dt, gravity = -980, iters = 5, sphere = null) {
    // Застосувати гравітацію
    for (const p of this.particles) { if (!p.pinned) p.acc.y += gravity; }
    // Проінтегрувати
    for (const p of this.particles) p.integrate(dt);
    // Релаксація зв'язків
    for (let i = 0; i < iters; i++)
      for (const c of this.constraints) c.satisfy();
    // Зіткнення зі сферою
    if (sphere) for (const p of this.particles) {
      const dx = p.pos.x-sphere.x, dy = p.pos.y-sphere.y, dz = p.pos.z-sphere.z;
      const len = Math.sqrt(dx*dx+dy*dy+dz*dz);
      if (len < sphere.r) {
        p.pos.x = sphere.x + dx/len*sphere.r;
        p.pos.y = sphere.y + dy/len*sphere.r;
        p.pos.z = sphere.z + dz/len*sphere.r;
      }
    }
  }
}

// Використання: тканина 20×20, підвішена за верхній ряд
const cloth = new Cloth(20, 20, 15);
const sphere = {x: 140, y: -150, z: 0, r: 80};
function loop() {
  cloth.update(0.016, -980, 5, sphere);
  // ... рендеринг через canvas або Three.js
  requestAnimationFrame(loop);
}
loop();

8. Розширення та подальше читання

Пов'язана симуляція: Симуляція тканини на цьому сайті — це інтерактивна реалізація цієї системи пружина-маса з інтегруванням Верле, з керуванням мишею та елементами керування вітром.
🧵 Відкрити симуляцію тканини →