当前位置: 首页 > news >正文

Unity 曲线插值(Hermite插值和Catmull_Rom插值)

Unity 曲线插值(Hermite插值和Catmull_Rom插值)

https://blog.csdn.net/xdedzl/article/details/84641343

 

1.三次Hermite样条

        埃尔米特插值时颇为常用的插值算法,其根本也是三次贝塞尔曲线,有关贝塞尔曲线的知识可以参考这篇文章,有动图,看起来非常直观https://www.cnblogs.com/hnfxs/p/3148483.html下面是三次贝塞尔曲线模拟和公式

其中,P0和P3是一条曲线段的起点和终点,P1和P2是这个曲线段的两个外控制点。

        三次Hermite差值实际上是贝塞尔曲线的转型,它将两个外控制点转成了两个切线,维基百科对Cubic Hermite spline解释比较清楚,贴上链接以供参考https://en.wikipedia.org/wiki/Cubic_Hermite_spline。下为三次Hermite 样条曲线的公式

c67f2482bc328d3c50769b8d860b9488c936af63uploading.4e448015.gif转存失败重新上传取消\boldsymbol{p}(t) = (2t^3-3t^2+1)\boldsymbol{p}_0 + (t^3-2t^2+t)\boldsymbol{m}_0 + (-2t^3+3t^2)\boldsymbol{p}_1 +(t^3-t^2)\boldsymbol{m}_1,t∈[0,1]

P0和P1为曲线段的起点和终点,M0和M1为起点和终点的切线。
2.Catmull-Rom Spline

              原理可参考http://www.dxstudio.com/guide_content.aspx?id=70a2b2cf-193e-4019-859c-28210b1da81f

         14cab67f-70f8-4c3e-873a-b028bf64ef4d.pnguploading.4e448015.gif转存失败重新上传取消

         注意,上图的四个点只能模拟出P1到P2,之间的曲线,在实际运用中,除了给的一组关键点以外,我们还需要给这组的收尾各添加一个点以画出整个曲线的第一个和最后一个曲线段。同样,贴上公式模拟P1到P2曲线的公式

为了拟合P0到P1和P2到P3之间的曲线,我们需要在这几个曲线段外再取两个点,我的做法是取P1P0和P2P3两个向量计算出首位两个点。
3.样条曲线类

