A maneira mais eficiente de desenhar vértice com o OpenGL

8

Estou escrevendo um jogo OpenGL 3D. Haverá toneladas de triângulos para terrenos e objetos em uso.

Estou estudando no guia oficial do OpenGL e o primeiro método apresentado é chamar uma função glVertexapós o glBeginpara cada vértice que você deseja desenhar.

No entanto, esse método parece bastante antigo e ineficiente quando você precisa desenhar milhares de triângulos. Eu acho que existem métodos para armazenar na memória da placa gráfica as informações que não mudam em todos os quadros, de modo que, quando você renderiza cada quadro, essas informações já estão lá. Enquanto, para obter mais informações "instáveis", você provavelmente precisará enviar novos dados de vértice a cada quadro e, portanto, uma técnica diferente poderá ser mais eficiente.

Voltando à minha pergunta: eu gostaria de uma explicação clara das funções e técnicas OpenGL mais modernas e eficientes usadas para enviar informações de vértices ao hardware e dar instruções para renderizar.

Quais são as melhores maneiras e ferramentas OpenGL de enviar a ordem para renderizar dados de vértices que raramente mudam durante os quadros (penso em terrenos e objetos) e qual a melhor maneira de enviar dados de vértices que mudam bastante durante os quadros?

Marco
fonte
Você está certo, glBegin () e glEnd () fazem parte da renderização imediata e são antiquados. Hoje em dia se usaria, por exemplo, VBOs. Como exatamente e o que você usa depende de seu cenário!
thalador
Eu também sou um aluno tentando aprender OpenGL. Eu sei que você precisará usar os Verbo Buffer Objects (VBOs) para desenhar vértices mais rapidamente. Embora eu não saiba o suficiente para lhe dizer como fazer isso, posso fornecer um link para um ótimo livro (on-line gratuito). Eu o deslizei e fiz um pequeno triângulo, mas é realmente complexo. Tutorial de OpenGL moderno sobre arcsíntese Estou passando pelos tutoriais do LazyFoo agora. Eles começam com o pipeline de função fixa glBegin (), glEnd () e depois passam para o OpenGL mais moderno, usando o pipeline de função fixa como base. Eu j
benbot

Respostas:

19

Em termos gerais, a resposta é "depende". A maneira de enviar atualizações de partículas é um pouco diferente da maneira como você envia uma série de modelos de GPU.

Buffers de vértice / índice

No sentido geral, porém, tudo hoje em dia é feito com um VBO ( objeto de buffer de vértice ). A antiga API de modo imediato ( glBegin/ glEnd) provavelmente é apenas implementada como um invólucro de gordura em torno do sistema de buffer de vértice interno do driver.

Para objetos estáticos, crie um VBO estático e preencha-o com seus dados de vértice. Se algum dos vértices for compartilhado (geralmente o caso), você provavelmente também desejará criar um buffer de índice. Isso reduz a necessidade de enviar os mesmos dados mais de uma vez para alterar malhas (potencialmente economizando na largura de banda de transferência) e no processamento (economia no tempo de sombreamento do vértice). Observe que você desenha com funções diferentes ao fazer desenhos indexados ou não indexados.

Para objetos dinâmicos, faça o mesmo, embora com um conjunto dinâmico de buffers.

Notas Avançadas

Para pedaços maiores, como terrenos, você provavelmente não quebrará a malha em vários pedaços. Fazer a GPU renderizar cem milhões de triângulos quando apenas duzentos mil deles são visíveis é um grande desperdício, especialmente se não forem classificados e houver muitas invocações de overdraw e desperdício de shader de fragmento. Divida a malha em pedaços grandes e, em seguida, processe apenas aqueles que estão dentro da visão frustrante. Existem também várias técnicas de descarte mais avançadas que você pode usar para eliminar pedaços que podem ser frustrantes, mas que estão inteiramente atrás de uma colina, prédio ou algo assim. Manter a contagem decrescente de chamadas é boa, mas há um equilíbrio (que você precisa encontrar para seu aplicativo / hardware específico) entre minimizar chamadas de chamadas e minimizar o desenho da geometria oculta.

Uma das principais coisas a ter em mente com um buffer da GPU é que você não pode gravar nele enquanto a GPU está lendo. Você precisa informar ao motorista que não há problema em descartar a cópia antiga do buffer (quando estiver pronto) e fornecer uma nova (se o antigo estiver ocupado). É claro que, por um longo tempo, não havia função do OpenGL para fazer isso (agora há InvalidateBufferData for GL 4.3 e algumas implementações mais antigas como uma extensão). Em vez disso, existe um comportamento não padrão, mas comum, que a maioria dos drivers implementa. Faça isso para descartar o buffer antes de atualizá-lo:

glBindBuffer(GL_ARRAY_BUFFER, my_vbo);
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_DYNAMIC_DRAW);

É claro que mude GL_ARRAY_BUFFERe GL_DYNAMIC_DRAWpara os valores apropriados para o buffer. Os buffers estáticos não serão atualizados (ou não deveriam ser), portanto é improvável que você precise se preocupar com o descarte de um buffer.

Observe que pode ser mais rápido usar glBufferDataou glBufferSubDataou mais rápido glMapBuffer. Realmente depende do driver e do hardware. A geração atual do hardware do PC provavelmente será mais rápida, glBufferDatamas com certeza testará.

Outra técnica é usar instanciamento . A instância permite fazer uma única chamada de desenho que desenha várias cópias dos dados em um buffer de vértice / índice. Se você tivesse, digamos, 100 pedras idênticas, convém desenhá-las de uma só vez, em vez de fazer 100 empates independentes.

Ao instanciar, você precisa colocar os dados por instância em outro buffer (como a posição do objeto de cada indivíduo). Pode ser um buffer uniforme ( buffer constante na terminologia do D3D) ou um buffer de textura ou um atributo de vértice por instância. Novamente, o que é mais rápido depende. Os atributos por instância são provavelmente mais rápidos e definitivamente muito mais fáceis de usar, mas muitas implementações GL comuns ainda não suportam, glBindingAttribDivisorportanto você terá que ver se está disponível para uso e se é realmente mais rápido (alguns drivers mais antigos emularam instanciando anexando buffers e acabou sendo mais lento usar instanciamentos neles do que fazer chamadas independentes e não há uma maneira padrão de descobrir ... as alegrias de usar o OpenGL).

Também existem algoritmos para a otimização do cache de vértices , que é o ato de ordenar vértices / índices em seus buffers para reproduzirem com o cache de vértices nas GPUs modernas. Uma GPU executa apenas o sombreador para um vértice e o armazena em cache no cache de vértices, mas pode ter que ser despejado muito cedo para liberar espaço para outros vértices. (Digamos, dois triângulos compartilham um vértice, mas existem outros 100 triângulos desenhados entre eles; o vértice compartilhado provavelmente acabará sendo desperdiçado por duas vezes pelo shader de vértice.)

Alguns desses recursos requerem uma versão suficientemente nova do GL ou GLES. O GLES2 não suportava instâncias, por exemplo.

Sempre perfil

Novamente, se você se preocupa com o desempenho, teste cada método possível e veja qual é mais rápido para o seu aplicativo no hardware de destino. Não apenas diferentes hardwares / drivers de diferentes fabricantes serão diferentes, mas algumas classes inteiras de hardware são naturalmente diferentes. Uma GPU móvel típica é uma fera muito diferente de uma GPU de desktop discreta típica. Técnicas que são "melhores" em um não serão necessariamente melhores em outro.

Quando se trata de desempenho, seja sempre cético .

Sean Middleditch
fonte