No es suficiente contar polígonos para optimizar modelos 3D

imagen

Después de comprender los conceptos básicos del proceso de representación de malla, puede aplicar varias técnicas para optimizar la velocidad de representación.

Introduccion


¿Cuántos polígonos puedo usar? Esta es una pregunta muy común que los artistas hacen al crear modelos para renderizar en tiempo real. Esta pregunta es difícil de responder, porque no es solo una cuestión de números.

Comencé mi carrera como artista 3D en la era de la primera PlayStation, y luego me convertí en programador de gráficos. Me gustaría leer este artículo antes de comenzar a crear modelos 3D para juegos. Los fundamentos fundamentales considerados en él son útiles para muchos artistas. Aunque la mayor parte de la información en este artículo no afectará significativamente la productividad de su trabajo diario, le dará una comprensión básica de cómo la unidad de procesamiento de gráficos (GPU) procesa las mallas que crea.

La velocidad de su representación generalmente depende del número de polígonos en la malla. Sin embargo, aunque el número de polígonos a menudo se correlaciona con la velocidad de fotogramas por segundo (FPS), puede encontrar que incluso después de reducir el número de polígonos, la malla aún se renderiza lentamente. Pero al comprender cómo se representan las mallas en general, puede aplicar un conjunto de técnicas para aumentar la velocidad de representación.

Cómo se presentan los datos poligonales


Para comprender cómo la GPU dibuja polígonos, primero debe considerar la estructura de datos utilizada para describir los polígonos. Un polígono consiste en un conjunto de puntos llamados vértices y enlaces. Los vértices a menudo se almacenan como matrices de valores, por ejemplo, como en la Figura 1.


Figura 1. Una matriz de valores de polígonos simples.

En este caso, cuatro vértices en tres dimensiones (x, y y z) nos dan 12 valores. Para crear polígonos, la segunda matriz de valores describe los vértices mismos, como se muestra en la Figura 2.


Figura 2. Una matriz de enlaces a los vértices.

Estos vértices conectados entre sí forman dos polígonos. Tenga en cuenta que dos triángulos, cada uno con tres ángulos, se pueden describir con cuatro vértices, porque los vértices 1 y 2 se usan en ambos triángulos. Para que la GPU procese estos datos, se supone que cada polígono es triangular. Las GPU esperan que trabajes con triángulos porque están diseñados específicamente para dibujarlos. Si necesita dibujar polígonos con un número diferente de vértices, entonces necesita una aplicación que los divida en triángulos antes de renderizar en la GPU. Por ejemplo, si crea un cubo de seis polígonos, cada uno de los cuales tiene cuatro lados, entonces esto no es más efectivo que crear un cubo de 12 polígonos que consta de tres lados; Son estos triángulos los que dibujará la GPU. Recuerde la regla: necesita contar no polígonos, sino triángulos.

Los datos de vértice utilizados en el ejemplo anterior son tridimensionales, pero esto no es necesario. Dos dimensiones pueden ser suficientes para usted, pero a menudo necesita almacenar otros datos, por ejemplo, coordenadas UV para texturas y normales para iluminación.

Dibujo poligonal


Al representar un polígono, la GPU primero determina dónde dibujar el polígono. Para hacer esto, calcula la posición en la pantalla donde deberían estar los tres vértices. Esta operación se llama transformar. Estos cálculos en la GPU se realizan mediante un pequeño programa llamado sombreador de vértices.

El sombreador de vértices a menudo realiza otros tipos de operaciones, como procesar animaciones. Después de calcular las posiciones de los tres vértices del polígono, la GPU calcula qué píxeles están en este triángulo y luego comienza a llenar estos píxeles con otro pequeño programa llamado "fragment shader" (fragment shader). Un sombreador de fragmentos generalmente se ejecuta una vez por píxel. Sin embargo, en algunos casos raros, se puede realizar varias veces por píxel, por ejemplo, para mejorar el suavizado. Los sombreadores de fragmentos a menudo se denominan sombreadores de píxeles, porque en la mayoría de los casos los fragmentos corresponden a píxeles (ver Figura 3).


Figura 3. Un polígono dibujado en la pantalla.

La Figura 4 muestra la secuencia de acciones realizadas por la GPU al representar el polígono.


Figura 4. El orden de la GPU que representa el polígono.

Si divide el triángulo en dos y dibuja ambos triángulos (consulte la Figura 5), ​​el procedimiento corresponderá a la Figura 6.


Figura 5. División del polígono en dos.


Figura 6. El procedimiento de la GPU dibujando dos polígonos.

