StratusGFX motor de render en tiempo real

StratusGFX motor de render en tiempo real que además es de código abierto, aún está en fase beta, pero se puede integrar en otros motores. Está construido con C ++ 17 y OpenGL 4.6 y puede usarse con fines educativos o integrarse en otros motores de uso más general.

StratusGFX motor de render en tiempo real
StratusGFX motor de render en tiempo real

Funciones gráficas admitidas actualmente

  • Canalización de rugosidad metálica basada en la física
  • Iluminación global en tiempo real
  • Iluminación y sombras volumétricas Raymarched
  • Mapeo de sombras en cascada
  • Iluminación diferida
  • Generación y selección de LOD de malla
  • Eliminación de GPU Frustum
  • Oclusión ambiental del espacio de pantalla (SSAO)
  • Mapeo de tonos fílmicos
  • Niebla
  • Floración
  • Suavizado rápido de proximidad (FXAA)

Un video con lo que es capaz de hacer

Video de StratusGFX motor de render en tiempo real
Video de StratusGFX motor de render en tiempo real

Características del motor:

  • Asignaciones de grupos
  • Utilidades de subprocesos múltiples
  • Mapa hash concurrente
  • Sistema de componentes de entidad (ECS)
  • Inicio sesión

Funciones modernas de la API de gráficos utilizadas

  • Calcular sombreadores
  • Acceso estatal directo
  • Tracción de vértice programable
  • Elementos de dibujo múltiple indirectos
  • Búferes de almacenamiento de sombreadores

Requisitos mínimos de hardware

TipoMínimo
UPCRyzen 3 1200 (cuatro núcleos)
RAM8 GB
GPUNvidia GTX 1050Ti
*Almacenamiento (Implementar binarios)700 MB
*Almacenamiento (Binarios + Fuente + Dependencias)7 GB
*Almacenamiento (binarios + fuente + datos de demostración + dependencias)16 GB
Cuadro de requisitos de hardware

Construcción para Windows y Linux

Esta base de código actualmente no funcionará en MacOS. Tanto Linux como Windows deberían estar bien siempre que el controlador de gráficos admita OpenGL 4.6 y el compilador admita C++ 17.

Primero configure el repositorio

git clone --recursive https://github.com/KTStephano/StratusGFX.git
cd StratusGFX

Cree dependencias de terceros, sólo debería necesitar hacer esto una vez por clon

python3 ./dependency_build.py

Ahora construye la fuente StratusGFX

mkdir build; cd build
cmake ..
cmake --build . -j 8 --config RelWithDebInfo

Todos los ejecutables se colocarán en StratusGFX/Bin. Asegúrate de ejecutarlos mientras estás dentro de Bin/. Buenos para ejecutar para ver si funcionó son

