OpenGL orientado a objetos

12

He estado usando OpenGL durante un tiempo y he leído una gran cantidad de tutoriales. Además del hecho de que muchos de ellos todavía usan la tubería fija, generalmente arrojan toda la inicialización, los cambios de estado y el dibujo en un archivo fuente. Esto está bien para el alcance limitado de un tutorial, pero estoy teniendo dificultades para resolver cómo escalarlo a un juego completo.

¿Cómo divide su uso de OpenGL en los archivos? Conceptualmente, puedo ver los beneficios de tener, por ejemplo, una clase de renderizado que solo muestra cosas en la pantalla, pero ¿cómo funcionarían cosas como sombreadores y luces? ¿Debería tener clases separadas para cosas como luces y sombreadores?

Sullivan
fuente
1
Hay juegos de código abierto, así como tutoriales más grandes que tienen más código. Mire algunos de ellos y vea cómo está organizado el código.
MichaelHouse
¿Eres un principiante cuando se trata de renderizar motores?
Samaursa
Esta pregunta no puede ser respondida razonablemente sin una reducción del alcance. ¿Qué estás tratando de construir? ¿Qué tipo de juego, qué tipo de gráficos tiene, etc.?
Nicol Bolas
1
@Sullivan: "Pensé que este tipo de conceptos se aplicaría a casi cualquier juego". Ellos no. Si me pides que haga Tetris, no me molestaré en hacer una gran capa de envoltura o algo alrededor de OpenGL. Solo úsalo directamente. Si me pide que haga algo como Mass Effect, necesitaremos varias capas de abstracción en torno a nuestro sistema de renderizado. Lanzar el motor Unreal a Tetris es usar una escopeta para cazar moscas; hace la tarea mucho más difícil que simplemente codificarla directamente a mano. Solo debe construir un sistema tan grande y complejo como sea necesario , y no más grande.
Nicol Bolas
1
@Sullivan: la única complejidad que implica su pregunta es cuando habla de dividir en varios archivos. Que es algo que haría incluso en Tetris. Y hay muchos niveles de complejidad entre Tetris y el motor Unreal. Así que de nuevo: ¿por cuál preguntas?
Nicol Bolas

Respuestas:

6

Creo que OO OpenGL no es tan necesario. Es diferente cuando se habla de sombreador, modelo, clase, etc.

Básicamente, primero harías la inicialización del juego / motor (y otras cosas). Luego, cargaría texturas, modelos y sombreadores en RAM (si es necesario) y objetos de almacenamiento intermedio, y cargaría / compilaría sombreadores. Después de eso, usted, en su estructura de datos o clase de sombreador, modelo, tiene ID int de sombreadores, modelo y objetos de búfer de textura.

Creo que la mayoría de los motores tienen componentes de motor y todos tienen ciertas interfaces. Todos los motores que he examinado tienen algún componente como Renderer o SceneManager o ambos (depende de la complejidad del juego / motor). De lo que puede tener la clase OpenGLRenderer y / o DXRenderer que implementan la interfaz Renderer. Luego, si tiene SceneManager y Renderer , puede hacer algunas de las siguientes cosas:

  • Atraviese SceneManager y envíe cada objeto al Renderer para su representación
  • Encapsular mayúsculas en algún método SceneManager
  • Enviar SceneManager a Renderer para que lo maneje

El procesador probablemente llamaría la función de dibujo del objeto, que llamaría la función de dibujo de cada malla que está compuesta y la malla uniría el objeto de textura, el sombreador de enlace, llamaría a la función de dibujo de OpenGL y luego no usaría sombreadores, objetos de búfer de datos de textura y objeto.

NOTA: este es solo un ejemplo, debe estudiar SceneManager con más detalle y analizar su caso de uso para ver cuál es la mejor opción de implementación

Por supuesto, tendría otros componentes del motor, como MemoryManager , ResourceLoader , etc., que se encargarían del uso de memoria de video y RAM, para que pudieran cargar / descargar ciertos modelos / sombreadores / texturas según sea necesario. Los conceptos para esto incluyen almacenamiento en memoria caché, mapeo de memoria, etc. Hay muchos detalles y conceptos sobre cada componente.

Eche un vistazo a una descripción más detallada de otros motores de juegos, hay muchos de ellos y su documentación está bastante disponible.

Pero sí, las clases hacen la vida más fácil; debe usarlos por completo y recordar sobre la encapsulación, la herencia, las interfaces y más cosas interesantes.