En este caso, se requieren el doble de transformaciones y preparaciones, pero dado que el número de píxeles sigue siendo el mismo, la operación no necesita rasterizar píxeles adicionales. Esto muestra que duplicar el número de polígonos no necesariamente duplica el tiempo de renderizado.

Usar caché de vértices


Si observa los dos polígonos del ejemplo anterior, puede ver que tienen dos vértices comunes. Se puede suponer que estos vértices tendrán que calcularse dos veces, pero un mecanismo llamado caché de vértices le permite reutilizar los resultados del cálculo. Los resultados de los cálculos del sombreador de vértices para su reutilización se almacenan en la memoria caché, un área pequeña de memoria que contiene los últimos vértices. El procedimiento para dibujar dos polígonos usando el caché de vértices se muestra en la Figura 7.


Figura 7. Dibujando dos polígonos usando el caché de vértices.

Gracias a la memoria caché de vértices, puede dibujar dos polígonos casi tan rápido como uno si tienen vértices comunes.

Nos ocupamos de los parámetros de los vértices.


Para que el vértice sea reutilizable, no debe modificarse con cada uso. Por supuesto, la posición debería permanecer igual, pero otros parámetros tampoco deberían cambiar. Los parámetros pasados ​​a la parte superior dependen del motor utilizado. Aquí hay dos parámetros comunes:

  • Coordenadas de textura
  • Normal

Cuando se aplica UV a un objeto 3D, cualquier costura creada significará que los vértices a lo largo de la costura no se pueden compartir. Por lo tanto, en el caso general, se deben evitar las costuras (ver Figura 8).


Figura 8. Textura de sutura UV.

Para una iluminación adecuada de la superficie, cada vértice generalmente almacena un normal, un vector dirigido desde la superficie. Debido al hecho de que todos los polígonos con un vértice común están definidos por una normal, su forma parece suave. Esto se llama sombreado suave. Si cada triángulo tiene sus propias normales, entonces los bordes entre los polígonos se vuelven pronunciados y la superficie parece plana. Por lo tanto, esto se llama sombreado plano. La Figura 9 muestra dos mallas idénticas, una con sombreado suave y la otra con sombreado plano.


Figura 9. Comparación de sombreado liso con sombreado plano.

Esta geometría sombreada suave consta de 18 triángulos y tiene 16 vértices comunes. El sombreado plano de 18 triángulos requiere 54 vértices (18 x 3), porque ninguno de los vértices se comparte. Incluso si dos mallas tienen el mismo número de polígonos, su velocidad de representación seguirá siendo diferente.

Importancia de la forma


Las GPU funcionan rápido principalmente porque pueden realizar muchas operaciones en paralelo. Los materiales de marketing de GPU a menudo se centran en la cantidad de canalizaciones que determinan cuántas GPU pueden funcionar al mismo tiempo. Cuando la GPU dibuja el polígono, le da la tarea de muchas tuberías para llenar los cuadrados de píxeles. Esto suele ser un cuadrado de ocho por ocho píxeles. La GPU continúa haciendo esto hasta que todos los píxeles estén llenos. Obviamente, los triángulos no son cuadrados, por lo que algunos píxeles del cuadrado estarán dentro del triángulo y otros afuera. El equipo funciona con todos los píxeles en un cuadrado, incluso aquellos que están fuera del triángulo. Después de calcular todos los vértices en el cuadrado, el equipo descarta los píxeles fuera del triángulo.

La Figura 10 muestra un triángulo, que requiere tres cuadrados (fichas) para dibujar. Se utilizan la mayoría de los píxeles calculados (cian), y los que se muestran en rojo van más allá de los límites del triángulo y se descartarán.


Figura 10. Tres fichas para dibujar un triángulo.

El polígono en la Figura 11 con exactamente el mismo número de píxeles, pero estirado, requiere más mosaicos para rellenar; La mayoría de los resultados en cada mosaico (área roja) se descartarán.


Figura 11. Relleno de mosaicos en una imagen estirada.

El número de píxeles representados es solo uno de los factores. La forma del polígono también es importante. Para aumentar la eficiencia, trate de evitar polígonos largos y estrechos y dé preferencia a los triángulos con lados aproximadamente iguales, cuyos ángulos están cerca de los 60 grados. Las dos superficies planas en la Figura 12 se triangulan de dos maneras diferentes, pero se ven iguales cuando se representan.


Figura 12. Superficies trianguladas de dos maneras diferentes.

Tienen exactamente el mismo número de polígonos y píxeles, pero dado que la superficie de la izquierda tiene polígonos más largos y estrechos que los de la derecha, su representación será más lenta.

Redibujando


Para dibujar una estrella de seis puntas, puede crear una malla de 10 polígonos o dibujar la misma forma con solo dos polígonos, como se muestra en la Figura 13.


