导语:求曲线就是求曲线上的点

一、简单了解一下Bezier曲线的概念(个人理解)

给定空间中n+1个点坐标(向量)Pi (i∈N);并依次连接成一个多边形,称为控制多边形或特征方程。从该多边形的起点(P0)用一条线逼近每一条线段直到多边形的终点(P4)所形成的一条曲线,称该曲线为Bezier曲线。如1-1图所示:


Android 贝塞尔曲线箭头 贝塞尔曲线 unity_c#

图 1 - 1      一个4阶Bezier曲线

当n=1时,为一阶Bezier曲线,有2个控制点

当n=2时,为二阶Bezier曲线,有3个控制

当n=3时,为三阶Bezier曲线,有4个控制点(以此类推)  

                 

二、用De CastelJau方法做几何图形(Bezier曲线的递推算法)

简述:假设控制多边形上的每一条线段上都有一个点 t ,(这个点可以从线段的起点移动到终点即 t 的值从0递增到1) 再将每条线段上的点 t 依次连接,形成新的控制多边形,一直循环,直到控制多边形为一条直线,则该直线上的 点 t 为Bezier曲线上的其中一个点。

用图形解释:

假设 t = 1/4  t∈[0,1]       即点 t 再每一条线段上的 1/4 处

 

 


Android 贝塞尔曲线箭头 贝塞尔曲线 unity_算法_02

图 2- 1  给出定点

 


Android 贝塞尔曲线箭头 贝塞尔曲线 unity_游戏引擎_03

图 2-2                        t 为 Q0 ,Q1, Q2, Q3

 


Android 贝塞尔曲线箭头 贝塞尔曲线 unity_算法_04

图 2-3          t 为 R0, R1 ,R2

 


Android 贝塞尔曲线箭头 贝塞尔曲线 unity_游戏引擎_05

图 2-4           t 为 S0 ,S1

 

 分析与结论:

由此可知,当 t 值为固定值时,可以求出Bezier曲线上的一个点,则曲线可以用P(t)表示,此时 t 为变量,t∈[0,1]  。 P(t) 随着 t 的值变化而变化,从而得出很多的点,将这些点连接后就成为曲线。

由于用公式推导求出P(t)过于复杂,本人就简单分析如何求出 t 点坐标 的通项公式,因为在Unity中知道该通项公式就足够了。

已知一条线段的起点,终点坐标,然后用向量的减法即可轻易求出,如图所示

Android 贝塞尔曲线箭头 贝塞尔曲线 unity_unity_06

(1 - t )* P0 + t *P1 

其中P0为线段起点坐标,P1为线段终点坐标。

求其他线段点 t 坐标 也是用这个公式,这里就不证明了,通过这个公式,用代码中的循环语句求出每条线段上的 t 点 ,都放入一个集合中,通过这些点又形成了一个新的控制多边形,反复的求(递归),直到求出Bezier曲线上的点坐标。之后用循环再控制 t 的增量,求出P(t)。

注意:Bezier曲线的形状仅与控制多边形个顶点的相对位置有关,而与坐标系的选择无关。

 三、用Unity实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Bezier曲线
/// </summary>
public class BezierDeCastelJau : MonoBehaviour
{
    private LineRenderer lineRenderer;    //线渲染器
    public Transform fxiedPointParent;    //定点所在的父物体
    public Transform[] fixedPoint;        //定点坐标集合
    private List<Vector3> bezierPoints;   //贝塞尔曲线上所有点的集合


    void Start()
    {
        fxiedPointParent = this.transform;
        lineRenderer = this.GetComponent<LineRenderer>();
        fixedPoint = fxiedPointParent.GetComponentsInChildren<Transform>();
        bezierPoints = new List<Vector3>();
    }


    void Update()
    {
        bezierPoints = BezierPosAll(fixedPoint);
        lineRenderer.positionCount = bezierPoints.Count;
        lineRenderer.SetPositions(bezierPoints.ToArray());
    }

    /// <summary>
    /// 获取Bezier曲线上的一个点的坐标
    /// </summary>
    /// <param name="fixedPoint">定点集合</param>
    /// <param name="t">定值</param>
    /// <returns></returns>
    private Vector3 BezierPos(List<Vector3> fixedPoint, float t)
    {
        if (fixedPoint.Count < 2)
            return fixedPoint[0];

        List<Vector3> temp = new List<Vector3>();
        for (int i = 0; i < fixedPoint.Count - 1; i++)
        {
            Debug.DrawLine(fixedPoint[i], fixedPoint[i + 1], Color.yellow);
            Vector3 tp = (1 - t) * fixedPoint[i] + t * fixedPoint[i + 1];
            temp.Add(tp);
        }
        return BezierPos(temp, t);
    }

    
    /// <summary>
    /// 获取Bezier曲线上所有点的坐标
    /// </summary>
    /// <param name="fixedPoint">定点坐标集合</param>
    /// <returns></returns>
    private List<Vector3> BezierPosAll(Transform[] fixedPoint)
    {
        List<Vector3> temp = new List<Vector3>();
        List<Vector3> temp2 = new List<Vector3>();
        for (int i = 1; i < fixedPoint.Length; i++)
        {
            temp2.Add(fixedPoint[i].position);
        }
        for (float i = 0; i <= 1; i += 0.01f)//假设 t 的增量为0.01
        {
            temp.Add(BezierPos(temp2, i));
        }
        return temp;
    }

效果图为:

Android 贝塞尔曲线箭头 贝塞尔曲线 unity_游戏引擎_07

 结语:

整篇文章内容仅为个人理解

Bezier曲线有两点不足:

1.不能局部修改,曲线上一个定点改变,整条曲线也会改变

2.多条Bezier曲线的拼接比较复杂

解决方法:使用B样条线