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

调用glDrawArrays触发访问违例 Chunk存入std::list渲染报错

问题根因

崩溃由两个核心问题叠加导致,栈上单独实例化时两个问题都未触发,放入std::list批量构造时就会触发访问违例:

  • OpenGL VBO写入错误(直接崩溃原因):OpenGL中VAO仅在调用glVertexAttribPointer时记录当前关联的VBO,绑定VAO本身不会自动把关联的VBO绑定到GL_ARRAY_BUFFER全局靶点。单独实例化Chunk时是构造一个、立刻更新一个VBO,构造完ch1时GL_ARRAY_BUFFER还绑定着ch1的VBO,调用updateVBO时数据能正确写入;构造ch2时才会把GL_ARRAY_BUFFER绑定切换为ch2的VBO,之后updateVBO也能正确写入。但放入std::list时是先批量构造完所有Chunk,最后一个构造的Chunk会把GL_ARRAY_BUFFER绑定为自己的VBO,之后循环调用前序Chunk的updateVBO时,仅绑定了Chunk的VAO,没有绑定对应VBO,导致glBufferData把所有Chunk的顶点数据都写到了最后一个Chunk的VBO里,前序Chunk的VBO从未写入数据,绘制时读取空VBO就触发访问违例。
  • 违反Rule of Five(内存隐患):Chunk类持有裸指针管理的堆内存blocks、OpenGL的VAO/VBO资源,但是只实现了析构函数,没有自定义拷贝构造、拷贝赋值、移动构造、移动赋值逻辑。编译器生成的默认拷贝/移动是浅拷贝,会导致多个Chunk对象指向同一块blocks内存,对象析构时会触发重复释放,属于未定义行为,后续只要发生Chunk拷贝就会崩溃。

另外还有两个次要问题:

  • fillVertices中判断空气块的索引计算错误,x * CHUNK_WIDTH应为x * CHUNK_DEPTH,当前因为宽深都是16所以没触发越界,修改区块尺寸后会出问题
  • 析构函数没有释放VAO/VBO,会造成OpenGL资源泄漏
修复方案
  1. 修正updateVBO逻辑,每次更新数据前显式绑定当前Chunk的VBO
  2. 禁用Chunk的拷贝语义(区块对象不需要拷贝),正确实现移动语义,直接用std::vector<Block>代替裸指针管理blocks数组,自动处理内存
  3. 修正块索引计算错误
  4. 析构函数中添加VAO/VBO的释放逻辑
修复后核心代码
struct Chunk {
    
    // 用vector代替裸指针自动管理blocks内存,不需要手动new/delete
    std::vector<Block> blocks;
    unsigned int VAO = 0;
    unsigned int VBO = 0;
    float chunkPosX = 0, chunkPosY = 0, chunkPosZ = 0;
    std::vector<Vertex> vertices;
    int chunkPosUniformLoc = 0;

    // 禁用拷贝构造和拷贝赋值,避免浅拷贝问题
    Chunk(const Chunk&) = delete;
    Chunk& operator=(const Chunk&) = delete;

    // 实现移动构造和移动赋值,支持存入STL容器
    Chunk(Chunk&& other) noexcept {
        *this = std::move(other);
    }
    Chunk& operator=(Chunk&& other) noexcept {
        if (this != &other) {
            // 释放当前持有的OpenGL资源
            if (VAO) glDeleteVertexArrays(1, &VAO);
            if (VBO) glDeleteBuffers(1, &VBO);
            // 转移资源所有权
            blocks = std::move(other.blocks);
            VAO = other.VAO;
            VBO = other.VBO;
            chunkPosX = other.chunkPosX;
            chunkPosY = other.chunkPosY;
            chunkPosZ = other.chunkPosZ;
            vertices = std::move(other.vertices);
            chunkPosUniformLoc = other.chunkPosUniformLoc;
            // 源对象OpenGL ID置0,避免析构时重复释放
            other.VAO = 0;
            other.VBO = 0;
        }
        return *this;
    }

    Chunk(float x = 0.0f, float y = 0.0f, float z = 0.0f) : chunkPosX(x), chunkPosY(y), chunkPosZ(z) {
        blocks.resize(CHUNK_WIDTH * CHUNK_DEPTH * CHUNK_HEIGHT);
        
        glGenVertexArrays(1, &VAO);
        glBindVertexArray(VAO);

        glGenBuffers(1, &VBO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glUseProgram(sp);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(3 * sizeof(float)));
        glEnableVertexAttribArray(1);

        chunkPosUniformLoc = glGetUniformLocation(sp, "chunkPos");
        // 构造完解绑VAO/VBO,避免全局状态污染
        glBindVertexArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    void updateVBO() {
        if (!vertices.empty()) {
            glBindVertexArray(VAO);
            // 显式绑定当前Chunk的VBO,保证数据写入正确位置
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_DYNAMIC_DRAW);
            glBindVertexArray(0);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
        }
    }

    void fillVertices() {
        vertices.clear();

        for (int y = 0; y < CHUNK_HEIGHT; y++) {
            for (int x = 0; x < CHUNK_WIDTH; x++) {
                for (int z = 0; z < CHUNK_DEPTH; z++) {
                    // 提前计算块索引,避免重复计算写错偏移
                    size_t blockIdx = y * CHUNK_WIDTH * CHUNK_DEPTH + x * CHUNK_DEPTH + z;
                    unsigned int id = blocks[blockIdx].id;
                    BlockInfo bi = globalRe->getInfo(id);
                    if (id == BLOCKID::AIR)
                        continue;

                    if (z == 15 || !blocks[blockIdx + 1].isSolid()) { // Front面
                        vertices.emplace_back( x, y, 1 + z, bi.textCoord[0][0].left, bi.textCoord[0][0].bottom );
                        vertices.emplace_back( 1 + x, y, 1 + z, bi.textCoord[0][0].right, bi.textCoord[0][0].bottom );
                        vertices.emplace_back( 1 + x, 1 + y, 1 + z, bi.textCoord[0][0].right, bi.textCoord[0][0].top );
                        vertices.emplace_back( 1 + x, 1 + y, 1 + z, bi.textCoord[0][0].right, bi.textCoord[0][0].top );
                        vertices.emplace_back( x, 1 + y, 1 + z, bi.textCoord[0][0].left, bi.textCoord[0][0].top );
                        vertices.emplace_back( x, y, 1 + z, bi.textCoord[0][0].left, bi.textCoord[0][0].bottom );
                    }
                    
                    // 其余面逻辑保持不变,统一用提前计算的blockIdx做偏移即可
                }
            }
        }
    }

    void draw() {
        glBindVertexArray(VAO);
        glUseProgram(sp);
        glUniform3f(chunkPosUniformLoc, chunkPosX, chunkPosY, chunkPosZ);
        glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(vertices.size()));
        glBindVertexArray(0);
    }

    ~Chunk() {
        // 释放OpenGL资源
        if (VAO) glDeleteVertexArrays(1, &VAO);
        if (VBO) glDeleteBuffers(1, &VBO);
        // blocks为vector,自动释放内存,无需手动delete
    }

};

修复后不管是单独实例化还是存入std::list/std::vector等容器,都可以正常运行。


内容的提问来源于stack exchange,提问作者Julian M

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 17:09:21