Unity中的网格创建和曲线变形

慈云数据 2024-03-19 技术支持 90 0

Unity中的网格创建和曲线变形

  • 3D贝塞尔曲线变形
    • 贝塞尔曲线基础
      • 线性公式
      • 二次方公式
      • 三次方公式
      • Unity 实现3D贝塞尔曲线变形
        • 准备工作
        • 脚本概述
          • 变量定义
          • 变量解析
          • 函数解析 获取所有子节点
            • GetAllChildren 获取所有子节点
            • UpdateBezierBend 控制点更新
            • CalculateBezier Bezier 曲线公式
            • GetBendNormalVector 获取指定点上的法线向量偏移
            • UpdateControlPoint 更新控制点的旋转角度
            • CalculateBezierTangent 曲线求导(切线向量)
            • BesselCurveDeformation_ZH 完整代码
            • 自定义网格创建
              • GridCreation_ZH 完整代码
              • GridCreation_ZH 搭载
              • 自定义网格创建 运行效果
              • 网格创建和曲线变形 运行情况

                在本篇博客中,我们将探讨如何使用Unity实现3D贝塞尔曲线变形效果。

                3D贝塞尔曲线变形

                贝塞尔曲线是一种常用的数学曲线,通过控制点和曲线影响力物体我们可以实现对网格的弯曲和变形。
                在示例代码中,我们可以看到通过设置控制点数组、曲线影响力物体和控制物体曲线施加力等变量。
                实现了对网格进行贝塞尔曲线变形的效果。这种技术在游戏中常用于创建动态的形变效果。
                如弯曲的绳索、变形的角色模型等。
                

                贝塞尔曲线基础

                贝塞尔曲线是一种由控制点定义的数学曲线。
                在3D空间中,我们通常使用3次贝塞尔曲线,它由四个控制点(起始点、两个中间点和结束点)组成。
                
                贝塞尔曲线的形状受控制点的位置和权重影响。
                控制点的位置决定了曲线经过的路径,而权重控制了曲线在控制点之间的弯曲程度。
                

                请添加图片描述

                线性公式

                给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。
                

                请添加图片描述

                请添加图片描述

                二次方公式

                二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
                TrueType字型就运用了以贝兹样条组成的二次贝兹曲线。
                

                请添加图片描述

                请添加图片描述

                三次方公式

                P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。
                曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2。
                这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前
                走向P2方向的“长度有多长”。
                

                曲线的参数形式为:

                请添加图片描述

                现代的成象系统,如PostScript、Asymptote和Metafont
                运用了以贝兹样条组成的三次贝兹曲线,用来描绘曲线轮廓。
                

                请添加图片描述

                Unity 实现3D贝塞尔曲线变形

                准备工作

                首先,我们需要在Unity中创建一个空的GameObject,并将该脚本附加到该GameObject上。
                接下来,我们需要创建一个网格,可以通过导入一个3D模型或者使用Unity内置的网格创建工具来完成。
                

                脚本概述

                让我们先来看一下脚本的整体结构:
                	1.变量定义
                	2.Start()函数
                	2.Update()函数
                	3.初始化函数
                	4.获取所有子节点函数
                	5.贝塞尔曲线Mesh计算相关函数
                
                变量定义
                    [Header("存储贝塞尔曲线控制点数组")]
                    public List _CpArr;
                    [Header("曲线影响力物体")]
                    public Transform _ForceItem;
                    [Range(0, 100f)]
                    [Header("控制物体曲线施加力")]
                    public float _Force = 10;
                    [Header("原始顶点位置")]
                    private Vector3[] _OriVertices;
                    [Header("网格数据")]
                    private Mesh _Mesh;
                    [Header("最后一个控制点位置")]
                    //最后一个控制点的位置。用来计算mesh高度来计算t
                    private Vector3 _TopPos;
                

                变量解析

                _CpArr: 存储贝塞尔曲线控制点数组的列表。
                _ForceItem: 曲线影响力物体的Transform组件。
                _Force: 控制物体曲线施加力的大小,范围在0到100之间。
                _OriVertices: 原始顶点位置的数组。
                _Mesh: 网格数据的Mesh组件。
                _TopPos: 最后一个控制点的位置,用于计算mesh高度来计算t。
                

                函数解析 获取所有子节点

                Start(): 在脚本启动时调用,用于初始化操作。
                Update(): 在每一帧更新时调用,用于更新控制点和网格。
                Initialize(): 初始化函数,用于设置控制点数组和作用力变量等。
                
                GetAllChildren 获取所有子节点
                GetAllChildren: 获取所有子节点的函数,用于递归收集父节点下的所有子节点。
                
                /// 
                    /// 获取所有子节点
                    /// 
                    /// 
                    /// 
                    private List GetAllChildren(Transform _Parent)
                    {
                        List _ListTra = new List();
                        foreach (Transform _Child in _Parent)
                        {
                            //添加直接子节点
                            _ListTra.Add(_Child);
                            //递归收集后代节点
                            List _Descendants = GetAllChildren(_Child);
                            _ListTra.AddRange(_Descendants);
                        }
                        return _ListTra;
                    }
                
                UpdateBezierBend 控制点更新
                UpdateBezierBend: 控制点更新函数,根据施加在曲线上的力,计算并更新控制点的旋转角度。
                
                 /// 
                    /// 控制点更新
                    /// 根据施加在曲线上的力,计算并更新控制点的旋转角度
                    /// 
                    private void UpdateBezierBend()
                    {
                        //曲线弯曲方向
                        Vector3 _BendVector = new Vector3(0, 0, 0);
                        //弯曲布尔  true  无弯曲  false 弯曲
                        bool _IsVertical = true;
                        for (int i = 1; i  
                
                CalculateBezier Bezier 曲线公式
                CalculateBezier: Bezier曲线公式计算函数,根据给定的t值计算贝塞尔曲线上的点坐标。
                
                 /// 
                    /// Bezier 曲线公式
                    /// 计算贝塞尔曲线上给定参数值 _CurvePosition 对应的位置
                    /// 
                    /// 
                    /// 
                    private Vector3 CalculateBezier(float _CurvePosition)
                    {
                        //存储坐标
                        Vector3 _Ret = new Vector3(0, 0, 0);
                        //控制点数量
                        int _Number = _CpArr.Count - 1;
                        for (int i = 0; i 
                            //获取第 i 个控制点的世界坐标,通过将其位置从控制点的局部坐标系转换到世界坐标
                            Vector3 _Pi = _CpArr[i].TransformPoint(new Vector3(0, 0, 0));
                            //将转换后的世界坐标再次转换回第一个控制点的局部坐标系,用于与其他控制点进行计算
                            _Pi = _CpArr[0].InverseTransformPoint(_Pi);
                            //根据贝塞尔曲线的定义,计算贝塞尔基函数的乘积项,并与控制点的局部坐标相乘
                            //Cn_m(_Number, i) 是组合数函数  表示从 _Number 个控制点中选择 i 个的组合数
                            _Ret = _Ret + Mathf.Pow(1 - _CurvePosition, _Number - i) * Mathf.Pow(_CurvePosition, i) * Cn_m(_Number, i) * _Pi;
                        }
                        return _Ret;
                    }
                
                        //切线斜率
                        Vector3 _TangentVector = CalculateBezierTangent(_CurvePosition);
                        //切线竖直时,顶点在在弯曲向量上的投影向量即为法线向量
                        if (IsEqualZero(_TangentVector.x) == true && IsEqualZero(_TangentVector.z) == true)
                        {
                            //直接返回 原始位置 _OriPos 投影到弯曲向量 _BendVector 上的向量,作为法线向量
                            return Vector3.Project(new Vector3(_OriPos.x, 0, _OriPos.z), _BendVector);
                        }
                        //存储计算得到的法线向量
                        Vector3 _NormalVector = new Vector3(0, 0, 0);
                        //计算切线向量与原始位置向量的点乘
                        float _DirectFlag = Vector3.Dot(_BendVector, _OriPos);
                        //判断法线向量朝向(法线向量有两个方向)
                        //大于零 顶点坐标与弯曲方向同向
                        if (_DirectFlag  0)
                        {
                            //切线水平,法线向量竖直向下
                            if (IsEqualZero(_TangentVector.y) == true)
                            {
                                _NormalVector.y = -1;
                            }
                            else
                            {
                                //切线朝上,法线向量与切线水平同向
                                if (_TangentVector.y > 0)
                                {
                                    _NormalVector.x = _TangentVector.x;
                                    _NormalVector.z = _TangentVector.z;
                                }
                                //切线朝下,法线向量与切线水平反向
                                else
                                {
                                    _NormalVector.x = -_TangentVector.x;
                                    _NormalVector.z = -_TangentVector.z;
                                }
                                //使法线向量与切线向量水平且垂直于切线向量
                                _NormalVector.y = -(_TangentVector.x * _NormalVector.x + _TangentVector.z * _NormalVector.z) / _TangentVector.y;
                            }
                        }
                        //小于零 顶点坐标与弯曲方向反向
                        else
                        {
                            //切线水平,法线向量竖直向上
                            if (IsEqualZero(_TangentVector.y) == true)
                            {
                                _NormalVector.y = 1;
                            }
                            else
                            {
                                //切线朝上,法线向量与切线水平反向
                                if (_TangentVector.y > 0)
                                {
                                    _NormalVector.x = -_TangentVector.x;
                                    _NormalVector.z = -_TangentVector.z;
                                }
                                //切线朝下,法线向量与切线水平同向
                                else
                                {
                                    _NormalVector.x = _TangentVector.x;
                                    _NormalVector.z = _TangentVector.z;
                                }
                                //使得法线向量与切线向量水平且垂直于切线向量
                                _NormalVector.y = -(_TangentVector.x * _NormalVector.x + _TangentVector.z * _NormalVector.z) / _TangentVector.y;
                            }
                        }
                        //计算法线向量的模
                        //法线向量的模应为到投影到弯曲面后,到中心点的距离
                        float _Magnitude = Vector3.Project(new Vector3(_OriPos.x, 0, _OriPos.z), _BendVector).magnitude;
                        //将法线向量按照该模进行缩放,使其长度与距离一致
                        _NormalVector = _NormalVector.normalized * _Magnitude;
                        //返回法线向量
                        return _NormalVector;
                    }
                
                UpdateControlPoint 更新控制点的旋转角度
                UpdateControlPoint: 控制点更新函数,根据施加在曲线上的力,计算并更新控制点的位置。
                
                    /// 
                    /// 更新控制点的旋转角度
                    /// 根据受力计算各个控制点的旋转角度
                    /// 
                    private void UpdateControlPoint()
                    {
                        //受力强度
                        float _HandleForce = _Force;
                        //根据受力计算各个控制点旋转角度
                        for (int i = 1; i 
                            //计算最大弯曲方向
                            Vector3 _ForcePos = _ForceItem.transform.TransformPoint(new Vector3(0, 0, 0));
                            _ForcePos = _CpArr[i - 1].InverseTransformPoint(_ForcePos);
                            //计算从控制点到受力位置的向量
                            Vector3 _ToVector = _ForcePos - _CpArr[i].localPosition;
                            //得到该控制点的旋转角度
                            Quaternion _MaxRotation = Quaternion.FromToRotation(Vector3.up, _ToVector);
                            //获取控制点组件
                            ControlPoint_ZH _Cp = _CpArr[i].gameObject.GetComponent
                        //存储计算得到的切线向量
                        Vector3 _Ret = new Vector3(0, 0, 0);
                        //控制点数量
                        int _Number = _CpArr.Count - 1;
                        for (int i = 0; i 
                            Vector3 _Pi = _CpArr[i].TransformPoint(new Vector3(0, 0, 0));
                            _Pi = _CpArr[0].InverseTransformPoint(_Pi);
                            //根据贝塞尔曲线的定义,计算贝塞尔基函数的导数项,并与控制点的局部坐标相乘
                            //每次循环迭代,都将计算得到的导数项加到 _Ret 向量上
                            _Ret = _Ret + (-1 * (_Number - i) * Mathf.Pow(1 - _CurvePosition, _Number - i - 1) * Mathf.Pow(_CurvePosition, i) * Cn_m(_Number, i) * _Pi + i * Mathf.Pow(1 - _CurvePosition, _Number - i) * Mathf.Pow(_CurvePosition, i - 1) * Cn_m(_Number, i) * _Pi);
                        }
                        //返回计算得到的切线向量 _Ret,表示贝塞尔曲线上给定参数值 _CurvePosition 对应的切线向量
                        return _Ret;
                    }
                
                    [Header("存储贝塞尔曲线控制点数组")]
                    public List
                        Initialize();
                    }
                    void Update()
                    {
                        //根据受力,修改控制点
                        UpdateControlPoint();
                        //更新mesh
                        UpdateBezierBend();
                    }
                    /// 
                        //初始化  控制点数组
                        _CpArr = new List
                        //    //数组清空
                        //    _CpArr.Clear();
                        //    //获取所有控制端点
                        //    _CpArr = GetAllChildren(GameObject.Find("Node").transform);
                        //}
                        //else
                        {
                            GameObject _EmptyObject0 = new GameObject("P0");
                            GameObject _EmptyObject1 = new GameObject("P1");
                            GameObject _EmptyObject2 = new GameObject("P2");
                            GameObject _EmptyObject3 = new GameObject("P3");
                            GameObject _EmptyObject4 = new GameObject("P4");
                            GameObject _EmptyObject5 = new GameObject("P5");
                            _EmptyObject0.transform.SetParent(transform);
                            _EmptyObject0.transform.localPosition=new Vector3(GridCreation_ZH._Instance._MeshHeight/2*-1, 0,0);
                            _EmptyObject1.transform.SetParent(_EmptyObject0.transform);
                            _EmptyObject1.transform.localPosition = new Vector3(0, GridCreation_ZH._Instance._MeshHeight/6, 0);
                            _EmptyObject2.transform.SetParent(_EmptyObject1.transform);
                            _EmptyObject2.transform.localPosition = new Vector3(0, GridCreation_ZH._Instance._MeshHeight / 6, 0);
                            _EmptyObject3.transform.SetParent(_EmptyObject2.transform);
                            _EmptyObject3.transform.localPosition = new Vector3(0, GridCreation_ZH._Instance._MeshHeight / 6, 0);
                            _EmptyObject4.transform.SetParent(_EmptyObject3.transform);
                            _EmptyObject4.transform.localPosition = new Vector3(0, GridCreation_ZH._Instance._MeshHeight / 6, 0);
                            _EmptyObject5.transform.position = new Vector3(0, GridCreation_ZH._Instance._MeshHeight, 0);
                            //_EmptyObject5.transform.eulerAngles=new Vector3(0, 0, 90);
                            _EmptyObject5.transform.SetParent(_EmptyObject4.transform);
                            _CpArr.Add(_EmptyObject0.transform);
                            _CpArr.Add(_EmptyObject1.transform);
                            _CpArr.Add(_EmptyObject2.transform);
                            _CpArr.Add(_EmptyObject3.transform);
                            _CpArr.Add(_EmptyObject4.transform);
                            _CpArr.Add(_EmptyObject5.transform);
                            for (int i = 0; i 
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon