¿Cuáles son las técnicas comunes de optimización de renderizado para el paso de geometría en un renderizador de sombreado diferido? [cerrado]

16

He estado desarrollando un motor de juego usando OpenGL 3 y C ++ (y glfw para la gestión de ventanas). He avanzado hasta ahora, hice la mayoría de las cosas, excepto entidades de sonido y optimizaciones. El motor usa sombreado diferido, por lo que dado que el sombreado diferido es un proceso agotador para una GPU promedio, quiero optimizar el proceso de renderizado tanto como sea posible.

El sistema actual consiste en una Escena, que contiene un Renderer y el Mundo actual y el Mundo tiene entidades y entidades de iluminación separadas std::vectors.

Entonces, básicamente, cada vez que se llama a la escena ->render(), y llama al Renderer, pasa el mundo como un parámetro y obtiene los iteradores de entidad del mundo, los atrae al FBO y luego pasa por las entidades de iluminación para el segundo paso. Y creo que esto no es suficiente.

Mi algoritmo actual itera a través de todo, incluso si la entidad no está en el espacio de la pantalla. Estoy pensando en una forma de optimizar el algoritmo de renderizado actual para que solo llame a las funciones API solo para los objetos visibles, entonces, ¿cuáles son las técnicas comunes para optimizar dicho renderizador?

deniz
fuente

Respuestas:

41

El sombreado diferido es solo una técnica para "diferir" la operación de sombreado real para etapas posteriores, esto puede ser excelente para reducir la cantidad de pases necesarios (por ejemplo) para renderizar 10 luces que necesitan 10 pases. Mi punto es que, independientemente de la técnica de representación que esté utilizando, hay ciertas optimizaciones de representación posibles que reducen la cantidad de objetos (vértices, normales, etc.) que su canal de representación necesita procesar.

No hay un estándar de facto para las optimizaciones de renderizado, sino más bien una serie de técnicas que se pueden usar indistintamente o juntas para lograr ciertas características de rendimiento. El uso de cada técnica depende en gran medida de la naturaleza de la escena que se representa.

El renderizado diferido intenta resolver el problema cuando aumenta el número de luces, lo que en el renderizado directo puede hacer que explote el número de pases.

Esas técnicas no optimizan directamente la parte de sombreado diferido, pero según su descripción, la parte de sombreado diferido NO es su problema. Sin embargo, su problema es que está enviando toda la escena a la canalización de representación. Por lo tanto, su motor tiene que procesar (por ejemplo, todos los 100 millones de vértices) en su escena solo para poder enviar el resultado al búfer g, mientras que la mayoría de estos 100 millones de vértices pueden eliminarse trivialmente y no enviarse al preproceso de vértice y paso de fragmentos.

En el caso de un renderizador directo, el vértice N será procesado por la etapa de vértice como un total de vertex count*lights county por la etapa de fragmento como un total fragments count*number Lights, el sombreado diferido efectivamente reduce esto solo vertex counta la etapa de vértice y fragments countal recuento de fragmentos, antes de resolver el sombreado real Pero aún así, N puede ser demasiado para procesar, especialmente cuando la mayoría de ellos pueden ser descartados trivialmente.

Esto hace que el sacrificio sea más efectivo en caso de renderizado directo / pases múltiples. Pero tenga en cuenta que la mayoría de los motores usarán un enfoque de representación dual, ya que el sombreado diferido por sí solo no puede resolver objetos transparentes, esto hace que el uso de esas optimizaciones sea imprescindible, no conozco ningún motor comercial que no los haga todos.

El sacrificio de Frustum

Solo los Objetos que están total o parcialmente incluidos en la vista frustum, deben ser enviados a la canalización de renderizado. Este es el concepto básico de eliminación del frustum, desafortunadamente verificar si una malla está dentro / fuera de la vista del frustum puede ser una operación costosa, por lo que, en cambio, los diseñadores de motores usan un volumen delimitador aproximado como un cuadro delimitador o una esfera delimitadora del eje alineado (AABB) , aunque esto podría no ser tan preciso como usar la malla real, la diferencia de precisión no vale la pena comprobar con la malla real.

