¿Nivel correcto de abstracción para un componente de renderizado 3D?

8

He visto muchas preguntas en esta área, pero no esta pregunta exacta, así que disculpas si esto es un duplicado.

Estoy haciendo un pequeño juego en 3D. Bueno, para ser honesto, es solo un pequeño proyecto de pasatiempo y probablemente no resulte ser un juego real, estaré encantado de hacer una buena demostración de gráficos y aprender sobre renderizado 3D y diseño de c ++.

Mi intención es usar direct3d9 para renderizar, ya que tengo poca experiencia y parece cumplir con mis requisitos. Sin embargo, si he aprendido una cosa como programador es preguntar " ¿hay alguna razón concebible de que este componente pueda ser reemplazado por una implicación diferente " y si la respuesta es sí, entonces necesito diseñar una abstracción e interfaz adecuadas para ese componente . Entonces, aunque tengo la intención de implicar d3d9, necesito diseñar una interfaz 3d que pueda implementarse para d3d11, opengl ...

Mi pregunta es, ¿en qué nivel es mejor hacer esto? Estoy pensando que una interfaz capaz de crear y luego dibujar

  • Tampones de vértices y tampones de índice
  • Texturas
  • "Sombreadores" de vértices y píxeles
  • Alguna representación del estado de dibujo (modos de fusión, etc.)

En otras palabras, una interfaz de nivel bastante bajo en la que mi código para dibujar, por ejemplo, un modelo animado, usaría la interfaz para obtener buffers de vértices abstractos, etc.

La alternativa es hacer esto en un nivel superior donde la interfaz puede dibujar objetos, animaciones, paisajes, etc., e implementarlos para cada sistema. Parece más trabajo, pero supongo que es más flexible.

Entonces esa es mi pregunta realmente, cuando abstrae el sistema de dibujo, ¿qué nivel de interfaz funciona mejor?

JohnB
fuente
Es posible que no responda su pregunta por completo, pero le aconsejaría que consulte las fuentes de los motores DirectX + OpenGL como OGRE e Irrlicht para ver cómo lo han abstraído.
El pato comunista

Respuestas:

8

¡Disculpas por la duración de esta respuesta!

"¿hay alguna razón concebible de que este componente pueda ser reemplazado por una implicación diferente?" y si la respuesta es sí, entonces necesito diseñar una abstracción e interfaz adecuadas para ese componente.

"Cualquier razón concebible" es una postura demasiado extrema para adoptar. Casi todas las funciones se pueden parametrizar de alguna manera, a cada módulo se le da una estrategia o política diferente, y cada constante se modifica. Si proporciona un nivel adicional de abstracción para cada uno de estos, entonces termina escribiendo 2 veces más código sin ganancia inmediata, solo una ganancia potencial más adelante si alguna vez necesita esa flexibilidad.

Si sigue el camino de proporcionar interfaces para que cada una de estas cosas pueda ser una fachada para una de varias opciones diferentes, entonces debe decidir cómo proporcionar estas diferentes opciones, incluidos los valores predeterminados, y cuándo intenta hacerlo de manera simple Como el objeto original era, a menudo terminas con una capa extra completa en la parte superior. Esto puede (y lo hace) obtener absurda en un montón de mundo real 'empresa' de software - ¿puedo sugerir humildemente leer esta pieza sátira que por desgracia golpea demasiado cerca de casa, en muchos casos: Por qué odio a los marcos .

El mayor argumento en contra de esto es la complejidad. Supongamos (probablemente incorrecto) que existe una buena interfaz que podría aplicarse igualmente bien a dos API de renderizado 3D, y que alguien puede describirte esto en una respuesta aquí. Entonces estaría trabajando para esta interfaz, que, debido a su naturaleza multipropósito, sin duda cubriría elementos que DX9 no usa, por ejemplo, la gran cantidad de extensiones API que están disponibles en OpenGL pero no en DirectX 9. Esto es Una complicación adicional que hará que su código sea más difícil de entender y mantener, y ralentizará su proceso de aprendizaje. Ya es bastante malo cuando se utiliza una biblioteca multiplataforma existente como SDL, porque las personas siempre encuentran funciones y restricciones extrañas que existen únicamente para arreglar algo en una plataforma, pero usted ' No solo tiene que aprender por qué esa parte de la interfaz está allí, sino también cómo puede interactuar o no con las partes que está a punto de escribir. p.ej. Terminaría escribiendo objetos abstractos que encapsulan aspectos de las extensiones de OpenGL que son necesarios en su código de representación principal, a pesar de que todavía no tiene forma de usarlos.

