물리 엔진의 뼈대 · 적분기 · 이론
오일러는 왜 에너지를 늘리는가
한 줄 요약
가속도에서 속도를, 속도에서 위치를 구하는 방법이 여러 가지인데, 명시적 오일러는 진동하는 계에서 매 걸음마다 에너지를 조금씩 더합니다. 순서만 바꾼 반암시적 오일러는 그러지 않습니다.
왜 이게 필요했나
물리 엔진이 푸는 것은 미분방정식입니다. 힘에서 가속도가 나오고, 가속도를 시간에 대해 적분하면 속도가, 속도를 적분하면 위치가 나옵니다. 컴퓨터는 연속적인 적분을 못 하므로 시간을 잘게 끊어 근사합니다.
문제는 그 근사가 오차만 내는 것이 아니라 계의 성질 자체를 바꿔 놓는다는 것입니다. 용수철에 매달린 추는 영원히 같은 폭으로 진동해야 하는데, 가장 단순한 방법으로 계산하면 진폭이 점점 커져 몇 초 만에 화면 밖으로 날아갑니다. 힘 계산이 틀린 것이 아니라 적분 방법이 틀린 것입니다.
어떻게 동작하나
명시적 오일러는 한 걸음의 시작 시점 값만 씁니다.
x_new = x + v * dtv_new = v + a(x) * dt용수철(a = -x, 질량과 강성이 1)에 넣으면 한 걸음이 선형 변환이 되고, 그 변환의 확대율을 정확히 계산할 수 있습니다. 에너지 E = (x² + v²)/2 는 매 걸음마다 정확히 (1 + dt²) 배가 됩니다. dt=0.05 로 400걸음이면 1.0025^400 ≈ 2.715 배입니다. 오차가 아니라 체계적인 증가입니다. dt 를 줄이면 느려질 뿐 방향은 그대로입니다.
반암시적(심플렉틱) 오일러는 순서만 바꿉니다.
v_new = v + a(x) * dtx_new = x + v_new * dt # 방금 구한 새 속도를 쓴다한 줄 순서를 바꿨을 뿐인데 성질이 완전히 달라집니다. 이 방법은 원래 에너지와 아주 가까운 다른 양을 정확히 보존하고, 그래서 에너지가 작은 폭으로 진동할 뿐 발산하지 않습니다. 게임 물리 엔진이 거의 전부 이것을 쓰는 이유입니다. 계산 비용은 명시적 오일러와 똑같습니다.
속도 베를레는 한 걸음 안에서 가속도를 두 번 씁니다.
x_new = x + v * dt + 0.5 * a * dt²a_new = a(x_new)v_new = v + 0.5 * (a + a_new) * dt정확도가 한 차수 높아서(dt 의 2차) 같은 dt 에서 오차가 훨씬 작고, 심플렉틱 성질도 유지합니다. 분자 동역학처럼 오랜 시간을 정확히 굴려야 하는 곳에서 표준입니다. 대신 걸음마다 힘 계산이 한 번 더 들어갑니다.
여기서 중요한 것은 정확도와 안정성이 다른 축이라는 점입니다. 명시적 오일러의 문제는 정확하지 않다는 것이 아니라 시간이 갈수록 나빠지는 방향이 정해져 있다는 것입니다.
왜 순서 하나가 이렇게 큰 차이를 만드는가 를 조금 더 들여다볼 값어치가 있습니다. 명시적 오일러의 한 걸음을 행렬로 적으면 그 행렬의 행렬식이 1 + dt² 로 1 보다 큽니다. 행렬식은 위상 공간에서 넓이가 몇 배가 되는지를 뜻하므로, 매 걸음 상태 공간이 조금씩 부풀어 오릅니다. 반암시적 오일러의 행렬식은 정확히 1 입니다. 넓이를 보존하는 변환이라 발산할 수가 없습니다. 심플렉틱이라는 말이 뜻하는 것이 이 성질입니다.
한 가지 오해를 짚어 두겠습니다. 심플렉틱 적분기가 보존하는 것은 에너지 자체가 아닙니다. 원래 에너지와 아주 가까운 다른 양을 정확히 보존하고, 그 결과 에너지가 작은 폭으로 오르내릴 뿐 한쪽으로 흘러가지 않습니다. 그래서 실습에서 반암시적 오일러의 에너지 곡선은 평평한 직선이 아니라 얇게 진동하는 띠로 나옵니다. 그것이 정상입니다.
현장에서 만나는 모습
가변 시간 간격이 사고를 부릅니다. 프레임이 한 번 늦어져 dt 가 커지면 그 걸음에서 물체가 벽을 통과하거나 제약이 폭발합니다. 그래서 물리 엔진은 렌더링과 물리를 분리해 고정 dt 로 물리를 돌리고, 남는 시간만큼 걸음 수를 조절합니다. dt 를 크게 하는 대신 여러 걸음을 도는 것입니다.
또 하나는 감쇠를 흉내 내려고 매 걸음 속도에 0.99 를 곱하는 방식입니다. 간단하고 실제로 안정을 돕지만, 감쇠량이 dt 에 따라 달라져서 프레임 속도가 다른 기계에서 물체가 다르게 움직입니다. 물리적으로 옳은 방법은 감쇠를 힘으로 넣는 것입니다.
마지막으로 실행 순서에 관한 실무 규칙이 하나 있습니다. 물리 걸음 안에서는 힘 적용, 속도 갱신, 충돌 해법, 위치 갱신의 순서가 정해져 있어야 하고, 그 순서를 코드 여러 곳에서 조금씩 다르게 하면 재현이 되지 않습니다. 같은 입력이 같은 결과를 내지 않는 시뮬레이션은 디버깅이 거의 불가능해집니다 — 버그를 재현할 수 없기 때문입니다. 네트워크 게임에서 서버와 클라이언트가 같은 계산을 해야 하는 경우에는 이 재현성이 아예 요구 사항이 됩니다.
그리고 적분기를 고를 때의 실용적인 기준은 이렇습니다. 게임처럼 그럴듯해 보이면 되고 오래 굴러야 하는 경우에는 반암시적 오일러가 거의 언제나 정답입니다. 값이 정확하지 않아도 발산하지 않는 것이 훨씬 중요하기 때문입니다. 반면 궤도 계산이나 분자 동역학처럼 수치 자체가 결과인 경우에는 베를레나 그 이상의 방법을 씁니다.
다음 실습에서 할 것
같은 용수철을 세 가지 적분기로 400걸음 굴려 각각의 에너지를 CSV 로 남기고, 세 곡선을 한 장의 PNG 에 그려 오일러만 위로 치솟는 것을 눈으로 봅니다. 그다음 dt 를 바꿔 가며 에너지 증가율이 닫힌 식 (1 + dt²)^n 과 맞는지 확인하고, 마지막에는 감쇠를 힘으로 넣어 에너지가 단조롭게 줄어드는 것을 봅니다.