Estou tentando escrever um pequeno mecanismo voxel porque é divertido, mas luto para encontrar a melhor maneira de armazenar os voxels reais. Estou ciente de que precisarei de algum tipo de pedaço para não precisar ter o mundo inteiro na memória e estou ciente de que preciso renderizá-lo com desempenho razoável.
Eu li sobre octrees e, pelo que entendi, começa com 1 cubo, e nesse cubo pode haver mais 8 cubos, e em todos esses 8 cubos podem haver outros 8 cubos etc. Mas não acho que isso se encaixe no meu motor voxel porque meus cubos / itens de voxel terão exatamente o mesmo tamanho.
Portanto, outra opção é apenas criar uma matriz de tamanho 16 * 16 * 16 e ter um pedaço, e você a preencherá com itens. E partes onde não existem itens terão 0 como valor (0 = ar). Mas receio que isso desperdice muita memória e não seja muito rápido.
Outra opção é um vetor para cada pedaço e preencha-o com cubos. E o cubo mantém sua posição no bloco. Isso economiza memória (sem bloqueios de ar), mas torna a procura de um cubo em um local específico muito mais lenta.
Portanto, não consigo realmente encontrar uma boa solução, e espero que alguém possa me ajudar com isso. Então, o que você usaria e por quê?
Mas outro problema está sendo processado. Basta ler cada pedaço e enviá-lo para a GPU usando o OpenGL é fácil, mas muito lento. Gerar uma malha por pedaço seria melhor, mas isso significa que toda vez que eu quebro um bloco, tenho que reconstruir o pedaço inteiro, o que pode demorar um pouco, causando um soluço menor, mas perceptível, o que obviamente eu também não quero. Então isso seria mais difícil. Então, como eu renderizaria os cubos? Basta criar todos os cubos em um buffer de vértice por bloco e renderizar isso e talvez tentar colocar isso em outro thread, ou existe outra maneira?
Obrigado!
Respostas:
Armazenar os blocos como as posições e os valores é realmente muito ineficiente. Mesmo sem sobrecarga causada pela estrutura ou objeto usado, você precisa armazenar 4 valores distintos por bloco. Só faria sentido usá-lo sobre o método "armazenar blocos em matrizes fixas" (o que você descreveu anteriormente) é quando apenas um quarto dos blocos é sólido e, dessa forma, você nem usa outros métodos de otimização conta.
Octrees são ótimos para jogos baseados em voxel, pois são especializados em armazenar dados com recursos maiores (por exemplo, patches do mesmo bloco). Para ilustrar isso, usei um quadtree (basicamente octrees em 2d):
Este é o meu conjunto inicial contendo blocos de 32 x 32, o que equivale a 1024 valores:
Armazenar isso como 1024 valores separados não parece ineficiente, mas quando você alcança tamanhos de mapa semelhantes aos jogos, como Terraria , as telas de carregamento levam vários segundos. E se você aumentar para a terceira dimensão, ele começará a consumir todo o espaço do sistema.
Quadríceps (ou octrees em 3d) podem ajudar a situação. Para criar um, você pode ir dos ladrilhos e agrupá-los, ou ir de uma célula enorme e dividi-la até chegar aos ladrilhos. Vou usar a primeira abordagem, porque é mais fácil de visualizar.
Portanto, na primeira iteração, você agrupa tudo em células 2x2 e, se uma célula contém apenas blocos do mesmo tipo, você os solta e armazena o tipo. Após uma iteração, nosso mapa ficará assim:
As linhas vermelhas marcam o que armazenamos. Cada quadrado é apenas 1 valor. Isso reduziu o tamanho de 1024 para 439, o que representa uma redução de 57%.
Mas você conhece o mantra . Vamos dar um passo adiante e agrupá-los em células:
Isso reduziu a quantidade de valores armazenados para 367. Isso é apenas 36% do tamanho original.
Obviamente, você precisa fazer essa divisão até que cada 4 células adjacentes (8 blocos adjacentes em 3d) dentro de um pedaço seja armazenada dentro de uma célula, convertendo essencialmente um pedaço em uma célula grande.
Isso também tem outros benefícios, principalmente ao fazer colisões, mas convém criar uma octree separada para isso, que só se preocupa se um único bloco é sólido ou não. Dessa forma, em vez de verificar se há colisão em todos os blocos de um bloco, você pode fazer isso contra as células.
fonte
Octrees existem para resolver exatamente o problema que você descreve, permitindo armazenamento denso de dados esparsos sem grandes tempos de pesquisa.
O fato de seus voxels terem o mesmo tamanho significa que a octree tem uma profundidade fixa. por exemplo. para um pedaço de 16x16x16, você precisa de no máximo 5 níveis de árvore:
Isso significa que você tem no máximo 5 etapas para descobrir se existe um voxel em uma posição específica no bloco:
Muito mais curto do que digitalizar até 1% em uma matriz de até 4096 voxels!
Observe que isso nos permite compactar os dados sempre que houver um octante completo do mesmo valor - seja esse valor todo ar, rocha ou qualquer outra coisa. É apenas onde os octantes contêm valores mistos que precisamos subdividir ainda mais, até o limite dos nós das folhas de voxel único.
Para abordar os filhos de um pedaço, normalmente procederemos na ordem de Morton , algo como isto:
Portanto, nossa navegação no nó Octree pode ser algo como isto:
fonte