유튜브 Veritasium 한국어 쇼츠에서 무리 지어 움직이는 동물들 뒤에 수식이 있다는 이야기를 봤습니다. 새 떼든 물고기 떼든 그 복잡한 움직임이 같은 수식으로 설명된다는 내용이었고, 수식의 이름은 Vicsek 모델이었어요.
이 모델은 무리의 특이한 성질을 다룹니다. 물고기 무리에는 경로를 정하는 리더가 없고 각 개체가 보는 것도 바로 옆의 몇 마리뿐인데, 무리 전체는 한 몸처럼 같은 방향으로 움직입니다.
옆의 몇 마리만 보는 개체들이 어떻게 전체로는 한 방향을 갖게 될까?
이 모델을 제안한 논문의 답은 다음 규칙입니다. 각 개체는 가까운 이웃들의 평균 방향으로 방향을 틀고 거기에 작은 무작위 각도를 더합니다. 무리를 지으라는 명령은 어디에도 없습니다. 그런데도 논문은 이 규칙만으로 무리가 나타난다고 주장합니다. 규칙이 한 문장으로 정리되고 수식도 논문에 그대로 있어서 이것을 구현해 볼 수 있겠다 싶었어요.
먼저 살펴보기
수면을 누르면 그 자리에, Enter 키를 누르면 무리 한가운데에 먹이가 떨어집니다. 흩어져 있던 물고기들이 모여들어 알갱이를 갉아먹고, 다 먹으면 원래의 흐름으로 돌아갑니다. 본문은 이 연못의 세 장면을 차례로 설명합니다. 먹이를 주면서 아래 장면들을 미리 찾아 두면 뒤의 설명을 이해하기 쉬워요.
- 합쳐지는 무리 — 따로 돌던 작은 무리들이 어느 순간 하나의 흐름이 됨
- 먹이 앞의 간격 — 여러 마리가 한 점에 몰려도 서로 밀착하지 않음
- 몸이 도는 순서 — 방향을 틀 때 머리가 먼저, 꼬리가 나중에 돎
수식이 정하는 것
Vicsek 모델의 개체는 위치와 방향만 있는 점입니다. 속력은 모든 개체가 같은 값으로 고정되어 있어서 매 스텝 바뀌는 값은 진행 방향뿐입니다. 다음 방향을 정하는 재료는 둘입니다. 자신을 포함해 반경 r 안에 있는 개체들의 평균 방향과 거기에 더하는 작은 무작위 각도입니다.
이 세 단계를 논문은 두 줄로 적습니다.
x(t+1) = x(t) + v(t)Δt
θ(t+1) = 반경 r 안 개체들의 평균 방향 + Δθ
v: 크기가 모두 같은 속도, Δθ: -η/2 와 +η/2 사이에서 균등하게 뽑는 무작위 각도이 두 줄을 구현할 때 처음 막힌 곳은 평균이었어요. 359도와 1도는 둘 다 오른쪽을 가리키는 각도인데, 숫자로 평균하면 정반대 방향인 180도가 나옵니다.
그래서 각도를 숫자로 평균하는 대신 방향을 단위 벡터로 바꿔 평균한 뒤 다시 각도로 되돌립니다. 순환 평균(circular mean)이라는 계산이고, 논문의 평균 방향 정의도 처음부터 이 식입니다. 다만 이 구현은 평균에서 자신을 빼고 이웃만 셉니다. 자신을 포함하면 지금 방향이 평균에 섞이면서 급한 회전이 저절로 무뎌지는데, 그 완충이 없는데도 물고기가 부드럽게 도는 이유는 뒤에 나올 회전 속도 상한입니다.
둘째 줄 끝의 Δθ가 이 모델의 노이즈입니다. 노이즈의 폭이 좁으면 개체들이 한 방향으로 정렬되고, 폭이 어느 임계값을 넘으면 정렬이 사라집니다. 이렇게 질서가 특정 값을 경계로 급격히 바뀌는 현상을 물리학에서는 상전이(phase transition)라고 부릅니다. 논문이 이 무작위 항을 온도 같은 변수라고 소개하고 제목에도 상전이를 쓴 이유가 여기에 있습니다.
그런데 "정렬이 사라진다"는 변화는 연못을 눈으로만 봐서는 가늠하기 어렵습니다. 정렬이 얼마나 이뤄졌는지를 재는 숫자가 필요합니다.
정렬이 무너지는 지점
그래서 같은 규칙으로 도는 시뮬레이션에 측정 패널과 조절 슬라이더를 붙인 실험 버전을 준비했습니다. 패널의 정렬도 φ가 정렬을 재는 숫자입니다. 물고기마다 진행 방향을 길이 1인 화살표로 바꿔 전부 더한 길이를 마리 수로 나눈 값이라서, 모두 같은 방향이면 1이고 고르게 흩어져 있으면 0에 가깝습니다.
이 φ는 논문이 질서를 재는 데 쓴 측정을 연못에 맞게 방향만으로 옮긴 값입니다. 순간값만으로는 오르는 중인지 내리는 중인지 알 수 없어서, 값 옆의 작은 그래프에 최근 30초를 함께 기록했어요.
슬라이더 세 개가 이 시뮬레이션의 입력입니다. 상호작용 반경은 어디까지를 이웃으로 볼지 정하는 거리 r입니다. 노이즈는 무작위 각도의 최대 크기입니다. 분리 거리는 수식에 없는 값이라 다음 절에서 설명합니다. 세 값이 화면 어디에 걸리는지는 관찰 가이드가 보여 줍니다. 물고기 네 마리 위에는 상호작용 반경을 시안 원으로, 분리 거리를 노란 원으로, 무작위 각도를 보라 부채꼴로 그렸습니다. 같은 색은 위의 세 단계 그림과 슬라이더 앞의 점에도 쓰였어요.
슬라이더를 조절하기 전에 결과를 먼저 예상해 보면 변화가 더 선명하게 보입니다. 전체 정렬 프리셋에서 노이즈만 조금씩 올리면 φ는 어떻게 변할까요 — 올린 만큼 서서히 내려갈까요, 어느 지점까지 거의 그대로다가 빠르게 떨어질까요?
프리셋 작은 무리들·전체 정렬·무질서는 대표적인 세 상태를 만드는 슬라이더 조합입니다. 전체 정렬에서 노이즈를 조금씩 올리면 φ가 높게 유지되다가 어느 구간부터 뚜렷하게 떨어집니다. 이 급격한 변화가 앞 절에서 설명한 상전이입니다. 반대로 상호작용 반경을 넓히면 방향 정보가 더 멀리까지 전달되어 작은 무리들이 하나의 흐름으로 합쳐지는 첫 번째 장면이 자주 나타나요.
같은 변화는 색으로도 보입니다. 몸 색을 진행 방향 색으로 바꾸면 방향이 곧 색이 되어서, 무리가 정렬될수록 화면에 남는 색의 가짓수가 줄어듭니다.
다만 이 데모는 물고기 수십 마리가 벽에 둘러싸인 작은 계라서, 논문의 실험을 그대로 재현하는 장치는 아닙니다. 여기서는 정렬이 생기고 무너지는 경향까지만 볼 수 있습니다.
수식에 없는 것들
정렬 규칙과 노이즈, 정렬도까지는 논문에 있는 내용입니다. 이 연못에는 논문에 없는 항이 다섯 개 더 들어 있습니다. 점을 물고기 몸으로 바꾸는 과정에서 필요해진 것들이라, 어디까지가 모델이고 어디부터가 연출인지 이 절에서 구분합니다.
- 간격 유지 — 점은 겹쳐도 문제가 없지만 몸은 겹치면 형태를 잃음. 가까워진 개체끼리 서로 밀어내는 항(Boids의 separation)이 그래서 들어갔고,
분리 거리슬라이더가 그 기준 거리. 먹이 앞에서 간격이 유지되는 두 번째 장면이 이 항에서 나옴 - 회전 속도 상한 — 논문의 개체는 한 스텝에 몇 도든 돌지만, 몸이 한 프레임에 반대를 향하면 애니메이션이 끊김. 계산된 방향은 목표로만 두고, 그쪽으로 도는 속도는 초당 반 바퀴 남짓으로 제한
- 제각각인 속력 — 논문의 개체는 전부 같은 속력, 연못의 물고기는 크기와 수심에 따라 기본 속력이 다름
- 먹이 — 논문의 세계에 없는 것. 알갱이 반경 안의 물고기가 그쪽으로 방향을 틀고 최대 1.7배까지 빨라지는 별도의 항으로, 무리가 한 점으로 모이는 장면은 정렬이 아니라 이 항의 결과
- 경계 — 논문의 공간은 한쪽으로 나가면 반대쪽에서 들어오는 주기 경계. 가장자리가 있는 연못에서는 가장자리에 다가간 물고기가 안쪽으로 방향을 트는 항이 대신함
화면의 현상 중 무리 짓기와 정렬이 흐트러지는 변화는 모델의 결과이고, 위의 다섯 가지는 연출의 결과입니다. 이 구분이 없으면 연출까지 모델의 성질처럼 보입니다.
점 위에 물고기 그리기
수식에서 넘어오는 좌표는 여전히 점 하나인 머리의 위치와 방향뿐입니다. 몸통은 머리를 첫 관절로 삼아 관절 12개를 일렬로 이은 사슬입니다. 각 관절은 바로 앞 관절과 일정한 거리를 유지하며 따라오고 앞 관절 기준으로 꺾이는 각도는 22.5도까지로 제한됩니다. 머리가 방향을 틀면 회전이 관절을 하나씩 타고 내려가므로 별도의 애니메이션 없이도 몸이 곡선을 그리며 따라 돕니다. 머리가 먼저 돌고 꼬리가 나중에 도는 세 번째 장면이 이 구조에서 나와요.
몸의 무늬도 이미지 파일이 아니라 데이터입니다. 반점 하나는 다음 타입으로 정의됩니다.
type KoiSpot = {
along: number; // 몸 축 위의 위치 — 0(머리)에서 1(꼬리)
side: number; // 몸 축에 수직인 오프셋 — 그 지점 몸 반폭 대비 -1~1
radius: number; // 그 지점 몸 반폭 대비 반경 배수
stretch: number; // 몸 축 방향 확대 비율, 1이면 원
color: string;
};반점은 화면 좌표가 아니라 몸 축에 붙어 있습니다. 프레임마다 관절 위치에 맞춰 다시 그리면 몸이 휠 때 무늬도 같은 곡선을 따라 휩니다. 반점이 몸 밖으로 나가지 않는 것은 몸 윤곽을 클리핑 영역으로 쓰기 때문입니다. 무늬의 배합은 실제 비단잉어 품종 일곱 가지(코하쿠·단초·야마부키 등)를 단순화해 확률로 뽑습니다.
물고기가 완성되면 남는 작업은 물입니다. 물은 한 번만 그려 두는 부분과 매 프레임 그리는 부분으로 나뉩니다. 수심 그라디언트와 바닥 돌 그림자, 커스틱(caustics: 빛이 물을 지나 바닥에 만드는 어른거리는 무늬)은 화면 밖 캔버스에 미리 그려 둡니다. 매 프레임에는 그대로 합성하거나 위치만 조금씩 옮겨 겹칩니다. 다만 그 위에 물고기를 얹기만 하면 전부 같은 깊이에 떠 있는 것처럼 보이기 때문에, 깊이감은 층으로 만들었습니다. 물고기의 약 3분의 1은 뒷층에 작고 느리게 배치합니다. 그 위에 반투명한 물빛 덮개를 한 겹 씌우고 앞층 물고기를 그림자와 함께 그립니다.
물고기 두 층은 그리기에서만 나뉠 뿐, 시뮬레이션에서는 같은 평면의 이웃입니다. 물리적으로는 맞지 않는 단순화지만, 층 사이의 움직임이 자연스럽게 이어져 보여서 그대로 뒀어요.
마치며
만들고 보니 쇼츠의 이야기는 과장이 아니었습니다. 무리를 만드는 데 필요한 것은 정말 두 줄짜리 규칙뿐이었습니다. 각 개체는 이웃의 평균 방향을 따라 돌 뿐인데, 이 규칙이 수십 마리에 동시에 적용되는 동안 화면에는 같은 장면이 두 번 나오지 않아요.
가장 오래 들여다본 것은 전체 정렬 프리셋에서 노이즈를 조금씩 올리는 실험이었습니다. 높게 유지되던 φ가 어느 구간부터 뚜렷하게 떨어지는 장면을 몇 번이고 되풀이해서 봤습니다. 상전이라는 단어를 글로 읽을 때와 슬라이더로 직접 일으켜 볼 때의 차이가 그만큼 컸습니다. 다음에 또 이런 수식을 만나 구현해 볼 때도, 이번처럼 재미있는 결과가 나오면 좋겠어요.
참고 자료
- Vicsek et al., "Novel Type of Phase Transition in a System of Self-Driven Particles", Physical Review Letters 75, 1226 (1995) — 정렬 규칙과 정렬도, 상전이 결과의 출처
- Craig Reynolds, "Flocks, Herds, and Schools: A Distributed Behavioral Model", SIGGRAPH 1987 — 간격 유지 항을 가져온 Boids 모델
- Veritasium 한국어, "동물들은 이 수식을 보고 멘탈이 나가버렸습니다." — 이 연못의 출발점이 된 쇼츠
