Nível correto de abstração para um componente de renderização em 3d?

8

Já vi muitas perguntas nessa área, mas não exatamente, então peço desculpas se for uma duplicata.

Estou fazendo um pequeno jogo 3D. Bem, para ser honesto, é apenas um pequeno projeto de hobby e provavelmente não se tornará um jogo real. Ficarei feliz em fazer uma boa demonstração gráfica e aprender sobre renderização em 3D e design em c ++.

Minha intenção é usar o direct3d9 para renderizar, pois tenho pouca experiência com ele e parece atender aos meus requisitos. No entanto, se eu aprendi uma coisa como programador, é perguntar " existe alguma razão concebível para que esse componente possa ser substituído por uma implementação diferente " e se a resposta for afirmativa, preciso criar uma abstração e interface adequadas para esse componente . Portanto, mesmo que eu pretenda implantar o d3d9, preciso criar uma interface 3D que possa ser implementada para o d3d11, opengl ...

Minha pergunta então é em que nível é melhor fazer isso? Eu estou pensando que uma interface capaz de criar e desenhar mais tarde

  • Buffers de vértice e buffers de índice
  • Texturas
  • "Shaders" de vértice e pixel
  • Alguma representação do estado do desenho (modos de mesclagem etc ...)

Em outras palavras, uma interface de nível bastante baixo, onde meu código para desenhar, por exemplo, um modelo animado usaria a interface para obter buffers de vértice abstratos etc.

A alternativa é fazer isso em um nível superior, onde a interface possa desenhar objetos, animações, paisagens, etc., e implementá-los para cada sistema. Parece mais trabalho, mas acho mais flexível.

Então, na verdade, essa é minha pergunta: ao abstrair o sistema de desenho, qual o nível de interface que funciona melhor?

JohnB
fonte
Pode não responder totalmente à sua pergunta, mas eu aconselho a procurar fontes de mecanismos DirectX + OpenGL como OGRE e Irrlicht para ver como eles abstraíram.
O pato comunista

Respostas:

8

Desculpas pela duração desta resposta!

"existe alguma razão concebível para que esse componente possa ser substituído por uma implementação diferente" e se a resposta for afirmativa, preciso criar uma abstração e interface adequadas para esse componente.

"Qualquer razão concebível" é uma postura extrema demais para ser adotada. Quase todas as funções podem ser parametrizadas de alguma forma, cada módulo recebe uma estratégia ou política diferente e cada constante é aprimorada. Se você fornecer um nível extra de abstração para cada uma dessas opções, acabará escrevendo 2x mais código para nenhum ganho imediato, apenas um ganho potencial mais tarde, se precisar dessa flexibilidade.

Se você seguir o caminho de fornecer interfaces para que cada uma dessas coisas possa ser uma fachada para uma das várias opções diferentes, será necessário decidir como fornecer essas diferentes opções, incluindo padrões, e quando você tentar simplificar como o objeto original, muitas vezes você acaba com uma camada extra inteira por cima. Isso pode (e fica) absurdo em muitos softwares 'empresariais' do mundo real - posso sugerir humildemente a leitura desta peça de sátira que, infelizmente, chega perto demais de casa em muitos casos: Por que eu odeio frameworks .

O maior argumento contra isso é a complexidade. Vamos supor (provavelmente incorreto) que existe uma boa interface que possa ser aplicada igualmente bem a duas APIs de renderização 3D e que alguém possa descrevê-lo em uma resposta aqui. Você estaria trabalhando nessa interface, que, devido à sua natureza de múltiplos propósitos, sem dúvida abrangeria elementos que o DX9 não usa - por exemplo, a infinidade de extensões de API disponíveis no OpenGL, mas não no DirectX 9. uma complicação adicional que tornará seu código mais difícil de entender e manter e diminuirá o processo de aprendizado. Já é ruim o suficiente ao usar uma biblioteca multiplataforma existente, como SDL, porque as pessoas sempre encontram funções e restrições estranhas que existem apenas para corrigir algo em uma plataforma, mas você ' você não apenas precisa saber por que essa parte da interface está lá, mas também como ela pode ou não interagir com as partes que você está prestes a escrever. por exemplo. Você acabaria escrevendo objetos abstratos que encapsulam aspectos das extensões OpenGL que são necessárias no seu código de renderização principal, mesmo que você ainda não tenha como usá-los.

