¿Hay alguna manera de usar un número arbitrario de luces en un sombreador de fragmentos?

19

¿Hay alguna manera de pasar un número arbitrario de ubicaciones de luz (y colores) para el sombreador de fragmentos y recorrerlas en el sombreador?

Si no, ¿cómo se supone que se simularán varias luces? Por ejemplo, con respecto a la iluminación direccional difusa, no puede simplemente pasar una suma de los pesos de luz para el sombreador.

No real
fuente
No he estado trabajando con WebGL, pero en OpenGL, tiene un máximo de 8 fuentes de luz. En mi opinión, si desea pasar más que eso, debe usar, por ejemplo, variables uniformes.
zacharmarz
El método anterior era pasar siempre todas las luces, las luces no utilizadas se configuraron en 0 luminancia y, por lo tanto, no afectarían la escena. Probablemente ya no se usa mucho ;-)
Patrick Hughes
77
Cuando buscas en Google cosas como esta, no uses el término 'WebGL': la tecnología es demasiado joven para que la gente tenga la posibilidad de abordar estos problemas. Tome esta búsqueda, por ejemplo, 'Me siento afortunado' hubiera funcionado. Recuerde que un problema de WebGL debe traducirse exactamente al mismo problema de OpenGL.
Jonathan Dickinson el
Para más de 8 luces en el renderizado hacia adelante, generalmente uso un sombreador de varias pasadas y le doy a cada pasada un grupo diferente de 8 luces para procesar, usando una mezcla aditiva.
ChrisC

Respuestas:

29

Generalmente hay dos métodos para lidiar con esto. Hoy en día, se denominan renderizado directo y renderizado diferido. Hay una variación de estos dos que discutiré a continuación.

Representación hacia adelante

Renderice cada objeto una vez por cada luz que lo afecte. Esto incluye la luz ambiental. Utiliza un modo de mezcla aditiva ( glBlendFunc(GL_ONE, GL_ONE)), por lo que las contribuciones de cada luz se suman entre sí. Como la contribución de diferentes luces es aditiva, el framebuffer eventualmente obtiene el valor

Puede obtener HDR al renderizar a un framebuffer de punto flotante. Luego, realiza una pasada final sobre la escena para reducir los valores de iluminación HDR a un rango visible; aquí también sería donde implementas bloom y otros efectos posteriores.

Una mejora de rendimiento común para esta técnica (si la escena tiene muchos objetos) es usar un "paso previo", donde se procesan todos los objetos sin dibujar nada en el buffer de cuadros de color (se usa glColorMaskpara desactivar las escrituras en color). Esto solo llena el búfer de profundidad. De esta manera, si renderiza un objeto que está detrás de otro, la GPU puede omitir rápidamente esos fragmentos. Todavía tiene que ejecutar el sombreador de vértices, pero puede omitir los cálculos de sombreadores de fragmentos típicamente más caros.

Esto es más simple de codificar y más fácil de visualizar. Y en algunos equipos (principalmente GPU móviles e integrados), puede ser más eficiente que la alternativa. Pero en el hardware de gama alta, la alternativa generalmente gana para escenas con muchas luces.

Renderizado diferido

El renderizado diferido es un poco más complicado.

La ecuación de iluminación que utiliza para calcular la luz de un punto en una superficie utiliza los siguientes parámetros de superficie:

  • Posición de la superficie
  • Normales de superficie
  • Color difuso superficial
  • Color especular de superficie
  • Brillo especular superficial
  • Posiblemente otros parámetros de superficie (dependiendo de cuán compleja sea su ecuación de iluminación)

En el renderizado hacia adelante, estos parámetros llegan a la función de iluminación del sombreador de fragmentos al pasar directamente desde el sombreador de vértices, al extraerlos de las texturas (generalmente a través de las coordenadas de textura pasadas desde el sombreador de vértices), o generados a partir de un paño completo en el sombreador de fragmentos basado en Otros parámetros. El color difuso se puede calcular combinando un color por vértice con una textura, combinando múltiples texturas, lo que sea.

En la representación diferida, hacemos todo esto explícito. En la primera pasada, renderizamos todos los objetos. Pero no representamos colores . En cambio, representamos parámetros de superficie . Por lo tanto, cada píxel en la pantalla tiene un conjunto de parámetros de superficie. Esto se realiza mediante el procesamiento de texturas fuera de la pantalla. Una textura almacenaría el color difuso como su RGB, y posiblemente el brillo especular como el alfa. Otra textura almacenaría el color especular. Un tercero almacenaría lo normal. Y así.

La posición generalmente no se almacena. En su lugar, se reconstituye en el segundo paso por matemáticas que es demasiado complejo para entrar aquí. Baste decir que usamos el búfer de profundidad y la posición del fragmento del espacio de la pantalla como entrada para determinar la posición del espacio de la cámara del punto en una superficie.