edin-m
fuente
Esta es la respuesta que estaba buscando. Sin embargo, cuando dice "... cargar texturas, modelos y sombreadores en RAM (si es necesario) y objetos de búfer, y cargar / compilar sombreadores ..." me devuelve a mi pregunta original; ¿Debo usar objetos para encapsular cosas como modelos y sombreadores?
Sullivan
object es una instancia de una clase, y 'deberías usarlos por completo'.
edin-m
Lo siento, quise preguntar '¿cómo debo usar los objetos?' Culpa mía.
Sullivan
Bueno, depende del diseño de tu clase. Algún ejemplo básico sería intuitivo, algo así como: object3d (clase) tiene mallas (clase); cada malla tiene material (clase); cada material tiene textura (puede ser de clase, o solo id) y sombreador (clase). Pero a medida que lea acerca de cada componente, verá que hay enfoques diferentes pero iguales para hacer SceneManager, Renderer, MemoryManager (todos estos pueden ser de una sola clase o múltiples, heredados, etc.). Se escriben docenas de libros sobre este tema (arquitectura del motor del juego), y para responder a las preguntas en detalle se podría escribir uno.
edin-m
Creo que esta es la mejor respuesta que voy a obtener. Gracias por su ayuda :)
Sullivan
2

OpenGL ya tiene algunos conceptos de 'Objeto'.

Por ejemplo, cualquier cosa con una identificación puede pasar como un objeto (también hay cosas específicamente llamadas 'Objetos'). Búferes, texturas, objetos de búfer de vértices, objetos de matriz de vértices, objetos de búfer de marco, etc. Con un poco de trabajo puedes envolver clases alrededor de ellos. También le brinda una manera fácil de recurrir a las antiguas funciones de OpenGL en desuso si su contexto no admite las extensiones. Por ejemplo, un VertexBufferObject podría recurrir al uso de glBegin (), glVertex3f (), etc.

Hay algunas formas en que puede necesitar alejarse de los conceptos tradicionales de OpenGL, por ejemplo, probablemente desee almacenar metadatos sobre los búferes en los objetos de búfer. Por ejemplo, si el búfer almacena vértices. ¿Cuál es el formato de los vértices (es decir, posición, normales, códigos de texto, etc.)? Qué primitivas usa (GL_TRIANGLES, GL_TRIANGLESTRIP, etc.), información de tamaño (cuántos flotadores están almacenados, cuántos triángulos representan, etc.). Solo para facilitar su conexión a los comandos de arrays de arrays.

Te recomiendo que mires OGLplus . Son enlaces de C ++ para OpenGL.

También glxx , eso es solo para la carga de extensión.

Además de envolver la API de OpenGL, debería considerar hacer una compilación de un nivel ligeramente superior por encima.

Por ejemplo, una clase de administrador de materiales que es responsable de todos sus sombreadores, cargarlos y usarlos. También sería responsable de transferirles propiedades. De esa manera puede llamar a: materials.usePhong (); material.setTexture (alguna textura); material.setColor (). Esto permite una mayor flexibilidad, ya que puede usar cosas más nuevas, como objetos de almacenamiento intermedio uniformes compartidos para tener solo 1 gran almacenamiento intermedio que contenga todas las propiedades que usan sus sombreadores en 1 bloque, pero si no es compatible, recurra a la carga en cada programa de sombreador. Puede tener 1 sombreador monolítico grande e intercambiar entre diferentes modelos de sombreador usando rutinas uniformes si es compatible o puede recurrir a usar un montón de sombreadores pequeños diferentes.

También puede ver cómo gastar en las especificaciones GLSL para escribir su código de sombreador. Por ejemplo, #include sería increíblemente útil y muy fácil de implementar en el código de carga de su sombreador (también tiene una extensión ARB ). También puede generar su código sobre la marcha en función de qué extensiones son compatibles, por ejemplo, use un objeto uniforme compartido o recurra al uso de uniformes normales.

Finalmente, querrá una API de canalización de representación de nivel superior que haga cosas como gráficos de escenas, efectos especiales (desenfoque, brillo), cosas que requieren múltiples pases de representación como sombras, iluminación y demás. Y además, una API de juego que no tiene nada que ver con la API de gráficos, sino que solo trata con objetos en un mundo.

