테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

게임 개발 수학

캐릭터를 움직이고, 적이 나를 보는지 판정하고, 카메라를 부드럽게 돌리고, 총알이 벽에 맞았는지 계산하는 데 쓰이는 벡터·행렬·쿼터니언·보간·충돌·적분·난수를 작은 예로 직접 계산해 본다.

목차 (16)
  1. 좌표계: 월드, 화면, 그리고 손 방향
  2. 벡터: 위치, 방향, 속도
  3. 내적의 쓰임: 시야, 투영, 반사
  4. 외적: 왼쪽·오른쪽과 법선
  5. 행렬과 변환: 순서가 중요하다
  6. 동차 좌표: 이동도 행렬 곱으로
  7. 오일러 각과 짐벌 락
  8. 쿼터니언과 slerp
  9. 보간과 이징
  10. 곡선: 베지어와 캣멀–롬
  11. 충돌 판정
  12. 물리 적분과 고정 시간 간격
  13. 난수, 노이즈, 확률표
  14. 삼각함수와 atan2
  15. 부동소수점: ==로 비교하지 않는다
  16. 핵심 자료

좌표계: 월드, 화면, 그리고 손 방향

게임 속 위치는 모두 어떤 좌표계 위의 숫자다. 같은 점이라도 무엇을 기준으로 하느냐에 따라 숫자가 다르다.

  • 월드 좌표: 게임 세계 전체의 기준. 수학 교과서처럼 y가 위로 커지는 경우가 많다.
  • 로컬(모델) 좌표: 물체 자신의 중심을 원점으로 한 좌표. 캐릭터의 "앞"은 로컬 좌표에서 늘 같은 방향이다.
  • 화면 좌표: 픽셀 단위로, 왼쪽 위가 (0, 0)이고 y가 아래로 커진다. 마우스 위치와 UI가 이 좌표다. 2D 게임에서 월드와 화면을 오갈 때 y 부호를 뒤집는 것을 잊으면 물체가 거꾸로 움직인다.

3D에서는 z축 방향이 엔진마다 다르다. 오른손의 엄지·검지·중지를 x·y·z로 펴면 오른손 좌표계, 왼손이면 왼손 좌표계다. Unity와 Unreal은 왼손, OpenGL 관례와 Godot은 오른손을 쓰고, 위쪽도 y(Unity·Godot)와 z(Unreal·Blender)로 갈린다. 모델이나 애니메이션을 다른 도구에서 가져올 때 축을 바꿔 주지 않으면 뒤집히거나 누워 버린다.

같은 점이 월드 좌표에서는 (2, 1), 화면 좌표에서는 (240, 50)이다. 오른쪽은 z축 방향이 다른 왼손 좌표계와 오른손 좌표계월드: y가 위xy(2, 1)화면: 왼쪽 위가 (0, 0)xy월드 원점(240, 50)320×180 화면, 1칸 = 40px화면 y = 높이/2 − 월드 y × 배율왼손: Unity·Unrealxyz(앞, 화면 안)오른손: OpenGL·Godotxyz(화면 밖)
월드 좌표 (2, 1)은 320×180 화면(월드 원점이 가운데, 1칸 = 40픽셀)에서 (240, 50)이다. 오른쪽은 z축 방향만 다른 두 좌표계다.

벡터: 위치, 방향, 속도

벡터는 크기와 방향을 함께 가진 값이다. 위치(원점에서의 변위), 속도, 힘, 바라보는 방향이 모두 벡터다. 게임에서 가장 자주 쓰는 연산은 몇 개 되지 않는다.

  • 덧셈: 한 벡터의 끝에 다른 벡터를 이어 붙인다. 위치 + 속도 × 시간 = 다음 위치.
  • 뺄셈: 목표 위치 − 내 위치 = 나에서 목표로 가는 벡터. 추적·조준의 출발점이다.
  • 스칼라 곱: 방향은 두고 길이만 바꾼다.
  • 길이 ‖v‖ = √(x² + y²): 거리. 비교만 할 때는 제곱근을 빼고 제곱끼리 비교해 계산을 아낀다.
  • 정규화 v / ‖v‖: 길이를 1로 맞춰 "방향"만 남긴다.
  • 영벡터 정규화: 길이가 0인 벡터를 정규화하면 0/0이라 NaN이 나오고, NaN은 더하고 곱해도 NaN이라 위치까지 번진다. 멈춰 선 캐릭터의 이동 방향처럼 0이 될 수 있는 벡터는 길이부터 확인한다. Unity의 Vector3.normalized는 너무 작은 벡터에 영벡터를 돌려주도록 정해 두었다.

흔한 실수가 대각선 이동이다. 가로 입력 1과 세로 입력 1을 그대로 속도에 곱하면 (1, 1)의 길이가 √2라 대각선으로 갈 때 속력이 7.07로, 원래 5보다 41% 빨라진다. 입력 벡터를 정규화한 뒤(아날로그 스틱이라면 길이가 1을 넘을 때만 잘라 낸 뒤) 속력을 곱한다.

