2026 게임 수학 라이브러리 직접 만들어본 후기와 설계 가이드

profile_image
작성자 수치해석개발자 라온
댓글 0건 조회 1회

캐릭터가 벽에 닿을 때 미세하게 떨리고, 카메라 회전값에는 어느 순간 NaN이 섞이며, 같은 전투를 재생했는데 결과가 조금씩 달라지는 문제를 겪었습니다. 엔진이 제공하는 벡터와 행렬 기능만 호출하면 충분하다고 생각했지만, 프로젝트가 커질수록 게임 수학 라이브러리의 규칙을 직접 통제해야 할 이유가 분명해졌습니다.

그래서 실제 사이드 프로젝트에서 Vector, Matrix, Quaternion, Transform, 보간 함수를 작은 라이브러리로 분리해 약 10주간 사용했습니다. 이 글은 교과서식 수식 설명보다 직접 구현하고 게임 코드에 연결하면서 체감한 장점, 실패한 선택, 디버깅 요령을 중심으로 구성했습니다.

직접 만든 게임 수학 라이브러리가 필요했던 순간

기능 부족보다 규칙 불일치가 더 큰 문제였습니다

처음에는 벡터 덧셈과 내적 정도만 있으면 된다고 예상했습니다. 그러나 렌더링 코드는 열 우선 행렬을 가정하고, 물리 모듈은 행 우선 표기처럼 값을 읽으며, 콘텐츠 도구는 각도를 degree로 저장하는 상황이 겹쳤습니다. 개별 계산은 맞아도 모듈 경계를 통과하는 순간 축 방향이나 곱셈 순서가 달라지는 문제가 발생했습니다. 여러분의 코드에도 같은 Vector3 이름을 쓰면서 의미는 서로 다른 값이 섞여 있지 않은가요?

제가 얻은 가장 큰 효과는 연산 속도보다 좌표계와 단위의 계약을 한곳에 고정한 것이었습니다. 오른손 좌표계인지, 앞쪽 축이 +Z인지, 행렬 곱에서 변환 적용 순서는 무엇인지, Quaternion을 자동 정규화할지 등을 문서와 테스트로 묶었습니다. 이후 화면이 뒤집히거나 법선 방향이 어긋났을 때 확인할 범위가 크게 줄었습니다.

10주 사용 후 체감한 장점과 단점

장점은 디버거에서 내부 값을 원하는 방식으로 표시하고, 게임에 필요한 연산만 남길 수 있다는 점입니다. 반면 역행렬, 특이 행렬 처리, SIMD 정렬처럼 검증 비용이 큰 영역까지 욕심내면 개발 시간이 빠르게 늘어납니다. 직접 구현은 모든 수학 코드를 새로 쓰겠다는 선언이 아니라, 프로젝트가 의존할 얇고 명확한 수학 계층을 소유하는 선택에 가까웠습니다.

  • 좋았던 점: API 명명, 오차 정책, 좌표계 규칙을 통일할 수 있었습니다.
  • 불편했던 점: 플랫폼별 부동소수점 차이와 정렬 문제를 직접 검증해야 했습니다.
  • 예상 밖의 효과: 테스트가 수식 설명서 역할을 해 신규 코드 리뷰가 빨라졌습니다.
  • 주의점: 검증된 엔진 기능을 무조건 대체하면 유지보수 비용이 오히려 커집니다.
직접 구현할 범위는 ‘만들 수 있는 기능’이 아니라 ‘프로젝트에서 규칙을 통제해야 하는 기능’으로 정하는 편이 안전했습니다.

Vector와 Matrix부터 구현해 본 실제 순서

작은 타입과 실패 조건을 먼저 고정했습니다

첫 주에는 Vector2, Vector3, Vector4와 Matrix4만 만들었습니다. 연산자 오버로딩을 많이 넣기보다 add, dot, cross, length, normalize, transformPoint, transformDirection처럼 의미가 분명한 함수를 우선했습니다. 특히 점 변환과 방향 변환을 별도 함수로 나누니 동차좌표의 w 값을 잘못 적용하는 실수가 눈에 띄게 줄었습니다.

Normalize 구현에서는 길이가 거의 0인 벡터가 가장 까다로웠습니다. 처음에는 무조건 길이로 나눴다가 파티클 초기화 과정에서 NaN이 퍼졌습니다. 이후 길이 제곱이 임곗값보다 작으면 실패 여부를 반환하는 tryNormalize와, 명시한 기본 방향을 사용하는 normalizeOr 함수를 분리했습니다. 조용히 영벡터를 반환하는 방식보다 호출자가 실패 정책을 선택하게 한 것이 실제 디버깅에 유리했습니다.

