找回密码
 立即注册
查看: 368|回复: 0

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

[复制链接]
发表于 2022-2-28 17:09 | 显示全部楼层 |阅读模式


效果预览


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[0].position;
    Vector3 b1 = cp[1].position;
    Vector3 b2 = cp[2].position;
    Vector3 b3 = cp[3].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个实体为控制点的方式(个人实践认为更便于操作)


[HideInInspector]public List<Transform> ctrlPoints = new List<Transform>();



//......



[ContextMenu("Init Ctrl Points")]
    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实现绘制主体
[CustomEditor(typeof(CurveCtrl))]
class CurveCtrlEditor : Editor
{
    CurveCtrl mScript;
    Vector3 startPoint, endPoint, startTangent, endTangent;
    private void OnSceneGUI()
    {
        mScript = target as CurveCtrl;

        DrawBezierCurve();
    }
    private void DrawBezierCurve()
    {
        mScript.ctrlPoints[0].position = Handles.PositionHandle(mScript.ctrlPoints[0].position, Quaternion.identity);
        mScript.ctrlPoints[3].position = Handles.PositionHandle(mScript.ctrlPoints[3].position, Quaternion.identity);
        mScript.ctrlPoints[1].position = Handles.PositionHandle(mScript.ctrlPoints[1].position, Quaternion.identity);
        mScript.ctrlPoints[2].position = Handles.PositionHandle(mScript.ctrlPoints[2].position, Quaternion.identity);

        startPoint = mScript.ctrlPoints[0].position;
        endPoint = mScript.ctrlPoints[3].position;
        startTangent = mScript.ctrlPoints[1].position;
        endTangent = mScript.ctrlPoints[2].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;

[ExecuteInEditMode]
public class CurveCtrl : MonoBehaviour
{
    [HideInInspector]public List<Transform> ctrlPoints = new List<Transform>();
    public Transform mover;
    [Range(0.0f, 1.0f)]public float t;


    #region Init Ctrl Points
    [ContextMenu("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[0].position;
        Vector3 b1 = cp[1].position;
        Vector3 b2 = cp[2].position;
        Vector3 b3 = cp[3].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));
        }
    }
}

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

        DrawBezierCurve();
    }
    private void DrawBezierCurve()
    {
        mScript.ctrlPoints[0].position = Handles.PositionHandle(mScript.ctrlPoints[0].position, Quaternion.identity);
        mScript.ctrlPoints[3].position = Handles.PositionHandle(mScript.ctrlPoints[3].position, Quaternion.identity);
        mScript.ctrlPoints[1].position = Handles.PositionHandle(mScript.ctrlPoints[1].position, Quaternion.identity);
        mScript.ctrlPoints[2].position = Handles.PositionHandle(mScript.ctrlPoints[2].position, Quaternion.identity);

        startPoint = mScript.ctrlPoints[0].position;
        endPoint = mScript.ctrlPoints[3].position;
        startTangent = mScript.ctrlPoints[1].position;
        endTangent = mScript.ctrlPoints[2].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));
        }
    }
}

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-13 03:33 , Processed in 0.090985 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表