Estou escrevendo meu próprio clone do Minecraft (também escrito em Java). Funciona muito bem agora. Com uma distância de visualização de 40 metros, posso facilmente atingir 60 FPS no meu MacBook Pro 8,1. (Intel i5 + Intel HD Graphics 3000). Mas se eu colocar a distância de visualização em 70 metros, alcanço apenas 15-25 FPS. No Minecraft real, posso colocar a disnância de visualização longe (= 256m) sem nenhum problema. Então, minha pergunta é o que devo fazer para melhorar meu jogo?
As otimizações que implementei:
- Mantenha apenas partes locais na memória (dependendo da distância de visualização do player)
- Seleção de Frustum (primeiro nos pedaços, depois nos blocos)
- Desenhando apenas faces realmente visíveis dos blocos
- Usando listas por pedaço que contêm os blocos visíveis. Os pedaços que ficarem visíveis serão adicionados a esta lista. Se ficarem invisíveis, serão automaticamente removidos desta lista. Os blocos se tornam (in) visíveis construindo ou destruindo um bloco vizinho.
- Usando listas por bloco que contêm os blocos de atualização. Mesmo mecanismo que as listas de bloqueio visíveis.
- Não use quase nenhuma
new
instrução dentro do loop do jogo. (Meu jogo dura cerca de 20 segundos até que o Garbage Collector seja chamado) - Estou usando as listas de chamadas do OpenGL no momento. (
glNewList()
,glEndList()
,glCallList()
) Para cada lado de um tipo de bloco.
Atualmente, nem estou usando nenhum tipo de sistema de iluminação. Já ouvi falar de VBO's. Mas não sei exatamente o que é. No entanto, vou fazer algumas pesquisas sobre eles. Eles melhorarão o desempenho? Antes de implementar as VBOs, quero tentar usar glCallLists()
e passar uma lista de listas de chamadas. Em vez disso, use milhares de vezes glCallList()
. (Eu quero tentar isso, porque acho que o MineCraft real não usa VBOs. Correto?)
Existem outros truques para melhorar o desempenho?
A criação de perfil do VisualVM me mostrou isso (criação de perfil para apenas 33 quadros, com uma distância de visualização de 70 metros):
Criação de perfil com 40 metros (246 quadros):
Nota: Estou sincronizando muitos métodos e blocos de código, porque estou gerando pedaços em outro encadeamento. Eu acho que a aquisição de um bloqueio para um objeto é um problema de desempenho ao fazer isso em um loop do jogo (é claro, estou falando do momento em que existe apenas o loop do jogo e nenhum novo bloco é gerado). Isto está certo?
Edit: Depois de remover alguns synchronised
blocos e outras pequenas melhorias. O desempenho já é muito melhor. Aqui estão meus novos resultados de criação de perfil com 70 metros:
Eu acho que é bem claro que selectVisibleBlocks
é o problema aqui.
Desde já, obrigado!
Martijn
Atualização : Após algumas melhorias extras (como usar loops no lugar de cada um, armazenar em buffer variáveis fora dos loops, etc ...), agora posso executar a distância de visualização 60 muito bem.
Acho que vou implementar o VBO o mais rápido possível.
PS: Todo o código-fonte está disponível no GitHub:
https://github.com/mcourteaux/CraftMania
fonte
Respostas:
Você menciona fazer frustum selecionando blocos individuais - tente jogar isso fora. A maioria dos pedaços de renderização deve ser totalmente visível ou totalmente invisível.
O Minecraft reconstrói apenas um buffer de lista / vértice de exibição (não sei qual ele usa) quando um bloco é modificado em um determinado pedaço, e eu também . Se você estiver modificando a lista de exibição sempre que a exibição for alterada, não terá o benefício das listas de exibição.
Além disso, você parece estar usando pedaços de altura mundial. Observe que o Minecraft usa blocos cúbicos 16 × 16 × 16 para suas listas de exibição, ao contrário de carregar e salvar. Se você fizer isso, há ainda menos razões para selecionar pedaços individuais.
(Nota: eu não examinei o código do Minecraft. Todas essas informações são boatos ou minhas próprias conclusões de observar a renderização do Minecraft enquanto eu jogo.)
Mais conselhos gerais:
Lembre-se de que sua renderização é executada em dois processadores: CPU e GPU. Quando sua taxa de quadros é insuficiente, um ou outro é o recurso limitador - seu programa é vinculado à CPU ou à GPU (supondo que não esteja trocando ou tendo problemas de agendamento).
Se o seu programa estiver sendo executado com 100% da CPU (e não tiver uma outra tarefa ilimitada para concluir), sua CPU estará fazendo muito trabalho. Você deve tentar simplificar sua tarefa (por exemplo, fazer menos descarte) em troca de fazer com que a GPU faça mais. Suspeito fortemente que esse seja o seu problema, dada sua descrição.
Por outro lado, se a GPU é o limite (infelizmente, geralmente não há monitores de carga convenientes de 0% a 100%), você deve pensar em como enviar menos dados ou exigir que preencha menos pixels.
fonte
O que chama tanto o Vec3f.set? Se você está construindo o que deseja renderizar do zero a cada quadro, é definitivamente aí que você gostaria de começar a acelerar. Eu não sou muito usuário de OpenGL e não sei muito sobre como o Minecraft é processado, mas parece que as funções matemáticas que você está usando estão acabando com você agora (veja quanto tempo você gasta nelas e o número de vezes eles são chamados - morte por mil cortes chamando-os).
Idealmente, seu mundo seria segmentado para que você possa agrupar coisas para renderizar, construindo Objetos de buffer do vértice e reutilizando-os em vários quadros. Você só precisará modificar um VBO se o mundo que ele representa mudar de alguma forma (como o usuário o edita). Em seguida, você pode criar / destruir VBOs para o que está representando, visto que ele é visível para manter o consumo de memória baixo; você seria atingido apenas quando o VBO foi criado, em vez de todos os quadros.
Se a contagem de "invocação" estiver correta no seu perfil, você estará chamando muitas coisas muitas vezes. (10 milhões de chamadas para Vec3f.set ... ai!)
fonte
Minha descrição (de minha própria experimentação) aqui é aplicável:
Para renderização de voxel, o que é mais eficiente: VBO pré-fabricado ou um sombreador de geometria?
O Minecraft e seu código provavelmente usam o pipeline de função fixo; meus próprios esforços foram com GLSL, mas a essência é geralmente aplicável, eu sinto:
(De memória) criei um frustum meio bloco maior que o frustum da tela. Depois testei os pontos centrais de cada bloco (o minecraft possui 16 * 16 * 128 blocos ).
As faces de cada uma possuem extensões em uma VBO de matriz de elementos (muitas faces de partes compartilham a mesma VBO até ficar 'cheia'; pense como
malloc
; aquelas com a mesma textura na mesma VBO sempre que possível) e os índices de vértices para o norte faces, faces sul e assim por diante são adjacentes ao invés de misturadas. Quando desenho, faço umglDrawRangeElements
para as faces norte, com o normal já projetado e normalizado, em um uniforme. Então eu faço as faces sul e assim por diante, para que os normais não estejam em nenhum VBO. Para cada pedaço, eu só tenho que emitir as faces que serão visíveis - somente as que estão no centro da tela precisam desenhar os lados esquerdo e direito, por exemplo; isso é simplesGL_CULL_FACE
no nível do aplicativo.A maior aceleração, o iirc, foi selecionar faces internas ao poligonizar cada pedaço.
Também é importante o gerenciamento do atlas de textura e a classificação de faces por textura e a colocação das faces com a mesma textura no mesmo vbo que as de outros blocos. Você deseja evitar muitas alterações de textura e classificar as faces por textura e assim por diante minimiza o número de extensões na
glDrawRangeElements
. Mesclar faces adjacentes do mesmo mosaico em retângulos maiores também foi importante. Eu falo sobre a fusão na outra resposta citada acima.Obviamente, você poligoniza apenas os pedaços que já foram visíveis, você pode descartar aqueles que não são visíveis há muito tempo e poligonalizar os pedaços que são editados (pois essa é uma ocorrência rara em comparação à renderização).
fonte
De onde vêm todas as suas comparações (
BlockDistanceComparator
)? Se for de uma função de classificação, isso poderia ser substituído por uma classificação radix (que é assintoticamente mais rápida e não baseada em comparação)?Observando seus horários, mesmo que a classificação em si não seja tão ruim, sua
relativeToOrigin
função está sendo chamada duas vezes para cadacompare
função; todos esses dados devem ser calculados uma vez. Deve ser mais rápido classificar uma estrutura auxiliar, por exemploe depois no pseudoCode
Desculpe se essa não é uma estrutura Java válida (eu não toquei em Java desde a graduação), mas espero que você entenda.
fonte
Sim, use VBOs e CULL, mas isso vale para praticamente todos os jogos. O que você quer fazer é renderizar o cubo apenas se estiver visível para o jogador, E se os blocos estiverem tocando de uma maneira específica (digamos, um pedaço que você não pode ver porque é subterrâneo), adicione os vértices dos blocos e faça quase como um "bloco maior", ou no seu caso - um pedaço. Isso é chamado de malha gulosa e aumenta drasticamente o desempenho. Estou desenvolvendo um jogo (baseado em voxel) e ele usa um algoritmo de malha ganancioso.
Em vez de renderizar tudo assim:
Renderiza assim:
A desvantagem disso é que você precisa fazer mais cálculos por bloco na construção inicial do mundo, ou se o jogador remover / adicionar um bloco.
praticamente qualquer tipo de mecanismo voxel precisa disso para um bom desempenho.
O que faz é verificar se a face do bloco está tocando em outra face do bloco e, se estiver: renderize apenas como uma (ou zero) face (s) do bloco. É um toque caro quando você está processando pedaços muito rápido.
fonte
Parece que seu código está se afogando em objetos e chamadas de função. Medindo os números, não parece que ocorra algo interno.
Você pode tentar encontrar um ambiente Java diferente ou simplesmente mexer com as configurações que você possui, mas uma maneira simples e simples de criar seu código, não rápida, mas muito menos lenta é pelo menos internamente no Vec3f para parar codificação OOO *. Torne todos os métodos independentes, não chame nenhum dos outros métodos apenas para realizar alguma tarefa servil.
Edit: Embora exista sobrecarga em todo o lugar, parece que ordenar os blocos antes da renderização é o pior comedor de desempenho. Isso é mesmo necessário? Nesse caso, você provavelmente deve começar fazendo um loop e calcular a distância de cada bloco até a origem e, em seguida, classificar por isso.
* Orientado a objetos excessivamente
fonte
Você também pode tentar dividir as operações matemáticas em operadores bit a bit. Se você tem
128 / 16
, tentar fazer um operador bit a bit:128 << 4
. Isso ajudará muito com seus problemas. Não tente fazer as coisas correrem a toda velocidade. Faça a atualização do seu jogo a uma taxa de 60 ou algo assim, e até divida isso em outras coisas, mas você teria que destruir e / ou colocar voxels ou teria que fazer uma lista de tarefas, o que reduziria seus fps. Você pode fazer uma taxa de atualização de cerca de 20 para entidades. E algo como 10 para atualizações mundiais e / ou geração.fonte