OpenGL Orientado a Objetos

12

Uso o OpenGL há algum tempo e li um grande número de tutoriais. Além do fato de muitos deles ainda usarem o pipeline fixo, eles geralmente lançam toda a inicialização, alterações de estado e desenho em um arquivo de origem. Isso é bom para o escopo limitado de um tutorial, mas estou tendo dificuldades para descobrir como dimensioná-lo para um jogo completo.

Como você divide o uso do OpenGL entre arquivos? Conceitualmente, posso ver os benefícios de ter, digamos, uma classe de renderização que processa puramente coisas para a tela, mas como funcionariam coisas como sombreadores e luzes? Devo ter aulas separadas para coisas como luzes e shaders?

Sullivan
fonte
1
Existem jogos de código aberto por aí, além de tutoriais maiores que têm mais código. Confira alguns deles e veja como o código está organizado.
MichaelHouse
Você é iniciante no que diz respeito a mecanismos de renderização?
Samaursa
Esta pergunta não pode ser respondida razoavelmente sem um estreitamento do escopo. Apenas o que você está tentando construir? Que tipo de jogo, que tipo de gráficos ele possui, etc?
Nicol Bolas
1
@Sullivan: "Eu pensei que esse tipo de conceito se aplicaria a quase todos os jogos." Eles não. Se você me pedir para criar o Tetris, não vou me incomodar em criar uma grande camada de invólucro ou algo em torno do OpenGL. Basta usá-lo diretamente. Se você me pedir para criar algo como Mass Effect, precisaremos de várias camadas de abstração em torno do nosso sistema de renderização. Jogar o motor Unreal em Tetris está usando uma espingarda para caçar moscas; torna a tarefa muito mais difícil do que apenas codificá-la manualmente. Você só deve construir o sistema grande e complexo que precisar e não maior.
Nicol Bolas
1
@Sullivan: A única complexidade que sua pergunta implica é onde você fala sobre a divisão em vários arquivos. O que é algo que eu faria mesmo em Tetris. E há muitos níveis de complexidade entre o Tetris e o mecanismo Unreal. Então, novamente: qual você está perguntando?
Nicol Bolas

Respostas:

6

Eu acho que o OO OpenGL não é tão necessário. É diferente quando você fala sobre shader, modelo, classe etc.

Basicamente, você faria a inicialização do jogo / mecanismo primeiro (e outras coisas). Em seguida, você carregaria texturas, modelos e shaders na RAM (se necessário) e nos Buffer Objects, além de carregar / compilar shaders. Depois disso, você, em sua estrutura de dados ou classe de shader, modelo, possui IDs int de shaders, objetos de buffer de modelo e textura.

Eu acho que a maioria dos mecanismos tem componentes de mecanismo e todos eles têm determinadas interfaces. Todos os mecanismos que eu analisei têm algum componente, como Renderer, SceneManager ou ambos (depende da complexidade do jogo / mecanismo). Do que você pode ter a classe OpenGLRenderer e / ou DXRenderer que implementam a interface Renderer. Então, se você tiver o SceneManager e o Renderer, poderá executar algumas das seguintes ações:

  • Atravesse o SceneManager e envie cada objeto ao Renderer para renderização
  • Encapsula maiúsculas em algum método SceneManager
  • Envie o SceneManager para o Renderer para que ele lide com isso

O renderizador provavelmente chamaria a função de desenho do objeto, que chamaria a função de desenho de cada malha composta e a malha vincularia o objeto de textura, vincularia o sombreador, chamaria a função de desenho do OpenGL e depois usaria sombreadores, objetos de buffer de dados de textura e objeto.

NOTA: este é apenas um exemplo, você deve estudar o SceneManager com mais detalhes e analisar seu caso de uso para ver qual é a melhor opção de implementação

Obviamente, você teria outros componentes do mecanismo, como MemoryManager , ResourceLoader etc., que cuidariam do uso de memória de vídeo e RAM, para que eles pudessem carregar / descarregar certos modelos / shaders / texturas, conforme necessário. Os conceitos para isso incluem cache de memória, mapeamento de memória, etc. etc. Existem muitos detalhes e conceitos sobre cada componente.

Dê uma olhada na descrição mais detalhada de outros mecanismos de jogos, existem muitos deles e a documentação deles está disponível.

Mas sim, as aulas facilitam a vida; você deve usá-los totalmente e lembrar-se de encapsulamento, herança, interfaces e outras coisas interessantes.

