Я работаю над воксельным движком в C ++, и после реализации кусков я понял, что их создание действительно дорого. Этим я не имею в виду заполнение их блоками, я имею в виду создание чанка сетки.
Игра генерируется плавно после генерации кусков, за исключением размещения и удаления вокселей. Всякий раз, когда чанк изменяется, его сетка восстанавливается. Это дорогой процесс. Для одного фрагмента требуется около 0,36 секунды, что приводит к остановке примерно на 0,36 секунды при редактировании фрагмента. Кроме того, из-за этого 0,36-секундного всплеска для одного фрагмента загрузка мира с радиусом фрагмента больше, чем 3 или 4, занимает несколько минут. С 4 порциями это занимает 189 секунд, (4 * 2) ^ 3 * 0,36 (512 порций каждый по 0,36 секунды)
Это мой код генерации сетки. Он перебирает каждый блок в чанке и, если это не воздух, добавляет для него вершины куба, иначе игнорирует его. Позже это станет более сложным методом с некоторыми вещами, которые я запланировал, что плохо, если метод уже медленный.
void WorldRenderer::constructChunkMesh(Chunk* chunk)
{
if (!chunk->isInitialized() || chunk->getNumBlocks() <= 0)
return; //If the chunk isn't initialized, or is empty, don't construct anything for it.
ChunkMesh mesh;
//iterate over every block within the chunk.
//CHUNK_SIZE has a value of 16. Each chunk is 16x16x16 blocks.
for (int x = 0; x < CHUNK_SIZE; x++)
{
for (int y = 0; y < CHUNK_SIZE; y++)
{
for (int z = 0; z < CHUNK_SIZE; z++)
{
if (chunk->getBlock(x, y, z) != Blocks::BLOCK_TYPE_AIR) //if the block is solid, add vertices, otherwise, don't render it.
{
//the 8 vertices for a cube. mesh.addVertex(...) returns the index.
int i0 = mesh.addVertex(Vertex(glm::vec3(0.0F, 0.0F, 1.0F) + glm::vec3(x, y, z), glm::vec3(0.0F, 1.0F, 0.0F), glm::vec4(1.0F, 1.0F, 1.0F, 1.0F), glm::vec2(0.0F, 0.0F)));
int i1 = mesh.addVertex(Vertex(glm::vec3(1.0F, 0.0F, 1.0F) + glm::vec3(x, y, z), glm::vec3(0.0F, 1.0F, 0.0F), glm::vec4(1.0F, 1.0F, 1.0F, 1.0F), glm::vec2(0.0F, 0.0F)));
int i2 = mesh.addVertex(Vertex(glm::vec3(0.0F, 1.0F, 1.0F) + glm::vec3(x, y, z), glm::vec3(0.0F, 1.0F, 0.0F), glm::vec4(1.0F, 1.0F, 1.0F, 1.0F), glm::vec2(0.0F, 0.0F)));
int i3 = mesh.addVertex(Vertex(glm::vec3(1.0F, 1.0F, 1.0F) + glm::vec3(x, y, z), glm::vec3(0.0F, 1.0F, 0.0F), glm::vec4(1.0F, 1.0F, 1.0F, 1.0F), glm::vec2(0.0F, 0.0F)));
int i4 = mesh.addVertex(Vertex(glm::vec3(0.0F, 0.0F, 0.0F) + glm::vec3(x, y, z), glm::vec3(0.0F, 1.0F, 0.0F), glm::vec4(1.0F, 1.0F, 1.0F, 1.0F), glm::vec2(0.0F, 0.0F)));
int i5 = mesh.addVertex(Vertex(glm::vec3(1.0F, 0.0F, 0.0F) + glm::vec3(x, y, z), glm::vec3(0.0F, 1.0F, 0.0F), glm::vec4(1.0F, 1.0F, 1.0F, 1.0F), glm::vec2(0.0F, 0.0F)));
int i6 = mesh.addVertex(Vertex(glm::vec3(0.0F, 1.0F, 0.0F) + glm::vec3(x, y, z), glm::vec3(0.0F, 1.0F, 0.0F), glm::vec4(1.0F, 1.0F, 1.0F, 1.0F), glm::vec2(0.0F, 0.0F)));
int i7 = mesh.addVertex(Vertex(glm::vec3(1.0F, 1.0F, 0.0F) + glm::vec3(x, y, z), glm::vec3(0.0F, 1.0F, 0.0F), glm::vec4(1.0F, 1.0F, 1.0F, 1.0F), glm::vec2(0.0F, 0.0F)));
//The xyz coord in the iteration in world-relative coordinates, instead of chunk-relative
int wx = (chunk->getPos().x * CHUNK_SIZE) + x;
int wy = (chunk->getPos().y * CHUNK_SIZE) + y;
int wz = (chunk->getPos().z * CHUNK_SIZE) + z;
//top y+
if (World::getBlock(wx, wy + 1, wz) <= 0)
{
//if a block does not exist in the y+ direction to this one, add the top face.
mesh.addFace(i2, i3, i7);
mesh.addFace(i2, i7, i6);
}
//bottom y-
if (World::getBlock(wx, wy - 1, wz) <= 0)
{
//if a block does not exist in the y- direction to this one, add the top face.
mesh.addFace(i0, i4, i1);
mesh.addFace(i1, i4, i5);
}
//front z-
if (World::getBlock(wx, wy, wz - 1) <= 0)
{
//if a block does not exist in the z- direction to this one, add the top face.
mesh.addFace(i6, i7, i4);
mesh.addFace(i7, i5, i4);
}
//back z+
if (World::getBlock(wx, wy, wz + 1) <= 0)
{
//if a block does not exist in the z+ direction to this one, add the top face.
mesh.addFace(i0, i1, i2);
mesh.addFace(i1, i3, i2);
}
//right x+
if (World::getBlock(wx + 1, wy, wz) <= 0)
{
//if a block does not exist in the x+ direction to this one, add the top face.
mesh.addFace(i1, i7, i3);
mesh.addFace(i1, i5, i7);
}
//left x-
if (World::getBlock(wx - 1, wy, wz) <= 0)
{
//if a block does not exist in the x- direction to this one, add the top face.
mesh.addFace(i2, i6, i4);
mesh.addFace(i0, i2, i4);
}
}
}
}
}//The rest of this is OpenGL code, and doesn't add any significant
//performance drop. I have measured this.
GeometryData gd = MeshHandler::compileGeometry(mesh.vertices.data(), mesh.indices.data(), mesh.vertices.size(), mesh.indices.size());
RenderableChunk rc;
rc.pos = chunk->getPos();
auto a = std::find(chunks.begin(), chunks.end(), rc);
int index = a - chunks.begin();
if (a != chunks.end())
{
rc = chunks[index];
}
else
{
GLuint VAO;
GLuint* VBOs = new GLuint[2];
//1527864 bytes maximum per chunk (1.5MB)
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glGenBuffers(2, VBOs);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * 8 * MAX_BLOCKS, nullptr, GL_DYNAMIC_DRAW);
glVertexAttribPointer(ATTRIB_VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, position)));
glEnableVertexAttribArray(ATTRIB_VERTEX_ARRAY);
glVertexAttribPointer(ATTRIB_NORMAL_ARRAY, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, normal)));
glEnableVertexAttribArray(ATTRIB_NORMAL_ARRAY);
glVertexAttribPointer(ATTRIB_COLOUR_ARRAY, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, colour)));
glEnableVertexAttribArray(ATTRIB_COLOUR_ARRAY);
glVertexAttribPointer(ATTRIB_TEXTURE_ARRAY, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, texture)));
glEnableVertexAttribArray(ATTRIB_TEXTURE_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBOs[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * 36 * MAX_BLOCKS, nullptr, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
rc.VAO = VAO;
rc.VBOs = VBOs;
}
rc.numIndices = gd.numIndices;
glBindVertexArray(rc.VAO);
glBindBuffer(GL_ARRAY_BUFFER, rc.VBOs[0]);
glBufferSubData(GL_ARRAY_BUFFER, 0, gd.vboSize(), gd.vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, rc.VBOs[1]);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, gd.iboSize(), gd.indices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
if (index >= 0 && index < chunks.size())
{
chunks[index] = rc;
}
else
{
chunks.push_back(rc);
}
}
И структура ChunkMesh
используется, где я считаю, что проблема заключается в:
struct ChunkMesh
{
std::vector<Vertex> vertices;
std::vector<GLushort> indices;
int addVertex(Vertex v)
{
//add a vertex to the mesh, and return its index in the list.
vertices.push_back(v);
return vertices.size() - 1;
}
void addFace(int v0, int v1, int v2)
{
//construct a face with 3 vertices.
indices.push_back(v0);
indices.push_back(v1);
indices.push_back(v2);
}
};
Я считаю, что проблема в структуре ChunkMesh с использованием push_backs. std::vector
очень медленный для сотен push_backs, но я не могу найти альтернативу. Чем я могу заменить вектор?
Я собираюсь сделать чанки совершенно неправильными? Как я могу оптимизировать эту функцию?
Любая помощь приветствуется.
Благодарю.
Редактировать:
Я попытался зарезервировать векторы, которые, к моему замешательству, не повлияли на производительность. Осталось на 0,36 секунды.
Я добавил конструктор ChunkMesh
принять количество блоков, например, так:
ChunkMesh(int numBlocks)
{
vertices.reserve(numBlocks * 8); //8 vertices per cube
indices.reserve(numBlocks * 36); //36 indices per cube
}
Я бы порекомендовал оценить, нужны ли вам вершины, которых нет на поверхности фрагмента.
Если нет, вам не нужно добавлять их в ChunkMesh, что уменьшает количество заметных вершин и вызовов push_back.
Других решений пока нет …