행렬은 표기 규칙부터 테스트했습니다

Matrix4는 원소를 채우기 전에 메모리 배치와 수학적 표기를 구분해 문서화했습니다. 열 우선 저장과 열벡터 곱을 채택했고, A×B는 B 변환 후 A 변환이 적용된다고 명시했습니다. 이 규칙을 번역, 회전, 크기 조절이 섞인 테스트로 확인하지 않으면 단순 단위 행렬 테스트만 통과하고 실제 장면에서는 틀릴 수 있습니다.

  1. Vector 타입과 스칼라 연산을 먼저 구현합니다.
  2. 길이 0, 매우 큰 값, 무한대 입력의 정책을 결정합니다.
  3. 이동·회전·크기 행렬을 각각 테스트합니다.
  4. 점과 방향 변환의 차이를 사례로 검증합니다.
  5. 엔진 내장 함수 결과와 무작위 입력을 비교합니다.

이 단계에 약 2주를 썼으며 별도 구매 비용은 없었습니다. 다만 시간도 예산이라는 점을 무시하면 안 됩니다. 기능별 예상 시간과 검증 시간을 함께 배정하는 방식은 계획예산 제도의 개념처럼 목표와 자원을 연결해 생각하는 데 참고가 됐습니다. 제 경우 구현 1시간당 테스트와 통합에 1~2시간을 추가로 잡는 편이 현실적이었습니다.

Quaternion과 보간에서 가장 많이 실패한 부분

오일러 각 변환 순서를 암묵적으로 두면 깨집니다

Quaternion 자체의 곱셈보다 더 자주 문제를 일으킨 것은 오일러 각 변환 순서였습니다. 편집 도구는 Y-X-Z 순서를 기대했는데 런타임 함수는 X-Y-Z처럼 동작해 특정 자세에서만 회전이 튀었습니다. 함수 이름을 fromEuler로 끝내지 않고 fromEulerYXZDegrees처럼 단위와 순서를 드러내자 호출부가 길어졌지만 잘못된 사용은 크게 감소했습니다.

두 Quaternion 사이의 보간에서도 내적값이 음수일 때 한쪽 부호를 뒤집어 최단 경로를 선택해야 했습니다. 이 처리가 없으면 카메라가 가까운 방향 대신 거의 한 바퀴 돌아갑니다. 또한 두 회전이 매우 가까울 때는 slerp의 수치 안정성이 떨어질 수 있어 정규화된 선형 보간으로 전환했습니다. 작은 예외 처리가 실제 손맛을 좌우한다는 점을 가장 강하게 체감한 구간입니다.

프레임 기반 보간을 시간 기반으로 바꿨습니다

매 프레임 현재값에서 목표값까지 10% 이동시키는 코드는 간단하지만 프레임률과 호출 횟수에 따라 결과가 달라집니다. 저는 반감기 기반 감쇠 함수를 추가해 ‘몇 초 뒤 오차가 절반이 되는가’를 파라미터로 사용했습니다. 디자이너도 0.1이라는 추상적인 비율보다 0.2초 반감기라는 값을 더 쉽게 조정했습니다.

  • Quaternion 생성: 축은 먼저 정규화하고 각도 단위를 함수명에 표시했습니다.
  • 회전 보간: 내적 부호를 확인하고 결과를 다시 정규화했습니다.
  • 카메라 감쇠: delta time과 반감기를 이용해 프레임률 의존성을 줄였습니다.
  • 디버깅: Quaternion 원소보다 전방·상방 축을 화면에 선으로 표시했습니다.
회전 버그는 숫자 네 개만 바라보면 찾기 어렵습니다. 변환된 기저축과 예상 경로를 함께 시각화하니 원인을 훨씬 빨리 확인할 수 있었습니다.

기술을 발표 자료 수준으로 다듬는 과정도 도움이 됐습니다. 게임 개발자가 구현 사례와 실패 경험을 공유하는 행사 맥락은 GDC 관련 설명에서 확인할 수 있습니다. 저는 발표 슬라이드를 만든다는 생각으로 각 수학 규칙을 한 문장과 한 그림으로 설명해 보았고, 설명이 어려운 API는 실제 사용법도 복잡하다는 사실을 발견했습니다.

