Lemma
수학, 거꾸로
도입 · 손잡이 몇 개, 매끄러운 길

컴퓨터는 점 몇 개를 어떻게 매끄러운 곡선으로 바꿀까?

디자이너가 손잡이 네 개를 움직인다. 영화 캐릭터의 볼이 매끄러워지고, 자동차 보닛이 휘고, 글자에 곡선이 잡힌다. 손잡이는 곡선 위에 있지 않다 — 자석처럼 곡선을 끌어당길 뿐이다. 하나를 움직이면 그 근처 곡선만 바뀌고 나머지는 가만히 있다. 한 연산을 재귀로 적용하면 이 모두가 나온다. 그 연산은 두 점 사이의 선형 보간 — 그것뿐.

아래 위젯의 손잡이를 끌어보자. t를 슬라이드하면 작도가 한 층씩 무너져 한 점에 닿는다 — 그 점이 곡선을 그린다.

위젯 — 제어점
P0P1P2P3B(t)
B(0.50) = (260, 105)
P₀ … P₃ 중 아무거나 끌어보자. 점선 회색은 제어 다각형, 초록 곡선이 베지에. t를 0에서 1로 끌면 주황·갈색 층이 차례로 무너져 내려 초록 점에 닿는다. 곡선은 그 마지막 점이 단위 구간을 지나며 *그리는 자취*. 한 번 lerp, 그 결과로 또 lerp, 또 한 번 — 그게 알고리즘 *전부*.
흐름
1

왜 그냥 점들을 잇지 않을까?

컴퓨터에 “이 다섯 점을 지나는 매끄러운 곡선”을 그려달라 해보자. 첫 시도: 다항식 하나로 보간. 점이 두세 개면 잘 된다. 다섯 개를 넘으면 점들 사이에서 심하게 출렁인다 (Runge 현상). 스플라인은 국소 조각을 이어 붙여 이 문제를 푸는데, 조각 하나하나의 모양을 또 정해야 한다. 베지에의 발상은 다르다 — 점들을 지나는 곡선을 찾지 말고, 점들이 모양을 잡는 곡선을 찾자. 점들은 이 아니라 손잡이가 된다.

2

두 점 케이스 — lerp

두 점 P0P₀P1P₁ 사이의 선형 보간 lerp(P0,P1,t)=(1t)P0+tP1lerp(P₀, P₁, t) = (1−t)·P₀ + t·P₁. t=0t = 0이면 P0P₀, t=1t = 1이면 P1P₁, 그 사이는 직선. 너무 단순해서 아무것도 아닌 듯 보인다. 그런데 베지에 곡선에 필요한 도구는 이것뿐이다.

# A point is a 2-tuple. Lerp is one line.
def lerp(a, b, t):
    return (a[0] + (b[0] - a[0]) * t,
            a[1] + (b[1] - a[1]) * t)

lerp((0, 0), (4, 2), 0.0)    # → (0, 0)
lerp((0, 0), (4, 2), 0.5)    # → (2, 1)
lerp((0, 0), (4, 2), 1.0)    # → (4, 2)
3

재귀적 lerp — 드 카스텔조

네 개 P0,P1,P2,P3P₀, P₁, P₂, P₃에 이렇게 해보자. 인접 쌍을 모두 tt로 lerp해 새 점 셋을 얻는다. 그 셋의 인접 쌍을 lerp해 둘. 그 둘을 lerp해 하나. 그 마지막 점이 매개변수 tt에서의 B(t)B(t). 한 “층”이 점의 개수를 하나씩 줄이고, 제어점이 n+1n+1개면 nn층 만에 끝난다. 이것이 이고, 다항식 형태 B(t)=(1t)3P0+3(1t)2tP1+3(1t)t2P2+t3P3B(t) = (1−t)³P₀ + 3(1−t)²t·P₁ + 3(1−t)t²·P₂ + t³P₃완전히 동치 — 수는 같고, 셈만 더 친절하다.

위젯에서 층마다 색이 다르다. 점선 회색이 원래 제어 다각형, 주황이 레벨-1 lerp, 갈색이 레벨-2 lerp, 마지막 초록 점이 B(t)B(t). tt 슬라이더를 끌어보면 작도가 무너지면서 초록 점이 움직이고, 그 자취가 그대로 곡선이 된다. 곡선은 별도의 공식으로 그리는 게 아니다 — 재귀의 자취 자체다.

# De Casteljau: lerp every adjacent pair, then again, until 1 point remains.
def bezier(controls, t):
    pts = list(controls)
    while len(pts) > 1:
        pts = [lerp(pts[i], pts[i+1], t) for i in range(len(pts) - 1)]
    return pts[0]

