[Unity] Bezier 곡선 그리기
Bezier 곡선이란 간단히 말해서 2개 이상의 점들로 이루어진 곡선을 말한다. 자세한 내용은 위키를 참고하자.
유니티에서는 하나의 점과 두개의 Tangent 포지션으로 그릴 수 있는데, 아래 두 개의 스크립트를 통해 Beizer 곡선을 구성해 보았다.
- BezierCurve.cs
- 각 점에 대한 정보 (위치, Tangent 시작점, Tangent 끝점)
- Editor/BezierCurveEditor.cs
- Handles 클래스를 이용하여 Scene상에서 위치 조정
BezierCurve.cs의 소스는 아래와 같다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class PointInfo
{
public Vector3 Pos;
public Vector3 StartTangent;
public Vector3 EndTangent;
}
public class BezierCurve : MonoBehaviour
{
public List<PointInfo> Points;
public bool ShowBezier;
public int PolyDivisionCount = 5;
}
ShowBezier와 PolyDivisionCount는 일단 나중을 위해 미뤄두고, PointInfo 정보를 기본값으로 하여 원하는 수 만큼 추가한다. 복잡도를 줄이기 위해 Z는 0으로 통일한다.
각 PointInfo는 Pos외에 StartTangent와 EndTangent를 가져야 하는데, 1번과 2번 두 개의 점이 있다고 하면 1번 StartTangent와 2번 EndTangent를 이용하여 1번에서 2번을 잇는 곡선을 만들 수 있다.
이제 각 점의 정보를 이용하여 Scene에 그려보고, Pos와 Tangent위치를 조절하여 곡선을 구성해보자.
Scene뷰 상에서 위치를 조절하기 위해 Handles.PositionHandle API를 사용하였고, Tangent 포지션의 경우 시인성을 위해 Handles.FreeMoveHandle API를 사용하였다. 아래는 두 API를 이용하여 포지션을 움직이고 저장하는 코드이다.
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(BezierCurve)), CanEditMultipleObjects]
public class BezierCurveEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
}
protected virtual void OnSceneGUI()
{
BezierCurve bezierCurve = (BezierCurve)target;
if (bezierCurve.Points.Count < 1)
return;
for (int i = 0; i < bezierCurve.Points.Count; i++)
{
EditorGUI.BeginChangeCheck();
Vector3 newTargetPosition = Handles.PositionHandle(bezierCurve.Points[i].Pos, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
bezierCurve.Points[i].Pos = newTargetPosition; //새로운 Pos 저장
}
Handles.Label(bezierCurve.Points[i].Pos, i.ToString() + "P");
Vector3 snap = Vector3.one * 0.5f;
Vector3 newTargetStartTangent = Handles.FreeMoveHandle(bezierCurve.Points[i].StartTangent, Quaternion.identity, 0.2f, snap, Handles.RectangleHandleCap);
if (EditorGUI.EndChangeCheck())
{
bezierCurve.Points[i].StartTangent = newTargetStartTangent; //새로운 StartTangent 저장
}
Handles.Label(bezierCurve.Points[i].StartTangent, i.ToString() + " Start");
Vector3 newTargetEndTangent = Handles.FreeMoveHandle(bezierCurve.Points[i].EndTangent, Quaternion.identity, 0.2f, snap, Handles.RectangleHandleCap);
if (EditorGUI.EndChangeCheck())
{
bezierCurve.Points[i].EndTangent = newTargetEndTangent; //새로운 EndTangent 저장
}
Handles.Label(bezierCurve.Points[i].EndTangent, i.ToString() + " End");
}
}
}
두 개의 점이 잘 추가되었다면 아래와 같이 0P와 1P를 Pos로 하고, 사각형 Gizmo(Handles.RectangleHandleCap)로 된 0P의 StartTangent와 1P의 EndTangent가 표시된다.
이제 각 점을 잇는 선을 그려보자. Handles 클래스의 DrawBezier API로 Bezier곡선을 그릴 수 있고, DrawAAPolyLine API로 다각형 모양의 선도 그릴 수 있다.
for (int i = 0; i < bezierCurve.Points.Count; i++)
{
if (i + 1 >= bezierCurve.Points.Count)
break;
if (bezierCurve.ShowBezier)
{
Handles.DrawBezier(bezierCurve.Points[i].Pos,
bezierCurve.Points[i + 1].Pos,
bezierCurve.Points[i].StartTangent,
bezierCurve.Points[i + 1].EndTangent,
Color.red, null, 2f);
}
else
{
var points = Handles.MakeBezierPoints(bezierCurve.Points[i].Pos,
bezierCurve.Points[i + 1].Pos,
bezierCurve.Points[i].StartTangent,
bezierCurve.Points[i + 1].EndTangent,
bezierCurve.PolyDivisionCount);
Handles.DrawAAPolyLine(points);
}
}
아래 영상은 각 위치를 조절하여 곡선 모양을 만들고, ShowBezier와 PolyDivisionCount의 옵션을 통해 다양한 형태로 표현해본 것이다.