贝塞尔曲线的OpenGL坐标

6

基本上,我需要从OpenGL中的Bezier曲线实现中获取所有坐标。具体而言,我需要这些坐标来沿着曲线轨迹路径移动我的场景中的球形物体(棒球)。以下是我用来绘制曲线的代码:

GL2 gl = drawable.getGL().getGL2();    
float ctrlpoints[][] = new float[][]{
            {0.0f, 0.0f, 60f},
            {0.0f, 3.0f, 45.0f},
            {0.0f, 2.0f, 15.0f},
            {0.0f, 1.0f, 0f}};
    FloatBuffer ctrlpointBuf = FloatBuffer.allocate(ctrlpoints[0].length * ctrlpoints.length);
        for (int i = 0; i < ctrlpoints.length; i++) {
            for (int j = 0; j < 3; j++) {
                ctrlpointBuf.put(ctrlpoints[i][j]);
            }
        }
        ctrlpointBuf.rewind();

        gl.glMap1f(GL2.GL_MAP1_VERTEX_3, 0.0f, 1.0f, 3, numControlPoints, ctrlpointBuf);
        gl.glEnable(GL2.GL_MAP1_VERTEX_3);

        gl.glColor3f(1.0f, 1.0f, 1.0f);
        gl.glBegin(GL2.GL_LINE_STRIP);
        for (int i = 0; i <= 30; i++) {
            gl.glEvalCoord1f((float) i / (float) 30.0);
        }
        gl.glEnd();

有没有人知道如何从这个实现中获取点数?

5个回答

26

贝塞尔曲线的计算非常简单。首先,它是可分离的,这意味着您可以按坐标逐个计算(首先是x,然后是y,然后是z...)。对于给定的坐标,以下是一个使用定义的函数:

double bezier(double A,  // Start value
              double B,  // First control value
              double C,  // Second control value
              double D,  // Ending value
              double t)  // Parameter 0 <= t <= 1
{
    double s = 1 - t;
    double AB = A*s + B*t;
    double BC = B*s + C*t;
    double CD = C*s + D*t;
    double ABC = AB*s + BC*t;
    double BCD = BC*s + CD*t;
    return ABC*s + BCD*t;
}

请注意,在上述函数中,参数t不是曲线的弧长参数,而是一个通用参数,从t=0(点在曲线开头处)到t=1(点在曲线末尾处)。

Evaluation of a Bezier cubic for t=0.35

这里提供了一张互动的图片,你可以拖动A、B、C、D和AB点点击此处。它是用html/js/canvas实现的,并且只在Chrome、Firefox和Safari上测试过。

如果你需要以特定速度在XYZ轴移动物体,一种简单的方法是计算一个近似的折线(例如通过采样曲线100个t值),然后按照一定的速度在结果折线上行走。

对于Bezier立方曲线的真正弧长参数化(即使用沿曲线测量的长度作为参数),计算起来相当麻烦(如果我没记错的话,没有解析式的积分解决方案)。


1
但是弧长计算既不可分离也不简单。沿着曲线控制运动速度比简单地线性变化t要复杂得多。 - Ben Voigt
2
你可以通过评估每0.01个点来构建一个近似的折线,然后以所需速度沿着折线行走。这很简单,基本上与正确但在数学上令人讨厌的弧长参数化无法区分。 - 6502
@6502:这是一个合理的方法。我只是想让@David知道,直接使用t作为时间很可能会出现问题。 - Ben Voigt
@Ben Voigt:我加了一条关于弧长参数化问题的注释。 - 6502
现在是t时间吗?我尝试将t += 1/60插入每秒的每帧中,这产生了类似的曲线,但正如Ben所说,它并不完全与我的OpenGL实现中的控制点评估相同。 - user439441
显示剩余4条评论

3

我认为bezier()函数中的下面这行代码应该是:

double ABC = AB*s + CD*t;

而不是

double ABC = BC*s + CD*t;

通过一个.c程序的快速测试可以得到这些结果。请注意,曲线坐标从10.00开始,而不是使用上述未修改的函数从20.00开始。