Ex00_StartupShutdown.exe (runs through initialize, run one frame, shutdown sequence)
Ex01_StratusGFX.exe (you should see a forest of red cubes since textures aren't bundled with source)
StratusEngineUnitTests.exe
StratusEngineIntegrationTests.exe

Futuro de StratusGFX

Metas a corto plazo

  • Mejorar el sistema de compilación para que sea más fácil para las personas ponerse en marcha con el código.
  • Adición de TAA o TSSAA para ayudar con la estabilidad de la imagen mientras está en movimiento.
  • Animación.
  • Mejoras de rendimiento.

Metas a mediano plazo

  • Ordenar transparencia independiente.
  • Mejor manejo de la generación y transición de LOD.
  • Reflejos del espacio de la pantalla (SSR).

Metas a largo plazo

  • Cambio el backend a Vulkan, lo que permitirá que el motor se ejecute en MacOS y no solo en Windows/Linux.
  • Adición de funciones de iluminación horneadas para que el hardware más débil tenga una opción alternativa.
  • Adición de otras técnicas modernas de iluminación global para reemplazar o complementar lo que ya existe.

La historia y el alcance de StratusGFX motor de render en tiempo real

StratusGFX es un motor de renderización en tiempo real que escribió el desarrollador J Stephano para aprender sobre técnicas de programación de gráficos modernas. El desarrollo y la prueba se realizaron en Windows 10 con un Ryzen 5 1600 y una Nvidia GTX 1060. La velocidad de fotogramas objetivo era un mínimo de 30 fps (33,33 ms), pero la mayoría de las escenas en la demo técnica se ejecutaron a 60 fps (16,67 ms) la mayor parte del tiempo.

Cómo se renderiza un cuadro StratusGFX hace un uso intensivo de la extracción de vértices programable con recursos sin límites. Esto permite que todos los datos de malla de la escena se asignen desde un solo búfer de GPU gigante al que hacen referencia todas las colas de renderizado posteriores.

Los recursos sin límites significan que el renderizador puede determinar al comienzo del cuadro qué texturas se necesitan usar (mapas de sombras, mapas difusos, mapas normales, etc.) y asegurarse de que todos se vuelvan residentes en la GPU antes de realizar cualquier llamada de dibujo.

El motor de render agrupa las mallas opacas en una cola de renderizado

Estos dos enfoques permiten una técnica de optimización muy importante conocida como combinación de llamadas de dibujo. Por ejemplo, el renderizador puede agrupar todas las mallas opacas estáticas en una sola cola de renderizado y luego enviarlas con una sola llamada a glMultiDrawElementsIndirect. Esto también hace posible almacenar en caché las colas de renderizado generadas previamente y reutilizarlas en cuadros futuros si no ha cambiado nada.

Otra oportunidad interesante también se crea al usar este enfoque. Dado que todos los materiales y sus texturas se vuelven residentes al inicio del cuadro, cualquier sombreador en cualquier etapa puede acceder a cualquier propiedad de material o textura que puedan necesitar sin tener que agregar nuevo código C ++ para hacerlos disponibles para ese sombreador. Esto hace que sea muy fácil agregar nuevos sombreadores que trabajen en diferentes partes de los datos.

Cambios en las entidades y luces

Lo primero que hace el motor de renderizado es revisar las entidades y luces marcadas como «dinámicas» para ver si alguna de ellas ha cambiado en el último fotograma o si se han agregado nuevas. Esto se hace por dos razones:

  1. Si no se han cambiado ni agregado entidades, las colas de renderizado anteriores pueden ser completamente válidas para este nuevo fotograma y pueden ser reutilizadas.
  2. Si una luz no se ha movido o una entidad no se ha acercado a ella, se puede reutilizar los datos del mapa de sombras de la luz del fotograma anterior.

Todo lo marcado como estático se omite durante este paso para ahorrar en rendimiento.

Recompilar los shaders

Lo siguiente es verificar si el código de la aplicación ha solicitado una recompilación de shaders. Si es así, todos los shaders se marcan como inválidos y se carga y compila el código más reciente desde el disco.

Esta capacidad se agregó muy temprano en el desarrollo ya que ahorraba mucho tiempo. Muchos efectos de shader se escribieron y depuraron mientras el motor se estaba ejecutando.

Vista de GPU y Selección de Malla

StratusGFX motor de render en tiempo real con la malla seleccionada
StratusGFX motor de render en tiempo real con la malla seleccionada

El CPU despacha un shader de cómputo que es responsable de eliminar comandos de dibujo realizando una prueba de visibilidad de su cuadro delimitador alineado con los ejes (AABB) contra el frustum de vista.

Si un comando pasa la prueba del frustum de vista, lo que significa que la malla que representa está al menos parcialmente dentro del frustum, el shader de cómputo luego verifica a qué distancia en el espacio de vista se encuentra esa malla. Según la distancia, decide qué LOD debe usarse y finalmente escribe el comando en un búfer de comandos de dibujo de la GPU.

Si un comando falla la prueba del frustum de vista, lo que significa que está completamente fuera de al menos uno de los planos del frustum, la GPU marca ese comando como innecesario estableciendo su parámetro de recuento de instancias en 0.

Esto es una demostración de cómo las colas de representación respaldadas por búferes de comandos de dibujo de la GPU permiten que la GPU genere su propio trabajo. Puede tomar las colas de representación globales y crear una nueva cola de representación solo con los comandos de dibujo que representan la geometría de malla que está dentro del frustum de vista.

Caché y actualización de mapas de sombras

Las luces puntuales y las luces puntuales virtuales se obtienen de sus propias cachés de mapas de sombras. Las luces puntuales regulares utilizan un conjunto de mapas de sombras de alta resolución (256×256 y 512×512 parecen funcionar bien), mientras que las luces puntuales virtuales utilizan un conjunto de mapas de sombras mucho más grandes pero también de resolución mucho más baja.

Para cada luz activa cerca de la cámara, el renderizador verifica si su mapa de sombras ya está en la caché y si la luz no se ha marcado como no válida. Si es así, se reutiliza su información de mapa de sombras. Si no, entonces su mapa de sombras debe regenerarse.

Para ahorrar en rendimiento, no se realizan más de 3 actualizaciones de mapas de sombras por cuadro. Cada luz se ingresa en una cola de actualización de luces para evitar que se descuide ninguna luz durante demasiados cuadros.

Después de las luces puntuales, se regeneran los mapas de sombras en cascada para la luz direccional (si está habilitada). Cada una de las cuatro cascadas recibe la escena a diferentes niveles de detalle. La primera cascada utiliza el nivel de detalle más alto, mientras que la última cascada utiliza el nivel de detalle más bajo disponible. Esto se hace para ahorrar en rendimiento pero aún así dar sombras precisas a corta distancia.

Caché y actualización de mapas de sombras de StratusGFX
Caché y actualización de mapas de sombras de StratusGFX

Generación del búfer G (Búfer de geometría)

Ahora se genera el búfer G para que se pueda utilizar con los pases de iluminación diferida y procesamiento posterior. Las posiciones del espacio mundial se reconstruyen a partir del valor de profundidad, por lo que no se utiliza una textura explícita del espacio mundial. Estas son las siguientes texturas que forman parte del búfer G para esta implementación:

  1. Textura de normal del espacio mundial de 16 bits (convertida de espacio tangencial -> espacio mundial).
  2. Textura de albedo de 8 bits.
  3. Textura de reflectividad base de 8 bits.
  4. Textura de rugosidad-metallic-ambiental de 8 bits.
  5. Búfer de estructuras de 16 bits.
GBuffer de StratusGFX
GBuffer de StratusGFX

El buffer de estructuras está compuesto de tres elementos:

  1. La derivada parcial de la profundidad del espacio de cámara con respecto a la coordenada actual de la ventana x.
  2. La derivada parcial de la profundidad del espacio de cámara con respecto a la coordenada actual de la ventana y.
  3. La profundidad del espacio de cámara.

Iluminación directa basada en la física (Canalización de Metal-Roughness) La implementación actual de PBR para este motor se basa en el documento PBR publicado por el equipo Filament de Google. Esto se puede encontrar aquí: https://google.github.io/filament/Filament.md.html.

En este momento, el motor utiliza la función de distribución bidireccional de reflectancia del modelo estándar de dispersión simple, por lo que desafortunadamente perderá energía en valores altos de rugosidad. El término difuso utiliza el modelo de Disney, ya que los resultados son un poco mejores, aunque esto cuesta un poco de rendimiento. Google Filament se enfoca mucho en la movilidad, por lo que encontraron que no valía la pena el cálculo adicional, pero dado que Stratus solo está destinado a funcionar en el escritorio, debería estar bien en esta situación.

Iluminación global La iluminación global se maneja utilizando luces virtuales de punto (VPL). Esto es conceptualmente muy similar al enfoque Radiosidad instantánea.

Para esta implementación, las luces virtuales de punto se distribuyen por la escena a mano o mediante algoritmos o ambos. Con el fin de ahorrar rendimiento, se hacen las siguientes suposiciones:

La difusa indirecta de una sola reflexión es generalmente suficiente para las escenas de prueba que estoy usando. Solo la geometría estática participará completamente. La geometría dinámica puede recibir luz indirecta, pero no ocultará ni proyectará sombras. Esto me permite reutilizar en gran medida los mapas de sombras y difusión. Todas las luces virtuales de punto pueden potencialmente proyectar sombras. Sus datos de sombra se reutilizan en gran medida entre cuadros. Las luces virtuales de punto se mantienen entre cuadros incluso si no están activas actualmente para que se puedan reutilizar más tarde. La aplicación es libre de crear y destruir las luces virtuales de punto en tiempo de ejecución.

Aquí hay un resumen de los pasos del algoritmo:

  1. Se recopilan todos los VPL candidatos cercanos a la cámara. Luego, se envían a un sombreador de cómputo que los comprueba con los mapas de sombras en cascada para ver si el VPL es visible. Cada VPL visible obtiene su color muestreando su mapa difuso local a lo largo de la dirección en que se orienta la luz del mundo. Este color se combina con el color e intensidad de la luz del mundo para obtener un color final por VPL visible.
  2. Otro sombreador de cómputo divide la pantalla en mosaicos que tienen un tamaño de 2×4 píxeles. Para cada mosaico, se calcula la posición y normal promedio muestreando el GBuffer.
  3. La lista de VPL visibles y la posición y normal promedio por mosaico se toman luego por otro sombreador de cómputo que reduce los 6 VPL más importantes por mosaico.
  4. Un sombreador de fragmentos determina a qué grupo de mosaicos pertenece su píxel y calcula la iluminación para cada uno de los 6 VPL que pertenecen a ese mosaico. La salida de esta etapa es un mapa de luz difusa de pantalla con solo la iluminación indirecta.
  5. Otro sombreador de fragmentos realiza un desenfoque del mapa de luz difusa indirecta y lo combina de forma aditiva con la textura principal de la pantalla.

Oclusión ambiental del espacio de la pantalla

Este algoritmo se ejecuta en dos pases. El primer paso crea un búfer que representa la cantidad de luz ambiental que llega a una superficie determinada. Si el valor es bajo, el resultado es menos luz ambiental para ese píxel. Para construir este búfer, un sombreador de fragmentos muestrea el búfer de estructura en 4 lugares alrededor del píxel actual. Lo que está tratando de hacer es usar la información del búfer de estructura para averiguar si hay una geometría cercana que esté ocluyendo la luz ambiental que, de lo contrario, estaría llegando a la superficie actual.

Los 4 lugares desde los que lee el sombreador de fragmentos se compensan aleatoriamente con una textura de rotación de 4×4 que calculamos previamente en la CPU. Esta textura de rotación se configura y utiliza de tal manera que por cada grupo de 4×4 de píxeles cercanos, sus patrones de muestreo de búfer de estructura ascenderán a 64 muestras únicas.

Salida del primer pase SSAO
Salida del primer pase

Mirando de cerca, hay un patrón punteado presente en muchas partes de la textura. Si este resultado se usara directamente, daría como resultado una SSAO de baja calidad. Para evitar esto, se utiliza un segundo pase de desenfoque consciente de la profundidad. Para hacer esto, se utiliza la profundidad del espacio de la cámara del búfer de estructura.

Ahora todos los patrones de puntos se han suavizado. Este búfer de SSAO borroso se combina por píxel con cualquier término ambiental para reducir la intensidad ambiental de cualquier superficie en la que se haya estimado que está ocluida.

Iluminación volumétrica y sombreado

Esta implementación se basa en el capítulo de sombreado atmosférico que se encuentra en «Fundamentos del desarrollo de motores de juegos, Volumen 2: Representación».

El propósito de este paso es simular cómo la luz interactúa con los medios participantes, que podrían ser cosas como vapor de agua o partículas de polvo. Estos son los detalles de alto nivel del modelo utilizado aquí:

1) Una fracción de la luz que viaja por el aire se redirigirá hacia la cámara (dispersión)