성능 측정과 테스트를 함께 운영한 후기

빠른 함수보다 예측 가능한 함수가 먼저였습니다

초기에는 inverse와 normalize에 빠른 역제곱근 근사를 적용하고 싶었습니다. 하지만 일반적인 데스크톱 빌드에서는 컴파일러 최적화와 표준 연산만으로도 목표 프레임 예산을 충족했고, 근삿값 때문에 테스트 허용 오차만 복잡해졌습니다. 프로파일링 결과 수학 함수 자체보다 Transform 배열을 흩어진 객체에서 읽는 메모리 접근 비용이 더 컸습니다.

그래서 단일 연산의 나노초 숫자보다 실제 워크로드를 측정했습니다. 1만 개 Transform의 월드 행렬 갱신, 스켈레톤 계층 계산, 대량 AABB 변환을 각각 반복했고 디버그와 릴리스 빌드를 분리했습니다. 2026년에도 CPU와 컴파일러, 빌드 옵션에 따라 결과가 달라질 수 있으므로 다른 사람의 벤치마크 수치를 그대로 성능 약속으로 사용하지 않는 것이 중요합니다.

테스트는 정확한 동등성과 근사 비교를 나눴습니다

부동소수점 결과에 단순한 == 비교를 남발하면 플랫폼이나 연산 순서가 바뀔 때 테스트가 쉽게 깨집니다. 반대로 모든 값에 넓은 epsilon을 쓰면 실제 오류도 통과합니다. 저는 0, 1 같은 정확히 표현 가능한 상수와 구조적 조건은 정확 비교를 사용하고, 삼각함수와 정규화 결과에는 절대 오차와 상대 오차를 조합했습니다.

  • 단위 테스트: 알려진 입력, 경계값, 퇴화 입력을 빠르게 확인합니다.
  • 속성 테스트: dot(a,b)=dot(b,a), 회전 후 길이 보존 같은 성질을 무작위로 검사합니다.
  • 차등 테스트: 엔진 또는 신뢰할 수 있는 기존 라이브러리 결과와 비교합니다.
  • 회귀 테스트: 실제 버그를 일으킨 값을 그대로 저장해 재발을 막습니다.
  • 벤치마크: 워밍업과 반복 횟수를 고정하고 중앙값을 기록합니다.

테스트를 붙인 뒤 가장 만족스러웠던 변화는 리팩터링 속도였습니다. 내부 저장 형식을 바꾸거나 SIMD 경로를 실험해도 외부 동작이 유지되는지 즉시 확인할 수 있었습니다. 다만 epsilon 값을 하나의 전역 상수로 두는 설계는 실패했습니다. 위치 단위, 정규화 판정, 행렬 역산처럼 문제의 규모가 다르기 때문에 용도별 허용 오차를 정의하는 편이 더 명확했습니다.

게임 프로젝트에 통합하며 확인한 장단점

엔진 경계에는 변환 어댑터를 두었습니다

자체 Vector3와 엔진 Vector3를 암시적으로 변환하면 편해 보이지만, 어느 타입으로 계산되는지 파악하기 어려웠습니다. 저는 렌더링, 물리, 에디터 경계에 toEngine과 fromEngine 함수를 두고 변환이 발생하는 위치를 검색 가능하게 만들었습니다. 이 방식은 코드가 몇 줄 늘어도 좌표계 변환과 단위 변환을 한곳에서 검토할 수 있다는 장점이 있었습니다.

콘텐츠 제작 단계에서는 기획 의도를 수치 계약으로 옮기는 일이 중요했습니다. 예를 들어 ‘적이 자연스럽게 플레이어를 바라본다’는 요구를 최대 각속도, 목표 방향, 반감기, 정지 오차로 나눴습니다. 게임 제작에서 기획 역할의 일반적 맥락은 기획자 관련 지식백과 설명도 참고할 수 있습니다. 개발자는 요구를 곧바로 보간식 하나로 바꾸기보다 조정 가능한 의미 단위로 번역해야 협업이 편해집니다.

실제로 추천하는 적용 범위

