Dibujando muchos mosaicos con OpenGL, la forma moderna

35

Estoy trabajando en un pequeño juego de PC basado en fichas / sprites con un equipo de personas, y nos encontramos con problemas de rendimiento. La última vez que utilicé OpenGL fue alrededor de 2004, así que me enseñé a mí mismo cómo usar el perfil central y me encuentro un poco confundido.

Necesito dibujar en el vecindario de 250-750 48x48 mosaicos en la pantalla en cada cuadro, así como quizás alrededor de 50 sprites. Las fichas solo cambian cuando se carga un nuevo nivel, y los sprites cambian todo el tiempo. Algunas de las fichas están formadas por cuatro piezas de 24x24, y la mayoría (pero no todas) de los sprites son del mismo tamaño que las fichas. Muchos de los azulejos y sprites usan la mezcla alfa.

En este momento estoy haciendo todo esto en modo inmediato, lo que sé que es una mala idea. De todos modos, cuando uno de los miembros de nuestro equipo intenta ejecutarlo, obtiene velocidades de cuadro muy malas (~ 20-30 fps), y es mucho peor cuando hay más mosaicos, especialmente cuando muchos de esos mosaicos son del tipo que Se cortan en pedazos. Todo esto me hace pensar que el problema es la cantidad de llamadas realizadas.

He pensado en algunas posibles soluciones para esto, pero quería dirigirlas por algunas personas que saben de lo que están hablando para que no pierda mi tiempo en algo estúpido:

Azulejos

  1. Cuando se carga un nivel, dibuje todas las fichas una vez en un búfer de cuadro unido a una textura de bocina grande, y simplemente dibuje un rectángulo grande con esa textura en cada cuadro.
  2. Coloque todos los mosaicos en un búfer de vértices estáticos cuando se cargue el nivel y dibuje de esa manera. No sé si hay una manera de dibujar objetos con diferentes texturas con una sola llamada a glDrawElements, o si esto es algo que quisiera hacer. ¿Tal vez solo coloque todos los mosaicos en una gran textura gigante y use coordenadas de textura divertidas en el VBO?

SPRITES:

  1. Dibuja cada sprite con una llamada separada a glDrawElements. Esto parece implicar una gran cantidad de cambio de textura, lo que me dicen que es malo. ¿Las matrices de texturas pueden ser útiles aquí?
  2. Use un VBO dinámico de alguna manera. La misma pregunta de textura que el número 2 anterior.
  3. Punto sprites? Esto es probablemente una tontería.

¿Alguna de estas ideas es sensata? ¿Hay una buena implementación en algún lugar que pueda mirar?

Nic
fuente
Si los mosaicos no se mueven ni cambian y se ven de la misma manera en todo el nivel, debe usar la primera idea: el búfer de cuadros. Será lo más eficiente.
zacharmarz
Intente usar un atlas de texturas para no tener que cambiar las texturas, pero manteniendo todo lo demás igual. ¿Cómo está su velocidad de cuadros?
user253751

Respuestas:

25

La forma más rápida de representar los mosaicos es empacar los datos de vértice en un VBO estático con índices (como indica glDrawElements). Escribirlo en otra imagen es totalmente innecesario y solo requerirá mucha más memoria. El cambio de textura es MUY costoso, por lo que probablemente desee empaquetar todos los mosaicos en un llamado Atlas de textura y dar a cada triángulo en el VBO las coordenadas de textura correctas. En base a esto, no debería ser un problema renderizar 1000, incluso 100000 mosaicos, dependiendo de su hardware.

La única diferencia entre la representación de mosaico y la representación de Sprite es probablemente que los sprites son dinámicos. Por lo tanto, para obtener el mejor rendimiento, pero fácil de lograr, puede simplemente poner las coordenadas de los vértices del sprite en un flujo dibuje VBO cada cuadro y dibuje con glDrawElements. Empaque también todas las texturas en un Atlas de texturas. Si sus sprites rara vez se mueven, también podría intentar hacer un VBO dinámico y actualizarlo cuando se mueva un sprite, pero eso es una exageración total aquí, ya que solo desea renderizar algunos sprites.

Puedes ver un pequeño prototipo que hice en C ++ con OpenGL: Particulate

Supongo que renderizo unos 10000 puntos de sprites, con un promedio de fps de 400 en una máquina habitual (Quad Core @ 2.66GHz). Tiene un límite de CPU, lo que significa que la tarjeta gráfica podría reproducir aún más. Tenga en cuenta que no uso Atlas de texturas aquí, ya que solo tengo una textura única para las partículas. Las partículas se representan con GL_POINTS y ​​los sombreadores calculan el tamaño cuádruple real en ese momento, pero creo que también hay un renderizador cuádruple.

Ah, y sí, a menos que tenga un cuadrado y use sombreadores para el mapeo de texturas, GL_POINTS es bastante tonto. ;)

