TL;DR — 쿼터니언
r은 그 자체로 월드 회전도 로컬 회전도 아닙니다.r * q(왼쪽 곱)이면 월드로 나온 뒤r이 작용하여 월드 축 기준 회전이 되고,q * r(오른쪽 곱)이면 월드로 나가기 전에r이 작용하여 로컬 축 기준 회전이 됩니다. FPS 카메라에서 yaw를 왼쪽에, pitch를 오른쪽에 곱하는 이유가 여기서 나옵니다.
Table of contents
Open Table of contents
들어가며
쿼터니언으로 회전을 누적할 때, 같은 angleAxis 값을 왼쪽에 곱하느냐 오른쪽에 곱하느냐에 따라 결과가 완전히 달라집니다. 처음에는 “왼쪽 = 월드, 오른쪽 = 로컬”이라고 외웠지만, 왜 그런지를 수식으로 풀어보니 결합법칙에서 자연스럽게 도출되는 구조였습니다.
이 글에서는 column-vector 컨벤션(GLM)에서 쿼터니언 곱 순서가 공간 기준을 결정하는 원리를 수식으로 전개하고, FPS 카메라의 yaw/pitch를 예시로 정리했습니다.
이 글에서 다루는 내용:
- orientation 쿼터니언
q가 의미하는 것 - 왼쪽 곱
r * q와 오른쪽 곱q * r의 수식 전개 r자체는 월드도 로컬도 아니라는 핵심 인사이트- FPS 카메라에서의 실제 적용
사전 지식: 쿼터니언의 기본 연산(켤레, 역, 회전 적용 )과
GLM의 column-vector 컨벤션을 전제로 합니다.
1. orientation 쿼터니언이 의미하는 것
오브젝트의 현재 자세를 나타내는 orientation 쿼터니언 q는 identity에서 출발하여 회전을 누적한 결과입니다. q는 로컬 공간의 벡터를 월드 공간으로 변환하는 역할을 합니다:
q가 identity이면 로컬과 월드가 일치하고, q에 회전이 쌓일수록 로컬 축이 월드 축에서 벗어납니다. 여기에 새로운 회전 r을 추가할 때, r을 어느 쪽에 곱하느냐에 따라 결과가 달라집니다.
2. 왼쪽 곱 — 월드 축 기준 회전
새 회전 r을 왼쪽에 곱하면 합성 쿼터니언은 r * q가 됩니다:
역쿼터니언 성질 을 적용하면:
괄호로 실행 순서를 나타내면:
- 안쪽: 가 을 월드 공간으로 보냅니다
- 바깥쪽: 이 이미 월드에 있는 결과를 한 번 더 돌립니다
은 월드 공간의 벡터에 작용하므로, 의 축은 월드 축 기준으로 해석됩니다.
3. 오른쪽 곱 — 로컬 축 기준 회전
새 회전 r을 오른쪽에 곱하면 합성 쿼터니언은 q * r이 됩니다:
을 적용하면:
괄호로 실행 순서를 나타내면:
- 안쪽: 이 을 아직 로컬 공간에서 돌립니다
- 바깥쪽: 가 그 결과를 월드로 보냅니다
은 월드로 나가기 전에 작용하므로, 의 축은 로컬 축 기준으로 해석됩니다.
4. 핵심 인사이트 — r은 월드도 로컬도 아니다
수식을 전개하면서 깨달은 점이 있습니다. r 자체에는 “월드 회전” 또는 “로컬 회전”이라는 성질이 없습니다. r은 단지 “어떤 축으로 얼마만큼 회전”이라는 정보일 뿐입니다.
곱하는 위치가 공간 기준을 결정합니다.
| 왼쪽 곱 | 오른쪽 곱 | |
|---|---|---|
| 이 작동하는 시점 | 월드로 나온 후 | 월드로 나가기 전 |
| 이 보는 공간 | 월드 공간 | 로컬 공간 |
| 결과 | 월드 축 기준 회전 | 로컬 축 기준 회전 |
이 구조는 결합법칙으로 자연스럽게 전개되는 것이지, 별도의 규칙을 외워야 하는 것이 아닙니다.
5. FPS 카메라 예시
FPS 카메라에서 yaw(좌우 회전)와 pitch(상하 회전)는 서로 다른 공간 기준이 필요합니다:
- Yaw: 캐릭터가 어디를 보고 있든 항상 세상의 위쪽(월드 축) 기준으로 좌우를 돌려야 합니다
- Pitch: 현재 바라보는 방향에서 자기 기준(로컬 축)으로 위아래를 돌려야 합니다
GLM 코드로 표현하면 다음과 같습니다:
glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // identity
void rotateWorldY(float degrees) {
glm::quat qYaw = glm::angleAxis(glm::radians(degrees), glm::vec3(0, 1, 0));
orientation = glm::normalize(qYaw * orientation); // 왼쪽 곱 → 월드 축
}
void rotateLocalX(float degrees) {
glm::quat qPitch = glm::angleAxis(glm::radians(degrees), glm::vec3(1, 0, 0));
orientation = glm::normalize(orientation * qPitch); // 오른쪽 곱 → 로컬 축
}
qYaw * orientation—qYaw가 왼쪽에 있으므로, 월드로 변환된 후에 작용합니다. 세상 기준 위쪽 축으로 몸 전체를 좌우로 돌립니다.orientation * qPitch—qPitch가 오른쪽에 있으므로, 월드로 나가기 전에 작용합니다. 현재 몸체의 로컬 축으로 고개를 든다/숙입니다.
같은 glm::angleAxis로 만든 회전이라도, 곱하는 위치가 공간 기준을 결정합니다.
5.1 만약 pitch를 왼쪽에 곱하면?
pitch를 qPitch * orientation으로 왼쪽에 곱하면 월드 축 기준 회전이 됩니다. 카메라가 yaw로 90도 돌아간 상태에서 pitch를 적용하면, 카메라 기준 위아래가 아니라 세상 기준 축으로 돌아가므로 의도와 다른 회전이 발생합니다.
정리하며
핵심 요약:
- orientation
q는 로컬 벡터를 월드로 보내는 변환입니다: - 왼쪽 곱
r * q는 월드로 나온 후r이 작용합니다 — 월드 축 기준 회전 - 오른쪽 곱
q * r은 월드로 나가기 전r이 작용합니다 — 로컬 축 기준 회전 r자체에 월드/로컬 속성이 있는 것이 아니라, 곱하는 위치가 해석을 결정합니다- 이 구조는 결합법칙에서 자연스럽게 도출되므로, “왼쪽 = 월드” 규칙을 외우기보다 수식 전개를 한 번 따라가 보는 것을 권장합니다
참고 자료
- 3D Math Primer for Graphics and Game Development — 쿼터니언과 회전의 수학적 기초
- GLM Documentation —
glm::angleAxis,glm::quat연산 레퍼런스
이 게시물은 학습한 내용을 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 내용을 검수하고 다듬어 완성되었습니다.