

a x b: 2개의 3D 벡터에 대한 외적
a, b 모두에 수직인 새로운 3D 벡터를 구함
a x b의 방향은 오른속 법칙으로 결정
b x a = -a x b
|a x b| = |a||b|sinθ
a x a = 0 (θ가 0이므로)
3차원 물체의 면에 대해 수직인 벡터
삼각형을 이루는 세 점을 p0, p1, p2라고 했을 때
n = (p1 - p0) x (p2 - p0) = (a,b,c)
평면의 방정식: ax + by + cz + d = 0
벡터의 길이가 1인 단위 벡터로 만드는 방법
벡터의 길이
v: (vx,vy,vz)라고 하면
|v| = √(vx^2 + vy^2 + vz^2)
|v|^2 = vx^2 + vy^2 + vz^2 = v∘v
정규화
주어진 벡터와 방향이 일치하는 단위 벡터를 구함
u = v/|v|
허수 i = √-1, i^2 = -1
복소수 = 실수부 + 허수부
z = x + i y = r cosΦ + i r sinΦ = r e^(i * Φ)
(길이 r인 벡터를 Φ만큼 회전한 2차원 좌표)
이런 식으로 복소수를 2차원 좌표에 대응시킴
복소수 끼리의 곱으로 2차원 회전을 구현 가능
단위 벡터 z' = e^(i * θ)
z ∘ z' = z ∘ e^(i * θ) = (x + i y)(cosθ + i sinθ)
= (x cosθ - y sinθ) + i(x sinθ + y cosθ)

복소수를 확장
쿼터니언은 회전축이 되는 3D 벡터와 회전각도가 되는 스칼라 값으로 이루어져 있음
q = qx i + qy j + qz k + qw = (qx,qy,qz,qw) = (v, qw)
v = qx i + qy j + qz k = (qx,qy,qz)
v는 허수부, qw가 실수부

3차원 좌표를 쿼터니언으로 표현
실수 부분을 0으로 표현
p = (p,0) = px i + py j + pz k + 0
3차원 회전을 쿼터니언으로 표현
회전축 v, 회전각도 θ

3차원 좌표를 회전하는 식
점 p에 회전식 r과 r 인버스를 앞뒤에 곱해주면 점 p를 θ만큼 회전시킨 결과가 나옴

윈도우 앞에 가상의 구가 있다고 가정
윈도우에서 마우스를 클릭했을 때 구 상의 좌표로 매핑
2차원에서의 마우스 트래킹을 3차원의 회전으로 변환
matrix의 분리
한 번에 회전시키는 것이 아닌 축을 기준으로 여러번 반복해서 조금씩 회전
p' = matDrag * matUpdated * p
dragging 도중에는 matDrag만 수정
dragging이 끝나면 matUpdated에 모두 적용
단위 벡터 계산 함수
윈도우 상의 (x,y) 좌표를 구에 대응하는 3차원 좌표로 반환
glm::vec3 calcUnitVec(const glm::vec2& raw) {
glm::vec2 scr;
// 윈도우 밖을 벗어나지 않도록 예외처리
scr.x = std::clamp(raw.x, 0.0F, (float)WIN_W);
scr.y = std::clamp(raw.y, 0.0F, (float)WIN_H);
// normal processing
const GLfloat radius = sqrtf(WIN_W * WIN_W + WIN_H * WIN_H) / 2.0F;
glm::vec3 v;
v.x = (scr.x - WIN_W / 2.0F) / radius;
v.y = (WIN_H / 2.0F - scr.y) / radius;
v.z = sqrtf(1.0F - v.x * v.x - v.y * v.y);
return v;
}
glm::mat4 calcTrackball(const glm::vec2& start, const glm::vec2& cur) {
glm::vec3 org = calcUnitVec(start);
glm::vec3 dst = calcUnitVec(cur);
glm::quat q = glm::rotation(org, dst); // 회전 쿼터니언 반환
glm::mat4 m = glm::toMat4(q); // 회전 행렬로 변환
return m;
}
void cursorPosFunc(GLFWwindow* win, double xscr, double yscr) {
if (mousePressed == GL_TRUE) {
glm::vec2 dragCur = glm::vec2((GLfloat)xscr, (GLfloat)yscr);
matDrag = calcTrackball(dragStart, dragCur);
mat = matDrag * matUpdated; // vertex shader에 전달할 회전 행렬
}
}
// mat 배열을 uniform 레지스터에 등록
void drawFunc(void) {
...
GLuint locMat = glGetUniformLocation(prog, "uMat");
glUniformMatrix4fv(locMat, 1, GL_FALSE, glm::value_ptr(mat));
...
}
void mouseButtonFunc(GLFWwindow* win, int button, int action, int mods) {
GLdouble x, y;
switch (action) {
case GLFW_PRESS:
mousePressed = GL_TRUE;
glfwGetCursorPos(win, &x, &y);
dragStart = glm::vec2((GLfloat)x, (GLfloat)y); // 시작점 설정
break;
case GLFW_RELEASE:
mousePressed = GL_FALSE;
glfwGetCursorPos(win, &x, &y);
glm::vec2 dragCur = glm::vec2((GLfloat)x, (GLfloat)y);
matDrag = calcTrackball(dragStart, dragCur);
mat = matDrag * matUpdated;
matDrag = glm::mat4(1.0F); // 단위 행렬로 초기화 (나중을 위해)
matUpdated = mat; // update to the object matrix
break;
}
fflush(stdout);
}
#version 330 core
in vec4 aPos; // vertex position: attribute
in vec4 aColor; // vertex color: attribute
out vec4 vColor; // varying color: varying
uniform mat4 uMat; // matrix: uniform
void main(void) {
gl_Position = uMat * aPos; // transformation
gl_Position.z *= -1.0F; // negation
vColor = aColor;
}
마우스로 드래그하여 피라미드를 회전시키는 것을 확인

keyframes: 애니메이터가 설정하는 중요한 장면
in-betweens: 컴퓨터가 자동 생성하는 중간 장면
3차원 회전의 보간 방법
쿼터니언을 활용한 회전 보간
slerp(q0, q1; t) = q0 (q0^(-1) * q1)^t = q1 (q1^(-1) * q0)^(1-t)
= (q1 * q0^(-1))^t q0
(시간 t는 0.0~1.0)