Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
베지어 곡선과 경로
베지어 곡선은 자연적인 기하학적 모양의 수학적 근사입니다. 이들을 사용하면 가능한 한 적은 정보를 이용하여 최대한 높은 유연성으로 곡선을 나타낼 수 있습니다.
다른 추상적인 수학적 개념들과는 다르게, 베지어 곡선은 산업 디자인을 위해 만들어졌습니다. 이는 그래픽 소프트웨어 업계에서 매우 인기 있는 도구입니다.
이들은 우리가 이전 글에서 보았던 선형 보간법 에 의존하며, 여러 단계를 조합하여 부드러운 곡선을 만듭니다. 베지어 곡선이 어떻게 작동하는지 더 잘 이해할 수 있도록, 우선 가장 간단한 형태인 2차 베지어 곡선부터 살펴봅시다.
2차 베지어
2차 베지어 곡선을 만들기 위해, 최소한의 요구치인 점 3개를 잡아 봅시다:
두 점을 잇는 곡선을 그리려면 먼저 세 점으로 이루어진 두 개의 선분 각각의 두 꼭짓점에 대해 0부터 1까지의 값을 사용하여 서서히 보간합니다. 이렇게 하면 t 값을 0에서 1로 변경함에 따라 선분을 따라 이동하는 두 점을 얻게 됩니다.
func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float):
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
private Vector2 QuadraticBezier(Vector2 p0, Vector2 p1, Vector2 p2, float t)
{
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
}
얻은 두 점인 q0 과 q1 을 보간하면, 곡선을 그리며 이동하는 한 점 r 을 얻을 수 있습니다.
var r = q0.lerp(q1, t)
return r
Vector2 r = q0.Lerp(q1, t);
return r;
이와 같은 곡선을 2차 베지어 곡선이라고 부릅니다.
(이미지 출처: 위키백과)
3차 베지어 (Cubic Bezier)
바로 전 예제를 바탕으로, 세 점 대신 네 점을 사용하면 곡선을 더 잘 제어할 수 있습니다.
우선, p0, p1, p2, p3 이 4개의 점을 인자로 받는 함수를 정의합니다:
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
public Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
}
그 뒤 각각의 꼭짓점을 따라 선형 보간을 적용하여 점 3개를 얻습니다:
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
var q2 = p2.lerp(p3, t)
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
Vector2 q2 = p2.Lerp(p3, t);
같은 방법으로 점 3개를 이용해 2개의 점을 얻고:
var r0 = q0.lerp(q1, t)
var r1 = q1.lerp(q2, t)
Vector2 r0 = q0.Lerp(q1, t);
Vector2 r1 = q1.Lerp(q2, t);
마지막으로 하나의 점을 얻으면:
var s = r0.lerp(r1, t)
return s
Vector2 s = r0.Lerp(r1, t);
return s;
아래는 완성된 함수입니다:
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
var q2 = p2.lerp(p3, t)
var r0 = q0.lerp(q1, t)
var r1 = q1.lerp(q2, t)
var s = r0.lerp(r1, t)
return s
private Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
Vector2 q2 = p2.Lerp(p3, t);
Vector2 r0 = q0.Lerp(q1, t);
Vector2 r1 = q1.Lerp(q2, t);
Vector2 s = r0.Lerp(r1, t);
return s;
}
결과적으로 네 점을 따라서 보간되는 매끄러운 곡선이 그려지게 됩니다:
(이미지 출처: 위키백과)
참고
3차 베지어 보간은 Vector2 를 Vector3 으로만 바꾸면 3D에서도 동일하게 작동합니다.
제어점 만들기
큐빅 베지어를 기반으로 두 점의 작동 방식을 변경하여 곡선의 모양을 자유롭게 제어할 수 있습니다. p0, p1, p2 및 p3 대신 다음과 같이 저장합니다.
point0 = p0: 첫 번째 지점, 소스입니다.control0 = p1 - p0: 첫 번째 제어점을 기준으로 한 벡터입니다.control1 = p3 - p2: 두 번째 제어점을 기준으로 한 벡터입니다.point1 = p3: 두 번째 지점, 목적지입니다.
이런 식으로 우리는 각 점에 대한 상대적인 벡터인 두 개의 점과 두 개의 제어점을 갖게 됩니다. 이전에 그래픽이나 애니메이션 소프트웨어를 사용해 본 적이 있다면 다음이 익숙할 것입니다.
이것이 그래픽 소프트웨어가 사용자에게 베지어 곡선을 제시하는 방법과 Godot에서 작동하고 보이는 방법입니다.
Curve2D, Curve3D, 경로 및 Path2D
곡선을 포함하는 두 개체가 있습니다: Curve3D와 Curve2D (3D와 2D 각각).
여러 점을 포함할 수 있으므로 더 긴 경로가 사용 가능합니다. 그들을 노드로 설정할 수도 있습니다: Path3D와 Path2D (역시 3D와 2D 각각):
그러나 이를 사용하는 것이 완전히 명확하지 않을 수 있으므로 다음은 베지어 곡선의 가장 일반적인 사용 사례에 대한 설명입니다.
평가 중
그것들을 평가하는 것만으로도 선택 사항이 될 수 있지만 대부분의 경우 별로 유용하지 않습니다. 베지어 곡선의 가장 큰 단점은 t = 0``에서 ``t = 1``까지 일정한 속도로 곡선을 이동하는 경우 실제 보간이 일정한 속도로 이동하지 *않는* 것입니다. 속도는 또한 점 ``p0, p1, p2 및 p3 사이의 거리 사이의 보간이며 일정한 속도로 곡선을 횡단하는 수학적으로 간단한 방법은 없습니다.
다음 의사 코드를 사용하면:
var t = 0.0
func _process(delta):
t += delta
position = _cubic_bezier(p0, p1, p2, p3, t)
private float _t = 0.0f;
public override void _Process(double delta)
{
_t += (float)delta;
Position = CubicBezier(p0, p1, p2, p3, _t);
}
보시다시피 ``t``가 일정한 속도로 증가하더라도 원의 속도(초당 픽셀 수)는 다양합니다. 이로 인해 베지어를 기본적으로 실용적인 용도로 사용하기가 어렵습니다.
그리기
베지어(또는 곡선을 기반으로 한 개체) 그리기는 매우 일반적인 사용 사례이지만 쉽지 않습니다. 거의 모든 경우에 베지어 곡선을 일종의 세그먼트로 변환해야 합니다. 그러나 매우 많은 양을 생성하지 않으면 일반적으로 어렵습니다.
그 이유는 곡선의 일부 섹션(특히 모서리)에는 상당한 양의 점이 필요할 수 있지만 다른 섹션에는 그렇지 않을 수 있기 때문입니다.
또한 두 제어점이 모두 ``0, 0``(상대 벡터라는 점을 기억하세요)인 경우 베지어 곡선은 직선일 뿐입니다(따라서 많은 양의 점을 그리는 것은 낭비입니다).
베지어 곡선을 그리기 전에 *테셀레이션*이 필요합니다. 이는 곡률 양이 특정 임계값보다 작아질 때까지 곡선을 분할하는 재귀 또는 분할 및 정복 기능을 사용하여 수행되는 경우가 많습니다.
Curve 클래스는 Curve2D.tessellate() 함수(재귀 및 각도 tolerance 인수의 선택적 stages 수신)를 통해 이를 제공합니다. 이렇게 하면 곡선을 기반으로 무언가를 그리는 것이 더 쉽습니다.
순회
곡선의 마지막 일반적인 사용 사례는 곡선을 횡단하는 것입니다. 일정한 속도에 관해 이전에 언급한 것 때문에 이 역시 어렵습니다.
이를 더 쉽게 만들려면 곡선을 등거리 지점으로 *구워*야 합니다. 이런 식으로 정규 보간법을 사용하여 근사화할 수 있습니다(3차 옵션을 사용하면 더욱 향상될 수 있음). 이렇게 하려면 Curve2D.get_baked_length() 방법을 사용하면 됩니다. 둘 중 하나에 대한 첫 번째 호출은 내부적으로 곡선을 굽습니다.
그러면 다음 의사 코드를 사용하여 일정한 속도로 탐색할 수 있습니다.
var t = 0.0
func _process(delta):
t += delta
position = curve.sample_baked(t * curve.get_baked_length(), true)
private float _t = 0.0f;
public override void _Process(double delta)
{
_t += (float)delta;
Position = curve.SampleBaked(_t * curve.GetBakedLength(), true);
}
그러면 출력은 일정한 속도로 이동합니다.