You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenGL+GLFW开发井字棋:点击绘制的圆圈松开鼠标后消失

OpenGL井字棋:点击绘制圆圈消失问题修复

问题根源

  1. 临时数据被清空:主循环中flag1为true时,绘制完圆圈就执行vertices.clear()和indices.clear(),导致后续帧无数据可绘制。
  2. VAO/VBO重复覆盖:每次点击调用buildCircle都会重新生成VAO、VBO、EBO,覆盖之前的缓冲区,没保留历史圆圈数据。
  3. 绘制逻辑受限:仅flag1为true时才调用glDrawElements,后续帧不再绘制已创建的圆圈。
  4. 线条资源重复创建:主循环每帧都新建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 01:27:00