[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의 옵션을 통해 다양한 형태로 표현해본 것이다.

Leave a Reply

Your email address will not be published. Required fields are marked *