You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NURBS四分之一圆弧实现偏差问题排查求助

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;
    }

错误分析与修复方案

核心错误点

  1. 控制点位置错误:中间控制点坐标不符合四分之一圆弧的几何特性,导致曲线偏离标准圆弧
  2. 节点向量配置不规范:非均匀节点向量会破坏NURBS曲线的圆弧拟合特性,必须使用与阶数匹配的重复节点
  3. 遗漏齐次坐标转换:NURBS计算结果是齐次坐标,需转换为笛卡尔坐标才能正确绘制
  4. 参数采样范围截断:原代码中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;
    }

关键修改解释

  1. 控制点修正:根据圆弧圆心和半径计算中间控制点,确保其位于圆弧的切线路径上
  2. 节点向量标准化:使用{0,0,0,1,1,1}的标准节点向量,保证2阶NURBS曲线的端点插值和圆弧拟合特性
  3. 齐次坐标转换:新增将计算得到的齐次坐标转换为笛卡尔坐标的步骤,这是NURBS曲线正确绘制的核心环节
  4. 参数范围修正:将tMin和tMax改为覆盖完整节点区间,确保曲线绘制完整

内容的提问来源于stack exchange,提问作者Alex1974

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 16:07:03