edin-m
fonte
Esta é a resposta que eu estava procurando. No entanto, quando você diz "... carregar texturas, modelos e sombreadores para a RAM (se necessário) e Buffer Objects e fazer upload / compilar sombreadores ..." me traz de volta à minha pergunta original; devo usar objetos para encapsular coisas como modelos e shaders?
Sullivan
O objeto é instância de uma classe e 'você deve usá-los totalmente'.
Edin-m
Desculpe, eu queria perguntar 'como devo usar objetos'. Foi mal.
Sullivan
Bem, isso depende do design da sua classe. Alguns exemplos básicos seriam intuitivos, algo como: object3d (class) possui malhas (class); cada malha possui material (classe); cada material possui textura (pode ser classe ou apenas ID) e sombreador (classe). Porém, ao ler sobre cada componente, você verá que existem abordagens diferentes, mas iguais, para criar SceneManager, Renderer, MemoryManager (todas elas podem ser de classe única ou múltipla, herdadas, etc). Dezenas de livros são escritos sobre esse tópico (arquitetura de mecanismo de jogo) e, para responder à pergunta em detalhes, é possível escrever uma.
Edin-m
Eu acho que essa é a melhor resposta que vou receber. Obrigado pela vossa ajuda :)
Sullivan
2

O OpenGL já possui alguns conceitos de 'Objeto'.

Por exemplo, qualquer coisa com um ID pode passar como um objeto (também existem itens especificamente chamados 'Objetos'). Buffers, Texturas, Objetos de buffer de vértice, Objetos de matriz de vértice, Objetos de buffer de quadro e assim por diante. Com um pouco de trabalho, você pode agrupar as aulas em torno deles. Ele também fornece uma maneira fácil de você voltar a funções obsoletas do OpenGL, se o seu contexto não suportar as extensões. Por exemplo, um VertexBufferObject pode voltar a usar glBegin (), glVertex3f () e assim por diante.

Existem algumas maneiras de você precisar se afastar dos conceitos tradicionais do OpenGL; por exemplo, você provavelmente deseja armazenar metadados sobre os buffers nos objetos de buffer. Por exemplo, se o buffer armazena vértices. Qual é o formato dos vértices (ou seja, posição, normais, cabos de texto e assim por diante). Quais primitivas ele usa (GL_TRIANGLES, GL_TRIANGLESTRIP, etc ...), informações de tamanho (quantos carros alegóricos são armazenados, quantos triângulos eles representam, etc ...). Apenas para facilitar a conexão deles nos comandos draw arrays.

Eu recomendo que você olhe para o OGLplus . São ligações C ++ para OpenGL.

Também glxx , isso é apenas para carregamento de extensões.

Além de agrupar a API do OpenGL, você deve criar um nível um pouco mais alto sobre ele.

Por exemplo, uma classe de gerente de materiais responsável por todos os shaders, carregando e usando-os. Também seria responsável por transferir propriedades para eles. Dessa forma, você pode simplesmente chamar: materials.usePhong (); material.setTexture (alguma textura); material.setColor (). Isso permite mais flexibilidade, já que você pode usar coisas mais recentes, como objetos de buffer uniforme compartilhado, para ter apenas um grande buffer contendo todas as propriedades que seus shaders usam em um bloco, mas se isso não for suportado, você volta a carregar em cada programa de shader. Você pode ter um grande shader monolítico e alternar entre diferentes modelos de shader usando rotinas uniformes, se houver suporte, ou pode voltar a usar vários shaders pequenos e diferentes.

Você também pode analisar as especificações GLSL para escrever seu código de sombreador. Por exemplo, o #include seria incrivelmente útil e muito fácil de implementar no código de carregamento do shader (também existe uma extensão ARB ). Você também pode gerar seu código rapidamente, com base em quais extensões são suportadas, por exemplo, usar um objeto uniforme compartilhado ou voltar a usar uniformes normais.

Finalmente, você desejará uma API de pipeline de renderização de nível superior que faça coisas como gráficos de cena, efeitos especiais (desfoque, brilho), coisas que exijam várias passagens de renderização, como sombras, iluminação e outras coisas. Além disso, uma API de jogo que não tem nada a ver com a API gráfica, mas apenas lida com objetos em um mundo.