왼쪽은 벡터 a와 b를 이어 붙여 a + b를 만드는 그림, 오른쪽은 대각선 입력을 그대로 쓰면 원 밖으로 나가 더 빠르고 정규화하면 원 위에 머무는 그림덧셈과 정규화a = (3, 1)b = (1, 2)a + b = (4, 3)‖a‖ = 3.16, a / ‖a‖ = (0.95, 0.32), 길이 1대각선 이동(속력 5)7.075(1, 1) × 5: 속력 7.07정규화 뒤 × 5: 속력 5.00
왼쪽: a와 b를 이어 붙인 a + b, 그리고 a를 정규화한 길이 1짜리 방향(빨강). 오른쪽: 반지름이 속력인 원. 대각선 입력을 그대로 쓰면 원 밖으로 나간다.

내적의 쓰임: 시야, 투영, 반사

두 벡터의 내적 a·b = aₓbₓ + a_yb_y = ‖a‖‖b‖cos θ다. 둘 다 단위 벡터면 내적이 곧 사이 각의 코사인이라, 각도를 직접 구하지 않고도 방향 관계를 알 수 있다.

  • 앞인가 뒤인가: 바라보는 방향과 대상 방향의 내적이 양수면 앞, 음수면 뒤.
  • 시야 안인가: 시야각이 90°면 반각 45°의 코사인 0.707보다 내적이 크면 시야 안이다. acos를 부르지 않고 미리 구한 문턱값과 비교만 한다.
  • 투영: v·n(n은 단위 벡터)은 v 가운데 n 방향 성분의 크기다. 속도에서 벽 법선 성분을 빼면 벽을 따라 미끄러지는 속도가, 두 배를 빼면 튕겨 나가는 반사 속도가 된다. 조명 계산의 밝기 n·l(표면 법선과 빛 방향의 내적)도 같은 원리다.
왼쪽은 오른쪽을 보는 경비의 90도 시야 부채꼴과 대상 세 개. 시야 안에 있는 것은 A 하나다. 오른쪽은 바닥에 비스듬히 부딪는 속도를 법선 방향과 바닥 방향으로 나눠 미끄러짐과 반사를 구하는 그림시야 판정: cos ≥ cos 45° = 0.71경비A cos = 0.97B cos = 0.55C cos = −0.95A만 시야 안. C는 cos가 음수라 등 뒤다.각도(acos) 없이 내적 한 번으로 판정한다.바닥에 부딪는 속도nv반사미끄러짐v = (3, −2), n = (0, 1), v·n = −2미끄러짐 v − (v·n)n = (3, 0)반사 v − 2(v·n)n = (3, 2)
왼쪽: 경비가 +x쪽을 보고 시야각 90°일 때, 대상 방향과의 코사인을 0.707과 비교한다. 오른쪽: 바닥 법선 n으로 속도 v를 나눠 미끄러짐과 반사를 구한다.

외적: 왼쪽·오른쪽과 법선

3차원 외적 a × b는 a와 b에 모두 수직인 벡터이고, 길이는 두 벡터가 만드는 평행사변형의 넓이다. 방향은 좌표계의 손 방향을 따른다. 삼각형의 두 변을 외적하면 면의 법선이 나와 조명, 뒷면 제거, 경사면 판정에 쓴다. 정점을 감는 순서(반시계·시계)를 바꾸면 법선이 반대로 뒤집히므로, 모델의 감는 순서가 엔진 관례와 맞아야 한다.

2차원에서는 외적의 z 성분 aₓb_y − a_yb_x만 쓴다. 이 값이 양수면 b가 a의 왼쪽(반시계 방향), 음수면 오른쪽이다. 적이 플레이어 쪽으로 왼쪽으로 돌지 오른쪽으로 돌지, 점이 다각형 변의 안쪽인지, 세 점이 반시계로 놓였는지를 모두 이 부호 하나로 판정한다.

왼쪽은 정면 방향 화살표와 목표 두 개. 외적이 양수인 P는 왼쪽, 음수인 Q는 오른쪽이다. 오른쪽은 세 축 위의 점으로 만든 삼각형과 외적으로 구한 법선 화살표왼쪽·오른쪽 판정: f × t의 부호정면 f = (2, 1)P(1, 3): +5 → 왼쪽Q(3, -1): −5 → 오른쪽f × t = fₓt_y − f_yt_x. 양수면 반시계(왼쪽),음수면 시계 방향(오른쪽)으로 돌면 된다(y 위쪽 기준).삼각형 법선: (B − A) × (C − A)A(1, 0, 0)B(0, 1, 0)C(0, 0, 1)nn = (1, 1, 1), ‖n‖ = 1.73넓이 = ‖n‖ / 2 = 0.87
왼쪽: 정면 f와 대상 방향의 2차원 외적 부호로 회전 방향을 정한다. 오른쪽: 세 축 위의 점으로 만든 삼각형의 법선은 (1, 1, 1) 방향이고, 외적의 길이 절반이 삼각형 넓이다.

행렬과 변환: 순서가 중요하다

