图形学 | 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(&#34;没有移动器&#34;);
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]