Por que os tutoriais usam abordagens diferentes para a renderização do OpenGL?

43

http://www.sdltutorials.com/sdl-opengl-tutorial-basics

http://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/

Esses dois tutoriais usam abordagens completamente diferentes para obter quase o mesmo resultado. O primeiro usa coisas como glBegin(GL_QUADS). O segundo usa coisas como vertexBufferObjectsshaders baseadas no GLEW. Mas o resultado é o mesmo: você obtém formas básicas.

Por que essas diferenças existem?

A primeira abordagem parece muito mais fácil de entender. Qual é a vantagem da segunda abordagem complicada?

reynmar
fonte
4
Nunca existe apenas uma maneira de esfolar um gato.
Philipp
4
@Philipp Sim, mas há maneiras certas e maneiras erradas, velhas formas e novas formas (e como as respostas abaixo demonstram, as velhas e novas formas podem não ser compatíveis em todas as situações)
Andrew Hill
3
nenhuma maneiras certas e maneiras erradas, maneiras única piores e melhores maneiras (em várias dimensões diferentes).
precisa saber é o seguinte
glBegine glEndforam descontinuados porque são extremamente ineficientes para as arquiteturas gráficas atuais
Alex

Respostas:

77

O OpenGL possui quatro versões principais diferentes, sem contar as versões para dispositivos móveis e sistemas incorporados (OpenGL | ES) e a Web via JavaScript (WebGL). Assim como o Direct3D 11 tem uma maneira diferente de fazer as coisas que o Direct3D 8, o OpenGL 3 tem uma maneira diferente de fazer as coisas que o OpenGL 1. A grande diferença é que as versões do OpenGL são apenas complementos das versões mais antigas (mas não inteiramente).

Além das diferentes edições e versões do OpenGL, o OpenGL principal também adicionou o conceito de perfis. Ou seja, o perfil de compatibilidade (que habilita o suporte a APIs de versões mais antigas) e o perfil principal (que desabilita essas APIs antigas). Coisas como glBeginsimplesmente não funcionam quando você usa o Perfil Principal, mas funcionam quando você usa o Perfil de Compatibilidade (que é o padrão).

Como mais uma complicação importante, algumas implementações do OpenGL (como a da Apple, entre outras) só habilitarão os recursos mais recentes do OpenGL quando você estiver usando o Perfil principal. Isso significa que você deve parar de usar APIs mais antigas para usar APIs mais recentes.

Você acaba com vários cenários muito confusos para os tutoriais:

  1. O tutorial é antigo e usa apenas APIs obsoletas.
  2. O tutorial é novo e bem escrito e usa apenas APIs compatíveis com Core.
  3. O tutorial é novo, mas comete o erro de assumir que você está trabalhando com um driver que habilita todas as APIs no modo de compatibilidade e combina livremente APIs novas e antigas.
  4. O tutorial é para uma edição diferente do OpenGL, como o OpenGL | ES, que não suporta nenhuma das APIs antigas, em nenhuma versão.

Coisas como glBeginfazem parte do que às vezes é chamado de API de modo imediato. Isso também é super confuso, porque não existe um modo retido no OpenGL e o "modo imediato" já tinha uma definição diferente nos gráficos. É muito melhor referir-se a essas APIs do OpenGL 1.x, pois elas estão obsoletas desde o OpenGL 2.1.

A API 1.x do OpenGL enviava imediatamente vértices para o pipeline de gráficos nos velhos tempos. Isso funcionou bem quando a velocidade do hardware que renderizou os vértices estava aproximadamente no mesmo nível da velocidade da CPU que gerava os dados do vértice. O OpenGL naquela época descarregava a rasterização do triângulo e não muito mais.

Hoje em dia, a GPU pode vasculhar um grande número de vértices em velocidades muito altas enquanto executa uma transformação avançada de vértices e pixels, e a CPU simplesmente não consegue acompanhar remotamente. Além disso, a interface entre a CPU e a GPU foi projetada com base nessa diferença de velocidade, o que significa que nem é possível enviar vértices para a GPU, um de cada vez.

