为计算机图形学的学习笔记,附有算法的glut实现,参考视频(7.3.1 参数连续性_哔哩哔哩_bilibili)
1、基本概念
(1)规则曲线(圆、抛物线、双曲线等)
可以用曲线方程式表达的曲线——解析曲线。
(2)不规则曲线(如汽车、飞机曲线)
不能确切给出描述整个曲线的方程,而是由从实际测量中得到的一系列离散数据点采用曲线拟合的方法来逼近形成的曲线。
2、曲线的表达形式
(1)显示方程
(2)隐函数方程
(3)参数方程(易于表示成矢量和矩阵)
3、曲线的插值与逼近
(1)曲线的插值
当用一组型值点(插值点)来制定曲线形状时,形状完全通过给定的型值点序列确定。
(2)曲线的逼近
当用一组控制点来制定曲线的形状是,求出的形状不必通过控制点。
(图片来自:插值与逼近 - 知乎 (zhihu.com))
4、曲线的连续性
(1)曲线连续性的类型
参数连续性
几何连续性
(2)(参考自曲线曲面 - 连续性, 坐标变换矩阵_曲线的连续性-CSDN博客)
①参数连续性:
零阶参数连续性,指相邻两段曲线在结合点处具有相同的坐标
一阶参数连续性,指相邻两段曲线在结合点处具有相同的一阶导数;
二阶参数连续性,指相邻两段曲线在结合点处具有相同的二阶导数;
②几何连续性:
与参数连续性不同的是,几何连续性只要求参数成比例而非相等。
零阶几何连续性,指相邻两段曲线在结合点处具有相同的坐标;
一阶几何连续性,指相邻两段曲线在结合点处的一阶导数成比例、但大小不一定相等;
二阶几何连续性,指相邻两段曲线在结合点处的一阶导数成比例、二阶导数成比例,即曲率一致,但大小不一定相等。
几何连续性是几何学中的概念,强调形状的平滑和连续性,不涉及函数值的具体变化。通常参数连续性能确保几何连续性,反之不亦然。
5、曲线的插值
(1)拉格朗日插值
①原理
对某个多项式函数,已知有给定的k + 1 个取值点:
假设其中任意两个不同的都互不相同,那么应用拉格朗日插值公式所得到的拉格朗日插值多项式为:
其中,每个为拉格朗日基本多项式(或成为插值基函数),其表达式为:
(参考自拉格朗日插值法(理论详解)_拉格朗日插值多项式-CSDN博客)
②代码实现(OpenGL)
// 拉格朗日插值函数
float lagrangeInterpolation(float x) {
float result = 0.0f;
for (int i = 0; i < numControlPoints; i++) {
float term = controlPoints[i].y;
for (int j = 0; j < numControlPoints; j++) {
if (j != i) {
term *= (x - controlPoints[j].x) / (controlPoints[i].x - controlPoints[j].x);
}
}
result += term;
}
return result;
}
// 绘制拉格朗日插值曲线
void drawLagrangeCurve() {
if (numControlPoints >= 2) {
glColor3f(1.0f, 0.0f, 0.0f); // 设置曲线颜色为红色
glBegin(GL_LINE_STRIP); // 开始绘制线段
for (float x = controlPoints[0].x; x <= controlPoints[numControlPoints - 1].x; x += 0.01f) {
float y = lagrangeInterpolation(x);
glVertex2f(x, y);
}
glEnd(); // 结束绘制线段
}
}
// 绘制控制点
void drawControlPoints() {
glPointSize(5.0f); // 设置点的大小
glColor3f(0.0f, 0.0f, 1.0f); // 设置点的颜色为蓝色
glBegin(GL_POINTS);
for (int i = 0; i < numControlPoints; i++) {
glVertex2f(controlPoints[i].x, controlPoints[i].y);
}
glEnd();
}
(2)三次样条插值
①三次参数曲线表达式
将x,y,z分别表示成参数t的三次多项式
矢量形式:
②求解目标
将一组n+1个控制点,找出n段三次曲线拼接而成,且通过控制点的曲线有二阶参数连续性。也就是每两个插值点之间都是一个三次方程。三次样条方差需要满足每个相邻数据点处,插值函数的一阶、二阶连续。
③原理
请参考:
三次样条(cubic spline)插值 - 知乎 (zhihu.com)
④代码实现
// 三次样条插值函数
void computeSplineCoefficients(vector<float>& a, vector<float>& b, vector<float>& c, vector<float>& d) {
int n = numControlPoints - 1;
vector<float> h(n), alpha(n), l(n + 1), mu(n), z(n + 1);
for (int i = 0; i < n; i++) {
h[i] = controlPoints[i + 1].x - controlPoints[i].x;
}
for (int i = 1; i < n; i++) {
alpha[i] = 3 * (controlPoints[i + 1].y - controlPoints[i].y) / h[i] -
3 * (controlPoints[i].y - controlPoints[i - 1].y) / h[i - 1];
}
l[0] = 1;
mu[0] = 0;
z[0] = 0;
for (int i = 1; i < n; i++) {
l[i] = 2 * (controlPoints[i + 1].x - controlPoints[i - 1].x) - h[i - 1] * mu[i - 1];
mu[i] = h[i] / l[i];
z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i];
}
l[n] = 1;
z[n] = 0;
c[n] = 0;
for (int j = n - 1; j >= 0; j--) {
c[j] = z[j] - mu[j] * c[j + 1];
b[j] = (controlPoints[j + 1].y - controlPoints[j].y) / h[j] - h[j] * (c[j + 1] + 2 * c[j]) / 3;
d[j] = (c[j + 1] - c[j]) / (3 * h[j]);
}
for (int j = 0; j < n; j++) {
a[j] = controlPoints[j].y;
}
}
// 绘制三次样条曲线
void drawSplineCurve() {
if (numControlPoints >= 2) {
vector<float> a(numControlPoints), b(numControlPoints), c(numControlPoints), d(numControlPoints);
computeSplineCoefficients(a, b, c, d);
glColor3f(1.0f, 0.0f, 0.0f); // 设置曲线颜色为红色
glBegin(GL_LINE_STRIP); // 开始绘制线段
for (int i = 0; i < numControlPoints - 1; i++) {
for (float t = 0; t <= 1; t += 0.01) {
float x = controlPoints[i].x + t * (controlPoints[i + 1].x - controlPoints[i].x);
float dx = controlPoints[i + 1].x - controlPoints[i].x;
float y = a[i] + b[i] * t * dx + c[i] * pow(t * dx, 2) + d[i] * pow(t * dx, 3);
glVertex2f(x, y);
}
}
glEnd(); // 结束绘制线段
}
}
6、曲线的拟合
(1)Bezier曲线
(参考自贝塞尔曲线(Bezier Curve)原理、公式推导及matlab代码实现-CSDN博客,这里只简单摘录)
①原理
*一阶贝瑟尔曲线
给定点P0P1,线性Bezier曲线只是一条两点之间的直线。
其中,公式中的P0P1表示为横坐标或者纵坐标。
*二阶贝瑟尔曲线
给定点P0P1P2,三点之间形成一条拟合的曲线。假设P0P1上的点为A,P1P2上的点为B,AB上的点为C(也即C为曲线上的点)。则根据一次Bezier曲线公式有:
将上式中的A、B带入C中,即可得到二次Bezier曲线的公式。
*n次Bezier曲线(一般参数公式)
给定n+1个控制点Pi(i=0,1,2...n)成为n次Bezier曲线。可由如下递归式表达:
Bezier曲线的一般公式:
其中,给定n+1个控制点Pi(i=0,1,2……n),称为n次 Bezier曲线,Bernstein基函数为:
②特点
A.端点性质:Bezier曲线的起点和终点分别位于顶点P0和Pn上。
B.一阶导数:Bezier曲线的起点和终点的切线方向位于控制多边形的起始边和终止边的切线方向上。
C.凸包性:Bezier曲线位于控制多边形构成的凸包之内。
D.几何不变性:Bezier曲线位置与形状与其特征多边形顶点的位置有关,它不依赖坐标系的选择。
③代码实现
// Bezier曲线插值函数
float bezierInterpolation(float t, bool isX) {
float result = 0.0f;
for (int i = 0; i < numControlPoints; i++) {
float term = combination(numControlPoints - 1, i);
term *= pow(t, i) * pow(1 - t, numControlPoints - 1 - i);
if (isX)
result += term * controlPoints[i].x;
else
result += term * controlPoints[i].y;
}
return result;
}
// 绘制Bezier曲线
void drawBezierCurve() {
if (numControlPoints >= 2) {
glColor3f(1.0f, 0.0f, 0.0f); // 设置曲线颜色为红色
glBegin(GL_LINE_STRIP); // 开始绘制线段
for (float t = 0.0f; t <= 1.0f; t += 0.01f) {
float x = bezierInterpolation(t, true);
float y = bezierInterpolation(t, false);
glVertex2f(x, y);
}
glEnd(); // 结束绘制线段
}
}
④Bezier曲线拼接
含义:两条Bezier曲线P(t)和Q(t),相应控制点为Pi(i=0,1,...,n)和Qi(i=0,1,...,n)。,,曲线拼接后连续的充要条件为:
G0连续的充要条件为:
G1连续的充要条件为:三点共线。
G2连续的充要条件为:在G1连续的条件下,满足。
(2)B样条曲线
①原理
Bezier曲线是改变任意控制点P,整个曲线上的点都会改变 。如何控制只让P相邻的曲线变化?(结合三次样条曲线和bezier曲线)
B样条曲线的数学表达式:
其中,称为k阶(k-1)B样条基函数,k是刻画次数。其中k可以是2到控制点个数n+1之间的任意整数。
对于Bezier曲线来说,阶数和次数是一样的。但对于B样条,阶数是次数加1。B样条基函数是一个成为节点矢量的非递减的参数u的序列所决定的k阶分段多项式,这个序列称为节点向量。
特点:
曲线是关于u的k-1阶多项式,k-1阶参数连续。
曲线有n+1个控制点,并由n+1个混合函数Bk,d(u)来描述该曲线。
每一混合函数Bk,d(u)由d个子区间关于u函数构成,每个子区间从uk开始,uk+d结束。
参数u共有n+d+1个值节点,分成n+d个子区间。
曲线由控制点坐标p,参数d和参数u和参数u节点值决定。
代码博主没写出来,等一个好心人。