물체를 옮기고(이동), 돌리고(회전), 키우는(크기) 변환은 행렬로 쓸 수 있고, 여러 변환을 행렬 곱 하나로 합쳐 정점 수천 개에 한 번에 적용한다. 행렬 곱은 순서를 바꾸면 결과가 달라진다. 열 벡터 관례(v′ = M v)에서 T·R은 "R을 먼저, 다음 T"다.

  • 회전 → 이동(T·R): 제자리에서 돈 뒤 옮긴다. 화살표 끝 (1, 0)은 (3, 1)로 간다. 보통 원하는 결과다.
  • 이동 → 회전(R·T): 옮긴 뒤 원점을 중심으로 돌아, 물체가 원점 둘레로 크게 휘돌아 (0, 4)에 놓인다. 행성의 공전이나 문의 경첩처럼 일부러 이 효과를 쓰기도 한다.

그래서 게임 엔진은 보통 크기 → 회전 → 이동(M = T·R·S) 순서로 로컬에서 월드로 보내고, 부모–자식 관계(무기를 쥔 손)는 부모의 행렬을 앞에 곱해 쌓는다.

왼쪽은 원점에서 회전한 뒤 오른쪽으로 옮겨 위를 향한 화살표가 (3, 0) 근처에 놓인다. 오른쪽은 먼저 옮긴 뒤 원점을 중심으로 돌려 화살표가 (0, 3) 근처, 위쪽으로 날아간다회전 먼저, 다음 이동: T·R(3, 1)끝점 (1, 0) → (3, 1)이동 먼저, 다음 회전: R·T(0, 4)끝점 (1, 0) → (0, 4)회색: 처음 · 점선: 첫 변환 뒤 · 파랑: 두 변환 뒤
같은 회전 90°와 이동 (3, 0)을 순서만 바꿔 적용했다. 점선은 첫 변환만 한 모습이다.

동차 좌표: 이동도 행렬 곱으로

회전과 크기는 2×2 행렬 곱으로 되지만, 이동은 더하기라 곱으로 쓸 수 없다. 그래서 좌표에 성분 하나(w)를 덧붙여 2D는 3×3, 3D는 4×4 행렬을 쓴다. 이것이 동차 좌표다. 마지막 열에 이동량을 넣으면 이동도 곱 하나가 된다.

덤으로 점과 방향이 구분된다. 점은 w = 1이라 이동의 영향을 받고, 방향(속도·법선)은 w = 0이라 받지 않는다. (1, 0)을 이동 (3, 0) 행렬에 곱하면 점으로서는 (4, 0), 방향으로서는 (1, 0) 그대로다. 4×4 행렬의 w 자리는 원근 투영에서도 쓰인다. 멀리 있는 것을 작게 그리려고 투영 뒤 x, y를 w로 나누는데, 자세한 이야기는 컴퓨터 그래픽스 노트의 렌더링 파이프라인에 있다.

이동 행렬에 점을 곱하면 x가 3 늘어 옮겨지고, 방향 벡터를 곱하면 그대로다. 오른쪽 그림에서 점은 옮겨졌고 방향 화살표는 같은 모양이다이동 행렬 T(3, 0)[ 1 0 3 ][ 0 1 0 ][ 0 0 1 ]점 (x, y, 1): (1, 0, 1) → (4, 0, 1)방향 (x, y, 0): (0.8, 0.9, 0) → (0.8, 0.9, 0)마지막 열(이동량)은 w와 곱해지므로w = 0인 방향에는 더해지지 않는다.+3전후빨간 점: 위치, 파란 화살표: 방향
2D 동차 좌표의 이동 행렬. 마지막 열의 3이 점(w = 1)에만 더해지고 방향(w = 0)은 그대로다. 앞 절 그림의 변환도 모두 이 3×3 행렬로 계산했다.

오일러 각과 짐벌 락

3D 회전을 가장 직관적으로 쓰는 방법은 오일러 각, 곧 요(좌우)·피치(위아래)·롤(기울기) 세 각도를 정해진 순서로 적용하는 것이다. 에디터의 회전 입력 칸이 대개 이것이다. 문제는 피치가 ±90°가 되면 롤 축이 요 축과 같은 방향으로 겹쳐, 세 각도 가운데 둘이 같은 회전을 하게 되는 짐벌 락이다. 이때는 어떤 요·롤 조합을 넣어도 회전 자유도가 하나 사라진다.

또 오일러 각은 두 자세 사이를 각도별로 보간하면 이상한 경로로 돌고, 같은 자세를 나타내는 각도 조합이 여럿이라 비교도 까다롭다. 그래서 입력·표시는 오일러 각으로 하되, 내부 계산과 보간은 쿼터니언으로 하는 엔진이 많다.