David C. Bishop
fonte
2
"Eu recomendo que você olhe para o OGLplus. São ligações C ++ para o OpenGL." Eu recomendaria contra isso. Eu amo RAII, mas é totalmente inapropriado para a maioria dos objetos OpenGL. Objetos OpenGL são associados a uma construção global: o contexto OpenGL. Portanto, você não poderá criar esses objetos C ++ até ter um contexto e não poderá excluir esses objetos se o contexto já tiver sido destruído. Além disso, dá a ilusão de que você pode modificar objetos sem alterar o estado global. Isso é mentira (a menos que você esteja usando EXT_DSA).
Nicol Bolas
@ NicolBolas: Eu não poderia verificar se há um contexto e apenas inicializar então? Ou talvez apenas permita que os gerentes criem objetos que requerem um contexto OpenGL? --- btw, ótimo conjunto de tutoriais (link no seu perfil)! De alguma forma, nunca os encontrei ao pesquisar por OpenGL / Graphics.
Samaursa 19/10/12
@Samaursa: Para que fim? Se você possui um gerenciador de objetos OpenGL, então ... você realmente não precisa que os objetos se cuidem; o gerente pode fazer isso por eles. E se você apenas inicializá-los quando existe um contexto, o que acontece se você tentar usar um objeto que não foi inicializado corretamente? Ele apenas cria fragilidade na sua base de código. Também não muda nada do que eu disse sobre a capacidade de modificar esses objetos sem tocar no estado global.
Nicol Bolas
@ NicolBolas: "você não conseguiria criar esses objetos C ++ até ter um contexto" ... isso é diferente de usar construções OpenGL nuas? A meu ver, a oglplus::Contextclasse torna essa dependência altamente visível - isso seria um problema? Acredito que ajudará os novos usuários do OpenGL a evitar muitos problemas.
Xtofl
1

No OpenGL moderno, você pode separar quase totalmente objetos renderizados, usando diferentes vaos e programas de sombreamento. E mesmo a implementação de um objeto pode ser separada em várias camadas de abstração.

Por exemplo, se você deseja implementar um terreno, você pode definir um TerrainMesh cujo construtor cria os vértices e os índices para o terreno e os define em buffers de matriz e - se você atribuir uma posição de atributo - ele faz sombrear seus dados. Ele também deve saber como renderizá-lo e deve-se reverter todas as alterações de contexto feitas para configurar a renderização. Essa classe em si não deve saber nada sobre o programa de sombreador que a renderizará e também não deve saber nada sobre outros objetos na cena. Acima dessa classe, você pode definir um Terreno, que conhece o código do sombreador, e seu trabalho é criar a conexão entre o sombreador e o TerrainMesh. Isso deve significar obter atributos e posições uniformes, carregar texturas e coisas assim. Esta classe não deve saber nada sobre como o terreno é implementado, qual algoritmo LoD usa, é apenas responsável por sombrear o terreno. Acima disso, você pode definir a funcionalidade que não é do OpenGL, como behivour e detecção de colisão e outras.

Chegando ao ponto, mesmo que o OpenGL seja projetado para ser usado em nível baixo, você ainda pode criar camadas independentes de abstração, que permitem escalar para aplicações com o tamanho de um jogo Unreal. Mas o número de camadas que você deseja / precisa realmente depende do tamanho do aplicativo que deseja.

Mas não minta para si mesmo sobre esse tamanho, não tente imitar o modelo de objeto do Unity em um aplicativo de linha de 10k; o resultado será um desastre completo. Crie as camadas gradualmente, aumente apenas o número de camadas de abstração, quando necessário.

Csala Tamás
fonte
0

O ioDoom3 é provavelmente um ótimo ponto de partida, pois você pode confiar no Carmack para seguir boas práticas de codificação. Também acredito que ele não usa megatexturing no Doom3, por isso é relativamente direto como um tubo de renderização.

chrisvarnz
fonte
6
"como você pode confiar no Carmack para seguir boas práticas de codificação" Oh, cara, essa é boa. O Carmack segue boas práticas de codificação em C ++. Isso é um tumulto! Oh ... você não estava brincando. Hum ... você já olhou para o código dele? Ele é um programador C e é assim que ele pensa. O que é bom para C, mas a questão é sobre C ++ .
Nicol Bolas
Eu sou do segundo plano C, então o código dele faz muito sentido para mim: P e sim, passei um tempo trabalhando em jogos baseados em terremotos.
Chrisvarnz