希望能给使用同样环境的朋友作参考(比如跟着learnopengl一书学下来的)
先上效果
实现了鼠标拖动顶点,添加新点后鼠标右键刷新曲线,backspace清空
下面是代码参考
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "shader.h"
#include "bezier.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
using namespace std;
// 窗口大小变化回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
// 鼠标移动回调函数
void mouse_pos_callback(GLFWwindow* window, double posX, double posY);
// 鼠标按键回调函数
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);
// 键盘按键回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
// 窗口大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// 最大顶点数和默认步长
const unsigned int MAX_VERTEX_COUNT = 16;
const float DEFAULT_DELTA = 0.02f;
Bezier bezierInstance; // 贝塞尔曲线实例
float vertexes[200] = {}; // 顶点数组
bool first_mouse = true;
float lastX = 800.0f / 2;
float lastY = 600.0f / 2;
void renderQuad();
int main()
{
// 初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_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);
glfwSetCursorPosCallback(window, mouse_pos_callback);
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetKeyCallback(window, key_callback);
// 加载OpenGL函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 编译着色器程序
Shader simpleShader("simple.vert", "simple.frag");
float controlPoints[MAX_VERTEX_COUNT * 2] = {};
// 生成控制点的顶点缓冲对象和顶点数组对象
unsigned int controlPointVBO, controlPointVAO;
glGenVertexArrays(1, &controlPointVAO);
glGenBuffers(1, &controlPointVBO);
glBindVertexArray(controlPointVAO);
glBindBuffer(GL_ARRAY_BUFFER, controlPointVBO);
glEnableVertexAttribArray(0);
unsigned int vertexsVBO, vertexsVAO;
glGenVertexArrays(1, &vertexsVAO);
glGenBuffers(1, &vertexsVBO);
glBindVertexArray(vertexsVAO);
glBindBuffer(GL_ARRAY_BUFFER, vertexsVBO);
glEnableVertexAttribArray(0);
// 设置正交投影矩阵
glm::mat4 projection = glm::ortho(0.0f, (float)SCR_WIDTH, 0.0f, (float)SCR_HEIGHT, -1.0f, 1.0f);
glm::mat4 model = glm::mat4(1.0f);
glm::vec4 color = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
simpleShader.use();
simpleShader.setMat4("projection", projection);
glBindVertexArray(0);
while (!glfwWindowShouldClose(window)) {
// 清空颜色缓冲区和深度缓冲区
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
simpleShader.use();
simpleShader.setVec4("color", color);
int controlPointCount = bezierInstance.GetControlPointCount();
// 绘制控制点
for (int i = 0; i < controlPointCount; i++) {
float vertex[2];
bezierInstance.GetControlPoints(i, vertex);
controlPoints[i * 2] = vertex[0];
controlPoints[i * 2 + 1] = vertex[1];
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(vertex[0], vertex[1], 0.0f));
model = glm::scale(model, glm::vec3(4.0f, 4.0f, 1.f));
simpleShader.setMat4("model", model);
renderQuad();
}
// 绘制控制点之间的连线
model = glm::mat4(1.0f);
simpleShader.setMat4("model", model);
simpleShader.setVec4("color", glm::vec4(0.7f, 1.0f, 1.0f, 1.0f));
glBindVertexArray(controlPointVAO);
glBindBuffer(GL_ARRAY_BUFFER, controlPointVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(controlPoints), controlPoints, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
glDrawArrays(GL_LINE_STRIP, 0, controlPointCount);
// 绘制贝塞尔曲线
simpleShader.setVec4("color", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
if (bezierInstance.GetControlPointCount() >= 3) {
glBindVertexArray(vertexsVAO);
glBindBuffer(GL_ARRAY_BUFFER, vertexsVBO);
//传输顶点数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
glDrawArrays(GL_LINE_STRIP, 0, 50);
}
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &controlPointVAO);
glDeleteBuffers(1, &controlPointVBO);
glfwTerminate();
return 0;
}
// 窗口大小变化回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
// 鼠标移动回调函数
void mouse_pos_callback(GLFWwindow* window, double posX, double posY)
{
if (first_mouse) {
lastX = posX;
lastY = posY;
first_mouse = false;
}
float offestX = posX - lastX;
float offestY = lastY - posY;
lastX = posX;
lastY = posY;
posY = SCR_HEIGHT - posY;
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) != GLFW_PRESS) {
return;
}
int index = bezierInstance.CursorOnControlPoint(posX, posY, 4.0f);
if (index != -1) {
bezierInstance.SetControlPoint(index, posX, posY);
bezierInstance.bezierDeCasteljau(DEFAULT_DELTA, vertexes);
}
}
// 鼠标按键回调函数
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
double posX, posY;
glfwGetCursorPos(window, &posX, &posY);
if (first_mouse) {
lastX = posX;
lastY = posY;
first_mouse = false;
}
float offestX = posX - lastX;
float offestY = lastY - posY;
lastX = posX;
lastY = posY;
posY = SCR_HEIGHT - posY;
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
if (bezierInstance.CursorOnControlPoint(posX, posY, 4.0f) == -1) {
bezierInstance.AddControlPoint(posX, posY);
}
}
else if (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS) {
bezierInstance.bezierDeCasteljau(DEFAULT_DELTA, vertexes);
}
}
// 键盘按键回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_BACKSPACE && action == GLFW_PRESS) {
bezierInstance.ClearControlPoints(vertexes, 200);
}
}
unsigned int quadVAO = 0;
unsigned int quadVBO = 0;
// 渲染四边形
void renderQuad()
{
float quadVertices[] = {
// positions
-1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
1.0f, -1.0f, 0.0f
};
if (quadVAO == 0)
{
// 设置四边形的顶点数组对象和顶点缓冲对象
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
}
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
#pragma once
#include<iostream>
using namespace std;
class Bezier
{
private:
int controlPointCount = 0; // 控制点数量
float controlPoints[16][2]; // 控制点数组,最多16个点
public:
// 构造函数
Bezier() {
float tmp[1] = { 0 }; // 为了调用ClearControlPoints初始化顶点数组
this->ClearControlPoints(tmp, 0); // 清空控制点
}
// 添加控制点
void AddControlPoint(float x, float y) {
if (controlPointCount >= 16) { // 控制点数量已满,无法添加
return;
}
for (int i = 0; i < 16; i++) {
if (controlPoints[i][0] == -1 && controlPoints[i][1] == -1) {
controlPoints[i][0] = x;
controlPoints[i][1] = y;
break;
}
}
controlPointCount++; // 控制点数量加一
}
// 设置控制点的位置
void SetControlPoint(int i, float x, float y) {
if (i >= 16 || i < 0) { // 索引超出范围
return;
}
controlPoints[i][0] = x;
controlPoints[i][1] = y;
}
// 清空控制点
void ClearControlPoints(float* vertexes, int count) {
for (int i = 0; i < 16; i++) {
controlPoints[i][0] = -1;
controlPoints[i][1] = -1;
}
// 清空顶点数组(使用VBO)
for (int i = 0; i < count; i++) {
vertexes[i] = 0;
}
controlPointCount = 0; // 控制点数量清零
}
// 计算贝塞尔曲线的顶点,返回顶点数量
int bezierDefinition(float delta, float* vertexes) {
if (controlPointCount <= 2) { // 控制点数量小于等于2,无法绘制贝塞尔曲线
return 0;
}
float vertexesCount = 1 / delta;
for (float t = 0; t <= 1; t += delta) {
float x = 0;
float y = 0;
for (int i = 0; i < controlPointCount; i++) {
x += controlPoints[i][0] * C(controlPointCount - 1, i) * pow(1 - t, controlPointCount - 1 - i) * pow(t, i);
y += controlPoints[i][1] * C(controlPointCount - 1, i) * pow(1 - t, controlPointCount - 1 - i) * pow(t, i);
}
*vertexes++ = x;
*vertexes++ = y;
}
return vertexesCount;
}
// 使用De Casteljau算法计算贝塞尔曲线的顶点,返回顶点数量
int bezierDeCasteljau(float delta, float* vertexes) {
if (controlPointCount <= 2) { // 控制点数量小于等于2,无法绘制贝塞尔曲线
return 0;
}
float vertexesCount = 1 / delta;
for (float t = 0; t <= 1; t += delta) {
float x = 0;
float y = 0;
float tmp[16][2];
for (int i = 0; i < controlPointCount; i++) {
tmp[i][0] = controlPoints[i][0];
tmp[i][1] = controlPoints[i][1];
}
for (int i = 0; i < controlPointCount - 1; i++) {
for (int j = 0; j < controlPointCount - 1 - i; j++) {
// 相邻点的线性插值
tmp[j][0] = tmp[j][0] * (1 - t) + tmp[j + 1][0] * t;
tmp[j][1] = tmp[j][1] * (1 - t) + tmp[j + 1][1] * t;
}
}
*vertexes++ = tmp[0][0];
*vertexes++ = tmp[0][1];
}
return vertexesCount;
}
// 获取控制点的位置
void GetControlPoints(int i, float vertex[2]) {
if (i >= 16 || i < 0) { // 索引超出范围
return;
}
vertex[0] = controlPoints[i][0];
vertex[1] = controlPoints[i][1];
}
// 获取控制点数量
int GetControlPointCount() {
return controlPointCount;
}
// 检查鼠标是否在控制点上,如果是返回控制点的索引,否则返回-1
int CursorOnControlPoint(float x, float y, float halfL) {
for (int i = 0; i < controlPointCount; i++) {
if (controlPoints[i][0] - halfL <= x && x <= controlPoints[i][0] + halfL && controlPoints[i][1] - halfL <= y && y <= controlPoints[i][1] + halfL)
return i;
}
return -1;
}
// 计算组合数C(m, n)
int C(int m, int n) {
if (m < n) {
return 0;
}
if (m == n) {
return 1;
}
if (n == 0) {
return 1;
}
return C(m - 1, n - 1) + C(m - 1, n);
}
};
顶点着色器和片元着色器main函数各只有一行
gl_Position = projection * model * vec4(aPos, 0.0, 1.0);
frag_color = vec4(1.0f, 1.0f, 1.0f, 1.0f) * color;