요약

조절점들을 늘어놓으면 곡선이 그 점들 근처를 부드럽게 지나가되, 점을 꼭 지나지는 않는다. 각 조각은 이웃한 조절점 네 개만 보고, 이웃 조각과 조절점 셋을 함께 써서 이음점에서 위치·속도·가속도가 모두 맞는다(\(C^2\)). 점 하나를 옮기면 근처 조각 네 개만 바뀌어 고치기 쉽다. 대신 조절점을 정확히 지나는 곡선을 원할 때는 맞지 않고, 같은 길이의 곡선에 베지어보다 조각이 많아 계산이 더 든다.

예시로 보기

조절점이 \((0, 0)\), \((1, 3)\), \((3, 3)\), \((4, 0)\), \((6, 1)\), \((7, 4)\)로 6개다. 네 개씩 겹쳐 묶어 조각 세 개를 만든다.

조각 쓰는 조절점 출발점 \(\mathbf p(0)\)
1 \(\mathbf p_0..\mathbf p_3\) \(\frac16(\mathbf p_0 + 4\mathbf p_1 + \mathbf p_2) = (\frac76, \frac52)\)
2 \(\mathbf p_1..\mathbf p_4\) \(\frac16(\mathbf p_1 + 4\mathbf p_2 + \mathbf p_3)\)
3 \(\mathbf p_2..\mathbf p_5\) \(\frac16(\mathbf p_2 + 4\mathbf p_3 + \mathbf p_4)\)

첫 조각은 \(\mathbf p_1 = (1, 3)\)에서 시작하지 않는다. \(\mathbf p_1\) 쪽으로 끌려간 \((\frac76, \frac52)\)에서 시작한다. \(\mathbf p_5\)를 \((100, 100)\)으로 옮겨도 조각 1, 2는 그대로이고 조각 3만 바뀐다1.

그림

회색 점선이 조절점이고, 색 선이 세 조각이다. 곡선은 조절점을 지나지 않고 안쪽으로 끌려 들어간다. 조각이 바뀌는 점(동그라미)에서도 꺾임 없이 이어진다2.

검증: 블렌딩 함수와 \(M_S\), 합 1·0 이상, 예시와 카드 C2, 무작위 200개 곡선의 이음점에서 0·1·2계 도함수 일치, 3계는 다름, 국소 조절 — 16_b-spline_verify.py

정의

스플라인은 조절점들로 곡선을 만들되, 가운데 두 점 사이를 지나는 조각을 만든다. 조절점을 꼭 지날 필요는 없고 가까이 가기만 한다. 점 \(\mathbf p_{i-1}, \mathbf p_i, \mathbf p_{i+1}, \mathbf p_{i+2}\)로 \(\mathbf p_i\)와 \(\mathbf p_{i+1}\) 사이의 조각을 만든다. 점 하나를 더하면 다음 조각(\(\mathbf p_i..\mathbf p_{i+3}\))이 생긴다.

조절점        p0    p1    p2    p3    p4    p5
조각 1       [p0 -------------- p3]
조각 2             [p1 -------------- p4]
조각 3                   [p2 -------------- p5]
그리는 칸           p1~p2 p2~p3 p3~p4

조각마다 조절점 네 개의 창을 한 칸씩 밀며 쓴다. 이웃한 두 창은 조절점 셋을 함께 쓰고, 각 조각은 창 가운데 두 점 사이의 한 칸만 그린다.