왼쪽은 요(세로), 피치(사선), 롤(가로) 세 회전축이 서로 다른 방향이다. 오른쪽은 피치를 90도 돌려 롤 축이 세로로 서서 요 축과 겹친 모습이다피치 0°: 세 축이 서로 다르다요(z)피치(y)롤(x)피치 90°: 롤 축이 요 축과 겹친다요(z)피치(y)롤(−z)피치 90°에서 (요 30°, 롤 0°) · (요 0°, 롤 −30°) · (요 60°, 롤 30°)의 행렬 차이: 0(반올림 오차 수준)요 − 롤만 결과를 정한다. 피치 0°면 앞의 두 조합은 차이가 0.50인 다른 회전이다.
회전 R = Rz(요)·Ry(피치)·Rx(롤). 피치가 90°면 롤 축이 −z가 되어 요 축과 겹치므로, 서로 다른 오일러 각 세 쌍이 정확히 같은 행렬이 된다.

쿼터니언과 slerp

쿼터니언은 수 네 개 (w, x, y, z)로 회전을 나타낸다. 축 u를 중심으로 θ만큼 도는 회전은 (cos(θ/2), u·sin(θ/2))이다. 각도가 반으로 들어가는 것이 특징이라, 160° 회전은 4차원 단위 구 위에서 80°만큼 떨어진 두 점 사이의 일이 된다. 짐벌 락이 없고, 회전을 합치는 곱이 4×4 행렬 곱보다 싸며, 수치 오차로 길이가 조금 흐트러져도 정규화 한 번으로 바로잡힌다.

  • lerp: 성분을 그대로 섞으면 구의 현을 따라가 길이가 1보다 짧아진다. 0°→160°의 가운데에서 길이가 0.77이다.
  • nlerp: lerp한 뒤 정규화한다. 싸고 늘 같은 경로로 가지만, 각속도가 고르지 않아 가운데에서 빨라진다. 각도 차이가 작으면 거의 티가 나지 않아 애니메이션 블렌딩에 많이 쓴다.
  • slerp(구면 선형 보간): 구의 호를 따라 일정한 각속도로 간다. 카메라가 천천히 돌아야 할 때처럼 속도가 고르게 보여야 할 때 쓴다. q와 −q는 같은 회전이므로, 내적이 음수면 한쪽 부호를 뒤집어 짧은 쪽으로 돌게 한다.
원호 위에 같은 간격으로 놓인 slerp 점들과, 두 끝을 잇는 현 위에 같은 간격으로 놓인 lerp 점들. 오른쪽 표는 각 t에서 회전각을 비교한다원점0°160°z축 회전 쿼터니언의 (w, z) 평면tslerpnlerp‖lerp‖00°0°1.000.2540°34°0.830.580°80°0.770.75120°126°0.831160°160°1.00slerp는 40°씩 고르게 돌고,nlerp는 가운데에서 빠르다.
z축 회전 쿼터니언은 w–z 평면 위의 점이다. slerp(파랑)는 호를 같은 간격으로, lerp(빨강)는 현을 같은 간격으로 가고, nlerp는 lerp 점을 원 위로 밀어낸 자리(흐린 선)라 간격이 고르지 않다.

보간과 이징

선형 보간 lerp(a, b, t) = a + (b − a)·t는 t가 0에서 1로 갈 때 a에서 b로 고르게 간다. 위치·색·음량·카메라 거리 등 거의 모든 값의 전환에 쓰인다. 다만 일정한 속도로 갑자기 출발하고 멈추면 기계적으로 보이므로, t를 이징 함수에 한 번 통과시켜 속도에 표정을 준다.

  • smoothstep 3t² − 2t³: 양 끝의 기울기가 0이라 부드럽게 출발하고 멈춘다. t = 0.25에서 0.15625이다. 셰이더에서도 경계를 부드럽게 할 때 표준처럼 쓴다.
  • ease-in(t²): 천천히 출발해 빨라진다. 떨어지는 물체처럼 가속하는 느낌.
  • ease-out(1 − (1 − t)³): 빠르게 출발해 감속하며 멈춘다. UI 창이 열릴 때 가장 자연스러워 보이는 경우가 많다.

카메라가 목표를 따라가게 할 때 흔히 매 프레임 pos = lerp(pos, target, 0.1)을 쓴다. 이것은 프레임마다 남은 거리의 10%를 가는 것이라 프레임이 많을수록 빨리 따라간다. 1초 뒤 30fps에서는 95.8%, 60fps에서는 99.8%를 간다. 비율을 1 − e−λ·dt로 바꾸면 프레임률과 상관없이 같은 곡선이 된다. 애니메이션 곡선과 블렌딩은 게임 개발 › 애니메이션에서 더 다룬다.

왼쪽은 0에서 1로 가는 이징 곡선 네 가지. 오른쪽은 1초 동안 목표를 따라간 정도를 프레임률별로 비교한 막대로, 비율 방식은 프레임률마다 다르고 지수 감쇠는 같다이징 곡선 (가로 t, 세로 진행률)선형smoothstepease-in(제곱)ease-out(세제곱)01smoothstep(t) = 3t² − 2t³1초 뒤 따라간 정도매 프레임 남은 거리의 10%30fps95.8%60fps99.8%144fps100.0%지수 감쇠 1 − e^(−6·dt)30fps99.8%60fps99.8%144fps99.8%
왼쪽: 이징 곡선 네 가지. 오른쪽: 1초 동안 목표를 따라간 정도. 고정 비율 방식은 프레임률마다 다르고, 지수 감쇠(λ = 6)는 같다.