Entonces, ahora que estas texturas contienen esencialmente toda la información de la superficie para cada píxel visible en la escena, comenzamos a representar quads de pantalla completa. Cada luz tiene un render cuádruple a pantalla completa. Tomamos muestras de las texturas de los parámetros de la superficie (y reconstituimos la posición), luego las usamos para calcular la contribución de esa luz. Esto se agrega (nuevamente glBlendFunc(GL_ONE, GL_ONE)) a la imagen. Seguimos haciendo esto hasta que se nos acaben las luces.

HDR nuevamente es un paso posterior al proceso.

El mayor inconveniente del renderizado diferido es el antialiasing. Se requiere un poco más de trabajo para antialias correctamente.

La mayor ventaja, si su GPU tiene mucho ancho de banda de memoria, es el rendimiento. Solo renderizamos la geometría real una vez (o 1 + 1 por luz que tiene sombras, si estamos haciendo mapeo de sombras). Nosotros nunca se pierde ningún tiempo en píxeles o geometría oculta que no es visible después de esto. Todo el tiempo de iluminación se gasta en cosas que son realmente visibles.

Si su GPU no tiene mucho ancho de banda de memoria, entonces el pase de luz realmente puede comenzar a doler. Tirar de 3-5 texturas por píxel de pantalla no es divertido.

Pre-Pase Ligero

Esta es una especie de variación en el renderizado diferido que tiene compensaciones interesantes.

Al igual que en la representación diferida, representa los parámetros de su superficie en un conjunto de búferes. Sin embargo, ha abreviado los datos de superficie; los únicos datos de superficie que le interesan en este momento son el valor del búfer de profundidad (para reconstruir la posición), normal y el brillo especular.

Luego, para cada luz, calcula solo los resultados de la iluminación. Sin multiplicación con colores de superficie, nada. Solo el punto (N, L) y el término especular, completamente sin los colores de la superficie. Los términos especulares y difusos deben mantenerse en búferes separados. Los términos especulares y difusos para cada luz se resumen dentro de los dos buffers.

Luego, vuelve a renderizar la geometría, utilizando los cálculos totales de iluminación especular y difusa para hacer la combinación final con el color de la superficie, produciendo así la reflectancia general.

La ventaja aquí es que obtienes múltiples muestras (al menos, más fácil que con diferido). Hace menos renderizado por objeto que el renderizado hacia adelante. Pero lo más diferido que esto proporciona es un momento más fácil para tener diferentes ecuaciones de iluminación para diferentes superficies.

Con la representación diferida, generalmente dibuja toda la escena con el mismo sombreador por luz. Por lo tanto, cada objeto debe usar los mismos parámetros materiales. Con el paso previo de luz, puede darle a cada objeto un sombreador diferente, para que pueda hacer el paso final de iluminación por sí solo.

Esto no proporciona tanta libertad como el caso de renderizado directo. Pero aún es más rápido si tiene el ancho de banda de textura de sobra.

Nicol Bolas
fuente
-1: no se menciona LPP / PPL. -1 diferido: la representación es una ganancia instantánea en cualquier hardware DX9.0 (sí, incluso en mi computadora portátil 'comercial'), que son los requisitos de referencia alrededor de 2009. A menos que esté apuntando a DX8.0 (que no puede hacer Diferido / LPP) Diferido / LPP es el valor predeterminado . Finalmente, 'una gran cantidad de ancho de banda de memoria' es una locura: por lo general, ni siquiera estamos saturando PCI-X x4, además, LPP reduce sustancialmente el ancho de banda de la memoria. Finalmente, -1 por tu comentario; bucles como este ¿OK? Sabes que esos bucles están sucediendo 2073600 veces por cuadro, ¿verdad? Incluso con el parrelismo de la tarjeta gráfica, es malo.
Jonathan Dickinson el
1
@JonathanDickinson Creo que su punto era que el ancho de banda de memoria para el prepasado diferido / ligero es generalmente varias veces mayor que para el renderizado directo. Esto no invalida el enfoque diferido; es solo algo a considerar al elegirlo. Por cierto: sus memorias intermedias diferidas deben estar en la memoria de video, por lo que el ancho de banda PCI-X es irrelevante; lo que importa es el ancho de banda interno de la GPU. Los sombreadores de píxeles largos, por ejemplo, con un bucle desenrollado, no son nada del otro mundo si están haciendo un trabajo útil. Y no hay nada malo con el truco prepass z-buffer; funciona bien.
Nathan Reed
3
@ JonathanDickinson: Esto está hablando de WebGL, por lo que cualquier discusión sobre "modelos de sombreadores" es irrelevante. Y qué tipo de renderizado usar no es un "tema religioso": es simplemente una cuestión de en qué hardware se está ejecutando. Una GPU integrada, donde la "memoria de video" es solo la RAM de la CPU, funcionará muy mal con el renderizado diferido. En un renderizador móvil basado en mosaicos, es aún peor . El renderizado diferido no es una "ganancia instantánea" independientemente del hardware; tiene sus compensaciones, al igual que cualquier hardware.
Nicol Bolas
2
@JonathanDickinson: "Además, con el truco de aprobación previa de z-buffer, tendrás dificultades para eliminar la lucha z con los objetos que se deben dibujar". Eso es una tontería total. Está renderizando los mismos objetos con las mismas matrices de transformación y el mismo sombreador de vértices. La renderización multipass se realizó en el Voodoo 1 días; Este es un problema resuelto . La iluminación acumulada no hace nada para cambiar eso.
Nicol Bolas
8
@ JonathanDickinson: Pero no estamos hablando de renderizar una estructura metálica, ¿verdad? Estamos hablando de renderizar los mismos triángulos que antes. OpenGL garantiza la invariancia para el mismo objeto que se procesa (siempre que esté utilizando el mismo sombreador de vértices, por supuesto, e incluso entonces, existe la invariantpalabra clave para garantizarlo en otros casos).
Nicol Bolas
4