# Cubic Bezier — four control points
P = [(0, 0), (1, 2), (3, 2), (4, 0)]
bezier(P, 0.0)   # → (0, 0)            (= P[0], starts at first)
bezier(P, 1.0)   # → (4, 0)            (= P[-1], ends at last)
bezier(P, 0.5)   # → (2.0, 1.5)         (midpoint by recursion)
4

그림에서 읽히는 것들

증명 없이, 작도를 들여다보기만 해도 네 가지 사실이 떨어진다.

  • 끝점. t=0t = 0에서 모든 lerp가 왼쪽 점을 돌려주므로 B(0)=P0B(0) = P₀. 대칭으로 B(1)=P3B(1) = P₃. 곡선은 첫 제어점과 마지막 제어점을 지난다.
  • 끝점에서의 접선. t0t ≈ 0 부근의 레벨-1 lerp는 선분 P0P1P₀P₁ 위에 놓이고, 곡선은 그 방향으로 출발한다. 따라서 P0P₀에서 P1P₁ 쪽으로 출발하고, P3P₃에는 P2P3P₂P₃ 방향으로 도착한다. 디자이너는 이 사실로 두 곡선을 매끄럽게 잇는다 — 끝 손잡이를 정렬하면 된다.
  • 볼록껍질. 층마다 lerp는 이전 층의 볼록조합 — 마지막 점은 제어점들의 볼록조합이다. 그래서 곡선은 다각형의 볼록껍질 안에 머물고 밖으로 나가지 않는다. 충돌 판정과 클리핑에 유용하다.
  • 제어 다각형은 곡선이 아니다. 곡선을 가두고, 곡선의 방향을 가리키며, 끌어 움직일 수 있는 도구지만, 곡선은 다각형 안쪽에 — 모서리에서 부드럽게 멀어진 채로 — 자리 잡는다.
# Bernstein form — algebraically equivalent to De Casteljau.
def bezier_bernstein(P, t):
    s = 1 - t
    bx = (s**3 * P[0][0] + 3*s*s*t * P[1][0]
        + 3*s*t*t * P[2][0] + t**3 * P[3][0])
    by = (s**3 * P[0][1] + 3*s*s*t * P[1][1]
        + 3*s*t*t * P[2][1] + t**3 * P[3][1])
    return (bx, by)

bezier_bernstein(P, 0.5)  # → (2.0, 1.5)   same answer, different bookkeeping
#
# B'(0) = 3(P[1] - P[0])  →  tangent at start points along P0→P1
# B'(1) = 3(P[3] - P[2])  →  tangent at end points along P2→P3
# A designer reads "the curve leans into the next handle" off these two facts.
5

왜 모든 그래픽 스택이 이걸 넣고 다닐까

위의 성질이야말로 도구에 정확히 필요한 것들이다. 국소 제어 — 손잡이 하나를 움직이면 근처 곡선만 바뀐다. 아핀 불변성 — 제어점을 변환하면 곡선도 같은 방식으로 따라 변환된다 (회전·스케일·이동에 식 재계산이 필요 없다). 수치 안정성 — 드 카스텔조는 lerp뿐이라 고차 다항식 특유의 상쇄가 없다. 이어붙이기 — 3차 베지에를 끝점·접선 맞춰 줄줄이 잇으면 B-스플라인이 된다 — CAD와 애니메이션의 일꾼.

구체적으로: TrueType은 2차 베지에, PostScript와 현대 폰트 대부분은 3차; SVG의 path 데이터는 약식 표기를 더한 베지에 문법; Figma·Illustrator·Inkscape — 바닥엔 모두 같은 재귀; 픽사의 매끄러운 애니메이션 곡선, CSS의 “ease-in-out” 타이밍 함수 — 사람이 손수 고른 3차 베지에다. 한 가지 알고리즘, 네 개의 손잡이, 곡선 산업 전체.

더 깊은 다리: 베지에 곡선은 매개변수 곡선 모듈을 소비한다. 곡선의 이미지는 매끄러운 경로, 매개변수화tB(t)t ↦ B(t). 디자이너에게 보이는 것은 보통 이미지뿐이지만, 그것을 한 단계씩 따라가 그려내는 쪽은 매개변수화다.

이제 깨봐