Marco
fuente
Los sprites cambian sus posiciones y qué textura están usando, y la mayoría de ellos lo hacen en cada cuadro. Además, sprites y ser creado y destruido muy a menudo. ¿Son estas cosas que puede manejar un stream draw VBO?
Nic
2
El sorteo de flujo básicamente significa: "Enviar estos datos a la tarjeta gráfica y descartarlos después de dibujar". Por lo tanto, debe enviar los datos nuevamente cada fotograma y eso significa que no importa cuántos sprites renderice, qué posición tienen, qué coordenadas de textura o qué color. Pero enviar todos los datos a la vez y dejar que la GPU lo procese es MUCHO más rápido que el modo inmediato, por supuesto.
Marco
Todo esto tiene sentido. ¿Vale la pena usar un búfer de índice para esto? Los únicos vértices que se repetirán son dos esquinas de cada rectángulo, ¿verdad? (Entiendo que los índices son la diferencia entre glDrawElements y glDrawArrays. ¿Es eso cierto?)
Nic
1
Sin índices, no puede usar GL_TRIANGLES, que generalmente es malo, ya que este método de dibujo es el que tiene el mejor rendimiento garantizado. Además, la implementación de GL_QUADS está en desuso en OpenGL 3.0 (fuente: stackoverflow.com/questions/6644099/… ). Los triángulos son la malla nativa de cualquier tarjeta gráfica. Entonces, "usa" 2 * 6 bytes más para guardar 2 ejecuciones de sombreador de vértices y vertex_size * 2 bytes. Por lo tanto, generalmente puede decir que SIEMPRE es mejor.
Marco
2
El enlace a Particulate está muerto ... ¿Podría proporcionar uno nuevo por favor?
SWdV
4

Incluso con este número de sorteo llama no debería estar viendo ese tipo de caída de rendimiento - el modo inmediato puede ser lento, pero no es que lenta (para referencia, aunque queridos de edad Quake puede manejar varios miles de llamadas en modo inmediato por trama sin caer tan mal)

Sospecho que hay algo más interesante pasando aquí. Lo primero que debe hacer es invertir algo de tiempo en el perfil de su programa, de lo contrario, corre un gran riesgo de volver a armar basándose en una suposición que puede resultar en una ganancia de rendimiento cero. Así que ejecútelo incluso en algo tan básico como GLIntercept y vea a dónde va su tiempo. De acuerdo con los resultados de eso, podrá abordar el problema con información real sobre cuáles son sus principales cuellos de botella.

Maximus Minimus
fuente
He hecho algunos perfiles, aunque es incómodo porque los problemas de rendimiento no están ocurriendo en la misma máquina que el desarrollo. Soy un poco escéptico de que el problema esté en otra parte porque los problemas definitivamente aumentan con el número de mosaicos, y los mosaicos literalmente no hacen nada excepto ser dibujados.
Nic
¿Qué tal cambios de estado entonces? ¿Estás agrupando tus mosaicos opacos por estado?
Maximus Minimus
Esa es una posibilidad. Esto definitivamente merece más atención de mi parte.
Nic
2

Bien, ya que mi última respuesta se me fue de las manos aquí, es una nueva que quizás sea más útil.


Acerca del rendimiento 2D

Primero algunos consejos generales: 2D no es exigente para el hardware actual, incluso el código en gran medida no optimizado funcionará. Sin embargo, eso no significa que debas usar el Modo Intermedio, al menos asegúrate de no cambiar los estados cuando sea innecesario (por ejemplo, no unas una nueva textura con glBindTexture cuando la misma textura ya está unida, si la verificación de la CPU es toneladas más rápido que una llamada glBindTexture) y no usar algo tan incorrecto y estúpido como glVertex (incluso glDrawArrays será mucho más rápido y no es más difícil de usar, aunque no es muy "moderno"). Con esas dos reglas muy simples, el tiempo de fotograma debería ser de al menos 10 ms (100 fps). Ahora, para obtener aún más velocidad, el siguiente paso lógico es el procesamiento por lotes, por ejemplo, agrupar tantas llamadas de extracción en una sola, para esto debe considerar implementar atlas de texturas, para que pueda minimizar la cantidad de enlaces de textura y así aumentar la cantidad de rectángulos que puede dibujar con una llamada a una gran cantidad. Si ahora no tienes alrededor de 2 ms (500 fps), estás haciendo algo mal :)


Mapas de azulejos

Implementar el código de dibujo para los mapas de mosaico es encontrar el equilibrio entre flexibilidad y velocidad. Puede usar VBO estáticos, pero eso no funcionará con mosaicos animados o simplemente puede generar los datos de vértice en cada cuadro y aplicar las reglas que expliqué anteriormente, eso es muy flexible, pero no tan rápido.

En mi respuesta anterior, había introducido un modelo diferente en el que el sombreador de fragmentos se encarga de toda la textura, pero se señaló que requiere una búsqueda de textura dependiente y, por lo tanto, podría no ser tan rápido como los otros métodos. (La idea es básicamente que cargue solo las indicaciones de mosaico y en el sombreador de fragmentos calcule las coordenadas de textura, lo que significa que puede dibujar todo el mapa con solo un rectángulo)


Sprites

Los sprites requieren mucha flexibilidad, lo que hace que sea muy difícil de optimizar, aparte de los discutidos en la sección "Acerca del rendimiento 2D". Y a menos que desee diez mil sprites en la pantalla al mismo tiempo, probablemente no valga la pena el esfuerzo.

