计算机图形学作业( 七):利用 OpenGL 绘制 Bezier 贝塞尔曲线

Bezier 曲线原理

Bezier 曲线的原理我参考了这篇博客:https://www.cnblogs.com/hyb1/p/3875468.html

Bezier 曲线是应用于二维图形的曲线。曲线由顶点和控制点组成,通过改变控制点坐标可以改变曲线的形状。

一次 Bezier 曲线公式:

一次 Bezier 曲线是由 P0 至 P1 的连续点,描述的一条线段。

二次 Bezier 曲线公式:

二次 Bezier 曲线是 P0 至 P1 的连续点 Q0 和 P1 至 P2 的连续点 Q1 组成的线段上的连续点 B(t),描述一条抛物线。

三次 Bezier 曲线公式:

由此可得 Bezier 曲线的一般方程为:

OpenGL 实现思路

在 OpenGL 窗口中,我们希望能通过左键点击窗口添加 Bezier 曲线的控制点,右键点击则对当前添加的最后一个控制点进行消除。然后根据鼠标绘制的控制点实时更新 Bezier 曲线。

捕获鼠标点击时的坐标

我们需要用一个回调函数,该函数是在鼠标移动时不断获取鼠标在窗口的坐标。

首先我们要声明全局的鼠标位置变量,代码如下:

1
float mouseXPos, mouseYPos;

然后在鼠标事件中不断更新全局位置变量的值。代码如下

1
2
3
4
5
void cursor_position_callback(GLFWwindow* window, double x, double y) { 
	mouseXPos = float((x - WINDOW_WIDTH / 2) / WINDOW_WIDTH) * 2;
	mouseYPos = float(0 - (y - WINDOW_HEIGHT / 2) / WINDOW_HEIGHT) * 2;
	return; 
}

根据顶点画出连续的线段

前面我们获取的鼠标的当前位置,那么当鼠标点击左键时,我们要捕获该点击事件,将顶点数据添加到 lineVertices 并通过绑定 VAO 画出线段。

先声明全局的顶点数据变量:

1
2
3
4
5
6
// 声明全局顶点变量, 点个数为 vertexLen / 3
int lineVertexLen = 0;
float lineVertices[MAX_VERTEX_LEN] = {
	//位置
	//-0.5f, 0.5f, 0.0f
};

然后再鼠标点击事件中操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) { 
	if (action == GLFW_PRESS) switch (button) { 
		case GLFW_MOUSE_BUTTON_LEFT:			
			// 每隔两个点画一条直线
			// 鼠标点击的点
			lineVertices[lineVertexLen] = mouseXPos;
			lineVertexLen++;
			lineVertices[lineVertexLen] = mouseYPos;
			lineVertexLen++;
			lineVertices[lineVertexLen] = 0.0f;
			lineVertexLen++;
			// 添加索引,前一个点也新的点一起确定新线段
			if (lineIndicesLen >= 2) {
				lineIndices[lineIndicesLen] = lineIndices[lineIndicesLen - 1];
				lineIndicesLen++;
				lineIndices[lineIndicesLen] = lineIndices[lineIndicesLen - 1] + 1;
				lineIndicesLen++;
			}
			else {
				lineIndices[lineIndicesLen] = lineIndicesLen;
				lineIndicesLen++;
			}
			break;
		default:				
			break;
	}	
	return; 
}

之后便是通过 VAO、VBO 和 GL_LINES 等画出线段。

根据顶点画出 Bezier 贝塞尔曲线

根据之前的 Bezier 曲线一般式,我们能很容易地根据顶点计算出 Bezier 曲线的点数据。只要新声明一个函数,传入顶点的数据和长度,就能计算出各个位置上的 Bezier 曲线的点数据,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int getBezierVertex(float lineVertices[MAX_VERTEX_LEN], int lineVertexLen, float bezierVertices[MAX_BEZIER_VERTEX_LEN]) {
	int bezierVertexLen = 0;
	if (lineVertexLen == 0) return bezierVertexLen;
	else if (lineVertexLen == 3) {
		bezierVertices[bezierVertexLen] = lineVertices[0];
		bezierVertexLen++;
		bezierVertices[bezierVertexLen] = lineVertices[1];
		bezierVertexLen++;
		bezierVertices[bezierVertexLen] = lineVertices[2];
		bezierVertexLen++;
	}
	else {
		for (float t = 0.000f; t <= 1.000f; t = t + 0.001f) {
			double new_xPos = 0, new_yPos = 0;
			for (int index = 0; index < lineVertexLen / 3; index++) {
				// x坐标
				new_xPos += lineVertices[index * 3] * pow(t, index) * pow((1 - t), (lineVertexLen / 3 - 1 - index));
				// y坐标
				new_yPos += lineVertices[index * 3 + 1] * pow(t, index) * pow((1 - t), (lineVertexLen / 3 - 1 - index));
			}
			bezierVertices[bezierVertexLen] = new_xPos;
			bezierVertexLen++;
			bezierVertices[bezierVertexLen] = new_yPos;
			bezierVertexLen++;
			bezierVertices[bezierVertexLen] = 0.0f;
			bezierVertexLen++;
		}
	}
	return bezierVertexLen;
}

之后便可以通过 VAO、VBO 和 GL_POINTS 等画出 Bezier 曲线。

效果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#include <iostream>
#include <cmath>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"

#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <gtc/type_ptr.hpp>

// 加载纹理的库
#include "stb_image.h"

//使用了官方的shader库文件,可以更方便地操作shader
//注意,使用官方库操作shadder时,自己写的顶点和片段着色器代码必须另外写成文件
//然后把文件路径传入shader.h提供的构造函数
#include "shader.h"  

using namespace std;

const int WINDOW_WIDTH = 1000;
const int WINDOW_HEIGHT = 800;
const int MAX_VERTEX_LEN = 300;
const int MAX_INDICES_LEN = 600;
const int MAX_BEZIER_VERTEX_LEN = 3000;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);
void cursor_position_callback(GLFWwindow* window, double x, double y);
int getBezierVertex(float lineVertices[MAX_VERTEX_LEN],int lineVertexLen, float bezierVertices[MAX_BEZIER_VERTEX_LEN]);

// 声明鼠标全局位置变量
float mouseXPos, mouseYPos;

// 声明全局顶点变量, 点个数为 vertexLen / 3
int lineVertexLen = 0;
float lineVertices[MAX_VERTEX_LEN] = {
	//位置
	//-0.5f, 0.5f, 0.0f,
	//0.5f, 0.5f, 0.0f,
	//0.5f, -0.5f, 0.0f
};
int lineIndicesLen = 0;
unsigned int lineIndices[MAX_INDICES_LEN] = { // 注意索引从0开始! 
	//0, 1, // 第一个线段
	//1, 2
};


int main() {
	//初始化opengl窗口和配置
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	
	GLFWwindow* window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "LearnOpenGL", NULL, NULL);
	if (window == NULL) {
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	glfwSetMouseButtonCallback(window, mouse_button_callback);
	glfwSetCursorPosCallback(window, cursor_position_callback);

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	// 深度测试
	glEnable(GL_DEPTH_TEST);

	//创建并绑定ImGui
	const char* glsl_version = "#version 130";
	IMGUI_CHECKVERSION();
	ImGui::CreateContext();
	ImGuiIO& io = ImGui::GetIO(); (void)io;
	ImGui::StyleColorsDark();
	ImGui_ImplGlfw_InitForOpenGL(window, true);
	ImGui_ImplOpenGL3_Init(glsl_version);

	Shader lineShader("vertexShader.vs", "lineFragmentShader.fs");
	Shader bezierShader("vertexShader.vs", "bezierFragmentShader.fs");

	bool show_window = true;

	while (!glfwWindowShouldClose(window)) {
		processInput(window);

		//清除屏幕
		glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		unsigned int VAO;
		unsigned int VBO;
		unsigned int EBO;

		//必须先绑定VA0
		glGenVertexArrays(1, &VAO);
		glBindVertexArray(VAO);

		//再绑定VBO
		glGenBuffers(1, &VBO);
		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(lineVertices), lineVertices, GL_STATIC_DRAW);

		//使用EBO画多个线段,组合成其它图形
		glGenBuffers(1, &EBO);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(lineIndices), lineIndices, GL_STATIC_DRAW);

		//再设置属性
		//位置属性
		//属性位置值为0的顶点属性
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);

		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glEnableVertexAttribArray(1);

		// 使用着色器程序
		lineShader.use();

		//画线段,
		glBindVertexArray(VAO);
		// 因为通过 indices 确定线段,每两点确定一线段,所以画多少线段,就填入线段数乘以2
		if (lineIndicesLen >= 2) {
			glDrawElements(GL_LINES, lineIndicesLen, GL_UNSIGNED_INT, 0);
		}


		// 以下是 bezier 点数据
		// 声明 bezier 顶点变量 
		int bezierVertexLen = 0;
		float bezierVertices[MAX_BEZIER_VERTEX_LEN] = {
			//-0.5f, 0.5f, 0.0f,
		};

		bezierVertexLen = getBezierVertex(lineVertices, lineVertexLen, bezierVertices);

		unsigned int bezierVAO;
		unsigned int bezierVBO;

		//必须先绑定VA0
		glGenVertexArrays(1, &bezierVAO);
		glBindVertexArray(bezierVAO);

		//再绑定VBO
		glGenBuffers(1, &bezierVBO);
		glBindBuffer(GL_ARRAY_BUFFER, bezierVBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(bezierVertices), bezierVertices, GL_STATIC_DRAW);

		//再设置属性
		//位置属性
		//属性位置值为0的顶点属性
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);

		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glEnableVertexAttribArray(1);

		// 使用着色器程序
		bezierShader.use();

		//画线段,
		glBindVertexArray(bezierVAO);
		glDrawArrays(GL_POINTS, 0, bezierVertexLen / 3);

		//创建imgui
		ImGui_ImplOpenGL3_NewFrame();
		ImGui_ImplGlfw_NewFrame();
		ImGui::NewFrame();

		ImGui::Begin("Edit cube", &show_window, ImGuiWindowFlags_MenuBar);


		ImGui::End();

		ImGui::Render();
		int display_w, display_h;
		glfwMakeContextCurrent(window);
		glfwGetFramebufferSize(window, &display_w, &display_h);
		glViewport(0, 0, display_w, display_h);
		ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

		glfwPollEvents();
		glfwSwapBuffers(window);
	}

	glfwTerminate();

	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window) {
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
		glfwSetWindowShouldClose(window, true);
	}
}

void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) { 
	if (action == GLFW_PRESS) switch (button) { 
		case GLFW_MOUSE_BUTTON_LEFT:			
			// 每隔两个点画一条直线
			// 鼠标点击的点
			lineVertices[lineVertexLen] = mouseXPos;
			lineVertexLen++;
			lineVertices[lineVertexLen] = mouseYPos;
			lineVertexLen++;
			lineVertices[lineVertexLen] = 0.0f;
			lineVertexLen++;
			// 添加索引,前一个点也新的点一起确定新线段
			if (lineIndicesLen >= 2) {
				lineIndices[lineIndicesLen] = lineIndices[lineIndicesLen - 1];
				lineIndicesLen++;
				lineIndices[lineIndicesLen] = lineIndices[lineIndicesLen - 1] + 1;
				lineIndicesLen++;
			}
			else {
				lineIndices[lineIndicesLen] = lineIndicesLen;
				lineIndicesLen++;
			}
			break;
		case GLFW_MOUSE_BUTTON_RIGHT:
			if (lineVertexLen >= 3) {
				lineVertexLen = lineVertexLen - 3;
			}

			if (lineIndicesLen >= 4) {
				lineIndicesLen = lineIndicesLen - 2;
			}
			else {
				lineIndicesLen = lineIndicesLen - 1;
			}

			break;
		default:				
			break;
	}	
	return; 
}

void cursor_position_callback(GLFWwindow* window, double x, double y) { 
	mouseXPos = float((x - WINDOW_WIDTH / 2) / WINDOW_WIDTH) * 2;
	mouseYPos = float(0 - (y - WINDOW_HEIGHT / 2) / WINDOW_HEIGHT) * 2;
	return; 
}

int getBezierVertex(float lineVertices[MAX_VERTEX_LEN], int lineVertexLen, float bezierVertices[MAX_BEZIER_VERTEX_LEN]) {
	int bezierVertexLen = 0;
	if (lineVertexLen == 0) return bezierVertexLen;
	else if (lineVertexLen == 3) {
		bezierVertices[bezierVertexLen] = lineVertices[0];
		bezierVertexLen++;
		bezierVertices[bezierVertexLen] = lineVertices[1];
		bezierVertexLen++;
		bezierVertices[bezierVertexLen] = lineVertices[2];
		bezierVertexLen++;
	}
	else {
		for (float t = 0.000f; t <= 1.000f; t = t + 0.001f) {
			double new_xPos = 0, new_yPos = 0;
			for (int index = 0; index < lineVertexLen / 3; index++) {
				// x坐标
				new_xPos += lineVertices[index * 3] * pow(t, index) * pow((1 - t), (lineVertexLen / 3 - 1 - index));
				// y坐标
				new_yPos += lineVertices[index * 3 + 1] * pow(t, index) * pow((1 - t), (lineVertexLen / 3 - 1 - index));
			}
			bezierVertices[bezierVertexLen] = new_xPos;
			bezierVertexLen++;
			bezierVertices[bezierVertexLen] = new_yPos;
			bezierVertexLen++;
			bezierVertices[bezierVertexLen] = 0.0f;
			bezierVertexLen++;
		}
	}
	return bezierVertexLen;
}

坚持原创技术分享,您的支持将鼓励我继续创作!