Sin embargo, en el futuro, una vez que sea competente con el funcionamiento del sistema DX9, puede ver cómo funciona el sistema OpenGL. Luego verá cómo necesita adaptar su sistema existente para que ambas partes funcionen, y lo hará, con la práctica existencia de su ruta de código DX9 existente como una prueba de unidad efectiva para garantizar que su refactorización sea correcta.

Tienes suerte de que estás trabajando con uno de los materiales más fluidos y maleables conocidos por la ingeniería: el código fuente almacenado como datos informáticos. Acepta esa bendición haciendo cambios cuando sabes que los necesitas, en lugar de hacerlo con meses de anticipación y cargarte mientras tanto al tener que escribir en una abstracción de la que aún no estás obteniendo.

Kylotan
fuente
Así es como me siento, pero al mismo tiempo, el consejo común que veo es que abstraer el código específico de la plataforma para un juego es fácil de hacer y debe hacerse ... No sé qué pensar.
Rei Miyasaka
5

Sin embargo, si he aprendido una cosa como programador es preguntar "¿hay alguna razón concebible de que este componente pueda ser reemplazado por una implicación diferente" y si la respuesta es sí, entonces necesito diseñar una abstracción e interfaz adecuadas para ese componente .

Esto realmente no responde a su pregunta, pero desde mi experiencia como programador, la generalización prematura es tan mala como la optimización prematura. Raramente obtienes algo y tienes que mantenerlo durante el transcurso del proyecto.

Mi consejo es utilizar directamente el código de cliente D3D9 en su proyecto, y cuando determine que necesita cambiar de interfaz, cree una interfaz con todas las cosas que está utilizando actualmente. Los programadores son terribles para predecir lo que podrían necesitar, doblemente en su caso donde no está terriblemente familiarizado con el material, por lo que de esta manera está haciendo la menor cantidad de trabajo.

Ver también: http://c2.com/xp/YouArentGonnaNeedIt.html

Tétrada
fuente
El problema con esto es que si luego tiene que ir a una abstracción, entonces tiene que volver a escribir todo su código de interfaz. Y si D3D9 se incluyó globalmente a lo largo de su proyecto, será muy difícil refactorizar todo ese código.
DeadMG
1
Sin embargo, tendrá que reescribir todo de todos modos cuando tenga un caso de uso que no esté cubierto por las suposiciones que haga ahora. Se debe adoptar la refactorización, especialmente para un proyecto de aprendizaje.
Tetrad
Estoy exagerando un poco el caso. Yo también odio la abstracción innecesaria. Pero en este caso puedo decir que en realidad hay una posibilidad buena que querrá hacer esto con una implementación diferente ...
JohnB
4

Parece que no tengo suficiente reputación para publicar comentarios en las respuestas.

Tanto Kylotan como Tetrad dieron excelentes respuestas. Lo que agregaría es sugerirle que separe el código de representación de la lógica del juego; esto servirá para varios propósitos:

  • tu código estará más limpio
  • te será más fácil hacer cambios en tu renderizador sin tener que reescribir la mitad de la lógica del juego cada vez
  • eventualmente, proporcionar módulos de representación completamente diferentes es más fácil de esta manera

Como una ventaja adicional, de esta manera construirá su abstracción gradualmente a medida que trabaje en su proyecto. Como usted dice, este es un proyecto de aprendizaje, así que tómelo con calma y hágalo simple, agregando complejidad y abstracciones a medida que avanza en lugar de hacer grandes planes, ya que probablemente carezca de experiencia para hacerlo bien inicialmente (como también sugirió Tetrad).