ingrese la descripción de la imagen aquí

Incluso con volúmenes delimitadores, realmente no necesita verificar cada uno, alternativamente, puede construir una jerarquía de volúmenes delimitadores para hacer un sacrificio anterior, el uso depende en gran medida de la complejidad de la escena.

Esta es una técnica buena y simple para un motor más pequeño, y casi se usa en todos los motores que he usado. Recomiendo usar una comprobación de volumen / volumen de límite "normal" sin jerarquías si su motor no requiere renderizar escenas muy complejas.

Jerarquía de volumen delimitador

Cara posterior de sacrificio

Esto es imprescindible, ¿por qué dibujar caras que no serán visibles de todos modos? Las API de representación proporcionan una interfaz para activar / desactivar el sacrificio de la cara posterior. A menos que tenga una buena razón para no encenderlo, como algunas de las aplicaciones CAD que necesitan dibujar caras posteriores en ciertas circunstancias, esto es algo que debe hacer.

Eliminación de oclusión

Usando el Z-buffer puede resolver la determinación de visibilidad. Pero el problema es que el búfer Z no siempre es excelente en términos de rendimiento, ya que el búfer Z solo se puede resolver en etapas posteriores de la tubería, los objetos ocluidos deben rasterizarse y pueden escribirse en el búfer Z y Tampón de color antes de fallar la prueba Z

El sacrificio por oclusión resuelve esto haciendo algunas pruebas iniciales para eliminar los objetos ocluidos que se encuentran en el tronco de renderizado. Una implementación práctica del sacrificio de oclusión es usar consultas basadas en puntos y verificar si ciertos objetos son visibles desde una vista de punto específica. Esto también se puede utilizar para eliminar luces que no contribuyen a la imagen final, esto es especialmente útil en un renderizador de motor diferido.

ingrese la descripción de la imagen aquí

Un gran ejemplo del mundo real de tal técnica es en GTA5, donde los rascacielos están ubicados estratégicamente en el centro de la ciudad, no solo son decoraciones, sino que también funcionan como oclusores, ocluyendo efectivamente el resto de la ciudad y evitando que sea rasterizado

LOD

Nivel de detalle

El nivel de detalle es una técnica muy utilizada, la idea es usar una versión más simple de la malla cuando la malla contribuye menos a la escena. hay dos implementaciones comunes; uno simplemente cambia la malla por una más simple cuando ya no contribuye mucho, la malla se selecciona en función de algún factor como la distancia y el número de píxeles (área en la pantalla) que ocupa la malla. La otra versión tesela dinámicamente la malla, esto se usa ampliamente en el renderizado del terreno.

ingrese la descripción de la imagen aquí

¿Qué pasa si todo esto no funciona?

Bueno, esa es una buena pregunta.

Lo primero que debe hacer es crear un perfil de su aplicación con un generador de perfiles de gráficos y determinar dónde está el cuello de botella. Tenga en cuenta que el cuello de botella puede cambiar a medida que cambia el contenido que se representa. Los cuellos de botella también pueden ser parte del código que se ejecuta en la CPU, por lo que también debe medir eso.

Después de eso, debe hacer algunas optimizaciones en el cuello de botella, tenga en cuenta que no hay una respuesta correcta para esto, y será diferente de un hardware a otro.

Algunos trucos comunes de optimización de GPU:

  • Evite ramificarse en sombreadores.
  • Pruebe diferentes estructuras de vértices, por ejemplo, {VNT}intercaladas en la misma matriz o {V},{N},{T}en diferentes matrices.
  • Dibuja la escena de adelante hacia atrás.
  • Apague Z-buffer en algunos puntos, por ejemplo, si una imagen no necesita pruebas de Z.
  • Usa texturas comprimidas.

Algunos trucos comunes de optimización de CPU:

  • Use funciones en línea para funciones pequeñas.
  • Utilice SIMD (Datos múltiples de una sola instrucción) cuando sea posible.
  • Evite los saltos de memoria hostiles de caché.
  • Use VBO con la cantidad "correcta" de datos. (dependiendo de su hardware), pero generalmente son mejores las llamadas de extracción.

Pero, ¿qué pasa si mi cuello de botella estaba en el sombreado diferido?

En este caso, dado que el sombreado diferido está más preocupado por las luces, la parte más obvia es optimizar los cálculos de sombreado reales. Algunos de los puntos a tener en cuenta:

  • Renderiza luces que realmente afectan la imagen final. En otras palabras, apaga las luces que no contribuyen. Esto se puede implementar de manera efectiva utilizando el sacrificio de oclusión que mencioné antes.
  • ¿Esta luz necesita el componente especular o algún otro? Tal vez no.
  • ¿Esta luz proyecta sombra? Algunas luces no necesitan proyectar sombras.
  • ¿Se puede calcular previamente esta contribución ligera? Si no se mueve, probablemente algunos aspectos se pueden calcular previamente.
concepto3d
fuente
Lo sentimos, estos no tienen nada que ver con el sombreado diferido, en realidad son los problemas de rendimiento exactos que la técnica mitiga de manera efectiva y, por lo tanto, las optimizaciones menos útiles para hacer, el foco debe estar en los pases de iluminación porque si el costo de la iluminación no es El sombreador diferido dominante que toma el tiempo es probablemente la elección incorrecta.
MickLH
@MickLH Desafortunadamente, aparentemente no leíste la pregunta, su problema era principalmente que itera sobre toda la escena cada vez, y no mencionó ningún cuello de botella con respecto al sombreado diferido. Al principio mencioné que el sombreado diferido resuelve el problema de explosión de pases cuando se tienen muchas luces / materiales. Pero luego agregué que esas son optimizaciones imprescindibles para cualquier motor, independientemente de la técnica de sombreado hacia adelante o diferido. Con respecto a que estos son los problemas exactos que migra la técnica, estoy totalmente en desacuerdo, no puedo abordar todos los puntos aquí (sigue)
concept3d
Es realmente estúpido construir un motor diferido sin sacrificar el frustum, por ejemplo, por lo que el motor procesará, por ejemplo (100 millones de vértices) solo para poder enviar el resultado al g-buffer. El sombreado diferente resuelve un problema diferente, que no era su problema, su problema era enviar toda la geometría a la tubería.
concept3d
aunque estoy de acuerdo en que los cálculos de iluminación deberían producirse cierta optimización, y si los cálculos de luz no eran los dominantes, entonces diferir es el camino equivocado. pero de nuevo ese no era su problema.
concept3d
Retiraré mi voto negativo si deja en claro que estas optimizaciones son en realidad las menos efectivas para un renderizador diferido, ya que es cierto que no le ha mostrado a él / ella + googlers que el problema de rendimiento no tiene nada que ver con el sombreado diferido.
MickLH
6

Su problema no está relacionado con el sombreado diferido , debe implementar los elementos básicos básicos de un renderizador antes de intentar acelerar alguna parte específica.

Cuando haya terminado con lo que concept3d ha explicado, si realmente encuentra que necesita optimizar el sombreador diferido (a diferencia del paso de rasterización completo), puede implementar el sombreado diferido basado en mosaico.

Si no está limitado por la cantidad de luces dinámicas, debe considerar por qué está utilizando sombreado diferido, pero si lo está , querrá probar la optimización que hizo posible Battlefield 3. (Lo insinúan en la diapositiva 10 de su PDF público: http://dice.se/wp-content/uploads/GDC11_DX11inBF3_Public.pdf )

MickLH
fuente