Debe utilizar renderizado diferido o iluminación previa al paso . Algunas de las tuberías de funciones fijas más antiguas (léase: sin sombreadores) admitían hasta 16 o 24 luces, pero eso es todo . El renderizado diferido elimina el límite de luz; pero a costa de un sistema de renderizado mucho más complicado.

Aparentemente, WebGL es compatible con MRT, que es absolutamente necesario para cualquier forma de representación diferida, por lo que podría ser factible; No estoy seguro de lo plausible que es.

Alternativamente, podría investigar Unity 5 , que ha diferido el renderizado de inmediato.

Otra forma sencilla de lidiar con esto es simplemente priorizar las luces (tal vez, en función de la distancia del jugador y si están en el centro de la cámara) y solo habilitar el top 8. Muchos títulos AAA lograron hacer esto sin mucho impacto sobre la calidad de la salida (por ejemplo, Far Cry 1).

También puede buscar mapas de luz calculados previamente . Los juegos como Quake 1 obtuvieron mucho kilometraje de estos, y pueden ser bastante pequeños (el filtrado bilineal suaviza muy bien los mapas de luz estirados). Lamentablemente, el cálculo previo excluye la noción de luces 100% dinámicas, pero realmente se ve muy bien . Puede combinar esto con su límite de 8 luces, por ejemplo, solo los cohetes o algo así tendrían una luz real, pero las luces en la pared o algo así serían mapas de luz.

Nota al margen : ¿ No quieres recorrerlos en un sombreador? Diga adiós a su actuación. Una GPU no es una CPU y no está diseñada para funcionar de la misma manera que, por ejemplo, JavaScript. Recuerde que cada píxel que renderiza (incluso si se sobrescribe) tiene que realizar el bucle, por lo que si toma la ejecución a 1920x1080 y un bucle simple que se ejecuta 16 veces, está ejecutando todo lo que está dentro de ese bucle 33177600 veces. Su tarjeta gráfica ejecutará muchos de esos fragmentos en paralelo, pero esos bucles seguirán consumiendo hardware más antiguo.

Jonathan Dickinson
fuente
-1: "Necesitas usar renderizado diferido" Esto no es cierto en absoluto. El renderizado diferido es ciertamente una forma de hacerlo, pero no es la única forma. Además, los bucles no son tan malos en términos de rendimiento, especialmente si se basan en valores uniformes (es decir, cada fragmento no tiene una longitud de bucle diferente).
Nicol Bolas
1
Por favor lea el cuarto párrafo.
Jonathan Dickinson el
2

Puede usar un sombreador de píxeles que admita n luces (donde n es un número pequeño como 4 u 8) y volver a dibujar la escena varias veces, pasando un nuevo lote de luces cada vez y usando una combinación aditiva para combinarlas.

Esa es la idea básica. Por supuesto, se necesitan muchas optimizaciones para hacer esto lo suficientemente rápido para una escena de tamaño razonable. No dibuje todas las luces, solo las visibles (eliminación de frustum y oclusión); en realidad no vuelva a dibujar la escena completa en cada pasada, solo los objetos dentro del alcance de las luces en esa pasada; tenga varias versiones del sombreador que admitan diferentes números de luces (1, 2, 3, ...) para que no pierda el tiempo evaluando más luces de las que necesita.

La representación diferida como se menciona en la otra respuesta es una buena opción cuando tiene muchas luces pequeñas, pero no es la única forma.

Nathan Reed
fuente