2) Una fracción de la luz que viaja por el aire se redirigirá lejos de la cámara (dispersión y retrodispersión)

3) Algunos serán absorbidos por los medios participantes y no llegarán a la cámara (extinción)

4) Algunos están completamente ocluidos debido a que un objeto distante bloquea los rayos de luz.

El resultado de esto es que partes de la escena aparecerán nubladas o confusas mientras permiten que el espectador vea a través del otro lado. Cuando hay objetos distantes que bloquean los rayos de luz, habrá áreas notablemente más oscuras en la niebla que dan la apariencia de rayos de luz/rayos de Dios (también conocidos como rayos crepusculares). Esto es muy notable en una escena boscosa o cuando la luz entra por una ventana pero está parcialmente bloqueada por el marco central.

Para lograr estos efectos, el renderizador realiza raycasting en el espacio de la cámara y utiliza los mapas de sombras en cascada combinados con el búfer de estructura. Para cada píxel, se emite un rayo y se muestrea a lo largo de 64 ubicaciones no lineales (recuento de muestreo exacto configurable en tiempo de ejecución). La densidad de muestreo es más alta en los puntos a lo largo del rayo más cercanos a la cámara y más escasa en los puntos que están más alejados de la cámara. Terminamos el muestreo temprano si el rayo se encuentra con alguna geometría que se verifica utilizando el búfer de estructura.