No entanto, no futuro, quando você for competente com o funcionamento do sistema DX9, poderá ver como o sistema OpenGL opera. Você verá como precisa adaptar seu sistema existente para que ambas as peças funcionem e o fará com a existência útil do caminho de código DX9 existente como um teste de unidade eficaz para garantir que a refatoração esteja correta.

Você tem sorte de estar trabalhando com um dos materiais mais fluidos e maleáveis ​​conhecidos pelo código-fonte de engenharia armazenados como dados do computador. Abrace essa bênção fazendo alterações quando você precisar delas, em vez de fazê-las com meses de antecedência e sobrecarregando-se nesse meio tempo, tendo que escrever para uma abstração da qual você não está obtendo ainda.

Kylotan
fonte
É assim que me sinto, mas, ao mesmo tempo, o conselho comum que vejo é que abstrair o código específico da plataforma para um jogo é fácil de fazer e deve ser feito ... Não sei o que pensar.
Rei Miyasaka 27/05
5

No entanto, se eu aprendi uma coisa como programador, é perguntar "existe alguma razão concebível para que esse componente possa ser substituído por uma implementação diferente" e se a resposta for afirmativa, preciso criar uma abstração e interface adequadas para esse componente .

Isso realmente não está respondendo à sua pergunta, mas, pela minha experiência como programador, a generalização prematura é tão ruim quanto a otimização prematura. Você raramente ganha algo e precisa mantê-lo ao longo do projeto.

Meu conselho é usar imediatamente o código do cliente D3D9 no seu projeto e, quando você determinar que precisa trocar de interface, faça uma interface com todas as coisas que você está usando no momento. Os programadores são péssimos em prever o que precisam, duplamente no seu caso em que você não está muito familiarizado com o material, dessa forma, está fazendo o mínimo de trabalho possível.

Veja também: http://c2.com/xp/YouArentGonnaNeedIt.html

Tetrad
fonte
O problema é que, se mais tarde ele precisar fazer uma abstração, precisará reescrever todo o código da interface. E se o D3D9 foi incluído globalmente em todo o projeto, será muito difícil refatorar todo esse código.
23411 DeadMG
11
De qualquer forma, ele terá que reescrever tudo quando tiver um caso de uso que não seja coberto por quaisquer suposições que ele faça agora. A refatoração deve ser adotada, especialmente para um projeto de aprendizagem.
Tetrad
Estou exagerando um pouco o caso. Eu também odeio abstrações desnecessárias. Mas neste caso eu posso dizer, na verdade, há uma boa chance de eu querer fazer isso com uma implementação diferente ...
JohnB
4

Hm, parece que não tenho reputação suficiente para postar comentários nas respostas.

Kylotan e Tetrad deram excelentes respostas. O que eu acrescentaria é sugerir que você separe o código de renderização da lógica do jogo - isso servirá a vários propósitos:

  • seu código será mais limpo
  • será mais fácil fazer alterações no renderizador sem reescrever metade da lógica do jogo a cada vez
  • eventualmente, fornecer módulos de renderização completamente diferentes é mais fácil dessa maneira

Como um bônus adicional, dessa maneira você aumentará sua abstração gradualmente enquanto trabalha no seu projeto. Como você diz, este é um projeto de aprendizado, então vá com calma e simplifique, adicionando complexidade e abstrações à medida que avança, em vez de fazer grandes planos, pois provavelmente você não possui experiência para fazer as coisas direito inicialmente (como também sugeriu Tetrad).

