베지어 곡선

Bézier curves

Written by Paul Bourke
Original: April 1989, Updated: December 1996

이 문서는 베지어 곡선이라는 특수한 곡선에 대한 수학적인 설명을 담고 있습니다. 이 곡선의 이름은, 1970년대 Renault car를 디자인하는 과정에서 이 곡선을 사용했던 Pierre Bézier라는 프랑스 엔지니어의 이름을 따서 만들어졌습니다. 그때부터 이 곡선은 식자 산업(typesetting industry)에서 절대적인 우위를 점하게 되었고, 특히 Adobe Postscript, 폰트 제품 등에서 널리 사용되었습니다.

3차원 공간에 있는 N+1개의 조절점(control point) pk (k=0,1,...,N)를 생각해봅시다. 베지어 parametric 곡선 공식은 다음과 같습니다.

B(u)는 서로 다른 위치에 있는(discrete) N개의 조절점에 의해 얻어지는 곡선을 구하기 위한 연속함수입니다. u=0이면 첫번째 조절점(k=0)에, u=1이면 마지막 조절점(k=N)에 도달합니다. 아래는 N=4, 즉 조절점이 4개인 경우의 곡선 모습입니다.
역자 주 : 첫번째 조절점이 시작점, 마지막 조절점이 끝점이라고 생각하시면 쉽겠죠? ^_^
그리고, N개의 조절점이 아니라 N+1의 조절점이라고 해야 하는 듯.
나중에 나오지만, 조절점의 위치가 모두 각각 다를 필요는 없습니다.

주의점:

이 베지어 곡선은, 조절점 중 처음과 끝 점을 빼고는 보통 어느 조절점과도 만나지 않습니다. 공식을 사용하면 B(0) = P0 이고 B(1) = PN 이 되겠지요.

곡선은, 항상 조절점으로 이루어진 다각형 안에 들어가게 됩니다. 곡선이 조절점들 사이에서 크게 벗어나거나 하는 일은 없습니다.

조절점이 P0 하나뿐이라면, 즉 N=0 이라면 모든 u에 대해 B(u) = P0 입니다. (역자 주 : u는 0이상 1 이하의 실수)

조절점이 P0 , P1 두개뿐이라면, 즉 N=1이라면 이 공식은 두 점 사이를 잇는 선분을 만들어냅니다.

라는 부분은 blending 함수라고 하는데, 이 부분에서 조절점들을 blend해서 곡선을 생성하기 때문입니다.

blending 함수는 조절점의 갯수보다 차수가 하나 낮은 다항함수입니다. 예를 들어, 3개의 조절점이 있다면 베지어 곡선은 포물선을 그리고, 4개의 조절점으로는 3차곡선을 얻게 됩니다.

첫번째 조절점의 위치와 마지막 조절점의 위치가 같을 경우 폐곡선이 생깁니다. 이때 처음의 두 조절점 사이의 접선(tangent)과 끝의 두 조절점의 접선이 같을 경우, first order continuity가 가능합니다. (역자 주 : P0 - P1 간의 기울기와 PN-1 - PN 간의 기울기가 같을 경우..라고 보시면 되겠습니다. first order continuity는, 두 곡선의 (1계)도함수가 같은, 즉 접선의 기울기가 서로 같은 상태를 의미합니다.)

비슷한 위치에 여러 조절점이 있을 경우, 그쪽으로 베지어 곡선을 "당기는" 정도가 증가합니다.

조절점의 갯수가 많아지면, 좀더 높은 차수의 식과, 많은 횟수의 팩토리얼 값을 계산해야 합니다. 그래서 보통 긴 곡선을 만들 때는, 그 곡선을 다시 여러 곡선으로 작게 쪼개는 방법을 씁니다. 이렇게 하면, 전체적인 모습에 변화를 주지 않고도 곡선의 부분적인 형태를 쉽게 바꿀 수 있는 효과도 얻게 됩니다. 물론 곡선이 첫번째 조절점에서 시작해서 마지막 조절점에서 끝나기 때문에, (나눠진) 곡선 조각들을 이어붙이기는 쉽습니다. 또한 베지어 곡선에서는 마지막 점에서의 접선이 마지막 두 조절점을 이은 선과 같기 때문에, (나누어진 곡선들의) 첫번째 조절점의 기울기도 맞출 수 있습니다.
Second order continuity는 보통 불가능합니다. (역자 주 : Second order continuity는, 이계도함수의 값이 같을 때의 상태입니다. 즉, 곡선의 변화율이 같다는 뜻입니다.)

조절점이 2개인 특수한 경우(직선)을 제외하고는, 한 베지어 곡선에 평행한 다른 베지어 곡선을 유도(derive)해내는 것은 불가능한 것으로 알려져 있습니다.

베지어 곡선으로 원을 완벽하게 표현할 수는 없습니다.

coincident parallel curves나 직선인 베지어 곡선의 경우를 제외하고는, 한 베지어 곡선에 평행한 베지어 곡선을 만드는 것은 불가능합니다.

조절점이 3개일 경우는 공식이 다음과 같이 정리됩니다 :

B(u) = P0 * ( 1 - u ) 2 + P1 * 2 * u ( 1 - u ) + P2 u2

조절점이 4개일 경우는 공식이 다음과 같이 정리됩니다 :

B(u) = P0 * ( 1 - u )3 + P1 * 3 * u * ( 1 - u )2 + P2 * 3 * u2 * ( 1 - u ) + P3 * u3