Para cada paso de muestreo, si esa ubicación no está en la sombra después de compararla con los mapas de sombras en cascada, aplica un modelo de niebla que estima cuánta luz se dispersa hacia la cámara en función de la distancia actual a lo largo del rayo. Si está en la sombra, esa muestra se omite. Estos pasos se acumulan en un valor final por píxel que se puede combinar con la textura de la pantalla principal.

Postprocesado

Durante el paso de procesamiento posterior, ocurre lo siguiente:

Futuro de StratusGFX

Probablemente continuaré trabajando en este motor de renderizado hasta cierto punto para poder seguir investigando y aprendiendo sobre la programación de gráficos. Estas son las áreas en las que estoy muy interesado en seguir adelante:

  • Portar el renderizador a Vulkan
  • Cambiar a un enfoque de iluminación global basado en el trazado de conos de vóxel o campos de distancia con signo de trazado de rayos (¿o tal vez otro enfoque? Todavía no estoy seguro)
  • Anti-aliasing de alta calidad incluso cuando la cámara se está moviendo usando Temporal Super Sampling Anti-Aliasing (TSSAA) que se mostró en Doom 2016

StratusGFX es compatible con Linux y Windows y se puede encontrar en GitHub. En el foro puedes ver la información ampliada y los comentarios, también puedes dejar el tuyo. Sigue leyendo…