样条线(Spline)是一个广泛的概念,指的是由多段连续且平滑的函数组成的曲线。样条线一般用来通过一组点(称为节点)来插值,并保证曲线在节点之间光滑过渡。
贝塞尔曲线
t 是一个从 0 到 1 的参数。每个 t t t对应一个向量。随着 t t t从 0 到 1 的变化,曲线上的点也随之变化。从而确定曲线。
仅通过首尾,复杂度高
任何一控制点的变化会引起曲线上所有点的改变,牵一发而动全身。所以,贝塞尔曲线是无法针对局部进行编辑的。
// 点=======================================================
QVector<QVector3D> x{ {1,1,0.1},{1,-1,0.2},{-1,-1,0.3},{-1,1,0.4},{1,1,0.1},{1,-1,0.5},{-1,-1,0.6},{-1,1,0.7} };
pushObject(new Spline(x));
// 计算贝塞尔曲线=======================================================
// 计算组合数(n 选 i)
int binomialCoefficient(int n, int i)
{
int res = 1;
for (int j = 0; j < i; ++j) {
res *= (n - j);
res /= (j + 1);
}
return res;
}
// 计算n次贝塞尔曲线的点(三维)
QVector3D bezierN(const QVector<QVector3D>& controlPoints, double t)
{
int n = controlPoints.size() - 1;
QVector3D point(0.0, 0.0, 0.0); // 用来存储计算结果
// 贝塞尔曲线的求和公式
for (int i = 0; i <= n; ++i) {
int binom = binomialCoefficient(n, i);
double weight = binom * pow(1 - t, n - i) * pow(t, i);
point += weight * controlPoints[i];
}
return point;
}
// 计算n次贝塞尔曲线上的多个点(三维)
QVector<QVector3D> calculateBezierCurve(const QVector<QVector3D>& controlPoints, int numPoints)
{
QVector<QVector3D> curvePoints;
// 计算曲线上的多个点
for (int i = 0; i <= numPoints; ++i) {
double t = i / (double)numPoints; // t 值的变化
QVector3D point = bezierN(controlPoints, t); // 计算当前t值对应的贝塞尔点
curvePoints.append(point);
}
return curvePoints;
}
// 绘制=======================================================
void Spline::initialize()
{
QOpenGLFunctions* functions = QOpenGLContext::currentContext()->functions();
vao.create();
vbo.create();
vao.bind();
vbo.bind();
vertexData = calculateBezierCurve(points, 100);
vbo.allocate(vertexData.data(), 3*vertexData.size() * sizeof(float));
functions->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
functions->glEnableVertexAttribArray(0);
vbo.release();
vao.release();
}
void Spline::draw()
{
QOpenGLFunctions* functions = QOpenGLContext::currentContext()->functions();
shaderProgram->bind();
vao.bind();
vbo.bind();
functions->glDrawArrays(GL_LINE_STRIP, 0, vertexData.size());
vbo.release();
vao.release();
shaderProgram->release();
}
B-spline
B样条是样条曲线的一种特殊表达形式,是B-样条基函数的线性组合,是贝塞尔曲线的一般化。
这是个分段函数,这些函数在他们的衔接处有连续性。
// Cox-de Boor算法,递归计算B样条基函数值
double coxDeBoor(const QVector<double>& knots, int i, int p, double t)
{
// 基本情况
if (p == 0) {
if (knots[i] <= t && t < knots[i + 1]) {
return 1.0;
}
else {
return 0.0;
}
}
double left = 0.0, right = 0.0;
if (i + p < knots.size() && knots[i + p] != knots[i]) {
left = (t - knots[i]) / (knots[i + p] - knots[i]) * coxDeBoor(knots, i, p - 1, t);
}
if (i + p + 1 < knots.size() && knots[i + p + 1] != knots[i + 1]) {
right = (knots[i + p + 1] - t) / (knots[i + p + 1] - knots[i + 1]) * coxDeBoor(knots, i + 1, p - 1, t);
}
return left + right;
}
// 计算B样条曲线上的点, p是B样条的阶数
QVector<QVector3D> calculateBSpline(const QVector<QVector3D>& controlPoints, int p, int numPoints)
{
QVector<QVector3D> curvePoints;
int n = controlPoints.size() - 1; // 控制点的数量
int m = n + p + 1; // 节点矢量的长度
// 计算节点矢量 (Uniform Knot Vector, 简单的均匀节点矢量)
QVector<double> knots(m);
for (int i = 0; i < m; ++i) {
if (i <= p) {
knots[i] = 0.0;
}
else if (i >= m - p) {
knots[i] = 1.0;
}
else {
knots[i] = (i - p) / (double)(n - p + 1);
}
}
// 计算曲线上的多个点
for (int i = 0; i < numPoints; ++i) {
double t = i / (double)numPoints;
QVector3D point(0.0, 0.0, 0.0);
// 对于每个控制点,使用Cox-de Boor公式计算B样条值并加权
for (int j = 0; j <= n; ++j) {
double weight = coxDeBoor(knots, j, p, t);
point += weight * controlPoints[j];
}
curvePoints.append(point);
}
return curvePoints;
}
参考:
https://frontzhm.github.io/2017/07/07/bezier/
https://zhuanlan.zhihu.com/p/539808761
https://zhuanlan.zhihu.com/p/672199076