곡선: 베지어와 캣멀–롬

베지어 곡선은 조절점 몇 개로 매끄러운 곡선을 정한다. 3차 베지어는 점 네 개를 쓰고, 양 끝 점은 지나지만 가운데 두 점은 곡선을 끌어당기기만 한다. 펜 툴, 폰트 윤곽, 애니메이션 곡선 편집기, 미사일의 휘는 궤적이 모두 베지어다. 곡선 위의 점은 드 카스텔조 알고리즘, 곧 lerp를 되풀이해 구한다.

조절점 네 개로 만든 3차 베지어 곡선. 단계마다 이웃 점 사이를 같은 비율로 나눈 점이 생기고, 마지막 점이 곡선 위에 놓인다P₀P₁P₂P₃B(0.4)t = 0.4: 각 선분의 40% 지점
1 / 5

조절점 P₀~P₃ (0, 0) (1, 3) (4, 3.5) (5, 0.5). 곡선은 P₀에서 출발해 P₃에 닿지만 P₁·P₂는 지나지 않고 끌어당기기만 한다. t = 0.4인 점을 찾자.

드 카스텔조 알고리즘. lerp만 되풀이하므로 수치적으로 안정하고, 같은 과정에서 곡선을 t 자리에서 둘로 나누는 조절점도 함께 얻는다. 가로 0~5, 세로 0~3.5인 좌표다.

베지어는 곡선이 조절점을 지나지 않아, "이 점들을 차례로 지나는 길"을 만들 때는 불편하다. 캣멀–롬 스플라인은 지정한 점을 모두 지나고, 각 점의 접선을 앞뒤 점을 잇는 방향으로 자동으로 정한다. 카메라 레일, 순찰 경로, 레이싱 게임의 코스 중심선에 쓴다. 곡선의 매개변수 t가 거리에 비례하지 않으므로, 일정한 속도로 움직이려면 곡선 길이를 표로 만들어 t를 거리로 바꿔 쓴다.

점 다섯 개를 차례로 지나는 매끄러운 곡선. 가운데 세 점에는 앞뒤 점을 잇는 방향과 나란한 접선이 그려져 있다(0, 1)(1.5, 3)(3, 1.5)(4.5, 3.2)(6, 2)빨간 선: 점 i의 접선 = (다음 점 − 앞 점) / 2 (길이는 줄여 그림)
점 다섯 개를 모두 지나는 캣멀–롬 스플라인. 각 점의 접선(빨강)은 앞뒤 점을 잇는 방향과 나란하다.

충돌 판정

물체마다 실제 모양 대신 단순한 도형(충돌체)을 씌워 판정한다. 싸고 빠른 순서로 먼저 걸러 내고, 걸린 것만 정밀하게 본다.

  • AABB(축 정렬 경계 상자): x 구간끼리, y 구간끼리 모두 겹치면 충돌. 비교 네 번이면 된다. 밀어낼 때는 겹친 양이 작은 축으로 그만큼 민다.
  • 원–원(구–구): 중심 거리가 반지름 합보다 작으면 충돌. 거리² < (반지름 합)²으로 비교해 제곱근을 피한다.
  • 광선 판정: 총알, 시야, 마우스 클릭은 광선 o + t·d로 다룬다. 원(구)과는 이차방정식의 작은 근, 평면과는 t = (p − o)·n / (d·n) 한 줄이다. t가 맞은 곳까지의 거리다.
왼쪽은 겹치는 두 상자, 가운데는 거의 닿을 듯하지만 떨어진 두 원, 오른쪽은 원에 맞는 수평 광선과 바닥에 맞는 비스듬한 광선AABBABx 겹침 1, y 겹침 1둘 다 양수 → 충돌원–원거리 3.61반지름 합 3.50떨어져 있음광선t = 3.27t = 5.66원: |o + td − c| = r 풀기평면: t = (p − o)·n / (d·n)
AABB는 두 축에서 모두 1만큼 겹쳐 충돌, 두 원은 거리가 반지름 합보다 약간 커서 떨어져 있다. 오른쪽은 원과 바닥에 맞는 두 광선과 맞은 거리 t다.

돌아간 상자나 볼록 다각형끼리는 분리축 정리(SAT)를 쓴다. 두 볼록 도형이 떨어져 있으면, 둘을 투영한 그림자가 겹치지 않는 축이 반드시 있다. 2D에서는 두 도형의 변 법선만 후보로 보면 충분하다. 한 축에서라도 투영 구간이 떨어지면 충돌이 아니고, 모든 축에서 겹치면 충돌이며 가장 적게 겹친 축이 밀어낼 방향이다. 물리 엔진의 실제 구조(넓은 단계·좁은 단계, 연속 충돌 검출)는 게임 개발 › 물리에 있다.

45도 돌린 정사각형과 삼각형. 점선 경계 상자는 겹치지만, 대각선 축에 두 도형을 투영하면 두 구간 사이에 틈이 있다경계 상자(점선): 겹침정사각형 변의 법선 축(0.707, 0.707)에 투영하면정사각형 [1.77, 3.89]삼각형 [4.53, 6.72]틈 0.64 → 충돌 아님볼록 다각형 둘은 변의 법선 가운데한 축에서라도 투영이 떨어지면충돌하지 않는다. 모든 축에서겹쳐야 충돌이다.
45° 돌린 정사각형과 삼각형. 경계 상자(점선)끼리는 겹치지만, 정사각형 변의 법선 축에 투영하면 두 구간 사이에 틈이 있어 충돌이 아니다.

물리 적분과 고정 시간 간격

물리 엔진은 매 걸음 힘에서 가속도를, 가속도에서 속도를, 속도에서 위치를 조금씩 쌓는다(수치 적분). 어떤 순서로 쌓느냐가 안정성을 가른다.

  • 명시적 오일러: 지금 속도로 위치를 옮기고, 그다음 속도를 바꾼다. 가장 단순하지만 용수철·궤도처럼 진동하는 계에서 에너지가 매 걸음 불어나 결국 폭발한다.
  • 반암시적(심플렉틱) 오일러: 속도를 먼저 바꾸고, 새 속도로 위치를 옮긴다. 계산량은 같은데 에너지가 오르내리기만 할 뿐 쌓이지 않는다. 대부분의 게임 물리 엔진이 쓰는 방식이다.
  • 베를레: 속도 없이 지금 위치와 이전 위치로 다음 위치를 낸다(x′ = 2x − x₋₁ + a·dt²). 거리 제약을 맞추기 쉬워 천·밧줄·래그돌 시뮬레이션에 많이 쓴다. 아래 그림은 위치가 똑같이 나오는 속도 베를레 꼴로 계산해 에너지도 함께 보였다.
걸음이 지날수록 명시적 오일러의 진동은 점점 커지고, 반암시적 오일러와 베를레는 정확한 코사인 곡선 가까이 머문다−2−1012071421명시적 오일러에너지 0.50에너지 0.77에너지 1.18에너지 1.82에너지 3.05반암시적 오일러에너지 0.50에너지 0.51에너지 0.52에너지 0.49에너지 0.49베를레에너지 0.50에너지 0.49에너지 0.50에너지 0.49에너지 0.50점선: 정확한 해처음 에너지 0.50
1 / 5

처음: x = 1, v = 0, 에너지 ½v² + ½x² = 0.50. 점선은 정확한 해 cos t다. 세 방법 모두 한 걸음에 dt = 0.3초씩 간다.

같은 용수철을 세 적분기로 푼 위치 x(세로)와 걸음 수(가로). 명시적 오일러는 지금 속도로 위치를 옮긴 뒤 속도를 바꾸고, 반암시적 오일러는 속도를 먼저 바꾼 뒤 새 속도로 옮긴다. 순서 하나 차이로 안정성이 갈린다.

적분 결과는 dt에 따라 달라진다. 정지 상태에서 1초 동안 떨어진 거리의 정답은 ½g·t² = 4.905m인데, 명시적 오일러는 30fps에서 4.741m, 60fps에서 4.823m, 반암시적 오일러는 5.068m와 4.987m다. 프레임률마다 점프 높이가 달라지고, 멀티플레이에서는 기기마다 결과가 어긋난다.

그래서 물리는 렌더링과 떼어 고정 시간 간격으로 돌린다. 프레임마다 흐른 시간을 통(누산기)에 쌓고, 고정 간격(예: 1/60초)만큼씩 덜어 내며 물리를 한 걸음씩 돌린다. 남은 시간은 다음 프레임으로 넘기고, 화면은 남은 비율 α로 이전 상태와 현재 상태를 보간해 그린다. 프레임이 아주 느려 걸음이 끝없이 쌓이는 것(죽음의 소용돌이)을 막으려 한 프레임의 최대 걸음 수도 둔다.

위 줄은 길이가 제각각인 화면 프레임 다섯 개, 아래 줄은 같은 길이의 물리 걸음. 프레임마다 물리 걸음을 0~2번 돌리고 남은 시간을 다음 프레임으로 넘긴다프레임물리10ms0걸음30ms2걸음8ms0걸음25ms2걸음20ms1걸음12345시간 →1번 프레임: 남은 10.0ms, α = 0.602번 프레임: 남은 6.7ms, α = 0.403번 프레임: 남은 14.7ms, α = 0.884번 프레임: 남은 6.3ms, α = 0.385번 프레임: 남은 9.7ms, α = 0.58물리 한 걸음 = 16.67ms(1/60초). 화면은 이전 상태와 현재 상태를 α로 보간해 그린다.
들쭉날쭉한 프레임 다섯 개(위)와 같은 길이의 물리 걸음(아래). 프레임마다 물리를 0~2번 돌리고, 남은 시간과 α를 다음으로 넘긴다.

난수, 노이즈, 확률표