베지어 곡선은 굉장히 안정적인 형태를 만들고, 계산하기도 쉽기 때문에 널리 쓰이고 있습니다. 이런 형태의 베지어 곡선 말고도 조금 다른 모습을 만들어 내는, 또다른 베지어 곡선 공식이 있습니다. 대체로 모습은 비슷하지만, 곡선이 각 조절점을 모두 통과한다는 점이 다릅니다. Spline 곡선을 참조하세요.

예제
분홍색 선이 조절점들을 이은 선이고, 회색 선이 베지어 곡선입니다.

베지어 곡선을 표현하는 다항식의 차수는 조절점의 갯수보다 하나 낮기 때문에, 조절점이 세 개인 이 경우에는 2차곡선이 만들어집니다. 두 곡선의 조절점의 모양이 대칭을 이루면, 곡선 또한 항상 대칭입니다.

곡선은 항상 끝점을 지나며, 처음의 두 점을 이은 선에 접하고 끝의 두 점을 이은 선에도 접합니다. 이 성질로 인해, first order continuity를 유지하면서 여러 베지어 곡선을 이을 수 있게 됩니다.

곡선은 항상 조절점들로 이루어진 볼록다각형(convex hull) 안에 있게 됩니다. 그래서 곡선은 "안정적이고", 엉뚱하게 튀어나가는 일이 없습니다.

시작점과 끝점을 갖게 했을때 곡선이 생깁니다. 시작점과 끝점의 접선(tangents)이 일치할 경우 곡선은 first order continuity를 유지하면서 닫히게 됩니다. 또, 조절점을 한 곳에 여러번 지정하게 되면, 그 쪽으로 곡선이 쏠리게 됩니다.

C source

(역자 주 : XYZ는 다음과 같은 struct입니다 - struct XYZ { double x,y,z; }; )

/*
조절점이 3개인 베지어 곡선 예제
mu는 0(곡선의 시작)부터 1(곡선의 끝) 사이의 값을 가질 수 있습니다.
*/
XYZ Bezier3(XYZ p1,XYZ p2,XYZ p3,double mu)
{
double mum1,mum12,mu2;
XYZ p;

mu2 = mu * mu;
mum1 = 1 - mu;
mum12 = mum1 * mum1;
p.x = p1.x * mum12 + 2 * p2.x * mum1 * mu + p3.x * mu2;
p.y = p1.y * mum12 + 2 * p2.y * mum1 * mu + p3.y * mu2;
p.z = p1.z * mum12 + 2 * p2.z * mum1 * mu + p3.z * mu2;

return(p);
}

/*
조절점이 4개인 베지어 곡선 예제
*/
XYZ Bezier4(XYZ p1,XYZ p2,XYZ p3,XYZ p4,double mu)
{
double mum1,mum13,mu3;
XYZ p;

mum1 = 1 - mu;
mum13 = mum1 * mum1 * mum1;
mu3 = mu * mu * mu;

p.x = mum13*p1.x + 3*mu*mum1*mum1*p2.x + 3*mu*mu*mum1*p3.x + mu3*p4.x;
p.y = mum13*p1.y + 3*mu*mum1*mum1*p2.y + 3*mu*mu*mum1*p3.y + mu3*p4.y;
p.z = mum13*p1.z + 3*mu*mum1*mum1*p2.z + 3*mu*mu*mum1*p3.z + mu3*p4.z;

return(p);
}

/*
일반적인 베지어 곡선 예제
조절점의 갯수는 n+1개가 됩니다.
0 <= mu < 1 *중요!* 마지막 조절점은 계산하지 않습니다.
*/
XYZ Bezier(XYZ *p,int n,double mu)
{
int k,kn,nn,nkn;
double blend,muk,munk;
XYZ b = {0.0,0.0,0.0};

muk = 1;
munk = pow(1-mu,(double)n);

for (k=0;k<=n;k++) {
nn = n;
kn = k;
nkn = n - k;
blend = muk * munk;
muk *= mu;
munk /= (1-mu);
while (nn >= 1) {
blend *= nn;
nn--;
if (kn > 1) {
blend /= (double)kn;
kn--;
}
if (nkn > 1) {
blend /= (double)nkn;
nkn--;
}
}
b.x += p[k].x * blend;
b.y += p[k].y * blend;
b.z += p[k].z * blend;
}

return(b);
}

FAQ

> 1. SpLines 코드 - 코드를 이해하기 위한, 간단한 테스트
> 프로그램이 있습니다. 정말 괜찮게 구현해놓았네요. 질문이 하나 있습니다:
> 어떻게 동적인 결과(dynamic resolution)를 얻을 수 있을까요?
> 2. Bezier 코드에서 - 어떻게 이 코드를 쓰는지에 대한 예제가 없네요..
> 세 함수 모두 곡선을 만드는 대신, 단지 점 하나만을 리턴하네요.
> 게다가 double형 인자인 mu( 0 < mu <1) 는 뭐죠?

함수를 호출할 때는, 0부터 1 사이의 범위에서 mu값을 바꾸면서 함수들을
호출하세요. mu의 값을 증가시킴에 따라, 곡선 위에서 (곡선 방향으로) 이동하는
점들을 얻게 됩니다. 그러므로 mu에 0.5를 넣는다면, 전체 곡선(길이)의 딱 중간에
있는 점을 함수가 리턴하겠지요. 하나 예를 들어볼게요.

#define NSTEPS 100
int ncontrolpoints=0;
XYZ p,*controlpoints;

... 이곳에 조절점 배열과, 데이터들을 만들고 읽는 루틴이 들어가겠지요.

for (i=0;i%lt;NSTEPS;i++) {
p = Bezier(controlpoints,ncontrolpoints,i/(double)N));

.... 이제 p 변수를 이용하세요. 예를 들어, 화면에 찍는다거나 등등.

}

spline 코드가 호출되는 방식도, 이와 같습니다.