下面的代码段是一个完成的样条曲线类,可以直接使用,后面会贴上这个类的使用方式

    // ==========================================
    // 描述:
    // 作者: HAK
    // 时间: 2018-11-28 11:31:34
    // 版本: V 1.0
    // ==========================================
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
     
    /// <summary>
    /// 样条类型
    /// </summary>
    public enum SplineMode
    {
        Hermite,               // 埃尔米特样条
        Catmull_Rom,           // Catmull_Rom 建议选择
        CentripetalCatmull_Rom,// 向心Catmull_Rom
    }
     
    /// <summary>
    /// 样条曲线
    /// </summary>
    public class SplineCurve
    {
        /// <summary>
        /// 曲线起始节点
        /// </summary>
        private Node startNode;
        /// <summary>
        /// 曲线终结点
        /// </summary>
        private Node endNode;
        /// <summary>
        /// 节点集合
        /// </summary>
        private List<Node> nodeList;
        /// <summary>
        /// 节点法线集合
        /// </summary>
        private List<Vector3> tangentsList;
        /// <summary>
        /// 曲线段集合
        /// </summary>
        public List<CurveSegement> segmentList { get; private set; }
        /// <summary>
        /// 曲线构造类型
        /// </summary>
        public SplineMode mode { get; private set; }
     
        public SplineCurve(SplineMode _mode = SplineMode.Catmull_Rom)
        {
            nodeList = new List<Node>();
            tangentsList = new List<Vector3>();
            segmentList = new List<CurveSegement>();
            mode = _mode;
        }
     
        /// <summary>
        /// 添加首尾控制点
        /// </summary>
        public void AddCatmull_RomControl()
        {
            if(mode != SplineMode.Catmull_Rom)
            {
                Debug.Log("不是Catmull样条");
                return;
            }
            if(nodeList.Count < 2)
            {
                Debug.Log("Catmull_Rom样条取点要大于等于2");
                return;
            }
            Node node = new Node(startNode.pos + (nodeList[0].pos - nodeList[1].pos), null, nodeList[0]);
            nodeList.Insert(0, node);
            node = new Node(endNode.pos + (endNode.pos - nodeList[nodeList.Count - 2].pos), nodeList[nodeList.Count - 1]);
            nodeList.Add(node);
        }
     
        /// <summary>
        /// 添加节点
        /// </summary>
        /// <param name="newNode"></param>
        public void AddNode(Vector3 pos, float c)
        {
            Node node;
            if(nodeList.Count < 1)
            {
                node = new Node(pos);
            }
            else
            {
                node = new Node(pos, nodeList[nodeList.Count - 1]);
            }
            nodeList.Add(node);
     
            
            if(nodeList.Count > 1)
            {
                CurveSegement a = new CurveSegement(endNode, node,this);
                a.c = c;
                segmentList.Add(a);
                CaculateTangents(segmentList.Count - 1);               // 计算新加入的曲线段起始切线
            }
            else // 加入第一个节点
            {
                startNode = node;
            }
            endNode = node;
        }
     
        /// <summary>
        /// 获取点
        /// </summary>
        /// <param name="index"></param>
        /// <param name="t"></param>
        public void GetPoint(int index, float t)
        {
            segmentList[index].GetPoint(t);
        }
     
        /// <summary>
        /// 获取切线
        /// </summary>
        /// <param name="index"></param>
        /// <param name="t"></param>
        public void GetTangents(int index, float t)
        {
            segmentList[index].GetTangents(t);
        }
     
        /// <summary>
        /// 计算曲线段首尾切线
        /// </summary>
        /// <param name="index"></param>
        private void CaculateTangents(int index)
        {
            CurveSegement segement = segmentList[index];
     
            if(index == 0)
            {
                segement.startTangents = segement.endNode.pos - segement.endNode.pos;
                segement.endTangents = segement.endNode.pos - segement.startNode.pos;
                return;
            }
     
            CurveSegement preSegement = segmentList[index - 1];
     
            segement.startTangents = 0.5f * (1 - segement.c) * (segement.endNode.pos - preSegement.endNode.pos);
            segement.endTangents = segement.endNode.pos - segement.startNode.pos;
            preSegement.endTangents = segement.startTangents;
     
        }
    }
     
    /// <summary>
    /// 曲线段
    /// </summary>
    public class CurveSegement
    {
        /// <summary>
        /// 所属曲线
        /// </summary>
        public SplineCurve rootCurve;
     
        /// <summary>
        /// 曲线段起始位置
        /// </summary>
        public Node startNode { get; private set; }
        /// <summary>
        /// 曲线段末尾位置
        /// </summary>
        public Node endNode { get; private set; }
     
        public Vector3 startTangents;
        public Vector3 endTangents;
     
        /// <summary>
        /// 张力系数
        /// </summary>
        public float c { get;  set; }
     
        public CurveSegement(Node _startNode,Node _endNode,SplineCurve _rootCurve)
        {
            startNode = _startNode;
            endNode = _endNode;
            rootCurve = _rootCurve;
            c = -5f;
        }
     
        /// <summary>
        /// 获取点
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public Vector3 GetPoint(float t)
        {
            Vector3 x = Vector3.zero;
            switch (rootCurve.mode)
            {
                case SplineMode.Hermite:
                    x = (2 * t * t * t - 3 * t * t + 1) * startNode.pos;
                    x += (-2 * t * t * t + 3 * t * t) * endNode.pos;
                    x += (t * t * t - 2 * t * t + t) * startTangents;
                    x += (t * t * t - t * t) * endTangents;
                    break;
                case SplineMode.Catmull_Rom:
                    x += startNode.preNode.pos * (-0.5f * t * t * t + t * t - 0.5f * t);
                    x += startNode.pos * (1.5f * t * t * t - 2.5f * t * t + 1.0f);
                    x += endNode.pos * (-1.5f * t * t * t + 2.0f * t * t + 0.5f * t);
                    x += endNode.nextNode.pos * (0.5f * t * t * t - 0.5f * t * t);
                    break;
                case SplineMode.CentripetalCatmull_Rom:
                    break;
                default:
                    break;
            }
     
            return x;
     
        }
     
        /// <summary>
        /// 获取切线
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public Vector3 GetTangents(float t)
        {
            Vector3 tangents = Vector3.zero;
            switch (rootCurve.mode)
            {
                case SplineMode.Hermite:
                    tangents = (6 * t * t - 6 * t) * startNode.pos;
                    tangents += (-6 * t * t + 6 * t) * endNode.pos;
                    tangents += (3 * t * t - 4 * t + 1) * startTangents;
                    tangents += (3 * t * t - 2 * t) * endTangents;
                    break;
                case SplineMode.Catmull_Rom:
                    tangents = startNode.preNode.pos * (-1.5f * t * t + 2 * t - 0.5f);
                    tangents += startNode.pos * (3.0f * t * t - 5.0f * t);
                    tangents += endNode.pos * (-3.0f * t * t + 4.0f * t + 0.5f);
                    tangents += endNode.nextNode.pos * (1.5f * t * t - 1.0f * t);
                    break;
                case SplineMode.CentripetalCatmull_Rom:
                    break;
                default:
                    break;
            }
            
            return tangents;
        }
    }
     
    /// <summary>
    /// 曲线节点
    /// </summary>
    public class Node
    {
        /// <summary>
        /// 节点位置
        /// </summary>
        public Vector3 pos;
        /// <summary>
        /// 前连接节点
        /// </summary>
        public Node preNode;
        /// <summary>
        /// 后连接节点
        /// </summary>
        public Node nextNode;
     
        public Node(Vector3 _pos)
        {
            pos = _pos;
        }
     
        public Node(Vector3 _pos, Node _preNode, Node _nextNode = null)
        {
            pos = _pos;
            if(_preNode != null)
            {
                preNode = _preNode;
                _preNode.nextNode = this;
            }
            if(_nextNode != null)
            {
                nextNode = _nextNode;
                _nextNode.preNode = this;
            }
        }
    }

 