이음점 조건을 다음처럼 정한다. 앞 조각 \(\mathbf q\)와 뒤 조각 \(\mathbf p\)가 이음점에서 같은 값을 갖도록, 이음점을 조절점의 무게 평균으로 둔다.

  • 위치: \(\mathbf q(1) = \mathbf p(0) = \frac16(\mathbf p_{i-1} + 4\mathbf p_i + \mathbf p_{i+1})\)
  • 속도: \(\mathbf q'(1) = \mathbf p'(0) = \frac12(\mathbf p_{i+1} - \mathbf p_{i-1})\)
  • 대칭으로 끝점도: \(\mathbf p(1) = \frac16(\mathbf p_i + 4\mathbf p_{i+1} + \mathbf p_{i+2})\), \(\mathbf p'(1) = \frac12(\mathbf p_{i+2} - \mathbf p_i)\)

이 선택이 유일하지는 않지만 잘 동작한다. 조절점 \(\mathbf p_0..\mathbf p_3\)에 대해 네 조건을 \(\mathbf c\)로 풀면 다음과 같다.

\[\mathbf p(u) = \mathbf u^\top M_S\mathbf p, \qquad M_S = \frac16\begin{pmatrix}1 & 4 & 1 & 0\\ -3 & 0 & 3 & 0\\ 3 & -6 & 3 & 0\\ -1 & 3 & -3 & 1\end{pmatrix}\]

블렌딩 함수는 다음과 같다.

\[\mathbf b(u) = \frac16\begin{pmatrix}(1 - u)^3\\ 4 - 6u^2 + 3u^3\\ 1 + 3u + 3u^2 - 3u^3\\ u^3\end{pmatrix}\]

네 함수는 \([0, 1]\)에서 모두 0 이상이고 합이 1이다. 그래서 조각은 네 조절점의 볼록 껍질 안에 있다1. 스플라인은 조각 하나가 조절점 사이 한 칸만 덮어 베지어(조각 하나가 네 점을 다 씀)보다 계산이 많다.

스스로 설명해 보기

1. 위치 조건만 정했는데 왜 2계 도함수까지 맞는가?

\(M_S\)로 2계 도함수를 계산하면 \(\mathbf p''(0) = \mathbf p_{i-1} - 2\mathbf p_i + \mathbf p_{i+1}\), \(\mathbf q''(1)\)도 같은 세 점의 같은 식이 된다. 이웃 조각이 조절점 셋을 같은 무게로 공유하기 때문이다(검증 코드에서 무작위 200개 모두 일치).

2. 조절점 하나가 조각 네 개에만 영향을 주는 이유

조각 하나는 조절점 네 개만 쓴다. 거꾸로 조절점 \(\mathbf p_k\)를 쓰는 조각은 \(\mathbf p_{k-3}..\mathbf p_k\)부터 \(\mathbf p_k..\mathbf p_{k+3}\)까지 넷뿐이다.

3. 곡선이 조절점을 지나지 않는 이유

이음점이 \(\frac16(\mathbf p_{i-1} + 4\mathbf p_i + \mathbf p_{i+1})\)로 세 점의 평균이다. \(\mathbf p_i\)에 가장 큰 무게(4/6)를 주지만 이웃도 섞이므로, 세 점이 한 직선 위에 같은 간격으로 있지 않으면 \(\mathbf p_i\)와 다르다.

이 방법의 핵심 아이디어는?

이음점의 위치와 기울기를 “이웃 조절점의 고정된 무게 평균”으로 정하면, 이웃 조각이 같은 공식을 써서 자동으로 매끄럽게 맞물린다.

이 방법을 쓸 수 있는 다른 상황은?

이웃끼리 같은 무게 평균을 쓰는 다른 곳. 예: 이미지 확대의 쌍3차 보간, 신호의 이동 평균 필터.

활용

  • 연습: 예제 사다리(완전한 풀이 → 빈칸 → 독립 문제)
  • CAD와 3D 모델링(NURBS는 B-스플라인에 무게와 불균등 간격을 더한 것), 폰트 설계, 로봇 경로처럼 \(C^2\) 매끄러움과 국소 수정이 필요한 곳에 쓴다1.
  • 흔한 실수: 끝 조절점에서 곡선이 시작한다고 생각하는 것. 균등 B-스플라인은 \(\mathbf p_0\)에서 시작하지 않는다. 끝을 맞추려면 끝 조절점을 세 번 겹쳐 놓는다.

연결

자주 하는 오해

“더 매끄러운 곡선이 조절점에 더 정확히 맞는다”

틀렸다. B-스플라인은 보간 곡선보다 매끄럽지만(\(C^2\) 대 \(C^0\)) 조절점을 지나지 않는다. 매끄러움과 정확한 통과는 서로 맞바꾸는 성질이다. 정확히 지나게 하면(3차 보간) 블렌딩 값이 음수가 되어 출렁이고, 무게를 모두 0 이상으로 두면(B-스플라인) 점들 안쪽으로 끌려 들어간다. 예시에서 첫 조각은 \((1, 3)\)이 아니라 \((\frac76, \frac52)\)에서 시작한다.

확인 문제

C1 균등 3차 B-스플라인의 이음점 위치와 속도 조건을 쓰라.

답: \(\mathbf p(0) = \frac16(\mathbf p_{i-1} + 4\mathbf p_i + \mathbf p_{i+1})\), \(\mathbf p'(0) = \frac12(\mathbf p_{i+1} - \mathbf p_{i-1})\). 끝 \(u = 1\)도 한 칸 옮긴 같은 식.

C2 조절점 \((0, 0)\), \((1, 3)\), \((3, 3)\), \((4, 0)\)으로 만든 조각의 출발점 \(\mathbf p(0)\)은?

답: \(\frac16((0, 0) + 4(1, 3) + (3, 3)) = \frac16(7, 15) = (\frac76, \frac52)\).

C3 B-스플라인 조각이 조절점 네 개의 볼록 껍질 안에 머무는 이유는?

답: 블렌딩 함수 네 개가 \([0, 1]\)에서 모두 0 이상이고 합이 1이다. 그래서 곡선의 점은 조절점의 가중 평균이고, 가중 평균은 볼록 껍질 밖으로 나갈 수 없다.

C4 여섯 점을 모두 정확히 지나는 곡선이 필요하다. 3차 보간, 에르미트, B-스플라인 중 무엇을 쓰고, B-스플라인은 왜 아닌가?

답: 점을 지나야 하므로 3차 보간이나 에르미트(이음점 매끄러움이 필요하면 에르미트, 접선은 이웃 점 차이로)를 쓴다. B-스플라인은 조절점을 지나지 않는다.

  1. 검증 코드로 확인했다. ↩ ↩2 ↩3

  2. 16_b-spline_plot.py로 그렸고, 같은 코드로 다음 값을 확인했다: 첫 조각의 출발점 \((\frac76, \frac52)\), 두 이음점에서 0·1·2계 도함수 일치. ↩