Gilead
fonte
Ah, de fato! Mas a questão é em que nível eu o separo? No nível de desenhar uma cidade inteira, ou no nível de desenhar um triângulo? Ou onde
JohnB 6/11/11
Uma maneira razoável seria manter a renderização das chamadas à API separadas. Provavelmente isso seria sobre 'desenhar um edifício ou um carro' na sua analogia (considerando que seus edifícios e carros são objetos únicos :)). Um exemplo: você deseja animar seu objeto (para simplificar, vamos supor que apenas um possa ser reproduzido por vez). Um objeto de jogo pode ter uma lista de seqüências disponíveis e seu comprimento e definir a animação ativa e sua velocidade, enquanto o renderizador gerencia, bem, a parte de renderização. Você também pode precisar recuperar dados do renderizador, por exemplo, a forma de colisão do objeto pode mudar durante a animação.
Gilead
1

Você não pode escrever uma abstração de baixo nível sobre esse tipo de coisa, porque os detalhes de um sombreador são completamente diferentes entre D3D9, 11, OGL, etc. É simplesmente impossível.

Você pode verificar as abstrações implementadas em, por exemplo, SFML, SDL, OGRE, se quiser ver o que outras pessoas optaram por fazer para renderizar abstrações.

DeadMG
fonte
A menos que os shaders não façam parte da abstração.
usar o seguinte comando
1

Eu tentei escrever uma API que envolve D3D e OGL ordenadamente. É um empreendimento enorme, eu aprendi. Muitas de suas diferenças podem ser reconciliadas. Por exemplo, carregando shaders. Encapsulo efetivamente o código de sombreador D3D e OGL em meu próprio formato. IE: No D3D, você precisa especificar o perfil de sombreador, para que o próprio arquivo tenha o perfil de sombreador gravado. Quando a fonte de sombreador encapsulado é entregue à minha API, ela chama o método de criação de sombreador D3D apropriado com o perfil de sombreador apropriado e tal.

No entanto, ainda existem muitas diferenças que eu não resolvi, nem nunca, porque decidi que uma abstração de nível superior é mais fácil de projetar e satisfatória para meus requisitos. Atualmente, estou trabalhando em algo semelhante à estrutura D3DXEffect. Os objetos têm uma técnica de renderização, que contém passes de renderização, que possuem um conjunto de estados de renderização, um conjunto de shaders e requerem determinadas entradas. Isso me permite abstrair mais da API.

Sion Sheevok
fonte
1

Não sei por que as pessoas saltam automaticamente para generalização / otimização prematuras quando perguntas como essas surgem. Há algo a ser dito sobre ser preventivo e perceber quando é provável que um componente de um sistema seja trocado. Todas as respostas sobre "não se preocupe e codifique até que você precise alterá-lo" geralmente colocam as pessoas em problemas no mundo real, pois elas provocam mudanças maiores no caminho ou deixam para trás códigos que devem ser projetados de maneira diferente. comece com, mas foi deixado porque "funciona". Pensar com antecedência e saber por onde parar antes de pensar demais é uma habilidade em si.

Seria muito mais fácil refatorar interfaces para acomodar diferenças entre o DX / OGL, do que introduzir uma implementação totalmente nova (por exemplo, DX) em um sistema estritamente codificado para funcionar com o OGL. A abstração do código deve acontecer em algum nível, pois você não gostaria de misturar código gráfico com código de lógica do jogo em todo o lugar ou repetir o código. Se estou trabalhando em um jogo e começando com o OpenGL (porque tenho experiência com ele), mas também sei que gostaria de, eventualmente, adquirir o jogo no Xbox / Windows (DX), seria bobagem para mim não leve isso em consideração ao projetar minha API gráfica. A idéia de refatorar algo tão complexo quanto um jogo e introduzir novas APIs gráficas é tediosa e propensa a erros.

Você deve considerar como vai usar sua API e construí-la à medida que suas necessidades / conhecimentos mudam durante o processo de desenvolvimento do jogo. Você não está fazendo uma abstração sobre OGL / DX de baixo nível, mas criando uma API para ajudar no carregamento de geometria, carregamento / compilação de sombreadores, carregamento de texturas etc.

nenchev
fonte