【Unity】弹性鱼竿简单实现-通过贝塞尔曲线修改Mesh

一、实现思路

弹性鱼竿,即可以根据受力状态自由弯曲的鱼竿,如何实现“弯曲”是关键。说到弯曲,自然而然想到曲线,从曲线的角度出发,那么关键就是如何生成曲线,以及如何根据曲线修改物体形状,从而达到弯曲的效果。
生成曲线的话,可以直接想到用贝塞尔曲线,由n个控制点绘制出n阶贝塞尔曲线,通过修改控制点的坐标来控制曲线变化。
然后我们可以考虑修改模型的Mesh顶点坐标来实现弯曲效果。
完成效果如下:


最终效果

二、贝塞尔曲线公式

贝塞尔曲线就不详细介绍了,具体可以参考百度百科。贝塞尔曲线简单来说就是通过n个控制点来生成曲线,而修改控制点位置可以修改曲线的形状。这里有个二阶贝塞尔曲线在线模拟工具,可以体验一下。

贝塞尔曲线随参数t生成示意

首先我们要实现贝塞尔曲线的计算公式,可知其n阶曲线的公式为:
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应该在鱼竿顶部位置。

三、模型应用曲线

当然,按照上面公式计算出的只是一条曲线,而我们的目的是模型能按照这个曲线进行弯曲,如示意图:


模型按照贝塞尔曲线弯曲

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

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;
}

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


控制点-曲线-模型

四、简单构造受力模型

接下来我们简单构造一个受力模型,通过物体施加拉力,拉力使控制点发生变化,从而使物体弯曲。我们简单设定一个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);
    }
}

效果如图:


拉力F变化

五、最后

该方法做出来的弯曲效果还是很自然的,使用也比较简单,且不需要关节控制。但是比较吃性能,另外考虑到光照,顶点坐标更新后需要重新计算下mesh的法线信息normals。
附上源码:https://github.com/dxxia/TestFishRod_BezierMesh

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容