Todos os drivers GL devem emular glBeginalocando internamente um buffer de vértice, colocando os vértices enviados glVertexnesse buffer e enviando esse buffer inteiro em uma única chamada de desenho quando glEndé chamado. A sobrecarga dessas funções é muito maior do que se você mesmo atualizasse o buffer de vértice, e é por isso que alguma documentação (muito equivocadamente!) Se refere a buffers de vértice como "uma otimização" (não é uma otimização; é a única maneira de realmente fale com a GPU).

Existem várias outras APIs que foram descontinuadas ou obsoletas no OpenGL ao longo dos anos. O chamado oleoduto de função fixa é outra dessas peças. Alguma documentação ainda pode usar esse pipeline ou misturar-se ao pipeline programável. O pipeline de função fixa vem dos velhos tempos em que as placas gráficas codificavam toda a matemática usada para renderizar cenas em 3D e a API do OpenGL limitava-se a definir alguns valores de configuração para essa matemática. Atualmente, o hardware possui muito pouca matemática codificada e (assim como a sua CPU) executa programas fornecidos pelo usuário (geralmente chamados de shaders).

Mais uma vez, os drivers devem emular a API antiga, pois os recursos de função fixa simplesmente não estão mais presentes no hardware. Isso significa que o driver possui vários shaders de compatibilidade incorporados que executam a matemática antiga a partir dos dias de função fixa que são usados ​​quando você não fornece seus próprios shaders. As antigas funções do OpenGL que modificam esse antigo estado de função fixa (como a antiga API de iluminação do OpenGL) estão na verdade usando recursos modernos do OpenGL, como buffers uniformes, para alimentar esses valores com os sombreadores de compatibilidade do driver.

Os drivers compatíveis com a compatibilidade precisam fazer muito trabalho nos bastidores para descobrir quando você está usando esses recursos obsoletos e certificando-se de combiná-los com os recursos modernos sem problemas, o que aumenta a sobrecarga e complica muito o driver. Esse é um dos motivos pelos quais alguns drivers o obrigam a permitir que o Core Profile obtenha recursos mais novos; simplifica muito os internos de drivers, não precisando oferecer suporte a APIs antigas e novas usadas simultaneamente.

Muita documentação pode recomendar que você comece com as APIs antigas simplesmente por serem mais fáceis de começar. O Direct3D resolveu esse problema para iniciantes, oferecendo uma biblioteca complementar ( DirectX Tool Kit ) que fornece APIs de desenho mais simples e shaders pré-escritos, que podem ser livremente misturados ao uso bruto do Direct3D 11 à medida que sua experiência aumenta. Infelizmente, a comunidade mais ampla do OpenGL mantém o Perfil de Compatibilidade para iniciantes, o que é problemático, pois novamente existem sistemas que não permitem que você misture APIs OpenGL antigas com as mais recentes. Existem bibliotecas e ferramentas não oficiais para renderização mais simples no novo OpenGL, com níveis variados de recursos e casos de uso e idiomas de destino ( MonoGame para usuários do .NET, por exemplo), mas nada oficialmente aprovado ou amplamente aceito.

A documentação que você encontra pode até não ser para o OpenGL, mas pode ser para uma das outras APIs semelhantes. O OpenGL | ES 1.x tinha renderização de função fixa, mas não tinha as APIs do OpenGL 1.x para envio de vértices. O OpenGL | ES 2.x + e o WebGL 1+ não possuem nenhum recurso de função fixa e não há modos de compatibilidade com versões anteriores para essas APIs.

Essas APIs parecem muito muito semelhantes ao OpenGL principal; eles não são totalmente compatíveis, mas existem extensões oficiais para o OpenGL que alguns (nem todos) os drivers suportam para se tornarem compatíveis com o OpenGL | ES (no qual o WebGL se baseia). Porque as coisas não eram confusas o suficiente antes.

Sean Middleditch
fonte
4
+1 resposta fantástica! Se você poderia mencionar um par dessas bibliotecas não oficiais e ferramentas para renderização simples sobre o novo OpenGL que seria ótimo :)
Mehrdad
2
Resposta brilhante. Eu tive o mesmo problema com o DirectX naquela época - muito mais simples do que com o OpenGL, mas o salto do modo retido / imediato para os shaders foi enorme. Felizmente, a documentação ajudou muito (ao contrário do OpenGL, pelo menos para mim), mas o começo de "como eu ilumino" era louco: D
Luaan
Eu sou o autor do opengl-tutorial.org e concordo com o Sean. A API evoluiu dessa maneira principalmente por razões de desempenho.
Calvin1602
Muito boa informação sobre o assunto ..
reynmar
1
@ Mehrdad: Não me lembro de nada em cima da minha cabeça; existem bibliotecas como SDL2 ou SFML que adicionam renderização 2D simplificada, várias bibliotecas de gráficos de cena, MonoGame para C # etc. etc., mas não estou ciente de nada diretamente equivalente ao Direct TK agora que penso nisso. Editará o post, já que dizer "muitos" pode ser uma grande mentira. :)
Sean Middleditch
9

A principal diferença é a atualidade das estratégias. O modo imediato usado no primeiro tutorial:

glBegin(GL_QUADS);
    glColor3f(1, 0, 0); glVertex3f(0, 0, 0);
    glColor3f(1, 1, 0); glVertex3f(100, 0, 0);
    glColor3f(1, 0, 1); glVertex3f(100, 100, 0);
    glColor3f(1, 1, 1); glVertex3f(0, 100, 0);
glEnd();

Está desatualizado e não é suportado em versões mais recentes.

O uso de buffers e shaders de vértices é o método atual de renderização com o OpenGL. Pode parecer mais complicado, mas tem um desempenho consideravelmente melhor. Além disso, depois que você tiver seu código de suporte finalizando o material do OpenGL, as diferenças serão abstraídas em sua maior parte.

Michaelhouse
fonte
2

Apenas para adicionar um pouco mais de contexto às outras excelentes respostas.

O modo imediato, conforme descrito no primeiro link, é como já foi dito, código legado das versões mais antigas do OpenGL (1.1). Foi usado quando as GPUs eram pouco mais que rasterizadores de triângulo e a idéia de dutos programáveis ​​não existia. Se você olhar o código-fonte para alguns dos jogos acelerados por hardware anteriores, como GLQuake e Quake 2, por exemplo, verá o modo imediato em uso. Em termos simples, a CPU envia instruções para os vértices, um de cada vez, para a GPU para começar a desenhar triângulos na tela. Para o registro, GL_QUADS tem o mesmo resultado que GL_TRIANGLES, exceto que a GPU precisa transformar esses quads em triângulos em tempo real.

O OpenGL moderno (3,2+) adota uma abordagem diferente. Ele armazena em buffer os dados do vértice na memória da GPU para acesso rápido, e você pode enviar instruções de desenho usando glDrawArrays ou glDrawElements. Você também possui o pipeline programável (glUseProgram), que permite personalizar como a GPU posiciona e colore os vértices.

Existem algumas razões pelas quais o modo imediato foi descontinuado, a principal razão sendo o desempenho. Como Sean disse em sua resposta, hoje em dia as GPUs podem processar os dados mais rapidamente do que a CPU pode carregá-los; portanto, você estaria prejudicando o desempenho da GPU. Há uma pequena sobrecarga envolvida em todas as chamadas para o OpenGL que você faz, é minúscula, mas quando você faz dezenas de milhares de chamadas a cada quadro, ele começa a se acumular. Simplificando, para desenhar um modelo texturizado usando o modo imediato, você precisa de pelo menos 2 chamadas por vértice (glTexCoord2f e glVertex3f) por quadro. Com o OpenGL moderno, você usa algumas chamadas no início para armazenar os dados em buffer e, em seguida, pode desenhar o modelo inteiro, independentemente de quantos vértices eles contêm, usando apenas algumas chamadas para vincular o objeto da matriz de vértices, ativar alguns ponteiros de atributo e depois, uma única chamada para glDrawElements ou glDrawArrays.

Qual técnica está certa? Bem, isso depende do que você está tentando fazer. Um jogo 2D simples que não requer nenhuma técnica de pós-processamento ou shaders sofisticados funcionará bem no modo imediato e provavelmente será mais fácil escrever o código. No entanto, um jogo 3D mais moderno seria realmente difícil, e se você planeja aprender o GLSL (linguagem shader), definitivamente aprenda a técnica moderna.

Neoptolemus
fonte