게임의 난수는 거의 모두 의사 난수 생성기(PRNG)다. 시드라는 숫자 하나에서 출발해 정해진 계산으로 수열을 만든다. 같은 시드면 늘 같은 수열이 나와, 리플레이·멀티플레이 동기화·버그 재현·같은 시드로 같은 맵을 만드는 절차적 생성이 가능해진다. 예컨대 이 노트가 쓰는 mulberry32는 시드 42에서 (0.601, 0.448, 0.852), 시드 43에서 (1.000, 0.276, 0.529)를 낸다. 시드가 1만 달라도 수열은 전혀 다르다. 용도별로 생성기를 따로 두면(전투용, 맵 생성용) 한쪽 호출 횟수가 바뀌어도 다른 쪽 결과가 흔들리지 않는다. 보안이 필요한 곳(도박성 보상의 서버 판정 등)에는 암호학적 난수를 쓴다(암호학 노트).

지형·구름·흔들림처럼 "매끄럽게 무작위인" 값이 필요하면 노이즈를 쓴다. 정수 격자점마다 시드로 정한 무작위 값을 두고 사이를 부드럽게 이으면 값 노이즈, 격자점마다 기울기를 두고 이으면 그레이디언트(펄린) 노이즈다. 그레이디언트 노이즈는 격자점에서 늘 0이라 격자 무늬가 덜 드러난다. 주파수를 2배, 진폭을 ½배씩 하며 여러 겹(옥타브)을 더하면 큰 언덕 위에 작은 돌기가 얹힌 자연스러운 모양(프랙탈 노이즈, fBm)이 된다. 노이즈로 맵을 만드는 방법은 게임 개발 › 절차적 생성에 있다.

세 줄의 1차원 노이즈 곡선. 위는 격자점 값을 잇는 완만한 곡선, 가운데는 격자점마다 0을 지나는 곡선, 아래는 여러 주파수를 더해 잔물결이 있는 곡선값 노이즈격자점 값을 smoothstep으로그레이디언트 노이즈격자점에서 0, 기울기로프랙탈(4옥타브)주파수 2배·진폭 ½배씩 합012345678시드 7, 격자 x
같은 시드의 1차원 노이즈 세 가지(세로 눈금은 모양을 보기 좋게 맞췄다). 세로 흐린 선이 정수 격자다.

아이템 드롭은 가중치 확률표로 고른다. 가중치를 차례로 누적한 띠 위에 0~1 난수 × 합계를 떨어뜨려, 그 값이 들어간 칸을 고른다. 낮은 확률은 생각보다 자주 안 나온다. 5% 아이템을 20번 뽑아도 한 번이라도 나올 확률은 64.2%에 그친다. 그래서 일정 횟수 안 나오면 확률을 올리거나 확정하는 천장(pity) 장치를 둔다.

일반 70, 희귀 25, 전설 5의 가중치를 이어 붙인 띠와 0.83에 떨어진 난수가 희귀 칸을 가리키는 모습. 아래는 뽑기 횟수에 따라 전설이 한 번이라도 나올 확률이 오르는 곡선가중치 합 100, 난수 r = 0.83 → r × 100 = 83일반 7070희귀 25955100전설r → 희귀전설(5.0%)이 n번 안에 한 번이라도 나올 확률 1 − (1 − p)ⁿ10번: 40.1%20번: 64.2%45번: 90.1%60번0100%90%를 넘기려면 45번
일반 70, 희귀 25, 전설 5의 누적 띠. 난수 0.83은 70~95 구간이라 희귀다. 아래 곡선은 5% 아이템이 n번 안에 한 번이라도 나올 확률이다.

삼각함수와 atan2

각도와 방향 벡터는 삼각함수로 오간다. 각도 θ의 방향은 (cos θ, sin θ)이고, 거꾸로 벡터 (dx, dy)의 각도는 atan2(dy, dx)다. atan(dy/dx)를 쓰면 나눗셈에서 부호 정보가 사라져, 왼쪽 위와 오른쪽 아래를 구별하지 못하고 dx = 0이면 0으로 나누게 된다. atan2는 두 부호를 따로 받아 −180°~180° 전체를 제대로 돌려준다.

  • 조준: 포탑에서 목표로 가는 벡터의 atan2가 포탑이 돌 각도다. 총알 속도는 (cos θ, sin θ) × 속력.
  • 원운동·흔들림: (cos t, sin t)로 원을 돌고, sin을 시간에 곱해 둥실둥실 떠 있는 효과를 낸다. 주기적인 신호의 수학은 신호 처리 노트의 푸리에 변환으로 이어진다.
  • 각도 차이: 170°에서 −170°로 돌 때 그냥 빼면 −340°라 거의 한 바퀴를 돈다. 차이를 −180°~180°로 감싸야 20°만 돈다.