개인 포트폴리오나 학습 프로젝트라면 Vector, Matrix, Quaternion과 테스트까지 직접 구현할 가치가 충분했습니다. 수학 지식을 보여줄 수 있고 디버깅 경험도 쌓입니다. 반면 출시 일정이 촉박하거나 복잡한 충돌 검출, 고정밀 기하 연산이 핵심이라면 검증된 라이브러리를 감싼 얇은 계층부터 시작하는 편이 안전합니다.

  • 직접 구현 추천: 좌표계 규칙 학습, 포트폴리오 시연, 특수한 데이터 배치가 필요한 경우입니다.
  • 래퍼 추천: 엔진 교체 가능성에 대비하면서 검증 비용을 낮추고 싶은 경우입니다.
  • 기존 기능 유지: 일정이 짧고 내장 수학 계층이 프로젝트 요구를 충분히 만족하는 경우입니다.
  • 혼합 방식: 기본 연산은 기존 라이브러리를 쓰고 단위·좌표·보간 정책만 자체 API로 제공합니다.

제가 다시 시작한다면 처음부터 헤더 전용 대형 라이브러리를 만들지 않을 것입니다. 실제 게임 기능 하나를 고르고 필요한 연산만 추가한 뒤 테스트와 문서를 동시에 작성하겠습니다. 사용되지 않는 투영 변형이나 희귀한 행렬 분해까지 미리 구현했던 시간은 학습에는 유익했지만 프로젝트 진척에는 거의 도움이 되지 않았습니다.

이것만은 꼭 기억하세요: 배포 전 체크리스트

API보다 먼저 확인할 운영 항목

수학 라이브러리는 한 번 널리 퍼지면 수정 비용이 큽니다. Transform 곱의 순서를 바꾸는 작은 변경도 렌더링, 물리, 애니메이션, 툴 데이터에 연쇄 영향을 줍니다. 따라서 버전 규칙을 정하고, 동작 변경은 변경 기록과 마이그레이션 예제를 함께 제공해야 합니다. 저는 0.x 단계에서도 의미가 바뀌는 수정은 이름을 새로 만들고 기존 함수에는 폐기 경고를 남겼습니다.

또한 디버그 빌드의 검증 코드를 적극 활용했습니다. 유한값 검사, 정규화 여부, 행렬식 크기 같은 사전 조건을 assertion으로 확인하되 릴리스 성능과의 차이를 측정했습니다. 오류를 무조건 숨기는 안전 함수와 실패를 즉시 드러내는 개발용 함수를 구분하니 콘텐츠 오류와 프로그래밍 오류를 다르게 처리할 수 있었습니다.

제가 현재 사용하는 최종 점검 순서

  1. 좌표계, 단위, 행렬 저장 방식과 곱셈 순서를 README 첫 화면에 기록합니다.
  2. 영벡터, 평행 벡터, 특이 행렬, 180도 회전 입력을 테스트합니다.
  3. 모든 공개 함수가 NaN과 무한대를 어떻게 처리하는지 정합니다.
  4. 엔진 변환 어댑터에서 축 변환과 단위 변환을 한 번만 수행합니다.
  5. 실제 게임 장면을 대표하는 벤치마크를 릴리스 빌드로 실행합니다.
  6. 월드 축, 로컬 축, 법선, 속도 벡터를 화면에 그리는 디버그 도구를 준비합니다.
  7. 포트폴리오에는 코드 양보다 문제, 선택 이유, 테스트 결과를 함께 제시합니다.

가장 실용적인 사용 팁은 새 연산을 추가할 때 반드시 실제 호출 사례와 실패 사례를 하나씩 붙이는 것입니다. 구현만 있고 사용 장면이 없다면 아직 라이브러리에 들어갈 준비가 되지 않은 기능일 가능성이 큽니다. 반대로 카메라 흔들림이나 조준 회전처럼 눈으로 확인할 수 있는 사례까지 연결하면 게임 프로그래밍 포트폴리오에서도 설계 능력을 설득력 있게 보여줄 수 있습니다.

처음 시작한다면 Vector3 정규화 정책과 Transform 곱셈 규칙 두 가지부터 직접 정의해 보세요. 규모는 작지만 수치 안정성, API 설계, 테스트, 엔진 통합이라는 핵심 과정을 모두 경험할 수 있습니다. 그다음 프로젝트에서 반복되는 불편이 발견될 때 Matrix, Quaternion, 보간 도구를 차례로 확장하는 흐름이 제가 사용해 본 방식 중 가장 부담이 적었습니다.

2026 게임 수학 라이브러리 직접 만들어본 후기와 설계 가이드

댓글목록

등록된 댓글이 없습니다.