David C. Bishop
fuente
2
"Te recomiendo que mires OGLplus. Son enlaces de C ++ para OpenGL". Yo recomendaría contra eso. Me encanta RAII, pero es completamente inapropiado para la mayoría de los objetos OpenGL. Los objetos OpenGL están asociados con una construcción global: el contexto OpenGL. Por lo tanto, no podrá crear estos objetos C ++ hasta que tenga un contexto, y no podrá eliminar estos objetos si el contexto ya ha sido destruido. Además, da la ilusión de que puede modificar objetos sin cambiar el estado global. Esto es una mentira (a menos que esté usando EXT_DSA).
Nicol Bolas
@NicolBolas: ¿No podría verificar si hay un contexto y solo inicializar entonces? ¿O tal vez solo permita que los gerentes creen los objetos que requieren un contexto OpenGL? --- por cierto, gran conjunto de tutoriales (enlace en su perfil)! De alguna manera nunca los encontré cuando buscaba OpenGL / Graphics.
Samaursa
@Samaursa: ¿Con qué fin? Si tiene un administrador para objetos OpenGL, entonces ... realmente no necesita que los objetos se cuiden solos; el gerente puede hacerlo por ellos. Y si solo los inicializa cuando existe un contexto, ¿qué sucede si intenta usar un objeto que no se inicializó correctamente? Simplemente crea fragilidad en su código base. Tampoco cambia nada de lo que dije sobre la capacidad de modificar estos objetos sin tocar el estado global.
Nicol Bolas
@NicolBolas: "no podría crear estos objetos C ++ hasta que tuviera un contexto" ... ¿es diferente de usar construcciones OpenGL desnudas? En mi opinión, la oglplus::Contextclase hace que esta dependencia sea muy visible: ¿sería un problema? Creo que ayudará a los nuevos usuarios de OpenGL a evitar muchos problemas.
xtofl
1

En OpenGL moderno, puede separar casi por completo los objetos renderizados entre sí, utilizando diferentes vaos y programas de sombreado. E incluso la implementación de un objeto se puede separar en muchas capas de abstracción.

Por ejemplo, si desea implementar un terreno, puede definir un TerrainMesh cuyo constructor crea los vértices e índices para el terreno, y los establece en búferes de matriz y, si le da una posición de atributo, el sombreador pliega sus datos. También debe saber cómo representarlo, y debe tener cuidado de revertir todos los cambios de contexto que ha realizado para configurar el renderizado. Esta clase en sí misma no debe saber nada sobre el programa de sombreado que lo va a representar, y tampoco debe saber nada sobre ningún otro objeto en la escena. Por encima de esta clase, podría definir un terreno, que conoce el código del sombreador, y su trabajo es crear la conexión entre el sombreador y TerrainMesh. Esto debería significar obtener atributos y posiciones uniformes y cargar texturas, y cosas así. Esta clase no debería saber nada sobre cómo se implementa el terreno, qué algoritmo LoD usa, solo es responsable de sombrear el terreno. Por encima de esto, podría definir la funcionalidad que no es OpenGL como comportamiento y detección de colisiones, etc.

Llegando al punto, a pesar de que OpenGL está diseñado para usarse a bajo nivel, aún puede construir capas independientes de abstracción, que le permiten escalar a aplicaciones con un tamaño de un juego irreal. Pero la cantidad de capas que desea / necesita realmente depende del tamaño de la aplicación que desea.

Pero no se mienta acerca de este tamaño, no intente imitar el modelo de objetos de Unity en una aplicación de línea de 10k, el resultado será un completo desastre. Construya las capas gradualmente, solo aumente el número de capas de abstracción, cuando sea necesario.

Csala Tamás
fuente
0

ioDoom3 es probablemente un excelente punto de partida, ya que puede confiar en Carmack para seguir una excelente práctica de codificación. También creo que él no usa texturas megate en Doom3, por lo que es relativamente sencillo como una tubería de renderizado.

chrisvarnz
fuente
66
"como puedes confiar en Carmack para seguir una gran práctica de codificación" Oh, hombre, esa es una buena. Carmack sigue una gran práctica de codificación en C ++. Eso es un motín! Oh ... no estabas bromeando. Um ... ¿alguna vez has mirado su código? Es un programador en C y así es como él piensa. Lo cual está bien para C, pero la pregunta es sobre C ++ .
Nicol Bolas
Vengo de un fondo C, por lo que su código tiene mucho sentido para mí: P y sí, pasé un poco de tiempo trabajando en juegos basados ​​en terremotos.
chrisvarnz