调用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资源泄漏
修复方案
- 修正updateVBO逻辑,每次更新数据前显式绑定当前Chunk的VBO
- 禁用Chunk的拷贝语义(区块对象不需要拷贝),正确实现移动语义,直接用
std::vector<Block>代替裸指针管理blocks数组,自动处理内存 - 修正块索引计算错误
- 析构函数中添加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
相关产品推荐
相关产品推荐

