NURBS四分之一圆弧实现偏差问题排查求助
NURBS四分之一圆弧拟合偏差修复方案
问题说明
尝试用NURBS绘制四分之一圆弧,但生成的绿色曲线与红色标准圆弧存在明显偏差,调整节点向量后仍未解决问题。偏差效果如下:
原实现代码
void GLWidget::NURBS() { // NURBS test glPushMatrix(); glLineWidth(5.0); glColor3f (0.0, 1.0, 0.0); std::vector<float> knotVector = {0.0f, 0.125f, 0.125f, 0.5f, 0.5f, 1.0f}; // std::vector<float> knotVector = {0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f}; int nknots = knotVector.size(); std::vector<float> controlPoints = {30.0f, 20.0f, 0.0f, 1.0f, 50.0f, 20.0f, 0.0f, 0.707f, 50.0f, 0.0f, 0.0f, 1.0f}; // NURBS degree int degree = 2; // define the parameter values along the curve float tMin = knotVector[degree]; float tMax = knotVector[nknots - degree - 1]; // float tMax = knotVector[nknots - 1]; // number of points int numPoints = 100; // step float dt = (tMax - tMin) / numPoints; // curve points QVector<float> curvePoints; for (float t = tMin; t <= tMax; t += dt) { // span calculation int span = findSpan(knotVector, nknots, degree, t); //basis calculation QVector<float> basisFunctions = calculateBasisFunctions(knotVector, span, t, degree); qDebug() << "basisFunctions" << basisFunctions; qDebug() << "span" << span; // accumulate the control points multiplied by the basis function values QVector<float> curvePoint(4, 0.0f); for (int i = 0; i <= degree; ++i) { float basis = basisFunctions[i]; for (int j = 0; j < 4; ++j) { curvePoint[j] += controlPoints[(span - degree + i) * 4 + j] * basis; } } // add the curve point to the vector curvePoints += curvePoint; } // paint the NURBS curve glBegin(GL_LINE_STRIP); for (int i = 0; i < curvePoints.size(); i += 4) { glVertex3f(curvePoints[i], curvePoints[i + 1], curvePoints[i + 2]); } glEnd(); update(); } int GLWidget::findSpan(const std::vector<float>& knots, int n, int degree, float t) { if (t >= knots[n-1]) return n - degree - 2; else if (t <= knots[degree]) return degree; int low = degree; int high = n; int mid = (low + high) / 2; while (t < knots[mid] || t >= knots[mid+1]) { if (t < knots[mid]) high = mid; else low = mid; mid = (low + high) / 2; } return mid; } QVector<float> GLWidget::calculateBasisFunctions(const std::vector<float>& knots, int span, float t, int degree) { QVector<float> basisFunctions(degree + 1, 0.0f); QVector<float> left(degree + 1, 0.0f); QVector<float> right(degree + 1, 0.0f); basisFunctions[0] = 1.00f; for (int j = 1; j <= degree; ++j) { left[j] = t - knots[span + 1 - j]; right[j] = knots[span + j] - t; float saved = 0.0f; for (int r = 0; r < j; ++r) { float temp = basisFunctions[r] / (right[r + 1] + left[j - r]); basisFunctions[r] = saved + right[r + 1] * temp; saved = left[j - r] * temp; } basisFunctions[j] = saved; } return basisFunctions; }
错误分析与修复方案
核心错误点
- 控制点位置错误:中间控制点坐标不符合四分之一圆弧的几何特性,导致曲线偏离标准圆弧
- 节点向量配置不规范:非均匀节点向量会破坏NURBS曲线的圆弧拟合特性,必须使用与阶数匹配的重复节点
- 遗漏齐次坐标转换:NURBS计算结果是齐次坐标,需转换为笛卡尔坐标才能正确绘制
- 参数采样范围截断:原代码中
tMax取截断值,导致曲线末端未完全覆盖
修正后的代码
void GLWidget::NURBS() { // NURBS test glPushMatrix(); glLineWidth(5.0); glColor3f (0.0, 1.0, 0.0); // 使用标准2阶NURBS圆弧节点向量:两端重复度=阶数 std::vector<float> knotVector = {0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f}; int nknots = knotVector.size(); // 四分之一圆弧参数:圆心(30.0f, 0.0f),半径20.0f float centerX = 30.0f; float centerY = 0.0f; float radius = 20.0f; float sqrt2_2 = std::sqrt(2.0f) / 2.0f; // 精确的√2/2权重 // 正确的控制点:起点(30,20)、中间点(30+20*cos45°, 0+20*sin45°)、终点(50,0) std::vector<float> controlPoints = { centerX, centerY + radius, 0.0f, 1.0f, // 起点 centerX + radius, centerY + radius, 0.0f, sqrt2_2, // 中间控制点(权重√2/2) centerX + radius, centerY, 0.0f, 1.0f // 终点 }; // NURBS degree int degree = 2; // 覆盖完整的参数区间:从第一个节点到最后一个节点 float tMin = knotVector[0]; float tMax = knotVector.back(); // number of points int numPoints = 100; // step float dt = (tMax - tMin) / numPoints; // curve points QVector<float> curvePoints; for (float t = tMin; t <= tMax; t += dt) { // span calculation int span = findSpan(knotVector, nknots, degree, t); //basis calculation QVector<float> basisFunctions = calculateBasisFunctions(knotVector, span, t, degree); // accumulate the control points multiplied by the basis function values QVector<float> curvePoint(4, 0.0f); for (int i = 0; i <= degree; ++i) { float basis = basisFunctions[i]; for (int j = 0; j < 4; ++j) { curvePoint[j] += controlPoints[(span - degree + i) * 4 + j] * basis; } } // 齐次坐标转笛卡尔坐标:除以w分量 if (curvePoint[3] != 0.0f) { curvePoint[0] /= curvePoint[3]; curvePoint[1] /= curvePoint[3]; curvePoint[2] /= curvePoint[3]; } // add the curve point to the vector curvePoints += curvePoint; } // paint the NURBS curve glBegin(GL_LINE_STRIP); for (int i = 0; i < curvePoints.size(); i += 4) { glVertex3f(curvePoints[i], curvePoints[i + 1], curvePoints[i + 2]); } glEnd(); update(); } // 以下findSpan和calculateBasisFunctions函数无需修改,保持原实现即可 int GLWidget::findSpan(const std::vector<float>& knots, int n, int degree, float t) { if (t >= knots[n-1]) return n - degree - 2; else if (t <= knots[degree]) return degree; int low = degree; int high = n; int mid = (low + high) / 2; while (t < knots[mid] || t >= knots[mid+1]) { if (t < knots[mid]) high = mid; else low = mid; mid = (low + high) / 2; } return mid; } QVector<float> GLWidget::calculateBasisFunctions(const std::vector<float>& knots, int span, float t, int degree) { QVector<float> basisFunctions(degree + 1, 0.0f); QVector<float> left(degree + 1, 0.0f); QVector<float> right(degree + 1, 0.0f); basisFunctions[0] = 1.00f; for (int j = 1; j <= degree; ++j) { left[j] = t - knots[span + 1 - j]; right[j] = knots[span + j] - t; float saved = 0.0f; for (int r = 0; r < j; ++r) { float temp = basisFunctions[r] / (right[r + 1] + left[j - r]); basisFunctions[r] = saved + right[r + 1] * temp; saved = left[j - r] * temp; } basisFunctions[j] = saved; } return basisFunctions; }
关键修改解释
- 控制点修正:根据圆弧圆心和半径计算中间控制点,确保其位于圆弧的切线路径上
- 节点向量标准化:使用
{0,0,0,1,1,1}的标准节点向量,保证2阶NURBS曲线的端点插值和圆弧拟合特性 - 齐次坐标转换:新增将计算得到的齐次坐标转换为笛卡尔坐标的步骤,这是NURBS曲线正确绘制的核心环节
- 参数范围修正:将
tMin和tMax改为覆盖完整节点区间,确保曲线绘制完整
内容的提问来源于stack exchange,提问作者Alex1974
相关产品推荐
相关产品推荐

