您的位置:首页 > 运维架构

OpenGL绘制简单的参数曲线(完)——三次B样条曲线

2015-08-21 15:57 281 查看
  我们今天来介绍一下B样条曲线。相比较Beizer曲线来说,B样条有着两个优点:(1)k次B样条曲线具有良好的局部性,它只与k+1个控制点有关;(2)B样条曲线拼接较为简单。不过B样条曲线的公式比较难懂,网上介绍原理的也着实不多,这里详细分享一下。



图1

  我们先来看看什么是B样条曲线,如图1,我们以三次B样条曲线为例。由于k次B样条曲线的控制点有k+1个,所以P0P1P2P3控制u1u2段曲线,P1P2P3P4控制u2u3段曲线,P2P3P4P5控制u3u4段曲线。所以这样就不会像beizer曲线那样,改变任一控制点,都会对整个曲线产生影响。接下来我们看一下B样条曲线的公式:



图2

  S(t)表示的是uaua+1段曲线,k表示的k次B样条曲线,所以S(t)就是控制点与基函数的乘积之和,其中控制点是从Pj点到Pi点,一共有i-j+1个。结合图1中的u1u2段曲线举个例子,Su1u2(t) = P0N0,3(t) + P1N1,3(t) + P2N2,3(t) + P3N3,3(t)。这个公式比较抽象,我们换种写法,同时我们再给出N(t)的公式:



图3

  图3、图2的公式参数意义略有不同。图3中,j表示的是起始的控制点,k表示的是k次B样条曲线,i表示的是迭代参数,相当于控制点是从PjPj+k。基函数N(t)中参数ik跟其上面的公式保持同步。这个公式看起来很复杂,光用文字说明并不能解释太清楚,我们举一个二次B样条曲线和一个三次B样条曲线的例子来说明一下。

  (1)二次B样条曲线:k=2,下面是二次B样条曲线的三个基函数(建议大家拿笔算一算,这样对公示理解的更深):



图4

  这里我们跟据上面的基函数给出P0,2(t)的公式及相关性质:


图5

  这条曲线的端点位置和端点切失如下:



图6

  根据上面的公式和性质可以得到如下曲线:



图7

  (2)三次B样条曲线:k=3,这个也是我们今天代码展示的曲线。下面是三次B样条曲线的四个基函数:


图8

  这里我们跟据上面的基函数给出P0,3(t)的公式及相关性质:



图9

  这条曲线的端点位置和端点切失如下:



图10

  根据上面的公式和性质可以得到如下曲线:



图11

  我们这里就讲完三种最基本也是最常用的曲线,我们在贴B样条曲线的代码之前,先讨论一下三种曲线的优缺点(这只是个人观点)。首先,Hermite曲线和Beizer曲线基本是一致的,虽然原理上有着一定的差异,但从性质以及参数的影响程度来说,大致上是相似的。而且这两种曲线在表达复杂的曲线时,都是利用低次曲线拼接的方式来表达。相比这两种曲线,B样条曲线就显得比较具有优势,具体优势开头也有提到,这里就不重复了,缺点也非常明显,控制点不在曲线上导致不好容易控制。

  说到这,OpenGL绘制曲线就正式完结了。最后贴出三次B样条曲线的代码(效果就是图1,不过可以拖动顶点调整曲线),大家可以试试。

#include <math.h>
#include <gl/glut.h>
#include <iostream>
using namespace std;

#define NUM_POINTS 6
#define NUM_SEGMENTS (NUM_POINTS-3)

struct Point2
{
double x;
double y;

Point2() { ; }
Point2(int px, int py) { x = px; y = py; }
void SetPoint2(int px, int py) { x = px; y = py; }
};

/*全局变量*/
Point2 vec[NUM_POINTS];
bool mouseLeftDown = false;

/*绘制B样条曲线*/
void Bspline(int n)
{
float f1, f2, f3, f4;
float deltaT = 1.0 / n;
float T;

glBegin(GL_LINE_STRIP);
for (int num = 0; num < NUM_SEGMENTS; num++)
{
for (int i = 0; i <= n; i++) {

T = i * deltaT;

f1 = (-T*T*T + 3*T*T - 3*T + 1) / 6.0;
f2 =(3*T*T*T - 6*T*T + 4) / 6.0;
f3 = (-3*T*T*T +3*T*T + 3*T + 1) / 6.0;
f4 = (T*T*T) / 6.0;

glVertex2f( f1*vec[num].x + f2*vec[num+1].x + f3*vec[num+2].x + f4*vec[num+3].x,
f1*vec[num].y + f2*vec[num+1].y + f3*vec[num+2].y + f4*vec[num+3].y);
}
}

glEnd();
}

void display()
{
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();

glLineWidth(1.5f);
glColor3f(1.0,0.0,0.0);
glBegin(GL_LINE_STRIP);
for(int i = 0;i < NUM_POINTS; i++)
{
glVertex2f(vec[i].x, vec[i].y);
}
glEnd();

glPointSize(10.0f);
glColor3f(0.0, 0.0, 1.0);
glBegin(GL_POINTS);
for(int i = 0;i < NUM_POINTS; i++)
{
glVertex2f(vec[i].x, vec[i].y);
}
glEnd();

Bspline(20);

glFlush();
glutSwapBuffers();
}

void init()
{
glClearColor(1.0, 1.0, 1.0, 0.0);
glShadeModel(GL_FLAT);

vec[0].SetPoint2(200, 400);
vec[1].SetPoint2(100, 300);
vec[2].SetPoint2(200, 200);
vec[3].SetPoint2(250, 300);
vec[4].SetPoint2(400, 200);
vec[5].SetPoint2(400, 400);
}

void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, (GLsizei)w, (GLsizei)h, 0.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
mouseLeftDown = true;
}

if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
{
mouseLeftDown = false;
}
}

double distance(int x1, int y1, int x2, int y2)
{
return sqrt((x1-x2) * (x1 -x2) + (y1-y2) * (y1-y2));
}

void motion(int x, int y)
{
if (mouseLeftDown)
{
for (int i = 0; i < NUM_POINTS; i++)
{
if (distance(vec[i].x, vec[i].y, x, y) < 20)
{
vec[i].SetPoint2(x, y);
}
}
}

glutPostRedisplay();
}

int main(int argc,char** argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE);
glutInitWindowSize(500, 500);
glutInitWindowPosition (200, 200);
glutCreateWindow("B-Spline Curve");
init();

glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMotionFunc(motion);
glutMainLoop();

return 0;

}


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: