한양과학기술고등학교

에듀테크 프로젝트 대시보드

2D 변환 행렬 (Transformation Matrix)

2D 변환 행렬 (Transformation Matrix)

게임·그래픽 엔진은 도형을 옮기고(이동), 돌리고(회전), 늘이고(크기), 비트는(전단) 일을 끝없이 한다. 이 모든 변환을 딱 한 개의 3×3 행렬로 표현하고, 점 하나에 그 행렬을 곱하기만 하면 변환된 위치가 나온다. 비결은 동차좌표(homogeneous coordinates) — 점 (x, y)를 (x, y, 1)로 한 칸 늘려서, 곱셈만으로는 안 되던 “이동”까지 행렬 곱 하나에 태우는 것이다. 여러 변환은 행렬을 미리 곱해서 하나로 합쳐 두면 (M = T · R · S) 점 수백만 개도 같은 행렬 한 번씩만 곱하면 된다. 아래에서 슬라이더를 움직여 M 이 실시간으로 어떻게 바뀌는지 확인해 보자.

[ x' y' 1 ]T  =  M · [ x y 1 ]T   ( M = T(t) · R(θ) · S(sx, sy) · Sh(k) )

실시간 변환 실험실 — 슬라이더로 도형을 주무르기

T · R · S · Sh
·
점 x y 1
=
x' x' y' 1
회전을 먼저 걸고 이동을 주면 도형이 원점을 중심으로 도는 게 아니라 옮겨진 자리에서 돈다. 순서 버튼으로 T·R·S ↔ S·R·T 를 바꿔 차이를 눈으로 확인해 보세요 — 행렬 곱은 교환법칙이 성립하지 않는다.

기본 변환 4종 — 각 행렬이 하는 일

변환3×3 행렬 (동차좌표)점 (x, y)에 미치는 효과
이동 T(tx, ty) [1 0 tx] [0 1 ty] [0 0 1] x' = x + tx,   y' = y + ty  — 세 번째 열이 없으면 표현 불가
크기 S(sx, sy) [sx 0 0] [0 sy 0] [0 0 1] 원점 기준 확대/축소. s<0 이면 축 반사(거울상)
회전 R(θ) [cosθ −sinθ 0] [sinθ cosθ 0] [0 0 1] 원점을 중심으로 반시계 방향 θ 회전. 행렬식 = 1 (넓이 보존)
전단 Sh(k) [1 k 0] [0 1 0] [0 0 1] y가 클수록 x를 더 밀어 기울임 (이탤릭체 글꼴이 이 변환)
  • 합성 = 행렬 곱: 변환을 순서대로 A → B → C 로 적용하려면 M = C · B · A (오른쪽부터 점에 닿는다).
  • 어파인 변환: 위 4종의 조합은 항상 마지막 행이 [0 0 1] 인 행렬. 직선은 직선으로, 평행은 평행으로 유지된다.
  • 임의 점 기준 회전: T(p) · R(θ) · T(−p) — 원점으로 당겨와 돌리고 되돌린다.
  • 역변환: M-1 로 되돌린다. 회전의 역은 전치(RT), 크기의 역은 1/s.
  • 3D·GPU: 똑같은 아이디어를 4×4로 키운 것이 model · view · projection 행렬. 정점 셰이더가 매 정점마다 이 곱을 한다.

코드 — 합쳐서 한 번에 곱하기

import math

def mat_mul(A, B):              # 3x3 · 3x3
    return [[sum(A[i][k]*B[k][j] for k in range(3)) for j in range(3)] for i in range(3)]

def translate(tx, ty): return [[1,0,tx],[0,1,ty],[0,0,1]]
def scale(sx, sy):     return [[sx,0,0],[0,sy,0],[0,0,1]]
def rotate(deg):
    c, s = math.cos(math.radians(deg)), math.sin(math.radians(deg))
    return [[c,-s,0],[s,c,0],[0,0,1]]

# 이동 → 회전 → 크기 순으로 적용: M = T · R · S
M = mat_mul(translate(120, 40), mat_mul(rotate(30), scale(1.5, 1.5)))

def apply(M, x, y):
    return (M[0][0]*x + M[0][1]*y + M[0][2],   # z=1 이라 마지막 열이 그대로 더해짐
            M[1][0]*x + M[1][1]*y + M[1][2])

pts = [apply(M, x, y) for x, y in polygon]   # 정점 전부에 같은 M 한 번씩
# canvas 2D: ctx.setTransform(a, b, c, d, e, f) 도 결국 이 3x3 (마지막 행 고정)