这个是我用 Catmull-Rom计算出来的点集结合Mesh绘图绘制出来的一条道路,拟合效果还算不错

最后贴上样条曲线类的使用,第一步创建,第二部加点,第三步添加首尾控制点,最后得到的path就是点集,大家可以遍历点集画线或者创建cube来观察曲线。

    SplineCurve curve = new SplineCurve();  //新建曲线,默认为Catmull-Rom样条
     
     curve.AddNode(point1);  // 加入至少两个关键点
     
     curve.AddNode(point2);  // 代码里的AddNode还有以参数c,可以去掉,这是用来测试的
     
     outCurve.AddNode(point3);
     
    。。。
     
     curve.AddCatmull_RomControl();  // 加入首位两个控制点
     
     
    List<Vector3> path = new List<Vector3>();
     for (int i = 0; i < outCurve.segmentList.Count; i++)
    {
        float add = 1f / 20;  // 表示两个关键点之间取20个点,可根据需要设置
        for (float j = 0; j < 1; j += add)
        {
            Vector3 point = centerCurve.segmentList[i].GetPoint(j);
            path.Add(point);
        }
    }

曲线拟合本身就是一个公式,简单点写可以之间写成一个方法传进一组关键点返回一组点集,但是这样不利于扩展,也不利于对整个曲线上的每条曲线段进行单个控制。
————————————————
版权声明:本文为CSDN博主「xdedzl」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xdedzl/article/details/84641343

 

https://blog.csdn.net/xdedzl/article/details/84641343

 

 

 

 

 

 

 

相关文章:

  • 参数化曲线:Hermite Catmull-Rom Bezier
  • 贝塞尔曲线原理(简单阐述)
  • 插值与样条
  • Unity 打包因为资源没有 meta ,打包 assetbundle 的时候,导致资源没有打包进去
  • 深入理解color model(颜色模型)
  • LMS色彩空间
  • 计算机图形中的色彩概念
  • 网游帧同步的分析与设计
  • UE4网络同步思考(一)---经典同步方案
  • UE4网络同步(二)——深入同步细节
  • 2021-03-13
  • UE4中自定义用于sequence的变量和函数
  • gradle基础配置
  • 用apksigner进行批量签名的脚本
  • 都到2020年了,Android 签名机制 v1、v2、v3你懂什么意思嘛!
  • 深入了解以太坊
  • JavaScript 如何正确处理 Unicode 编码问题!
  • 【React系列】如何构建React应用程序
  • CSS盒模型深入
  • Java-详解HashMap
  • Netty 4.1 源代码学习:线程模型
  • Unix命令
  • V4L2视频输入框架概述
  • 读懂package.json -- 依赖管理
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 如何编写一个可升级的智能合约
  • 数组大概知多少
  • 找一份好的前端工作,起点很重要
  • (1)(1.9) MSP (version 4.2)
  • (2)nginx 安装、启停
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (阿里云万网)-域名注册购买实名流程
  • (补)B+树一些思想
  • (多级缓存)多级缓存
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (终章)[图像识别]13.OpenCV案例 自定义训练集分类器物体检测
  • (转)使用VMware vSphere标准交换机设置网络连接
  • .aanva
  • .chm格式文件如何阅读
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .NET连接MongoDB数据库实例教程
  • .NET命令行(CLI)常用命令
  • .net与java建立WebService再互相调用
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面
  • /proc/stat文件详解(翻译)
  • [].slice.call()将类数组转化为真正的数组
  • [1181]linux两台服务器之间传输文件和文件夹
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]
  • [④ADRV902x]: Digital Filter Configuration(发射端)
  • [AAuto]给百宝箱增加娱乐功能
  • [AutoSar]BSW_Memory_Stack_004 创建一个简单NV block并调试
  • [BUAA软工]第一次博客作业---阅读《构建之法》