Particle에 대한 Attractor Force 적용

particle 시뮬레이션에서 attractor force(어트랙터 힘)란, 입자(particle)가 특정 지점 또는 객체를 향해 끌려가도록 만드는 힘을 의미한다. 이는 중력, 자기력, 전기력과 같은 물리적 개념을 추상화한 것으로, 시뮬레이션에서 입자의 집합적 거동을 제어하는 핵심 메커니즘 중 하나이다.

가장 기본적인 attractor force는 입자 위치와 어트랙터 위치 사이의 거리 벡터를 기반으로 계산된다. 일반적으로 힘의 방향은 (attractorPosition – particlePosition)으로 정의되며, 크기는 거리의 함수로 결정된다. 이때 거리 제곱에 반비례하는 형태(예: 중력 공식)를 사용하면, 가까울수록 강하게 끌리고 멀어질수록 약해지는 자연스러운 움직임을 만들 수 있다.

수식적으로는 다음과 같은 형태가 자주 사용된다.

F = G * (dir / (distance^n + ε))

여기서 dir은 정규화된 방향 벡터, distance는 입자와 어트랙터 사이의 거리, n은 감쇠 차수(보통 1 또는 2), ε는 수치적 불안정을 방지하기 위한 작은 값이다. 이 구조는 물리적으로 그럴듯하면서도 계산 비용이 비교적 낮다는 장점이 있다.

시뮬레이션 관점에서 attractor force는 속도(velocity)에 누적(accumulate)되는 힘으로 처리된다. 즉, 매 프레임마다 어트랙터 힘을 계산해 가속도(acceleration)에 반영하고, 이를 적분하여 속도와 위치를 갱신한다. 이 과정에서 타임스텝(delta time)을 고려하지 않으면 프레임 레이트에 따라 시뮬레이션 결과가 달라질 수 있다.

attractor는 단일 지점일 수도 있고, 여러 개가 동시에 존재할 수도 있다. 다중 어트랙터 환경에서는 각 어트랙터로부터의 힘을 합산하여 최종 힘을 계산하며, 이로 인해 입자가 특정 궤도에 머무르거나 카오스적인 움직임을 보이는 패턴이 생성된다. 이는 파티클 아트, 데이터 시각화, 은하 시뮬레이션 등에서 자주 활용된다.

실무적으로는 attractor force를 그대로 적용하면 입자가 과도하게 가속되어 불안정해지는 경우가 많기 때문에, force clamp(최대 힘 제한), damping(감쇠), 또는 soft radius(일정 거리 이내에서만 작동) 같은 보정 기법을 함께 사용한다. 이러한 제어 장치는 시각적으로 안정적이고 예측 가능한 결과를 만드는 데 필수적이다.

이제 구현 관점에서 설펴보자.

먼저 파티클을 간단히 정의해 보자.

class Particle {
  constructor(x, y, z) {
    this.position = new Vector3(x, y, z);
    this.velocity = new Vector3(0, 0, 0);
    this.mass = 1.0;
  }
}

각 파티클은 위치(position), 속도(velocity), 질량(mass)을 가진다. 질량은 힘 → 가속도 변환에 사용된다. 이제 Attractor에 대해 정의해 보자. 어트랙터는 위치와 힘의 세기(strength)를 가진다. 물리적으로는 “질량을 가진 중심점”에 해당한다.

class Attractor {
  constructor(x, y, z, strength = 10.0) {
    this.position = new Vector3(x, y, z);
    this.strength = strength;
  }
}

파티클에 대한 어트랙터가 미치는 힘의 영향을 계산하는 함수다. 입자 → 어트랙터 방향 벡터를 구하고, 거리 기반 감쇠를 적용한다. 여기서는 거리 제곱에 반비례하는 힘 모델을 사용한다.

function computeAttractorForce(particle, attractor) {
  const dir = attractor.position.clone().sub(particle.position);
  const distanceSq = Math.max(dir.lengthSq(), 0.0001); // 수치 안정성
  dir.normalize();

  // F = G / r^2
  const forceMagnitude = attractor.strength / distanceSq;

  return dir.multiplyScalar(forceMagnitude);
}

이제 시뮬레이션이 가능하다. 매 프레임마다 힘 → 가속도 → 속도 → 위치 순서로 갱신한다. 이 예제에서는 감쇠(damping)를 추가해 발산을 방지한다.

function updateParticle(particle, attractor, deltaTime) {
  // 1. attractor force 계산
  const force = computeAttractorForce(particle, attractor);

  // 2. F = m * a → a = F / m
  const acceleration = force.divideScalar(particle.mass);

  // 3. 속도 갱신
  particle.velocity.add(acceleration.multiplyScalar(deltaTime));

  // 4. 감쇠 (damping)
  particle.velocity.multiplyScalar(0.98);

  // 5. 위치 갱신
  particle.position.add(particle.velocity.clone().multiplyScalar(deltaTime));
}

three.js라면 Vector3 클래스를 제공하지만 three.js가 아닌 환경이라면 Vector3에 대한 정의는 다음과 같다.

class Vector3 {
  constructor(x = 0, y = 0, z = 0) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  clone() {
    return new Vector3(this.x, this.y, this.z);
  }

  add(v) {
    this.x += v.x;
    this.y += v.y;
    this.z += v.z;
    return this;
  }

  sub(v) {
    this.x -= v.x;
    this.y -= v.y;
    this.z -= v.z;
    return this;
  }

  multiplyScalar(s) {
    this.x *= s;
    this.y *= s;
    this.z *= s;
    return this;
  }

  divideScalar(s) {
    return this.multiplyScalar(1 / s);
  }

  lengthSq() {
    return this.x * this.x + this.y * this.y + this.z * this.z;
  }

  normalize() {
    const len = Math.sqrt(this.lengthSq());
    if (len > 0) this.divideScalar(len);
    return this;
  }
}

핵심을 좀더 반복해 보면, attractor force는 위치 차 벡터 기반으로 계산한다는 점. 거리 기반 감쇠가 없으면 시뮬레이션이 쉽게 불안정해진다는 점. damping, 최소 거리 제한은 사실상 필수라는 점. 이 구조는 CPU 파티클, GPU 파티클(Compute / GPGPU) 모두에 동일하게 적용된다는 점 등이다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다