简体   繁体   中英

How to use properly VAO and VBO in a voxel engine

I'm currently building a voxel engine (like Minecraft for example), and I'm using opengl.

The current design of the engine looks like this:

class Map -> contains 2d array of chunks (std::vector of std::vector)

class Chunk -> contains 3d array of block (also using std::vector)

class Block -> contains the vertices of the blocks. (cubes, built from 12 triangles);

What I want to do is make every chunk have a draw() function which will draw all the blocks in the chunk. I thought I'd make a buffer for each chunk using glGenBuffers(). From the tutorials I read should first create a vertex array with glVertexArray and than bind it, than bind a buffer to it with the target GL_ARRAY_BUFFER and fill it with data.

So far I managed to render a single chunk with a number of blocks using a buffer which created in the chunk with glGenBuffers() but in the main function I create glVertexArray and bind it, than call the draw() function in the chunk and it bind the chunk's buffer and drawing using glDrawArrays().

My question is, it is right to create a VBO for each chunk? I need to create a glVertexArray for each chunk too? or I should use one for all the chunks and every time bind another VBO?

I tried to study more about the VAO and the VBO throught tutorials and the opengl wiki but so far I didn't fully understand how it works and how it should be used properly.

I know I'm very late, but I'll put this answer here if anyone finds this in the future.

Minecraft does it differently to the way people presume. Rather than having the system you describe above with nested buffers, a chunk can get away with simply an array of integers (or unsigned chars if you have less than 256 block types). Instead of having many objects for blocks, you can have one block of each type and then render it, at a specific position if its 'ID' appears in the chunk array.

It could be a 3D array (or vector of vectors of vectors in your case, but this would not be very efficient) or a 1D array that can be accessed with width, height and depth. Each ID (an integer or an unsigned char) will be linked to one block type. Because of this, when you want to render a chunk, you can just go through all dimensions of the chunk - width, height, depth - and look at the ID at that point. If it is 3, render a block of dirt, if it is 1, make that a grass block, if it is 0, keep this as an air block etc. This system makes it great for shaping and generating the terrain, as you're just manipulating an integer array. If you arranged the blocks in order of density then you could do some arithmetic on regions of the chunk to shape terrain (subtract one from all IDs in this area of the chunk, for example) and all the block types would change.

This would be done with instancing, so that you can have one dirt block in a chunk, and just render it multiple times wherever its needed. To some extent it could even be done with uniforms that would shift the position of the block around and render it one by one.

To answer your question, you'd want one VBO that defines the position data, and texture coordinates of a block. One VAO too , for storing the way that that data is arranged in memory. But after that, you could bind different textures before rendering each block depending on the ID in the blockArray in the chunk. On loading, depending how big you've made the chunk, group all the locations of dirt blocks in a chunk, and bind the texture once and then render all the dirt blocks in one (as texture binding calls can be a performance issue if they're happening too often).

I think you might be overthinking things too early. Why is your map split up into chunks? Are you anticipating some kind of paging where not all of the map is in memory? If your whole map fits in GPU memory use one buffer per map and there is no need for chunks. Make your rendering pretty like that first.

After that you can think of optimizing, for example by only drawing the visible parts. Or drawing distant parts at lower detail. And at that point you decide how to keep GPU buffers around, when to update or discard them. It's just a caching problem. Not an easy one, but a caching problem.

More practically: If you organize in chunks, yes, one buffer per chunk. One vertex and one index buffer. Draw one at a time. Switching buffers is not bad for drawing. But when you load a buffer you want to load the whole thing, not parts. Do not bother with buffers under around 65k vertices.

As a first step just draw from CPU memory though. Once you get a better feel of how things line up caching will follow. Do not mix GPU structures into your design early. You will also need CPU structures for things like picking or AI. You should have one "ground truth" data set that then has different partitions and representations for different needs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM