La forma más eficiente de dibujar vértices con OpenGL


8

Estoy escribiendo un juego OpenGL 3D. Habrá toneladas de triángulos para terreno y objetos en uso.

Estoy estudiando de la guía oficial de OpenGL y el primer método presentado es llamar a una función glVertexdespués glBeginde cada vértice que desea dibujar.

Sin embargo, este método suena bastante antiguo e ineficiente cuando tienes que dibujar miles de triángulos. Supongo que hay métodos para almacenar en la memoria de la tarjeta gráfica la información que no cambia en cada cuadro, de modo que cuando renderiza cada cuadro esa información ya está allí. Si bien para obtener más información "inestable", probablemente tenga que enviar nuevos datos de vértice en cada cuadro, por lo que una técnica diferente podría ser más eficiente.

En cuanto a mi pregunta: me gustaría una explicación clara de las funciones y técnicas de OpenGL más modernas y eficientes utilizadas para enviar información de vértices al hardware y dar instrucciones para renderizar.

¿Cuáles son las mejores formas y herramientas de OpenGL para enviar el pedido de renderizar datos de vértices que rara vez cambian durante los cuadros (creo que el terreno y los objetos) y cuál es la mejor manera de enviar datos de vértices que cambian mucho durante los cuadros?


Tienes razón, glBegin () y glEnd () son parte de la representación en modo inmediato y bastante anticuadas. Hoy en día uno usaría, por ejemplo, VBO. Sin embargo, ¡cómo exactamente y qué usas depende de tu escenario!
thalador

También soy un estudiante que intenta aprender OpenGL. Sé que necesitará usar Vertex Buffer Objects (VBO) para dibujar vértices más rápido. Si bien no sé lo suficiente como para decirte cómo hacerlo, puedo darte un enlace a un gran libro (gratuito en línea) que sí lo hace. Lo hojeé e hice un pequeño triángulo, pero es realmente complejo. Arcsynthesis Modern OpenGL Tutorial Estoy revisando los tutoriales de LazyFoo en este momento. Comienzan con la canalización de funciones fijas glBegin (), glEnd () y luego pasan a OpenGL más moderno utilizando la canalización de funciones fijas como base. Voy a j
benbot

Respuestas:


19

En términos generales, la respuesta es "depende". La forma en que uno enviaría actualizaciones de partículas es bastante diferente de la forma en que enviaría una serie de modelos con aspecto de GPU.

Búferes de vértices / índices

Sin embargo, en el sentido general, todo en estos días se hace con un VBO ( objeto de búfer de vértices ). La antigua API de modo inmediato ( glBegin/ glEnd) probablemente solo se implementa como un envoltorio grueso alrededor del sistema de búfer de vértices interno del controlador.

Para objetos estáticos, cree un VBO estático y llénelo con sus datos de vértice. Si alguno de los vértices se comparte (generalmente el caso), probablemente también desee crear un búfer de índice. Eso reduce la necesidad de enviar los mismos datos más de una vez para cambiar las mallas (potencialmente ahorrando en el ancho de banda de transferencia) y en el procesamiento (ahorrando en el tiempo de sombreado de vértices). Tenga en cuenta que dibuja con diferentes funciones cuando realiza sorteos indexados frente a no indexados.

Para objetos dinámicos, haga lo mismo, aunque con un conjunto dinámico de buffers.

Notas avanzadas

Para piezas más grandes como el terreno, probablemente no romperás la malla en varias piezas. Hacer que la GPU rinda cien millones de triángulos cuando solo doscientos mil de ellos son visibles es un gran desperdicio, especialmente si no están clasificados y hay muchas invocaciones de sombreadores de fragmentos sobregirados y desperdiciados. Divide la malla en trozos grandes y luego solo procesa los que están dentro de la vista frustrum. También hay varias técnicas de eliminación más avanzadas que puede usar para eliminar trozos que pueden estar en el frustrum pero que están completamente detrás de una colina o edificio o algo así. Mantener la cuenta regresiva de las llamadas de sorteo es bueno, pero hay un equilibrio (que debe encontrar para su aplicación / hardware específico) entre minimizar las llamadas de sorteo y minimizar el dibujo de geometría oculta.

