Como carregar pedaços de empilhamento em tempo real?

8

Atualmente, estou trabalhando em um mundo infinito, principalmente inspirado em minecraft.
Um Chunk consiste em blocos 16x16x16. Um bloco (cubo) é 1x1x1.

Isso funciona muito bem com um ViewRange de 12 pedaços (12x16) no meu computador. Bem.
Quando mudo a altura do Chunk para 256, isso se torna - obviamente - incrível.

Então, basicamente, o que eu quero fazer é empilhar pedaços. Isso significa que meu mundo pode ter [∞, 16, ∞] pedaços grandes.

A questão agora é como gerar pedaços em tempo real?
No momento, gero pedaços circulares não existentes em torno da minha posição (perto ou longe). Como ainda não empilhe pedaços, isso não é muito complexo.

Como nota lateral importante aqui: eu também quero ter biomas, com diferentes alturas mín. / Máx. Assim, em Bioma Plains a camada mais alta com blocos seriam 8 (8x16) - em Bioma Montanhas a camada mais alta com blocos seriam 14 (14x16). Apenas como exemplo.

O que eu poderia fazer seria carregar 1 pedaço acima e abaixo de mim, por exemplo.
Mas aqui estaria o problema , que as transições entre diferentes átomos poderiam ser maiores que um pedaço de y.




Transições entre biomas




Meu pedaço atual de carregamento em ação

Exemplo de carregamento de pedaços



Para completar, meu atual algoritmo de carregamento de blocos

private IEnumerator UpdateChunks(){
    for (int i = 1; i < VIEW_RANGE; i += ChunkWidth) {
        float vr = i;
        for (float x = transform.position.x - vr; x < transform.position.x + vr; x += ChunkWidth) {
            for (float z = transform.position.z - vr; z < transform.position.z + vr; z += ChunkWidth) {

                _pos.Set(x, 0, z); // no y, yet
                _pos.x = Mathf.Floor(_pos.x/ChunkWidth)*ChunkWidth;
                _pos.z = Mathf.Floor(_pos.z/ChunkWidth)*ChunkWidth;

                Chunk chunk = Chunk.FindChunk(_pos);

                // If Chunk is already created, continue
                if (chunk != null)
                    continue;

                // Create a new Chunk..
                chunk = (Chunk) Instantiate(ChunkFab, _pos, Quaternion.identity);
            }
        }
        // Skip to next frame
        yield return 0;
    }
}
Brettetete
fonte

Respostas:

1

O que você precisa considerar para carregar / criar um pedaço acima e abaixo da superfície em qualquer pilha quando o player estiver na superfície, para que seu algoritmo de geração precise se preocupar com pilhas no nível superior em vez de pedaços ... quando o jogador estiver abaixo do solo, um acima e abaixo do nível atual do bloco é bom. Para esclarecer, uma pilha é uma coluna vertical de pedaços do alicerce à estratosfera :)

Outra maneira de ver isso seria dizer se a superfície está abaixo do nível atual do bloco - gere a superfície e uma abaixo, caso contrário, gere o nível atual e uma acima e abaixo.

Então, digamos que seu mundo terá 256 blocos de altura (* 16 = 4096 blocos de voxel) e, a qualquer momento, se uma pilha estiver dentro do alcance da visualização, você terá de 1 a 3 blocos nessa pilha realmente carregada e renderizada.

Os biomas apresentam um problema adicional de alturas de mesclagem nas bordas, mas você pode lidar com isso no código específico do bioma que será chamado para gerar os recursos de superfície e subsuperfície. Se você estiver usando ruído perlin / simplex para gerar alturas, se um pedaço delimitar um pedaço que é um bioma diferente, você poderá obter os valores de ruído que os dois tipos de bioma gerariam, então faça a média deles.

Ascendion
fonte
0

O que você pode fazer é fazer um pedaço com 256 de altura na direção y e dividi-lo em 16 seções, cada uma com 16 blocos de altura. Em seguida, você gera os dados para o pedaço e constrói a geometria dentro das seções.

Uma vantagem seria que você tem acesso aos dados de um pedaço completo, o que facilita o acesso aos dados acima e abaixo de uma seção.

Isso também tem a vantagem de ser capaz de separar facilmente muita geometria que não fica dentro do núcleo de visualização. Também haverá muitas seções que não possuem nenhuma geometria.

Se você ainda não o usar, carregar os pedaços em outro encadeamento também poderá fornecer melhores taxas de quadros, enquanto gera os dados para cada pedaço.

user000user
fonte
11
Bem, talvez eu tenha me explicado um pouco vago. Eu já planejei separar um pedaço em 16 camadas. Mas como decido quais camadas carregar? Todo bioma tem sua própria altura mínima / máxima. Imagine o bioma A [100,20] Bioma B [160,200] - Estou à beira de ambos os biomas. Eles têm uma transição suave. Como decidir quais camadas carregar? Mas, ao escrever isso, acho que só preciso verificar todas as camadas (de cima para baixo) e criá-las, quando a camada acima estiver vazia / transparente - e parar quando a primeira camada for criada. Espero que você consiga isso;) É chato você não pode adicionar linhas em branco nos comentários
Brettetete
3
Eu entendo o seu ponto. Mas se você implementar um algoritmo como a malha gananciosa, poderá carregar todas as camadas sem grande perda de desempenho. Você pode encontrar um artigo sobre malhas gananciosas aqui . Eu faço o mesmo no meu mecanismo voxel e está funcionando bem até agora.
user000user
Esta técnica é simplesmente incrível. Vou tentar implementar isso no meu mecanismo. O código fornecido é um pouco estranho, você compartilharia sua implementação? :)
Brettetete
2
Colei minha implementação de C ++ aqui e adicionei alguns comentários. Espero que ajude :)
user000user
Impressionante, isso parece ser muito útil. Obrigado! Vou tentar traduzir isso agora. Mas no # 67 - # 68 há um duplo && - você perdeu / dobrou acidentalmente algo para copiar? :) E você poderia em breve explicar / publicar os métodos SHIFT_ ? Também não vejo qualquer declaração / uso de tmp - Bem, sory por tudo o que perguntas
Brettetete
0

Não acredito que você possa fazer isso simplesmente carregando determinadas camadas por causa do problema de transições.

Minha inclinação seria armazenar alguns metadados com cada pedaço:

1) O bloco é totalmente de ar. Nesse caso, não há necessidade de renderizá-lo.

2) Para cada face do bloco é opaco. Um rosto opaco significa que você não precisa considerar o próximo pedaço. (Observe, porém, que dependendo de onde o pedaço está, pode haver até três faces envolvidas - um pedaço deve ser renderizado se qualquer um dos três não for opaco. Eu suspeito que seja melhor pré-calculado - render tanto ou b1 é visível e tem uma face não opaca f1 ou b2 é visível tem uma face não opaca f2 ou b3 é visível tem uma face não opaca f3.)

Infelizmente, existem mais de 7000 pedaços dentro da sua faixa de visão de 12 pedaços. No entanto, eu esperaria que poucos locais tenham mais de três blocos verticais que realmente precisam ser renderizados usando essa abordagem, que reduz a contagem de blocos para provavelmente não mais que 1500.

Eu aplicaria o mesmo tipo de lógica em um pedaço - quando você carrega um pedaço, calcula quais junções são transparentes e quais são opacas tocando em opacas - você só precisa renderizar rostos onde alguém possa vê-las. (Observe que no Minecraft você tem três tipos de bloco - transparente, opaco e que altera a visão - vidro, portas, cercas etc. Você pode pular apenas transparente-transparente e opaco-opaco.)

Loren Pechtel
fonte