OpenGL+GLFW开发井字棋:点击绘制的圆圈松开鼠标后消失
OpenGL井字棋:点击绘制圆圈消失问题修复
问题根源
- 临时数据被清空:主循环中
flag1为true时,绘制完圆圈就执行vertices.clear()和indices.clear(),导致后续帧无数据可绘制。 - VAO/VBO重复覆盖:每次点击调用
buildCircle都会重新生成VAO、VBO、EBO,覆盖之前的缓冲区,没保留历史圆圈数据。 - 绘制逻辑受限:仅
flag1为true时才调用glDrawElements,后续帧不再绘制已创建的圆圈。 - 线条资源重复创建:主循环每帧都新建line对象,既浪费内存又影响性能。
修复方案
1. 保存所有圆圈的绘制数据
定义容器存储每个圆圈的VAO、EBO及索引数量,保留历史绘制信息:
#include <vector> #include <glm/glm.hpp> struct CircleData { unsigned int vao; unsigned int ebo; int indexCount; }; std::vector<CircleData> circles;
2. 修改buildCircle函数,独立生成每个圆圈的缓冲区
移除全局VAO/VBO生成逻辑,为每个圆圈创建独立缓冲区并加入容器:
void buildCircle(float radius, int vCount, float pointX, float pointY) { float angle = 360.0f / vCount; int triangleCount = vCount - 2; std::vector<glm::vec3> tempVertices; std::vector<unsigned int> tempIndices; // 生成当前圆圈顶点 for (int i = 0; i < vCount; i++) { float currentAngle = angle * i; float x = pointX + radius * cos(glm::radians(currentAngle)); float y = pointY + radius * sin(glm::radians(currentAngle)); tempVertices.emplace_back(x, y, 0.0f); } // 生成当前圆圈索引 for (int i = 0; i < triangleCount; i++) { tempIndices.push_back(0); tempIndices.push_back(i + 1); tempIndices.push_back(i + 2); } // 创建当前圆圈的VAO/VBO/EBO unsigned int vao, vbo, ebo; glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); glGenBuffers(1, &ebo); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * tempVertices.size(), tempVertices.data(), GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * tempIndices.size(), tempIndices.data(), GL_STATIC_DRAW); // 存入容器 circles.push_back({vao, ebo, (int)tempIndices.size()}); // 解绑缓冲区 glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); }
3. 简化鼠标回调函数,移除flag1依赖
点击后直接生成圆圈数据,后续帧统一绘制所有圆圈:
void mouse_button(GLFWwindow* window, int button, int action, int mods) { if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { double xpos, ypos; glfwGetCursorPos(window, &xpos, &ypos); int width, height; glfwGetWindowSize(window, &width, &height); float x = (xpos / width) * 2 - 1; float y = (1 - ypos / height) * 2 - 1; buildCircle(0.1, 15, x, y); } }
4. 重构主循环,统一绘制所有元素
- 提前创建井字棋线条,避免每帧重复创建
- 修复
glClearColor参数(OpenGL颜色值范围为0.0-1.0) - 每帧绘制所有已保存的圆圈
line* line7 = nullptr; line* line8 = nullptr; line* line9 = nullptr; line* line10 = nullptr; int main(int argc, char** argv) { // GLFW、窗口、GLAD初始化代码保持不变 // 仅创建一次井字棋线条 line7 = line_init(vec2_init(0.7, 0.4), vec2_init(-0.7, 0.4)); line8 = line_init(vec2_init(0.3, 0.8), vec2_init(0.3, -0.8)); line9 = line_init(vec2_init(-0.3, 0.8), vec2_init(-0.3, -0.8)); line10 = line_init(vec2_init(-0.7, -0.4), vec2_init(0.7, -0.4)); // 着色器编译链接代码保持不变 while (!glfwWindowShouldClose(window)) { glfwPollEvents(); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // 绘制井字棋线条 line_draw(line7); line_draw(line8); line_draw(line9); line_draw(line10); // 绘制所有已创建的圆圈 glUseProgram(shaderProg); for (const auto& circle : circles) { glBindVertexArray(circle.vao); glDrawElements(GL_TRIANGLES, circle.indexCount, GL_UNSIGNED_INT, 0); } glfwSwapBuffers(window); } // 释放资源 for (const auto& circle : circles) { glDeleteVertexArrays(1, &circle.vao); glDeleteBuffers(1, &circle.ebo); } // 补充线条销毁函数后释放线条资源 glfwTerminate(); return 0; }
5. 补充线条资源释放函数(避免内存泄漏)
void line_destroy(line* l) { glDeleteVertexArrays(1, &l->VAO); glDeleteBuffers(1, &l->VBO); glDeleteProgram(l->shaderProgram); free(l->vertices); free(l->startPoint); free(l->endPoint); free(l->lineColor); free(l); }
额外优化建议
- 复用着色器程序:线条和圆圈共用同一着色器,减少资源占用
- 坐标对齐:将鼠标点击坐标对齐到井字棋格子中心
- 落子判断:防止同一格子重复绘制圆圈
内容的提问来源于stack exchange,提问作者Bang
相关产品推荐
相关产品推荐

