Unity ——使用贝塞尔曲线对三维管状物体进行弯曲

news2025/1/20 14:53:57

参考链接:【Unity】弹性鱼竿简单实现-通过贝塞尔曲线修改Mesh - 简书

参考论文:吴晓亮, 黄襄念. Unity 中使用贝塞尔曲线对三维物体进行弯曲[J]. 现代计算机, 2016 (5): 57-59.

unity项目下载:https://download.csdn.net/download/weixin_43042683/87690343

效果图

0 引言

随着虚拟现实的发展,在游戏引擎中对三维物体进行弯曲效果的模拟越来越重要。 在三维游戏引擎中,需要对一些三维的物体进行弯曲,以达到游戏操作中实时模拟物体弯曲。说到弯曲,自然而然想到曲线,从曲线的角度出发,那么关键就是如何生成曲线,以及如何根据曲线修改物体形状,从而达到弯曲的效果。 生成曲线的话,可以直接想到用贝塞尔曲线,传统的贝塞尔曲线算法被用 于各类图形制作软件中,如 Photoshop 等软件,但多限于二维线条的应用,在三维物体上的应用较少。 通过贝塞尔曲线算法结合三维物体的网格顶点,可以实现对条形三维物体进行弯曲变化。

1 贝塞尔曲线

Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。

参考链接:Unity 贝塞尔曲线(Beizer curve)的原理与运用

1.1 一阶贝塞尔曲线

标准公式:

 示意图:

代码实现:

    // 一阶贝塞尔曲线,参数P0、P1、t对应上方原理内的一阶曲线参数.
    Vector3 Bezier(Vector3 p0, Vector3 p1, float t)
    {
        return (1 - t) * p0 + t * p1;
    }

1.2二阶贝塞尔曲线

标准公式:

示意图:

代码实现:

    // 二阶贝塞尔曲线,参数对应上方原理内的二阶曲线参数.
    Vector3 Bezier(Vector3 p0, Vector3 p1, Vector3 p2, float t)
    {
        Vector3 p0p1 = (1 - t) * p0 + t * p1;
        Vector3 p1p2 = (1 - t) * p1 + t * p2;
        Vector3 temp = (1 - t) * p0p1 + t * p1p2;
        return temp;
    }

1.3三阶贝塞尔曲线

标准公式:

示意图:

   // 三阶贝塞尔曲线,参数对应上方原理内的三阶曲线参数.
    Vector3 Bezier(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
    {
        Vector3 temp;
        Vector3 p0p1 = (1 - t) * p0 + t * p1;
        Vector3 p1p2 = (1 - t) * p1 + t * p2;
        Vector3 p2p3 = (1 - t) * p2 + t * p3;
        Vector3 p0p1p2 = (1 - t) * p0p1 + t * p1p2;
        Vector3 p1p2p3 = (1 - t) * p1p2 + t * p2p3;
        temp = (1 - t) * p0p1p2 + t * p1p2p3;
        return temp;
    }

1.4 n 阶贝塞尔曲线

标准公式:

代码实现:

//贝塞尔曲线公式
private Vector3 CalculateBezier(float t)
{
    Vector3 ret = new Vector3(0, 0, 0);
    int n = 阶数;
    for(int i = 0; i <= n; i++)
    {
        Vector3 pi = 第i个控制点的坐标;
        ret = ret + Mathf.Pow(1 - t, n - i) * Mathf.Pow(t, i) * Cn_m(n, i) * pi;
    }
    return ret;
}
//组合数方程
private int Cn_m(int n, int m)
{
    int ret = 1;
    for(int i = 0; i < m; i++){
        ret = ret * (n - i) / (i + 1);  
    }
    return ret;    
}

设计思路

在三维物体上添加多个控制点,其中控制点可以使用n个空节点来代替,控制点的坐标即为空节点的坐标。至于t值,可以看作顶点到底部的距离与整个三维物体长度的比值,0<= t <=1。这样设计的话,我们第一个控制点P0应该在三维物体的底部位置,而最后一个控制点Pn应该在三维物体的顶部位置。

2.弯曲的实现 

根据上面公式计算出的只是一条曲线,而我们的目的是三维物体模型能按照这个曲线进行弯曲。

对于管状物体来说,我们计算出来的曲线其实是它的中心线,而mesh顶点应该位于中心线的两侧,所以顶点弯曲后的坐标是应该要由贝塞尔曲线计算的坐标经过一定变换得来。
经过观察可以发现,弯曲后顶点的坐标P'应由计算出的曲线上的坐标P进行两次偏移得出:在该点法线方向上进行偏移a、在垂直于弯曲面的方向上进行偏移b

 对应代码如下:

// 对原来的顶点做贝塞尔曲线变换,得到弯曲变换后对应的点位置
private void UpdateBezierBend()
{   
    oriVertices = 模型未弯曲时的顶点数组;
    topPos = 最后一个控制点的坐标,用来计算模型长度;
    bendVector = 弯曲方向;
    for(int i = 0; i < oriVertices.Length; i++)
    {
        //获取顶点坐标,计算t值
        Vector3 oriPos = oriVertices[i];
        float t = oriPos.y / topPos.y;
        //获取顶点在贝塞尔曲线上对应的坐标
        Vector3 p = CalculateBezier(t); 
        //获取顶点在曲线上应有的法线偏移向量
        Vector3 vectorA = GetBendNormalVector(t, oriPos, bendVector); 
        //获取顶点在曲线上应有的垂直偏移向量
        Vector3 vectorB = new Vector3(oriPos.x, 0, oriPos.z) - Vector3.Project(new Vector3(oriPos.x, 0, oriPos.z), bendVector); 
        //获取顶点最终弯曲位置
        vector3 p' = p + vectorA + vectorB;
    }
    todo-修改顶点坐标;
}
// 获取指定点上的法向量偏移
private Vector3 GetBendNormalVector(float t, Vector3 oriPos, Vector3 bendVector)
{
    Vector3 tangentVector = CalculateBezierTangent(t);//切线斜率
    Vector3 normalVector = 由法线和切线互相垂直计算出法线方向;
    //法线向量的模应为到投影到弯曲面后,到中心点的距离
    float magnitude = Vector3.Project(new Vector3(oriPos.x, 0, oriPos.z), bendVector).magnitude;
    normalVector = normalVector.normalized * magnitude;
    return normalVector;
}
//对曲线公式求导得出切线向量
private Vector3 CalculateBezierTangent(float t)
{
    Vector3 ret = new Vector3(0, 0, 0);
    int n = 阶数;
    for(int i = 0; i <= n; i++)
    {
        Vector3 pi = 第i个控制点的坐标;
        ret = ret + (-1 * (n - i) * Mathf.Pow(1 - t, n - i - 1) * Mathf.Pow(t, i) * Cn_m(n, i) * pi + i * Mathf.Pow(1 - t, n - i) * Mathf.Pow(t, i - 1) * Cn_m(n, i) * pi);
    }
    return ret;
}

这样我们就实现了通过控制点生成曲线,通过曲线弯曲物体的方法。

3.构造受力模型

简单构造一个受力模型,通过物体施加拉力,拉力使控制点发生变化,从而使物体弯曲。设定一个Cube为施加拉力F的物体,然后为每个控制点设定一个完全弯曲所需要的力Fc,然后设定控制点朝拉力方向弯曲的角度为:
a = Mathf.Clamp(F/Fc, 0, 1.0) * 拉力与控制点的夹角;
为了模拟比较真实的弯曲效果,Fc可以看成三维物体每小节的的弹力大小,越靠近底部的控制点Fc就越大,越难弯曲,反之,越靠近顶部的控制点Fc越小,也就越容易弯曲。
代码如下:

private void UpdateControlPoint()
{
    float F = Cube.force;
    //根据受力计算各个控制点旋转角度
    n = 控制点数量;
    for(int i = 1; i < n - 1; i++)//第一个和最后一个点不计算弯曲
    {
        //计算最大弯曲方向
        Vector3 toVector = 施力物体相对控制点pi的方向;
        Quaternion maxRotation =  Quaternion.FromToRotation(Vector3.up, toVector);
        //计算弯曲比例
        float rotateRate = Mathf.Clamp(F / Fc, 0f, 1.0f);
        //设置旋转角度
        pi.localRotation = Quaternion.Lerp(Quaternion.Euler(0, 0, 0), maxRotation, rotateRate);
    }
}

4. 结语

该方法做出来的弯曲效果还是很自然的(如效果图),使用也比较简单,且不需要关节控制。但是比较吃性能,另外考虑到光照,顶点坐标更新后需要重新计算下mesh的法线信息normals。 

目前该方法对于管状的三维物体效果较佳,对于形状复杂的三维物体效果很差。该方法要求三维物体的顶点数要尽量多才能有效果。顶点数少的话很容易造成三维物体的扭曲变形。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/427687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时间序列 | MATLAB实现CNN-GRU-Attention时间序列预测

时间序列 | MATLAB实现CNN-GRU-Attention时间序列预测 目录时间序列 | MATLAB实现CNN-GRU-Attention时间序列预测预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 MATLAB实现CNN-GRU-Attention时间序列预测&#xff0c;CNN-GRU结合注意力机制时间序列预测。 模型描…

涨点技巧:卷积变体DCNV2引入Yolov8,助力小目标涨点

1.DCN V2介绍 DCN V2: Improved Deep & Cross Network and Practical Lessons for Web-scale Learning to Rank Systems 论文:https://arxiv.org/abs/2008.13535 作者通过在DCN的基础上,增加了2个创新点,分别是调制模块和使用多个调制后的DCN模块,从形成了DCN的升级版…

解码营养行业新趋势 2023晶球益生菌与肠内营养健康高峰论坛圆满落幕

后疫情时代&#xff0c;国人自身健康管理意识日益提高&#xff0c;越来越多的人认识到到微生物组、营养吸收与免疫健康的密切联系&#xff0c;并逐渐认可微生态和肠内营养在临床应用过程中的积极作用&#xff0c;使得营养治疗研究成果进一步落地转化。消费升级新时代&#xff0…

一文带你读懂,这三个交换机层级的区别和联系。

01 第二层交换机 OSI参考模型的第二层叫做数据链路层&#xff0c;第二层交换机通过链路层中的MAC地址实现不同端口间的数据交换。 第二层交换机主要功能&#xff0c;就包括物理编址、错误校验、帧序列以及数据流控制。 因为这是最基本的交换技术产品&#xff0c;目前桌面型交…

CDN到期不想续费?!MINIO救个场!

一、安装MINIO 下载 wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20230413030807.0.0.x86_64.rpm -O minio.rpm 安装 yum install minio.rpm 二、启动 MinIO 服务器 创建启动实例目录 mkdir ~/minio 启动 MInIO实例 minio server ~/minio -…

解析hash(散列)数据结构

前言 在学习完map、set这两个由红黑树构成的容器后&#xff0c;我们来到了这里hash&#xff0c;首先我们要有一个基础的认知——哈希和map与set的仅在使用时的差别区别&#xff1a;前者内部的元素没有序&#xff0c;而后者有序&#xff0c;其它的都相同&#xff0c;这里我们可…

【C++进阶之路】第一篇:C++中的继承

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【C学习与应用】 ✒️✒️本篇内容&#xff1a;继承的基础概念&#xff0c;定义方法&#xff0c;基类和派生类的转换&#xff0c;继承中类的作用域&#xf…

VSCode配置React Native调试环境

首先&#xff0c;用VSCode打开新建的react native工程&#xff0c;此时只能运行&#xff0c;是无法调试的。如果想单步调试代码&#xff0c;需要配置。 点击VSCode左边三角形菜单&#xff1a; 点击“创建launch.json文件”&#xff0c; 选择“React Native”调试器&#xff0c;…

肖 sir_就业课__011性能测试讲解

性能测试讲解 一、你做过性能测试吗&#xff1f; 方法1&#xff1a;做过 方法2&#xff1a;在公司中性能测试有专门的性能小组做&#xff0c;但是我也会做性能 二、性能测试有哪些类型&#xff1f; 1&#xff09;压力测试&#xff08;破坏性测试&#xff09; 压力测试是系统在一…

WiFi协议曝安全漏洞:Linux、Android和iOS未能逃脱

来自美国东北大学和鲁汶大学的学者披露了一组IEEE 802.11 Wi-Fi协议标准的一个基础设计漏洞&#xff0c;影响到运行Linux、FreeBSD、Android和iOS的各种设备。 来自美国东北大学和鲁汶大学的学者披露了一组IEEE 802.11 Wi-Fi协议标准的一个基础设计漏洞&#xff0c;影响到运行L…

【C++核心】内存、引用、函数

一、内存四区域 C程序在执行时&#xff0c;将内存大方向划分为4个区域 程序运行前分为&#xff1a; 代码区&#xff1a;存放函数体的二进制代码exe&#xff0c;由操作系统进行管理的 exe机器指令、共享、只读 全局区&#xff1a;存放全局变量和静态变量以及常量&#xff08;字…

运行时内存数据区之虚拟机栈——操作数栈

操作数栈 每一个独立的栈帧中除了包含局部变量表以外&#xff0c;还包含一个后进先出(Last-In-First-Out)的操作数栈&#xff0c;也可以称之为表达式栈(Expression Stack)。操作数栈&#xff0c;在方法执行过程中&#xff0c;根据字节码指令&#xff0c;往栈中写入数据或提取数…

Netty缓冲区ByteBuf源码解析

在网线传输中&#xff0c;字节是基本单位&#xff0c;NIO使用ByteBuffer作为Byte字节容器&#xff0c; 但是其使用过于复杂&#xff0c;因此Netty 写了一套Channel&#xff0c;代替了NIO的Channel &#xff0c;Netty 缓冲区又采用了一套ByteBuffer代替了NIO 的ByteBuffer &…

微服务+springcloud+springcloud alibaba学习笔记【OpenFeign的使用】(5/9)

OpenFeign的使用 5/91、OpenFeign简介1.1、Feign及OpenFeign概念和作用1.2、Feign和OpenFeign区别2、OpenFeign使用步骤2.1、创建Feign消费端微服务2.2、修改POM文件配置2.3、编写yml配置文件2.4、编写主启动类2.5、编写业务类2.5.1、编写 service 层接口&#xff0c;用于服务提…

什么是线性回归?线性回归有什么特征?

什么是线性回归 线性回归定义与公式 线性回归(Linear regression)是利用回归方程(函数)对一个或多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方式。 特点&#xff1a;只有一个自变量的情况称为单变量回归&#xff0c;多于一个自变量情况的叫做多元回归 线…

Orange Pi 5B面世,传承经典,再续传奇

近日&#xff0c;香橙派又出大招。刚刚发布的Orange Pi 5B在性能和表现上再度升级。此前&#xff0c;Orange Pi 5凭借出色的性能和极低的价格被誉为“地表最高性价比开发板”&#xff0c;一经上市就引起市场轰动&#xff0c;销量引领同类产品市场&#xff0c;获得极佳口碑。那么…

【音视频第13天】另外一种拥塞控制算法-TransportCC

目录与Goog-REMB区别TrendLine滤波器与Goog-REMB区别 与Goog-REMB有两个区别&#xff1a; 将拥塞评估算法从接收端移动到了发送端&#xff0c;评估和控制是一体的。TransportCC是发送端评估发送端接着改变码率&#xff0c;REMB是接收端评估&#xff0c;把评估出来的值给发送端…

STL源码剖析-分配器 Allocator

分配器(Allocator) 分配器给容器用的&#xff0c;是一个幕后英雄的角色。分配器的效率非常重要。因为容器必然会使用到分配器来负责内存的分配&#xff0c;它的性能至关重要。 在C中&#xff0c;内存分配和操作通过new和delete完成。 new中包含两个操作&#xff0c;第一步是…

HTML5 Input 类型

文章目录HTML5 Input 类型Input 类型: colorInput 类型: dateInput 类型: datetimeInput 类型: datetime-localInput 类型: emailInput 类型: monthInput 类型: numberInput 类型: rangeInput 类型: searchInput 类型: telInput 类型: timeInput 类型: urlInput 类型: weekHTML…

手撕哈希表

&#x1f308;感谢阅读East-sunrise学习分享——[进阶数据结构]哈希表 博主水平有限&#xff0c;如有差错&#xff0c;欢迎斧正&#x1f64f;感谢有你 码字不易&#xff0c;若有收获&#xff0c;期待你的点赞关注&#x1f499;我们一起进步&#x1f680; &#x1f308;我们上一…