文章目录

  • 说明
  • B样条曲线
  • 代码


说明


B样条曲线

前面讲解了Bézier曲线,这个曲线有很多用处,但是也有不少缺点:

  1. 一旦确定了特征多边形的顶点数(n+1个),也就决定了曲线的阶次(n次)
  2. Bézier曲线或曲面的拼接比较复杂
  3. Bezier曲线或曲面不能作局部修改,移动一个控制顶点,整个曲线都会变化(因为每个Bernstein多项式在整个[0,1]区间上都有支撑(函数值不为0),并且曲线是这些函数的混合,所以每个控制项对0到1之间t值处的曲线都有影响。)

B样条曲线保留了Bézier曲线的所有优点,同时克服了缺点。所谓样条,就是分段连续多项式。

整条曲线用一个完整的表达形式,但是内在是一段一段的,比如三次曲线,两条之间满足二次连续。这样不仅克服了波动,并且曲线是低次的。有统一的表达和统一的算法。

比如5个点,通过Bézier是四阶多项式。但是B样条可以构造四段曲线,每一段都是三次的,段与段之间满足二阶几何连续。

python 样条函数形式 python b样条曲线_B样条

python 样条函数形式 python b样条曲线_c++_02

python 样条函数形式 python b样条曲线_图形学_03

python 样条函数形式 python b样条曲线_图形学_04

代码

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "shader.h"
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <vector>
using namespace std;

struct Point{//像素点
    double x,y;
    Point(double _x=0,double _y=0):x(_x),y(_y){};
    Point&operator=(const Point &p){
        x=p.x; y=p.y;
        return *this;
    };
    bool operator ==(const Point &p){
        return (x==p.x&&y==p.y);
    }
}templine[2],LockPoint(-1,-1);//临时线和锁定点
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const unsigned int MAXN=600+1;
const char* vertexShaderPath="/Users/longzhengtian/Desktop/代码/C++代码/ComputerGraphics/shader.vs";
const char* fragmentShaderPath="/Users/longzhengtian/Desktop/代码/C++代码/ComputerGraphics/shader.fs";
vector<Point> ControlPloy;//控制多边形
vector<Point> ControlLine;//控制线
vector<Point> BSplinePoint;//BSpline曲线上的点

bool DrawControlPoly=false,DrawControlPolyEnd=false;
bool change=false;//曲线要发生变化
bool moving=false;

unsigned int VBO[4], VAO[4];
unsigned int k=0;//阶数
int LockPos=-1;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);
void CalcuBspline(double &t,double pre);
float transX(double x){return (float)((float)(2*x)-SCR_WIDTH)/(SCR_WIDTH*1.0f);}
float transY(double y){return (float)(SCR_HEIGHT-2*(float)y)/(SCR_HEIGHT*1.0f);}

