물리 엔진
고친 사람 github-actions[bot]
물리 엔진은 게임 속 물체가 현실처럼 떨어지고 부딪히고 튕기도록 움직임을 계산해 줍니다. 개발자는 물체마다 질량과 처음 속도와 모양을 정해 둡니다. 물체가 다음 순간 어디에 있을지는 엔진이 아주 짧은 시간 간격마다 새로 계산합니다. 게임 밖에서는 로봇이나 기계를 컴퓨터 안에서 미리 움직여 보는 데 쓰입니다.
쉽고 빠른 이해
무슨 일을 하나 — 게임 세계에 중력과 부딪힘을 넣어 주는 부품입니다. 쌓아 둔 상자 더미에 공을 던지면 상자들이 흩어지며 쓰러집니다. 이 움직임을 개발자가 한 장면씩 짜지 않아도 엔진이 계산해 냅니다.
왜 필요한가 — 물체가 늘면 서로 부딪히는 짝이 금세 불어납니다. 짝마다 어떻게 튕길지를 사람이 미리 짜 둘 수는 없습니다. 그래서 운동 규칙 몇 개만 정해 두고 매번의 결과는 기계가 계산하게 합니다.
어떻게 도나
- 아주 짧은 시간 간격마다 중력 같은 힘을 반영해 속도를 바꿉니다
- 바뀐 속도만큼 물체를 조금씩 옮깁니다
- 옮긴 뒤 서로 겹친 물체들을 찾습니다
- 겹친 물체를 떼어 놓고 튕겨 냅니다
네 단계가 끝나면 새 위치를 게임에 넘기고 다음 간격으로 넘어갑니다.
대가 — 간격마다 계산이 들어서 물체가 많을수록 무거워집니다. 현실을 비슷하게 흉내 낸 값이라서 어긋남도 생깁니다. 쌓인 상자가 떨리거나 아주 빨리 움직이는 물체가 벽을 뚫고 지나갑니다.
상세
이 절은 물리 엔진이 무엇을 넘겨받아 무엇을 돌려주는지부터 봅니다. 그다음 한 번의 계산 안에서 도는 순서를 공 하나가 떨어지는 예로 따라갑니다.
끝에서는 이 계산이 현실과 어긋나는 경우와 물리 엔진을 쓰지 않는 경우를 짚습니다.
자동차 추격 장면을 찍는 영화감독은 차가 어떻게 뒤집힐지 한 컷씩 그려 두지 않습니다. 차를 어느 방향으로 얼마나 빨리 몰지만 정합니다. 차가 뒤집히는 모양은 도로와 중력이 정합니다.
게임에서 도로와 중력 노릇을 하는 부품이 물리 엔진입니다. 개발자는 물체의 질량과 처음 속도와 모양을 정합니다. 물체가 그다음 어디에 있을지는 엔진이 계산합니다.
물체를 움직이는 두 방법
게임이 물체를 움직이는 방법은 둘입니다. 하나는 코드가 위치를 직접 정하는 것입니다. 열리는 문이나 오르내리는 엘리베이터처럼 정해진 길을 가는 움직임이 이렇게 움직입니다.
다른 하나는 힘만 주고 결과는 계산에 맡기는 것입니다. 상자 서른 개가 서로 부딪히며 쓰러지는 모양은 매번 달라서 사람이 미리 짜 둘 수 없습니다. 물리 엔진은 이 둘째 방법을 맡습니다.
물체가 늘면 서로 부딪힐 수 있는 짝도 불어납니다. 물체 n 개에서 둘씩 짝지으면 n(n−1)/2 쌍이 나옵니다. 물체가 100 개면 4,950 쌍입니다. 그래서 짝마다 결과를 정해 두지 않습니다. 운동 규칙만 두고 계산은 기계에 맡깁니다.
엔진이 넘겨받는 물체
물리 엔진이 다루는 물체는 대개 강체입니다. 강체(rigid body)는 부딪혀도 모양이 변하지 않는다고 치는 물체입니다. 모양이 안 변한다고 두면 물체 하나를 위치와 방향과 속도만으로 나타낼 수 있습니다. 그만큼 계산이 가벼워집니다.
강체마다 엔진에 넘기는 값은 셋입니다. 질량과 처음 속도, 그리고 부딪힘을 잴 모양입니다. 질량이 큰 물체는 같은 힘을 받아도 덜 움직입니다. 부딪히면 상대를 더 세게 밀어냅니다.
부딪힘을 잴 모양은 화면에 보이는 모양과 따로 둡니다. 캐릭터의 손가락 하나하나까지 겹침을 재면 계산이 너무 많이 듭니다. 그래서 상자·구·캡슐처럼 단순한 도형을 씌웁니다. 이 도형을 콜라이더라고 부릅니다.
계산이 끝나면 엔진은 물체마다 새 위치와 방향을 돌려줍니다. 게임은 이 값을 받아 화면 속 모습을 옮겨 그립니다. 물리 엔진은 화면을 그리지 않습니다. 그리는 일은 렌더링을 맡은 부품이 따로 합니다.
한 스텝 안에서 도는 순서
물리 엔진은 시간을 아주 짧은 간격으로 잘라 한 칸씩 앞으로 나아갑니다. 한 칸을 계산하는 일을 물리 스텝이라고 부릅니다. 한 스텝은 아래 네 단계를 차례로 밟습니다. 네 단계가 끝나면 결과를 게임에 넘기고 다음 스텝으로 갑니다.
flowchart TD
subgraph S["물리 스텝 한 번 — 네 단계"]
A["1 힘을 반영해 속도를 바꾼다"] --> B["2 속도만큼 위치를 옮긴다"]
B --> C["3 겹친 물체 쌍을 찾는다"]
C --> D["4 겹친 물체를 떼어 놓고 튕긴다"]
end
D --> E(["새 위치를 게임에 넘긴다"])
E -->|다음 스텝| A
첫 단계는 힘을 속도에 반영하는 일입니다. 물체가 받은 힘을 질량으로 나누면 가속도가 나옵니다. 가속도는 속도가 1 초 동안 얼마나 바뀌는지를 나타내는 값입니다. 한 스텝 동안 속도는 가속도에 스텝 간격을 곱한 만큼 바뀝니다.
중력은 모든 물체를 아래로 당깁니다. 질량이 큰 물체일수록 중력을 더 세게 받지만 그만큼 덜 움직입니다. 힘이 질량과 같은 비율로 커지므로 중력이 주는 가속도는 질량과 상관없이 같습니다. 아래 공 예제에 질량이 나오지 않는 까닭입니다.
둘째 단계는 바뀐 속도만큼 위치를 옮기는 일입니다. 속도에 스텝 간격을 곱하면 그사이 움직인 거리가 나옵니다. 이처럼 짧은 간격마다 변화를 조금씩 더해 가는 계산을 수치 적분이라고 합니다.
높이 10 미터에서 가만히 놓은 공 하나로 두 단계를 따라가 봅니다. 스텝 간격은 50 분의 1 초로 잡습니다.
g = -9.8 # 중력 가속도, m/s²
dt = 1 / 50 # 스텝 간격, 초
v = 0.0 # 처음 속도, m/s
y = 10.0 # 처음 높이, m
v = v + g * dt # -0.196
y = y + v * dt # 9.99608
한 스텝 뒤 공은 초속 0.196 미터로 떨어지기 시작했습니다. 높이는 4 밀리미터쯤 줄었습니다. 같은 두 줄을 스텝마다 되풀이하면 속도가 스텝마다 0.196 씩 커집니다. 공은 떨어질수록 빨라집니다.
셋째 단계는 충돌 감지입니다. 옮긴 뒤 서로 겹친 물체 쌍을 찾습니다. 앞에서 본 대로 쌍의 수는 물체 수보다 훨씬 빨리 불어납니다.
이 단계는 두 번에 나눠 잽니다. 먼저 멀리 떨어진 쌍을 싸게 걸러 내는 단계가 브로드 페이즈입니다. 그다음 남은 가까운 쌍만 콜라이더 모양대로 정밀하게 재는 단계가 내로 페이즈입니다.
넷째 단계는 충돌 응답입니다. 겹친 물체를 겹치지 않을 만큼 떼어 놓습니다. 그리고 부딪힌 방향으로 속도를 바꿔 튕겨 냅니다.
튕겨 낼 때 얼마나 튀어 오를지는 반발 계수가 정합니다. 반발 계수는 물체마다 정해 두는 값입니다. 0 이면 전혀 튀지 않고, 1 이면 부딪히기 전 빠르기 그대로 튀어 오릅니다.
부딪힌 두 면은 서로 미끄러지기도 합니다. 바닥에 비스듬히 떨어진 상자는 튀어 오르는 대신 바닥을 따라 밀려 나갑니다. 마찰 계수는 이렇게 부딪힌 면을 따라 미끄러지는 속도를 얼마나 줄일지 정하는 값입니다. 얼음 바닥은 작게, 고무 바닥은 크게 둡니다.
스텝 간격을 고정해 두는 까닭
게임은 화면을 1 초에 수십 번 새로 그립니다. 한 번 그리는 단위를 프레임이라고 합니다. 프레임과 프레임 사이의 간격은 그 순간 화면이 얼마나 복잡한지에 따라 들쭉날쭉합니다.
물리 스텝은 보통 이 들쭉날쭉한 간격을 따르지 않습니다. 공의 예에서 봤듯이 스텝 간격이 계산 결과에 들어갑니다. 간격이 바뀌면 같은 장면도 다르게 흘러갑니다. 간격이 갑자기 길어지면 한 스텝에 물체가 멀리 뜁니다. 그러면 겹침이 커져 결과가 불안정해집니다.
이 때문에 물리 스텝은 늘 같은 간격으로 돕니다. 고정 타임스텝은 이렇게 스텝 간격을 고정해 두는 방식입니다.
게임은 프레임마다 입력 받기, 물리 계산, 화면 그리기를 차례로 되풀이합니다. 이 반복이 게임 루프입니다.
고정 타임스텝에서는 게임 루프가 물리 스텝을 프레임마다 한 번씩 돌리지 않습니다. 프레임마다 흐른 시간을 모아 둡니다. 모인 시간이 스텝 간격만큼 찰 때마다 물리 스텝을 한 번씩 돌립니다.
스텝 간격을 0.02 초로 두고 한 프레임이 0.03 초 걸렸다고 해 봅니다. 첫 프레임에는 0.03 초가 모여 스텝이 한 번 돌고 0.01 초가 남습니다. 다음 프레임에는 0.04 초가 모여 두 번 돕니다. 반대로 프레임이 0.01 초 만에 끝나면 모인 시간이 모자라 한 번도 안 돕니다.
흉내 낸 값이라서 생기는 어긋남
물리 엔진의 계산은 현실을 닮은 근사입니다. 게임에서는 한 프레임 안에 계산이 끝나야 합니다. 정밀함을 조금 덜어 내더라도 계산 시간을 줄이는 쪽을 고릅니다. 이 선택 때문에 게임에서 자주 보는 이상한 움직임이 둘 생깁니다.
첫째는 터널링입니다. 엔진은 스텝과 스텝 사이를 보지 않습니다. 아주 빨리 나는 총알은 한 스텝 만에 얇은 벽 너머로 건너갑니다. 벽과 겹친 순간은 한 번도 잡히지 않습니다.
빨리 움직이는 물체에는 움직인 경로 전체를 훑어 벽과 스쳤는지 보는 방법을 씁니다. 이 방법을 연속 충돌 감지라고 합니다. 계산이 더 들어서 빨리 움직이는 물체에만 켭니다.
둘째는 떨림입니다. 이 떨림을 흔히 지터라고 부릅니다. 상자를 여러 개 쌓아 두면 가만히 있어야 할 상자가 조금씩 떨리기도 합니다.
스텝마다 겹침을 떼어 놓는 계산에 작은 오차가 남습니다. 그 오차가 위에 쌓인 상자로 번져 떨림이 됩니다.
엔진은 거의 멈춘 물체를 계산에서 잠시 빼서 이 떨림을 줄입니다. 계산에서 빠진 물체를 잠든 물체라고 합니다. 슬리핑은 이렇게 물체를 재워 두는 기법입니다. 다른 물체가 와서 부딪히면 다시 깨워 계산에 넣습니다.
흉내 내는 운동의 종류
물리 엔진이 흉내 내는 운동은 강체의 움직임만이 아닙니다. 게임용 엔진은 강체를 중심에 두고 나머지를 필요할 때 더합니다. 아래 표는 흔히 다루는 종류입니다.
| 종류 | 무엇을 흉내 내나 | 흔한 예 |
|---|---|---|
| 강체 물리 | 모양이 안 변하는 물체의 낙하와 부딪힘 | 상자 · 공 · 자동차 몸체 |
| 조인트 | 두 물체를 경첩이나 사슬처럼 이어 둔 연결 | 문 경첩 · 흔들리는 다리 |
| 래그돌 | 조인트로 이은 강체 여럿이 쓰러지는 모양 | 힘없이 쓰러지는 캐릭터 몸 |
| 연체 물리 | 누르면 찌그러졌다 돌아오는 물체 | 고무공 · 젤리 |
| 천 시뮬레이션 | 접히고 펄럭이는 얇은 면 | 깃발 · 옷자락 |
| 유체 시뮬레이션 | 흐르고 퍼지는 물질 | 물 · 연기 |
표의 아래쪽으로 갈수록 물체 하나를 나타내는 데 드는 점이 많아집니다. 강체는 위치와 방향 하나면 됩니다. 천이나 물은 수많은 점의 움직임을 하나하나 계산해야 합니다. 아래쪽일수록 계산이 많이 드는 까닭입니다.
쓰는 곳과 안 쓰는 곳
물리 엔진은 물체끼리 부딪히고 쌓이고 무너지는 장면이 많은 게임에서 씁니다. 게임을 만드는 도구 묶음인 게임 엔진은 대개 물리 엔진을 부품으로 품고 있습니다.
게임 밖에서는 로봇이나 기계를 만들기 전에 컴퓨터 안에서 먼저 움직여 보는 데 씁니다. 이런 시뮬레이션은 게임과 반대로 빠르기보다 정밀함을 먼저 봅니다.
물체가 부딪힐 일이 없는 게임에는 물리 엔진이 필요 없습니다. 칸을 옮기는 보드게임이나 카드 게임이 그렇습니다.
플레이어가 조종하는 캐릭터도 물리에 전부 맡기지 않는 경우가 많습니다. 힘으로만 움직이면 서고 도는 반응이 둔해지기 때문입니다. 이런 물체는 엔진 안에 두되 코드가 위치를 직접 정합니다. 엔진 안에 두어야 다른 물체가 이 캐릭터에 부딪히고 밀려날 수 있습니다. 힘을 받지 않고 코드가 옮기는 이런 물체를 키네마틱 물체라고 부릅니다.
관련 항목
물리 엔진이 계산하는 물체와 그 속성
강체 · 콜라이더 · 질량 · 반발 계수 · 마찰 계수 · 키네마틱 바디 · 트리거 콜라이더
물리 엔진이 한 스텝 안에서 거치는 처리 단계
물리 스텝 · 적분 · 오일러 방법 · 충돌 · 충돌 감지 · 브로드 페이즈 · 내로 페이즈 · 충돌 응답 · 구속 조건
물리 엔진이 흉내 내는 운동의 하위 종류
강체 물리 · 조인트 · 래그돌 · 연체 물리 · 천 시뮬레이션 · 유체 시뮬레이션
물리 스텝을 돌리는 시간 단위와 반복 구조
게임 루프 · 프레임 · 프레임률 · 고정 타임스텝 · 가변 타임스텝 · 델타 타임 · 보간
물리 엔진 계산에서 자주 나는 오류와 그 대책
터널링 · 연속 충돌 감지 · 지터 · 슬리핑 · 부동소수점 오차
물리 엔진을 부품으로 품는 상위 분류
게임 엔진 · 게임 개발 · 시뮬레이션 · 로보틱스 · 그래픽스
물리 엔진을 구현한 라이브러리
Box2D · PhysX · Havok · Bullet Physics · Jolt Physics
물리 엔진과 한 프레임을 나눠 맡는 게임 엔진 부품
다른 이름: physics engine · 물리엔진