API-Bestia
fuente
1
E incluso si tiene diez mil sprites, el hardware moderno debería funcionar a una velocidad decente :)
Marco
@ API-Beast espera qué? ¿Cómo se calcula la textura UV en el sombreador de fragmentos? ¿No se supone que debes enviar los rayos UV al sombreador de fragmentos?
HgMerk
0

Si todo lo demás falla...

Configure un método de dibujo flip-flop. Solo actualice cada otro sprite a la vez. Sin embargo, incluso con VisualBasic6 y métodos simples de bit-blit, puede dibujar activamente miles de sprites por fotograma. Quizás debería considerar esos métodos, ya que su método directo de dibujar sprites parece estar fallando. (Suena más como si estuvieras usando un "método de renderizado", pero tratando de usarlo como un "método de juego". El renderizado se trata de claridad, no de velocidad).

Lo más probable es que estés redibujando constantemente toda la pantalla, una y otra vez. En lugar de volver a dibujar solo las áreas cambiadas. Eso es un montón de gastos generales. El concepto es simple, pero no es fácil de entender.

Utilice un búfer para el fondo estático virgen. Esto nunca se representa solo, a menos que no haya sprites en la pantalla. Esto se usa constantemente para "revertir" donde se dibujó un sprite, para desenredar el sprite en la próxima llamada. También necesita un búfer para "dibujar", que no es la pantalla. Dibujas allí, luego, una vez que todo está dibujado, lo vuelves a la pantalla, una vez. Eso debería ser una llamada de pantalla por todos tus sprites. (A diferencia de dibujar cada sprite en la pantalla, uno a la vez, o intentar hacerlo todo a la vez, lo que hará que su mezcla alfa falle). Escribir en la memoria es rápido y no requiere tiempo de pantalla para "dibujar" ". Cada llamada de sorteo esperará una señal de retorno, antes de que intente dibujar nuevamente. (No es una sincronización v, un tic de hardware real, que es mucho más lento que el tiempo de espera que tiene la RAM).

Me imagino que es parte de la razón por la que solo ve este problema en una computadora. O bien, está volviendo a la representación del software de ALPHA-BLEND, que no son compatibles con todas las tarjetas. ¿Comprueba si esa característica es compatible con hardware antes de intentar usarla? ¿Tiene un respaldo (modo sin mezcla alfa), si no lo tienen? Obviamente, no tienes un código que limite (número de cosas combinadas), ya que supongo que eso degradaría el contenido de tu juego. (A diferencia de si estos fueran solo efectos de partículas, todos mezclados alfa, y por lo tanto, por qué los programadores los limitan, ya que son muy exigentes en la mayoría de los sistemas, incluso con soporte de hardware).

Por último, sugeriría limitar lo que está mezclando alfa, solo a las cosas que lo necesitan. Si todo lo necesita ... No tiene más remedio que exigir a sus usuarios que tengan mejores requisitos de hardware o degradar el juego para obtener el rendimiento deseado.

JasonD
fuente
-1

Cree una hoja de sprites para objetos y un conjunto de fichas para el terreno como lo haría en otro juego 2D, no hay necesidad de cambiar las texturas.

Renderizar mosaicos puede ser una molestia porque cada par de triángulos necesita sus propias coordenadas de textura. Sin embargo, hay una solución a este problema, se llama renderizado instanciado .

Siempre que pueda ordenar sus datos de una manera que, por ejemplo, pueda tener una lista de mosaicos de hierba y sus posiciones, puede representar cada mosaico de hierba con una sola llamada de sorteo, todo lo que tiene que hacer es proporcionar una matriz de modelos a matrices mundiales para cada mosaico. Ordenar sus datos de esta manera no debería ser un problema incluso con el gráfico de escena más simple.

dreta
fuente
-1: Instanciar es una idea peor que la solución de sombreador puro del Sr. Beast. La instancia funciona mejor para el rendimiento al representar objetos de complejidad moderada (~ 100 triángulos más o menos). Cada mosaico triangular que necesita coordenadas de textura no es un problema. Simplemente crea una malla con un montón de quads sueltos que forman un mosaico.
Nicol Bolas
1
@NicolBolas está bien, voy a dejar la respuesta por el bien de aprender
dreta
1
Para mayor claridad, Nicol Bolas, ¿cuál es su sugerencia sobre cómo lidiar con todo esto? ¿El dibujo de la corriente de Marco? ¿Hay algún lugar donde pueda ver una implementación de esto?
Nic
@Nic: La transmisión a objetos de búfer no es un código particularmente complejo. Pero realmente, si solo estás hablando de 50 spites, eso no es nada . Las probabilidades son buenas de que sea su dibujo del terreno el que está causando el problema de rendimiento, por lo que cambiar a buffers estáticos probablemente sea lo suficientemente bueno.
Nicol Bolas
En realidad, si la creación de instancias funcionó como podríamos pensar que debería ser, sería la mejor solución, pero dado que no funciona, es la mejor opción hacer todas las instancias en un único vbo estático.
Jari Komppa