int main(){
    //实例化glfw函数
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    //创建glfw窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "B-Spline", nullptr, nullptr);
    if (window == nullptr){
        cout << "Failed to create GLFW window" << endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);//将window设置为接下来操作的主窗口
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetKeyCallback(window, key_callback);
    glfwSetMouseButtonCallback(window, mouse_button_callback);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){//使用glad加载glfw的所有函数指针
        cout << "Failed to initialize GLAD" << endl;
        return -1;
    }

    Shader ourShader(vertexShaderPath,fragmentShaderPath);//创建着色器程序

    GLfloat vertices[MAXN*MAXN];//B样条曲线上的点
    GLfloat lintVertices[7];//临时线
    GLfloat linVertices[MAXN];//现存线
    GLfloat ControlVertices[MAXN*MAXN];//控制线

    glGenVertexArrays(4, VAO);
    glGenBuffers(4, VBO);
    glBindVertexArray(VAO[0]);
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(VAO[1]);//临时边
    glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(lintVertices), lintVertices, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(VAO[2]);//现存边
    glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(linVertices), linVertices, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(VAO[3]);//控制边
    glBindBuffer(GL_ARRAY_BUFFER, VBO[3]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(ControlVertices), ControlVertices, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(0);

    double precision=0.005;
    double t=-precision;//时间控制

    while (!glfwWindowShouldClose(window)){
        //输入
        processInput(window);

        //渲染命令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        //临时边
        ourShader.setVec4("ourColor", 1.0f, 1.0f, 1.0f, 1.0f);
        ourShader.use();
        glBindVertexArray(VAO[1]);
        glDrawArrays(GL_LINES, 0, 2);

        //现存边
        ourShader.setVec4("ourColor", 1.0f, 1.0f, 1.0f, 1.0f);
        ourShader.use();
        glBindVertexArray(VAO[2]);
        glDrawArrays(GL_LINE_STRIP, 0, ControlPloy.size());

        //BSpline Curve
        if (change && k!=0) {
            if(t==-precision){//初次绘画
                ControlLine.clear();
                BSplinePoint.clear();
            }
            CalcuBspline(t, precision);
            vector<float> tempvecV;
            GLfloat *tempvec;

            tempvecV.clear();
            for (int i = 0; i < BSplinePoint.size(); i++) {
                tempvecV.push_back(transX(BSplinePoint[i].x));
                tempvecV.push_back(transY(BSplinePoint[i].y));
                tempvecV.push_back(1.0f);
            }
            tempvec = tempvecV.data();
            glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
            glBufferSubData(GL_ARRAY_BUFFER, 0, tempvecV.size() * sizeof(float), tempvec);
            glBindBuffer(GL_ARRAY_BUFFER, 0);

            if(t<ControlPloy.size()){
                tempvecV.clear();
                for (int i = 0; i < ControlLine.size(); i++) {
                    tempvecV.push_back(transX(ControlLine[i].x));
                    tempvecV.push_back(transY(ControlLine[i].y));
                    tempvecV.push_back(1.0f);
                }
                tempvec = tempvecV.data();
                glBindBuffer(GL_ARRAY_BUFFER, VBO[3]);
                glBufferSubData(GL_ARRAY_BUFFER, 0, tempvecV.size() * sizeof(float), tempvec);
                glBindBuffer(GL_ARRAY_BUFFER, 0);
            }
            else {
                t=-precision;
                change=false;//不写会无限循环
            }
        }
        if(t!=-precision){
            ourShader.setVec4("ourColor", 1.0f, 0.0f, 0.0f, 1.0f);
            ourShader.use();
            glBindVertexArray(VAO[3]);
            glDrawArrays(GL_LINES, 0, ControlLine.size());
        }
        ourShader.setVec4("ourColor", 0.0f, 1.0f, 0.0f, 1.0f);
        ourShader.use();
        glBindVertexArray(VAO[0]);
        glDrawArrays(GL_LINE_STRIP, 0, BSplinePoint.size());

        //检查并调用事件,交换缓冲
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(4, VAO);
    glDeleteBuffers(4, VBO);

    glfwTerminate();
    return 0;
}
void CalcuBspline(double &t,double precision){
    t+=precision;//时间分为1/precison份
    if(t<k-1) t=k-1;//直接跳到合适的区间,[t_{k-1},t_{n+1}]
    int j=t;//直接向下取整获得区间
    vector<vector<Point>> tempConLine;//tempConLine[i][j]是指第i层,第j个点
    vector<Point> tempv;
    for (int i=j-k+1; i<=j; i++){//第0次迭代,将P_{j-k+1},...P_j都加入控制线中
        tempv.push_back(ControlPloy[i]);
    }
    tempConLine.push_back(tempv);
    for (int r=1; r<=k-1; r++){//迭代k-1次
        tempv.clear();
        for (int i=j-k+r+1,ii=1; i<=j,ii<tempConLine[r-1].size(); i++,ii++){
            double alpha=t-i;
            double dev=(k-r);//t_{i+k-r}-t_{i};
            alpha=(dev!=0)?alpha/dev:0;//获得alpha的值
            tempv.emplace_back((1.0-alpha)*tempConLine[r-1][ii-1].x+alpha*tempConLine[r-1][ii].x,
                    (1.0-alpha)*tempConLine[r-1][ii-1].y+alpha*tempConLine[r-1][ii].y);
        }
        tempConLine.push_back(tempv);
    }
    ControlLine.clear();
    for (int i=1; i<=k-1; i++){
        ControlLine.push_back(tempConLine[i][0]);
        for (int l=1; l<tempConLine[i].size(); l++){
            ControlLine.push_back(tempConLine[i][l]);
            ControlLine.push_back(tempConLine[i][l]);
        }
        ControlLine.push_back(tempConLine[i][tempConLine[i].size()-1]);
    }
    BSplinePoint.push_back(tempConLine[k-1][0]);
}
void processInput(GLFWwindow *window){
    if(DrawControlPoly){//此时处于画直线状态
        double xpos,ypos;
        glfwGetCursorPos(window,&xpos,&ypos);
        templine[0]=ControlPloy[ControlPloy.size()-1];
        templine[1]=Point(xpos,ypos);
        float tempvec[]={transX(templine[0].x),transY(templine[0].y),1.0f,
                         transX(templine[1].x),transY(templine[1].y),1.0f
        };
        glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(tempvec), &tempvec);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }
    if(moving){
        double xpos,ypos;
        glfwGetCursorPos(window,&xpos,&ypos);
        float tempvec[]={transX(xpos),transY(ypos),1.0f};
        glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
        glBufferSubData(GL_ARRAY_BUFFER, LockPos*3*sizeof(float), sizeof(tempvec), &tempvec);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height){
    glViewport(0, 0, width, height);
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods){
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    else if (key == GLFW_KEY_P && action == GLFW_PRESS){//如果按下"P",开始画控制多边形,也可以用于增加点
        if(!DrawControlPoly){
            DrawControlPoly=true;
            double xpos,ypos;
            glfwGetCursorPos(window,&xpos,&ypos);
            float tempvec[]={transX(xpos),transY(ypos),1.0f};
            glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
            glBufferSubData(GL_ARRAY_BUFFER, ControlPloy.size()*3*sizeof(float), sizeof(tempvec), &tempvec);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            ControlPloy.emplace_back(xpos,ypos);
        }
        else {//这次是为了结束
            DrawControlPoly=false;
            templine[0]=Point(0,0);
            templine[1]=Point(0,0);
            float tempvec[]={0.0f,0.0f,1.0f,
                             0.0f,0.0f,1.0f
            };
            glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
            glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(tempvec), &tempvec);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            change=true;//发生改变,需要重新计算控制线
        }
    }
    else if (key == GLFW_KEY_M && action == GLFW_PRESS) {//如果按下"M",开始移动
        if(!moving && LockPos!=-1){//本来不移动,结果点击开始移动了
            moving=true;
        }
        else{//移动停止
            cout<<"You moved the "<<LockPos+1<<"th vertex in the control polygon."<<endl;
            moving=false;
            double xpos,ypos;
            glfwGetCursorPos(window,&xpos,&ypos);
            ControlPloy[LockPos]=Point(xpos,ypos);
            cout<<"New ControlPoly:"<<endl;
            for (int i=0; i<ControlPloy.size(); i++)
                cout<<"("<<ControlPloy[i].x<<','<<ControlPloy[i].y<<") ";
            cout<<endl;
            change=true;//发生改变,需要重新计算控制线
        }
    }
    else if (key == GLFW_KEY_D && action == GLFW_PRESS) {//如果按下"D",删除点
        if(LockPos!=-1){//此时刚刚锁定了一个点,可以删除了
            int tempsize=(ControlPloy.size()-LockPos)*3,tempcnt=0;
            float tempvec[tempsize];
            memset(tempvec,0,sizeof(tempvec));
            for (int i=LockPos+1; i<ControlPloy.size(); i++){
                tempvec[tempcnt++]=transX(ControlPloy[i].x);
                tempvec[tempcnt++]=transY(ControlPloy[i].y);
                tempvec[tempcnt++]=1.0f;
            }
            //控制多边形数组中删除
            auto it=find(ControlPloy.begin(),ControlPloy.end(),LockPoint);
            ControlPloy.erase(it);
            //缓冲区中删除
            glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
            glBufferSubData(GL_ARRAY_BUFFER, LockPos*3*sizeof(float), sizeof(tempvec), &tempvec);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            cout<<"You deleted the "<<LockPos+1<<"th vertex in the control polygon."<<endl;
            cout<<"New ControlPoly:"<<endl;
            for (int i=0; i<ControlPloy.size(); i++)
                cout<<"("<<ControlPloy[i].x<<','<<ControlPloy[i].y<<") ";
            cout<<endl;
            //解除锁定
            LockPoint=Point(-1,-1); LockPos=-1;
            change=true;//发生改变,需要重新计算控制线
            DrawControlPolyEnd=true;
        }
    }
    else if (key == GLFW_KEY_K && action == GLFW_PRESS) {//如果按下"K",改变阶数
        cout<<"Please enter the order of the B-spline:"<<endl;
        cin>>k;//输入阶数。我们规定,节点表从0开始,逐次+1。t向下取整可以直接确定区间。
        change=true;
    }
}
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods){
    if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS){//鼠标点击了左键
        double xpos,ypos;
        glfwGetCursorPos(window,&xpos,&ypos);
        if(DrawControlPoly){//此时是画线状态,需要增加线
            float tempvec[]={transX(xpos),transY(ypos),1.0f};
            glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
            glBufferSubData(GL_ARRAY_BUFFER, ControlPloy.size()*3*sizeof(float), sizeof(tempvec), &tempvec);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            ControlPloy.emplace_back(xpos,ypos);
        }
        else if(!moving){//此时锁定点,看情况是否删除
            LockPoint=Point(-1,-1); LockPos=-1;//初始化
            for (int i=0; i<ControlPloy.size(); i++){
                if(fabs(xpos-ControlPloy[i].x)<=5 && fabs(ypos-ControlPloy[i].y)<=5){//给定一个误差范围
                    LockPoint=ControlPloy[i]; LockPos=i;
                    cout<<"You locked the "<<LockPos+1<<"th vertex in the control polygon."<<endl;
                    break;
                }
            }
        }
    }
}