Una de las cosas clave a tener en cuenta con un búfer de GPU es que no puede escribir mientras la GPU está leyendo. Debe informar al controlador que está bien descartar la copia anterior del búfer (cuando esté lista) y darle una nueva (si la anterior está ocupada). Por supuesto, durante mucho tiempo no hubo función de OpenGL para hacer esto (ahora hay InvalidateBufferData para GL 4.3 y algunas implementaciones anteriores como extensión). Más bien, existe un comportamiento no estándar pero común que la mayoría de los controladores implementan. Haga esto para descartar el búfer antes de actualizarlo:

glBindBuffer(GL_ARRAY_BUFFER, my_vbo);
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_DYNAMIC_DRAW);

Por supuesto, cambiar GL_ARRAY_BUFFERy GL_DYNAMIC_DRAWlos valores apropiados para el búfer Los búferes estáticos no se actualizarán (o no deberían), por lo que es poco probable que deba preocuparse por el descarte de dicho búfer.

Tenga en cuenta que puede ser más rápido de usar glBufferDatao glBufferSubDatapuede ser más rápido con glMapBuffer. Realmente depende del controlador y el hardware. El hardware de PC de la generación actual probablemente será más rápido, glBufferDatapero pruebe para estar seguro.

Otra técnica es usar instancias . La creación de instancias le permite realizar una única llamada de dibujo que dibuja múltiples copias de los datos en un búfer de vértices / índices. Si tuviera, digamos, 100 rocas idénticas, entonces querría dibujarlas todas de una vez en lugar de hacer 100 sorteos independientes.

Al crear instancias, debe colocar los datos por instancia en otro búfer (como la posición del objeto de cada individuo). Puede ser un búfer uniforme ( búfer constante en la terminología D3D) o un búfer de textura o un atributo de vértice por instancia. De nuevo, lo que es más rápido depende. Los atributos por instancia son probablemente más rápidos y definitivamente mucho más fáciles de usar, pero muchas implementaciones GL comunes todavía no son compatibles, glBindingAttribDivisorpor lo que tendrá que ver si está disponible para usar y si realmente es más rápido (algunos controladores más antiguos emulan instancias agregando amortiguadores y terminó siendo más lento usar instancias en ellos que hacer llamadas de extracción independientes y no hay una forma estándar de descubrir ... las alegrías de usar OpenGL).

También hay algoritmos para la optimización de la memoria caché de vértices , que es el acto de ordenar vértices / índices en sus buffers para jugar con la memoria caché de vértices en las GPU modernas. Una GPU solo ejecutará el sombreador para un vértice y luego lo almacenará en caché en el caché de vértices, pero es posible que deba desalojarse demasiado pronto para dejar espacio para otros vértices. (Digamos, dos triángulos comparten un vértice, pero hay otros 100 triángulos dibujados entre ellos; el vértice compartido probablemente terminará siendo malgastado por el sombreador de vértices dos veces).

Algunas de estas características requieren una versión suficientemente nueva de GL o GLES. GLES2 no era compatible con instancias, por ejemplo.

Perfil siempre

Nuevamente, si le importa el rendimiento, pruebe cada método posible y vea cuál es más rápido para su aplicación en su hardware de destino. No solo diferentes hardware / controladores de diferentes fabricantes serán diferentes, sino que algunas clases completas de hardware son innatamente diferentes. Una GPU móvil típica es una bestia muy diferente de una GPU de escritorio discreta típica. Las técnicas que son "mejores" en una no necesariamente serán las mejores en otra.

Cuando se trata de rendimiento, siempre sea escéptico .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.