Galaad
fuente
Oh por cierto! Pero la pregunta es ¿en qué nivel lo separo? ¿Al nivel de dibujar una ciudad entera, o al nivel de dibujar un triángulo? O en el medio
JohnB
Una forma razonable sería mantener las llamadas de API por separado. Probablemente se trata de 'dibujar un edificio o un automóvil' en su analogía (considerando que sus edificios y automóviles son objetos únicos :)). Un ejemplo: desea animar su objeto (por simplicidad supongamos que solo se puede jugar uno a la vez). Un objeto de juego podría tener una lista de secuencias disponibles y su longitud y establecer la animación activa y su velocidad mientras el renderizador administraría, bueno, la parte de renderizado. También es posible que necesite obtener datos del renderizador, por ejemplo, la forma de colisión del objeto puede cambiar durante la animación.
Galaad
1

No puede escribir una abstracción de bajo nivel sobre este tipo de cosas, porque los detalles de un sombreador son completamente diferentes entre D3D9, 11, OGL, etc. Es simplemente imposible.

Puede ver las abstracciones implementadas en, por ejemplo, SFML, SDL, OGRE si desea ver lo que otras personas han elegido hacer para generar abstracciones.

DeadMG
fuente
A menos que los sombreadores no sean parte de la abstracción.
user253751
1

Traté de escribir una API que se ajusta perfectamente tanto a D3D como a OGL. Es una empresa masiva, aprendí. Muchas de sus diferencias pueden conciliarse. Por ejemplo, cargando sombreadores. Encapsulo efectivamente el código de sombreador D3D y OGL en mi propio formato. IE: en D3D, debe especificar el perfil de sombreador, por lo que el archivo tiene el perfil de sombreador escrito. Cuando la fuente de sombreador encapsulado se entrega a mi API, llama al método de creación de sombreador D3D apropiado con el perfil de sombreador apropiado y tal.

Sin embargo, todavía hay muchas diferencias que no he resuelto, ni podría haberlo hecho, porque he decidido que una abstracción de nivel superior es más fácil de diseñar y satisfactoria para mis requisitos. Actualmente estoy trabajando en algo similar al marco D3DXEffect. Los objetos tienen una técnica de renderizado, que contiene pases de renderizado, que tienen un conjunto de estados de renderizado, un conjunto de sombreadores y requieren ciertas entradas. Esto me permite abstraer más de la API.

Sion Sheevok
fuente
1

No sé por qué las personas saltan automáticamente a la generalización / optimización prematura cuando surgen preguntas como estas. Hay algo que decir acerca de ser preventivo y darse cuenta cuando un componente en un sistema es probable que sea intercambiado. Todas las respuestas sobre "no te preocupes y codifica hasta que necesites cambiarlo" generalmente causan problemas a las personas en el mundo real, ya que provocan cambios más grandes en el futuro o dejan código que debe diseñarse de manera diferente para empezar con, pero se quedó porque "funciona". Pensar con anticipación y saber dónde detenerse antes de pensar demasiado es una habilidad en sí misma.

Sería mucho más fácil refactorizar las interfaces para acomodar las diferencias entre DX / OGL, que introducir una implementación completamente nueva (DX, por ejemplo) en un sistema que esté estrictamente codificado para funcionar con OGL. De todos modos, la abstracción del código debería suceder en algún nivel, ya que no querría mezclar el código de gráficos con el código de lógica del juego en todo el lugar, o repetir el código. Si estoy trabajando en un juego y comenzando con OpenGL (porque tengo experiencia con él), pero también sé que eventualmente me gustaría obtener el juego en Xbox / Windows (DX), sería una tontería para mí. No tenga eso en cuenta al diseñar mi API de gráficos. La idea de refactorizar algo tan complejo como un juego e introducir nuevas API de gráficos es tediosa y propensa a errores.

Debes considerar cómo vas a usar tu API y desarrollarla a medida que cambien tus necesidades / conocimientos durante el proceso de desarrollo del juego. No está haciendo una abstracción sobre OGL / DX de bajo nivel, sino que está creando una API para ayudarlo a cargar geometría, cargar / compilar sombreadores, cargar texturas, etc.

nenchev
fuente