포탑에서 왼쪽 위의 목표를 향한 화살표와 135도 각도 호. 점선은 atan(dy/dx)로 구해 오른쪽 아래를 잘못 가리키는 방향이다포탑 (1, 1)목표 (-2, 4)135°−45°(dx, dy) = (-3, 3)atan2(dy, dx) = 135°atan(dy/dx) = −45°dy/dx 나눗셈에서 부호가 사라진다총알 속도 = 10 × (cos θ, sin θ)= (−7.07, 7.07)170°에서 -170°로 돌 때가장 짧은 회전 = 20°(그냥 빼면 −340°)
포탑 (1, 1)에서 목표 (−2, 4)로 조준. atan2는 135°를, atan(dy/dx)는 정반대인 −45°를 낸다.

부동소수점: ==로 비교하지 않는다

게임의 좌표와 속도는 거의 다 부동소수점이고, 부동소수점은 실수를 유한한 비트로 반올림해 담는다. 0.1과 0.2는 이진수로 끝나지 않아 64비트로 더해도 0.30000000000000004가 된다. 그래서 계산한 두 값을 ==로 비교하면 같아야 할 것이 다르다고 나온다. 차이가 작은 값 ε 이내인지로 비교하되, 고정된 ε은 아주 큰 수에서는 반올림 간격보다 작아 늘 거짓이 되고 아주 작은 수에서는 너무 커서 다른 값을 같다고 한다. 값의 크기에 비례하는 상대 오차로 비교하는 편이 안전하다(The Floating-Point Guide). Unity가 == 대신 Mathf.Approximately를 따로 두는 것도 같은 이유다.

GPU와 대부분의 엔진이 쓰는 32비트 float는 유효 숫자가 24비트(십진 약 7자리)라, 이웃한 두 값의 간격이 수의 크기에 비례해 벌어진다. 원점에서 1m 떨어진 곳의 간격은 약 0.000119mm지만, 1,000m에서는 0.061mm, 100,000m에서는 7.8mm다. 2²⁴ = 16,777,216을 넘으면 정수조차 하나 건너 하나만 담긴다. 그래서 넓은 월드에서 원점에서 멀리 간 물체는 위치가 몇 mm 단위로 튀고 그 위의 물리도 거칠어진다. 정밀도는 원점에서의 거리로 정해지므로, 계산이 원점 가까이에서 일어나도록 좌표를 옮기는 것이 대책의 기본이다.

핵심 자료

렌더링 쪽 수학(투영, 셰이딩, 텍스처 좌표)은 컴퓨터 그래픽스 노트, 머신러닝 쪽 수학은 AI를 위한 수학에 있다.

자료요지링크
Essential Mathematics for Games and Interactive Applications (Van Verth · Bishop)벡터·행렬·쿼터니언·보간·충돌·강체 물리·난수까지 게임 엔진에 필요한 수학을 한 권으로 다루는 교재. 누리집에 예제 코드와 강연 자료가 있다.책 누리집
Red Blob Games (Amit Patel)격자, 경로 찾기, 노이즈, 곡선 같은 게임 수학을 직접 조작하는 그림으로 설명하는 글 모음.Red Blob Games
Red Blob Games — Noise Functions and Map Generation1차원 노이즈부터 주파수·진폭을 바꿔 더하는 옥타브, 지형 생성까지 노이즈를 단계별로 쌓아 간다.Red Blob Games
Inigo Quilez — 글 모음거리 함수, 노이즈, 보간 함수, 셰이더 수학을 짧은 글과 코드로 정리한 자료. 그래픽 프로그래머들이 자주 찾아본다.iquilezles.org
Gaffer on Games — Fix Your Timestep!가변 프레임 시간을 누적해 고정 간격으로 물리를 돌리고 남은 비율로 보간하는 루프를 설명한다. 고정 시간 간격의 표준 참고 글.Gaffer on Games
Gaffer on Games — Integration Basics명시적 오일러, 반암시적 오일러, RK4를 용수철 예로 비교하며 적분기 선택이 물리의 안정성을 어떻게 바꾸는지 보여 준다.Gaffer on Games
Ken Perlin — Improving Noise (2002)펄린 노이즈의 페이드 곡선을 6t⁵ − 15t⁴ + 10t³로 바꾸고 기울기 집합을 정리한 개정판 논문.NYU
Visualizing quaternions (Grant Sanderson · Ben Eater)쿼터니언 곱과 회전을 4차원을 3차원으로 내려 보는 방식으로, 조작할 수 있는 해설 영상과 함께 보여 준다.eater.net
The Floating-Point Guide — Comparison==가 왜 틀리는지, 고정 ε이 큰 수·작은 수에서 왜 무너지는지, 상대 오차로 비교하는 함수를 정리한 글.floating-point-gui.de
Wikipedia — Single-precision floating-point formatfloat32의 유효 숫자 24비트(십진 약 7자리)와, 2²⁴을 넘으면 정수도 모두 담지 못한다는 점.Wikipedia
Unity 문서 — Mathf.Approximately부동소수점을 ==로 비교하면 부정확하다는 설명과 작은 오차 안에서 같다고 보는 비교 함수.Unity
Unity 문서 — Vector3.normalized정규화할 수 없을 만큼 작은 벡터에는 영벡터를 돌려준다는 약속.Unity