Figura 13. Dos formas diferentes de representar una estrella de seis puntas.

Puede decidir que es más rápido dibujar dos polígonos que 10. Sin embargo, en este caso, esto probablemente sea incorrecto, porque los píxeles en el centro de la estrella se dibujarán dos veces. Este fenómeno se llama sobregiro. En esencia, significa que los píxeles se vuelven a dibujar más de una vez. El redibujado ocurre naturalmente durante todo el proceso de renderizado. Por ejemplo, si un personaje está parcialmente oculto por una columna, se dibujará en su totalidad, a pesar de que la columna se superpone a parte del personaje. Algunos motores usan algoritmos complejos para evitar renderizar objetos que son invisibles en la imagen final, pero esta es una tarea difícil. La CPU a menudo es más difícil de entender qué no necesita ser renderizado que la GPU para dibujarla.

Como artista, debe aceptar el hecho de que no puede deshacerse del repintado, pero es una buena práctica eliminar las superficies que no se pueden ver. Si está colaborando con un equipo de desarrollo, solicite agregar un modo de depuración al motor del juego, en el que todo se vuelve transparente. Esto facilitará la búsqueda de polígonos ocultos que se pueden eliminar.

Implementando un cajón en el piso


La Figura 14 muestra una escena simple: una caja parada en el piso. El piso consta de solo dos triángulos, y la caja consta de 10 triángulos. El redibujado en esta escena se muestra en rojo.


Figura 14. Un cajón parado en el piso.

En este caso, la GPU dibujará parte del piso al piso con un cajón, a pesar de que no será visible. Si, en cambio, creáramos un agujero en el piso debajo de la caja, habríamos recibido más polígonos, pero mucho menos rediseñado, como se puede ver en la Figura 15.


Figura 15. Un agujero debajo del cajón para evitar volver a dibujar.

En tales casos, todo depende de su elección. A veces vale la pena reducir la cantidad de polígonos, obteniendo un nuevo dibujo a cambio. En otras situaciones, vale la pena agregar polígonos para evitar volver a dibujar. Otro ejemplo: las dos figuras que se muestran a continuación son las mismas mallas de superficie con puntos que sobresalen. En la primera malla (Figura 16), las puntas se encuentran en la superficie.


Figura 16. Las puntas se encuentran en la superficie.

En la segunda malla de la Figura 17, se cortan agujeros en la superficie debajo de las puntas para reducir la cantidad de redibujado.


Figura 17. Los agujeros se cortan debajo de las puntas.

En este caso, se agregaron muchos polígonos para cortar agujeros, algunos de los cuales tienen una forma estrecha. Además, la superficie de la redibujada, de la que nos deshacemos, no es muy grande, por lo que en este caso esta técnica no es efectiva.

Imagina que estás modelando una casa parada en el suelo. Para crearlo, puedes dejar la tierra sin cambios o cortar un agujero en el suelo debajo de la casa. Redibujar es más cuando el agujero no se corta debajo de la casa. Sin embargo, la elección depende de la geometría y del punto de vista desde el cual el jugador verá la casa. Si dibujas tierra debajo de la base de la casa, esto creará una gran cantidad de redibujos si entras a la casa y miras hacia abajo. Sin embargo, la diferencia no será particularmente grande si miras la casa desde un avión. En este caso, es mejor tener un modo de depuración en el motor del juego que haga que las superficies sean transparentes para que pueda ver lo que se dibuja debajo de las superficies visibles para el jugador.

Cuando los buffers Z tienen un conflicto Z


Cuando la GPU dibuja dos polígonos superpuestos, ¿cómo determina cuál está encima del otro? Los primeros investigadores de gráficos por computadora pasaron mucho tiempo investigando este problema. Ed Catmell (quien más tarde se convirtió en presidente de Pixar y Walt Disney Animation Studios) escribió un artículo que describía diez enfoques diferentes para esta tarea. En una parte del artículo, señala que la solución a este problema será trivial si las computadoras tienen suficiente memoria para almacenar un valor de profundidad por píxel. En las décadas de 1970 y 1980, era una gran cantidad de memoria. Sin embargo, hoy en día la mayoría de las GPU funcionan así: este sistema se llama Z-buffer.

El Z-buffer (también conocido como el buffer de profundidad) funciona de la siguiente manera: con cada píxel se asocia su valor de profundidad. Cuando el equipo dibuja un objeto, calcula qué tan lejos se dibuja un píxel de la cámara. Luego verifica el valor de profundidad de un píxel existente. Si está más lejos de la cámara que el nuevo píxel, se dibujará el nuevo píxel. Si un píxel existente está más cerca de la cámara que uno nuevo, entonces el nuevo píxel no se dibuja. Este enfoque resuelve muchos problemas y funciona incluso si los polígonos se cruzan.


