文章目录
- 说明
- B样条曲线
- 代码
说明
B样条曲线
前面讲解了Bézier曲线,这个曲线有很多用处,但是也有不少缺点:
- 一旦确定了特征多边形的顶点数(n+1个),也就决定了曲线的阶次(n次)
- Bézier曲线或曲面的拼接比较复杂
- Bezier曲线或曲面不能作局部修改,移动一个控制顶点,整个曲线都会变化(因为每个Bernstein多项式在整个[0,1]区间上都有支撑(函数值不为0),并且曲线是这些函数的混合,所以每个控制项对0到1之间t值处的曲线都有影响。)
B样条曲线保留了Bézier曲线的所有优点,同时克服了缺点。所谓样条,就是分段连续多项式。
整条曲线用一个完整的表达形式,但是内在是一段一段的,比如三次曲线,两条之间满足二次连续。这样不仅克服了波动,并且曲线是低次的。有统一的表达和统一的算法。
比如5个点,通过Bézier是四阶多项式。但是B样条可以构造四段曲线,每一段都是三次的,段与段之间满足二阶几何连续。
代码
#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;
}
}
}
}
}