~/sujith/cc > gcc oglBezier.c 
~/sujith/cc > ./a.out
Start. A=10.000000, B=20.000000, C=40.000000, D=5.000000, t=0.000000
Bezier pt= 10.000000
Bezier pt= 10.495490
Bezier pt= 10.981920
Bezier pt= 11.459230
Bezier pt= 11.927360
Bezier pt= 12.386250
Bezier pt= 12.835840
Bezier pt= 13.276070
Bezier pt= 13.706880
Bezier pt= 14.128210
Bezier pt= 14.540000
Bezier pt= 14.942190
Bezier pt= 15.334720
Bezier pt= 15.717530
Bezier pt= 16.090560
Bezier pt= 16.453750
Bezier pt= 16.807040
Bezier pt= 17.150370
Bezier pt= 17.483680
Bezier pt= 17.806910
Bezier pt= 18.120000
Bezier pt= 18.422890
Bezier pt= 18.715520
Bezier pt= 18.997830
Bezier pt= 19.269760
Bezier pt= 19.531250
Bezier pt= 19.782240
Bezier pt= 20.022670
Bezier pt= 20.252480
Bezier pt= 20.471610
Bezier pt= 20.680000
Bezier pt= 20.877590
Bezier pt= 21.064320
Bezier pt= 21.240130
Bezier pt= 21.404960
Bezier pt= 21.558750
Bezier pt= 21.701440
Bezier pt= 21.832970
Bezier pt= 21.953280
Bezier pt= 22.062310
Bezier pt= 22.160000
Bezier pt= 22.246290
Bezier pt= 22.321120
Bezier pt= 22.384430
Bezier pt= 22.436160
Bezier pt= 22.476250
Bezier pt= 22.504640
Bezier pt= 22.521270
Bezier pt= 22.526080
Bezier pt= 22.519010
Bezier pt= 22.500000
Bezier pt= 22.468990
Bezier pt= 22.425920
Bezier pt= 22.370730
Bezier pt= 22.303360
Bezier pt= 22.223750
Bezier pt= 22.131840
Bezier pt= 22.027570
Bezier pt= 21.910880
Bezier pt= 21.781710
Bezier pt= 21.640000
Bezier pt= 21.485690
Bezier pt= 21.318720
Bezier pt= 21.139030
Bezier pt= 20.946560
Bezier pt= 20.741250
Bezier pt= 20.523040
Bezier pt= 20.291870
Bezier pt= 20.047680
Bezier pt= 19.790410
Bezier pt= 19.520000
Bezier pt= 19.236390
Bezier pt= 18.939520
Bezier pt= 18.629331
Bezier pt= 18.305761
Bezier pt= 17.968751
Bezier pt= 17.618241
Bezier pt= 17.254171
Bezier pt= 16.876481
Bezier pt= 16.485111
Bezier pt= 16.080001
Bezier pt= 15.661091
Bezier pt= 15.228321
Bezier pt= 14.781631
Bezier pt= 14.320961
Bezier pt= 13.846251
Bezier pt= 13.357441
Bezier pt= 12.854471
Bezier pt= 12.337281
Bezier pt= 11.805811
Bezier pt= 11.260001
Bezier pt= 10.699791
Bezier pt= 10.125121
Bezier pt= 9.535931
Bezier pt= 8.932161
Bezier pt= 8.313751
Bezier pt= 7.680641
Bezier pt= 7.032771
Bezier pt= 6.370081
Bezier pt= 5.692512
Bezier pt= 5.000002

测试程序oglBezier.c:

#include <stdio.h>


double bezier(double A,  // Start value
              double B,  // First control value
              double C,  // Second control value
              double D,  // Ending value
              double t)  // Parameter 0 <= t <= 1
{
    double s = 1 - t;
    double AB = A*s + B*t;
    double BC = B*s + C*t;
    double CD = C*s + D*t;
    double ABC = AB*s + CD*t;
    double BCD = BC*s + CD*t;
    return ABC*s + BCD*t;
}

main()
{
        double a,b,c,d,t;

        a = 10.0f;
        b = 20.0f;
        c = 40.0f;
        d = 5.0f;
        t = 0.0f;
        printf("Start. A=%f, B=%f, C=%f, D=%f, t=%f\n", a,b,c,d,t);

        while(1)
        {   
                if(t>1.0f)
                        break;

                printf("Bezier pt= %f\n", bezier(a,b,c,d,t));

                t += 0.01f;
        }   

        return 1;
}

多年来,我在各种程序中使用了这段代码,并与其他来源计算的值进行了比较,我相当确定原始的“ABC = AB X s + BC X t”是正确的。我强烈建议任何应用上述“修复”方法的人自行检查它。 - Graham Toal

2
如果有人感兴趣,这是我最终使用贝塞尔曲线绘制轨迹在场景中实现投掷棒球的方法。我使用了6502非常有启发性的函数来计算每帧球的xyz坐标。起始值是球在绘制帧时所处的曲线位置。终止值和控制点用于绘制整条曲线都是完全相同的。让我花费一些时间才想出该给参数t什么样的参数。
我终于明白了t的值应该从曲线的起点为0到终点为1。因此,投手板上投出的棒球在距离本垒板60.5英尺处时t=0,在本垒板0英尺处时t=1。因此,t的计算可能就像这样简单:
t += 1.0 / 60.5;

我首先将整个曲线绘制为GL_LINE_STRIP,然后计算每帧球的坐标。当我运行程序时,球恰好沿着曲线轨迹移动。感谢所有提供答案和评论的人。


1
我采用了这个公式,进行了修正,并在github上创建了一个测试应用程序,网址为git://github.com/rmd6502/BezierLicious.git。 我强烈建议不要在生产应用程序中使用我在那里做的任何事情 - 仅将其视为研究目的!

0
你应该看一下德卡斯特尔乔算法。它允许您递归地细化曲线。只需在几个步骤后中止并使用生成的顶点即可。您还可以直接沿参数位置评估控制点并使用它们。直接评估Bézier-Splines并不难(例如,您可以在顶点着色器中执行此操作!)De Casteljau算法的优点是您永远不必计算(高)幂,尽管对于您的情况,两者都应该可以(而且直接评估肯定更容易实现/测试)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接