Figura 18. Polígonos de intersección procesados ​​por el tampón de profundidad.

Sin embargo, el Z-buffer no tiene una precisión infinita. Si dos superficies están casi a la misma distancia de la cámara, esto confunde la GPU y puede seleccionar aleatoriamente una de las superficies, como se muestra en la Figura 19.


Figura 19. Las superficies con la misma profundidad tienen problemas de visualización.

Esto se llama Z-fighting y se ve muy defectuoso. A menudo, los conflictos Z empeoran cuanto más se aleja la superficie de la cámara. Los desarrolladores de motores pueden incorporar correcciones en ellos para suavizar este problema, pero si un artista crea polígonos lo suficientemente cercanos y superpuestos, entonces todavía puede surgir un problema. Otro ejemplo es una pared con un póster colgado. El póster se encuentra casi a la misma profundidad de la cámara que la pared detrás de él, por lo que el riesgo de conflictos Z es muy alto. La solución es cortar un agujero en la pared debajo del póster. Esto también reducirá la cantidad de rediseño.


Figura 20. Un ejemplo de un conflicto Z de polígonos superpuestos.

En casos extremos, un conflicto Z puede ocurrir incluso cuando los objetos se tocan entre sí. La Figura 20 muestra el cajón en el piso, y dado que no cortamos un agujero en el piso debajo del cajón, el z-buffer puede confundirse al lado del borde donde el piso se encuentra con el cajón.

Usar llamadas de sorteo


Las GPU se han vuelto extremadamente rápidas, tan rápidas que las CPU pueden no seguirlas. Dado que las GPU están diseñadas esencialmente para realizar una tarea, es mucho más fácil comenzar a trabajar rápidamente. Los gráficos están inherentemente relacionados con el cálculo de múltiples píxeles, por lo que puede crear equipos que computen múltiples píxeles en paralelo. Sin embargo, la GPU solo representa lo que ordena para dibujar la CPU. Si la CPU no puede "alimentar" rápidamente la GPU con datos, entonces la tarjeta de video estará inactiva. Cada vez que la CPU ordena a la GPU que dibuje algo, se llama una llamada de extracción. La llamada de sorteo más simple consiste en renderizar una malla, que incluye un sombreador y un conjunto de texturas.

Imagine un procesador lento que puede transferir 100 llamadas de extracción por cuadro, y una GPU rápida que puede extraer un millón de polígonos por cuadro. En este caso, una llamada de sorteo ideal puede dibujar 10,000 polígonos. Si sus mallas consisten en solo 100 polígonos, entonces la GPU podrá dibujar solo 10,000 polígonos por cuadro. Es decir, el 99% de las veces la GPU estará inactiva. En este caso, podemos aumentar fácilmente el número de polígonos en las mallas sin perder nada.

En qué consiste la llamada de sorteo y el costo de la misma, depende en gran medida de motores y arquitecturas específicos. Algunos motores pueden combinar muchas mallas en una sola llamada de sorteo (realizar sus lotes, lotes), pero todas las mallas tendrán que tener el mismo sombreador o pueden tener otras restricciones. Las nuevas API como Vulkan y DirectX 12 están diseñadas específicamente para resolver este problema al optimizar la forma en que el programa se comunica con el controlador de gráficos, lo que aumenta el número de llamadas de extracción que se pueden transferir en un solo cuadro.

Si su equipo está escribiendo su propio motor, pregunte a los desarrolladores del motor qué limitaciones tienen las llamadas de extracción. Si utiliza un motor listo como Unreal o Unity, ejecute puntos de referencia de rendimiento para determinar los límites de las capacidades del motor. Puede descubrir que puede aumentar la cantidad de polígonos sin causar una disminución en la velocidad.

Conclusión


Espero que este artículo sirva como una buena introducción para ayudarlo a comprender los diversos aspectos del rendimiento de representación. En las GPU de diferentes fabricantes, todo se implementa un poco a su manera. Existen muchas reservas y condiciones especiales relacionadas con motores específicos y plataformas de hardware. Mantenga siempre un diálogo abierto con los programadores de renderizado para usar sus recomendaciones en su proyecto.

Sobre el autor


Eskil Steenberg es un desarrollador independiente de juegos y herramientas, y trabaja como consultor y en proyectos independientes. Todas las capturas de pantalla se tomaron en proyectos activos utilizando herramientas desarrolladas por Esquil. Puede obtener más información sobre su trabajo en el sitio web de Quel Solaar y en su cuenta de Twitter @quelsolaar.

Source: https://habr.com/ru/post/es433186/


All Articles