yukamu 发表于 2022-2-28 17:09

图形学 | Unity贝塞尔曲线(Bézier curve)控制器



效果预览


Unity 贝塞尔曲线控制器
https://www.zhihu.com/video/1478794476198449152
<hr/>使用指南



点击CurveCtrl来调整曲线当前位置值
<hr/>前置知识

贝塞尔曲线推导: GAMES101-现代计算机图形学入门-闫令琪_哔哩哔哩_bilibili
不了解贝塞尔曲线的小伙伴可以先看视频消化这个基础知识点, 视频里用简单的方式推导了贝塞尔曲线公式

二次贝塞尔曲线公式


这里附上我的笔记


<hr/>前言

关键代码部分贴出核心内容, 具体实现细节见完整代码
<hr/>关键代码

这里采用笔记图中的分布式, 得到不同t值贝塞尔曲线返回位置
如果你更喜欢一步到位的话, 可以直接用前文图片中的最终表达式
private Vector3 SetPosT(Vector3 b0, Vector3 b1, float t_t)
{
    Vector3 b2 = (1 - t_t) * b0 + t_t * b1;//两个点位置线性插值
    return b2;
}
private Vector3 SetPathPos(List<Transform> cp, float t_t)
{                        
    Vector3 b0 = cp.position;
    Vector3 b1 = cp.position;
    Vector3 b2 = cp.position;
    Vector3 b3 = cp.position;

    Vector3 b4 = SetPosT(b0, b1, t_t);
    Vector3 b5 = SetPosT(b1, b2, t_t);
    Vector3 b6 = SetPosT(b2, b3, t_t);

    Vector3 b7 = SetPosT(b4, b5, t_t);
    Vector3 b8 = SetPosT(b5, b6, t_t);

    Vector3 b9 = SetPosT(b7, b8, t_t);


    return b9;
}

得到曲线各处的切线方向:
这里用了增量Δt的方式得到导数, 返回值可理解为曲线上各处的速度方向
public Vector3 GetVelocity(float t_t, float step = 0.01f)
{
    Vector3 dir = SetPathPos(ctrlPoints, t_t + step) - mover.position;
    return dir.normalized;
}
至此, 已经用代码还原了贝塞尔表达式, 返回的结果就是SetPathPos函数的返回值
<hr/>在unity里包装

本文的核心是实现贝塞尔曲线功能, 所以包装部分, 提供思路与代码, 不会详细讲解, 相关API查阅文档即可理解

自动获得控制点:
也可以用代码方式抽象出四个点
这里采用在控制器下添加4个实体为控制点的方式(个人实践认为更便于操作)


public List<Transform> ctrlPoints = new List<Transform>();



//......




    public void InitCtrlPoints()
    {
      ctrlPoints.Clear();
      foreach (Transform b in transform)
      {
            ctrlPoints.Add(b);
      }
      Debug.Log(ctrlPoints.Count);
    }

添加一个mover物体作为游标:


public Transform mover;


//......



if (mover != null)
      {
            mover.position = SetPathPos(ctrlPoints, t);
            mover.rotation = Quaternion.LookRotation(GetVelocity(), Vector3.Cross(GetVelocity(), mover.transform.right));
      }

自定义inspector:
OnSceneGUI利用Handles.DrawBezier实现绘制主体

class CurveCtrlEditor : Editor
{
    CurveCtrl mScript;
    Vector3 startPoint, endPoint, startTangent, endTangent;
    private void OnSceneGUI()
    {
      mScript = target as CurveCtrl;

      DrawBezierCurve();
    }
    private void DrawBezierCurve()
    {
      mScript.ctrlPoints.position = Handles.PositionHandle(mScript.ctrlPoints.position, Quaternion.identity);
      mScript.ctrlPoints.position = Handles.PositionHandle(mScript.ctrlPoints.position, Quaternion.identity);
      mScript.ctrlPoints.position = Handles.PositionHandle(mScript.ctrlPoints.position, Quaternion.identity);
      mScript.ctrlPoints.position = Handles.PositionHandle(mScript.ctrlPoints.position, Quaternion.identity);

      startPoint = mScript.ctrlPoints.position;
      endPoint = mScript.ctrlPoints.position;
      startTangent = mScript.ctrlPoints.position;
      endTangent = mScript.ctrlPoints.position;

      Handles.DrawBezier(startPoint, endPoint, startTangent, endTangent, Color.red, null, 2f);

      Handles.color = Color.white;
      Handles.DrawDottedLine(startPoint, startTangent, 1f);
      Handles.DrawDottedLine(endTangent, endPoint, 1f);


      Handles.color = Color.blue;
      if (mScript.mover != null)
      {
            //绘制速度方向
            Handles.DrawLine(mScript.mover.position, mScript.mover.position + mScript.GetVelocity(mScript.t));
      }      
    }
}
关键代码大致如此, 具体实现细节见完整代码
<hr/>完整代码