베지에의 “매끄러움”은 국소 주장이지 전역 주장이 아니다. 제어 다각형이 날카롭게 꺾이는 3차 베지에는 자기 자신과 교차하는 곡선을 만든다 — 미분적으로는 여전히 매끄럽지만 (B’(t) 연속), 시각적으로는 병적이다. 위젯에서 P0=(60,80),P1=(440,240),P2=(60,240),P3=(440,80)P₀ = (60, 80), P₁ = (440, 240), P₂ = (60, 240), P₃ = (440, 80)으로 두면 자기 교차하는 Z가 나온다. 디자이너의 “매끄러움”과 미분기하학의 “매끄러움”은 여기서 갈라지지만, 손잡이 네 개라는 추상화는 그 사실을 한 마디도 경고해주지 않는다.

베지에는 lerp의 재귀. 두 점 사이의 한 연산을 인접 쌍에 적용하고, 그 결과 쌍에 또 적용하고, 또 한 번. 손잡이를 움직이면 lerp가 따라가고, 곡선은 lerp를 따라간다.

exercises · 손으로 풀기
1손으로 lerp계산기 없이

lerp((0,0),(4,2),0.25)lerp((0, 0), (4, 2), 0.25)를 계산하라. 그 다음 lerp((0,0),(4,2),0.75)lerp((0, 0), (4, 2), 0.75). 두 점을 양 끝점 사이에 그려보자.

23차 곡선의 중간점계산기 없이

제어점 P=[(0,0),(1,2),(3,2),(4,0)]P = [(0, 0), (1, 2), (3, 2), (4, 0)]인 3차 베지에에서 드 카스텔조로 B(0.5)B(0.5)를 계산하라. 모든 층을 다 보여라.

3끝점에서의 접선

왜 3차 베지에는 P1P0P₁ − P₀ 방향으로 출발하고 P3P2P₃ − P₂ 방향으로 도착할까? 드 카스텔조 작도로 기하적 논증을 하고, 다항식 형태에서 B(0)B'(0)를 계산해 확인하라.

4다각형 vs 곡선

제어 다각형과 베지에 곡선은 첫 점과 마지막 점이 같고 대략 비슷한 모양이다. 둘이 다른 세 가지 방식을 들어라. 차이가 분명히 보이는 예를 위젯으로 만들어보자.

왜 교과서는 이렇게 안 가르치나

컴퓨터 그래픽 교과서는 보통 베지에를 번스타인 기저 공식 — B(t)=Σbi,n(t)PiB(t) = Σ b_i,n(t) · P_i — 으로 소개한다. 독자가 믿을 만한 근거가 전혀 없는 다항식 합인 채로. Lemma는 반대편 끝에서 출발한다: lerp — 디자이너가 이미 아는 단 하나의 연산. 그 lerp를 재귀로 세 번 적용하면 번스타인 형식이 유도된다. 업계는 공식을 쓰지만, 이해는 재귀에서 시작한다. 번스타인 기저는 결과지, 출발점이 아니다.

용어집 · 이 페이지에서 쓰임 · 3
lerp·lerp
두 값 사이의 선형 보간: `lerp(a, b, t) = (1 − t) · a + t · b`. `t = 0`이면 `a`, `t = 1`이면 `b`, 그 사이는 직선으로 잇는다. 숫자·점·색·변환 — 덧셈과 스칼라곱이 정의된 모든 것에 작동한다. 베지에 곡선·애니메이션 트위닝·그라디언트 셰이딩·대부분의 컴퓨터 그래픽 보간이 그 위에 서 있는 _유일한_ 원시 연산.
control point·제어점
베지에 곡선을 정의하는 손잡이. 곡선은 첫 제어점에서 시작해 마지막 제어점에서 끝나고, 중간 제어점들 쪽으로 _휘기만_ 한다 (점을 통과하지 않는다). 제어점 하나를 움직이면 그 근처 곡선만 바뀐다 — 디자이너가 의존하는 _국소 제어_ 속성. 연속한 제어점들을 이은 다각형을 *제어 다각형*이라 부르며, 곡선을 가두긴 하지만 (베지에 곡선은 제어점들의 볼록껍질 안에 있다) 곡선 자체는 결코 아니다.
De Casteljau's algorithm·드 카스텔조 알고리즘
매개변수 `t`에서 베지에 곡선을 계산하는 재귀적 방법. 제어점 `n+1`개가 주어지면 인접 쌍을 모두 `t`로 lerp해서 새 점 `n`개를 얻고, 그 점들을 다시 lerp해서 `n−1`개를 얻고, … 점 하나가 남을 때까지 반복 — 그 점이 `B(t)`. 수치적으로 안정적, 기하적으로 투명, 번스타인 계수 관리 없음, 중간 "층" 자체가 디자이너가 벡터 도구에서 보는 보조선이 된다. 번스타인 다항식 형태와 수학적으로 동치이지만, 손으로 굴리기에 훨씬 친절하다.