github: Unity-Tool/CurveCtrl at main · VAherggoooooo/Unity-Tool (github.com)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;


public class CurveCtrl : MonoBehaviour
{
    public List<Transform> ctrlPoints = new List<Transform>();
    public Transform mover;
    public float t;


    #region Init Ctrl Points
   
    public void InitCtrlPoints()
    {
      ctrlPoints.Clear();
      foreach (Transform b in transform)
      {
            ctrlPoints.Add(b);
      }
      Debug.Log(ctrlPoints.Count);
    }
    #endregion
    #region set path position
    private Vector3 SetPathPos(List<Transform> cp, float t_t)
    {                        
      Vector3 b0 = cp.position;
      Vector3 b1 = cp.position;
      Vector3 b2 = cp.position;
      Vector3 b3 = cp.position;

      Vector3 b4 = SetPosT(b0, b1, t_t);
      Vector3 b5 = SetPosT(b1, b2, t_t);
      Vector3 b6 = SetPosT(b2, b3, t_t);

      Vector3 b7 = SetPosT(b4, b5, t_t);
      Vector3 b8 = SetPosT(b5, b6, t_t);

      Vector3 b9 = SetPosT(b7, b8, t_t);


      return b9;
    }
    private Vector3 SetPosT(Vector3 b0, Vector3 b1, float t_t)
    {
      Vector3 b2 = (1 - t_t) * b0 + t_t * b1;
      return b2;
    }
    #endregion
    #region get velocity
    public Vector3 GetVelocity(float t_t, float step = 0.01f)
    {
      Vector3 dir = SetPathPos(ctrlPoints, t_t + step) - mover.position;
      return dir.normalized;
    }
    #endregion

    private void Awake()
    {
      InitCtrlPoints();

      if (mover == null)
      {
            Debug.Log("没有移动器");
            enabled = false;
      }
    }

    private void Update()
    {
      if (ctrlPoints.Count <= 0)
      {
            return;
      }

      if (mover != null)
      {
            mover.position = SetPathPos(ctrlPoints, t);
            mover.rotation = Quaternion.LookRotation(GetVelocity(t), Vector3.Cross(GetVelocity(t), mover.transform.right));
      }
    }
}


class CurveCtrlEditor : Editor
{
    CurveCtrl mScript;
    Vector3 startPoint, endPoint, startTangent, endTangent;
    private void OnSceneGUI()
    {
      mScript = target as CurveCtrl;

      DrawBezierCurve();
    }
    private void DrawBezierCurve()
    {
      mScript.ctrlPoints.position = Handles.PositionHandle(mScript.ctrlPoints.position, Quaternion.identity);
      mScript.ctrlPoints.position = Handles.PositionHandle(mScript.ctrlPoints.position, Quaternion.identity);
      mScript.ctrlPoints.position = Handles.PositionHandle(mScript.ctrlPoints.position, Quaternion.identity);
      mScript.ctrlPoints.position = Handles.PositionHandle(mScript.ctrlPoints.position, Quaternion.identity);

      startPoint = mScript.ctrlPoints.position;
      endPoint = mScript.ctrlPoints.position;
      startTangent = mScript.ctrlPoints.position;
      endTangent = mScript.ctrlPoints.position;

      Handles.DrawBezier(startPoint, endPoint, startTangent, endTangent, Color.red, null, 2f);

      Handles.color = Color.white;
      Handles.DrawDottedLine(startPoint, startTangent, 1f);
      Handles.DrawDottedLine(endTangent, endPoint, 1f);


      Handles.color = Color.blue;
      if (mScript.mover != null)
      {
            Handles.DrawLine(mScript.mover.position, mScript.mover.position + mScript.GetVelocity(mScript.t));
      }
    }
}
页: [1]
查看完整版本: 图形学 | Unity贝塞尔曲线(Bézier curve)控制器