Usar dos sombreadores en lugar de uno con sentencias IF

9

He estado trabajando en portar una fuente relativamente grande de opengl ES 1.1 a ES 2.0.

En OpenGL ES 2.0 (lo que significa que todo usa sombreadores), quiero dibujar una tetera tres veces.

  1. El primero, con un color uniforme (ala el antiguo glColor4f).

  2. El segundo, con un color por vértice (la tetera también tiene su matriz de color de vértice)

  3. El tercero, con textura por vértice.

  4. Y quizás un cuarto, con textura y color por vértice. Y luego tal vez un quinto, con normales también ...

Hay dos opciones que tengo con la implementación, que yo sepa. El primero es hacer un sombreador que admita todo lo anterior, con un uniforme configurado para cambiar el comportamiento (por ejemplo, use el uniforme de color singular o el uniforme de color por vértice).

La segunda opción es crear un sombreador diferente para cada situación. Con algunos preprocesamientos de sombreadores personalizados, no es tan complicado de hacer, pero la preocupación es el costo de rendimiento en cambiar sombreadores entre objetos de dibujo. He leído que no es trivialmente pequeño.

Quiero decir, la mejor manera de hacerlo es construir ambos y medir, pero sería bueno escuchar cualquier entrada.

kamziro
fuente

Respuestas:

10

El costo de rendimiento de la ramificación no puede ser trivialmente pequeño también. En su caso, todos los vértices y fragmentos que se dibujan tomarán el mismo camino a través de sus sombreadores, por lo que en el hardware de escritorio moderno no sería tan malo como podría ser, pero está utilizando ES2, lo que implica que no está utilizando modernos hardware de escritorio

El peor de los casos con ramificación será algo como esto:

  • Se evalúan ambos lados de la rama.
  • El compilador del sombreador generará una instrucción de "mezcla" o "paso" y la insertará en su código para decidir qué lado usar.

Y todas estas instrucciones adicionales se ejecutarán para cada vértice o fragmento que dibuje. Eso es potencialmente millones de instrucciones adicionales para comparar con el costo de un cambio de sombreador.

La " Guía de programación OpenGL ES de Apple para iOS " (que se puede tomar como representante de su hardware objetivo) tiene esto que decir sobre la ramificación:

Evitar ramificaciones

Se desaconsejan las ramas en los sombreadores, ya que pueden reducir la capacidad de ejecutar operaciones en paralelo en procesadores de gráficos 3D. Si sus sombreadores deben usar ramas, siga estas recomendaciones:

  • Mejor rendimiento: bifurca en una constante conocida cuando se compila el sombreador.
  • Aceptable: se ramifica en una variable uniforme.
  • Potencialmente lento: bifurcación en un valor calculado dentro del sombreador.

En lugar de crear un sombreador grande con muchos mandos y palancas, cree sombreadores más pequeños especializados para tareas de renderizado específicas. Existe una compensación entre reducir la cantidad de ramas en tus sombreadores y aumentar la cantidad de sombreadores que creas. Pruebe diferentes opciones y elija la solución más rápida.

Incluso si está satisfecho de estar aquí en el espacio "Aceptable", debe tener en cuenta que con 4 o 5 casos para seleccionar, aumentará el recuento de instrucciones en sus sombreadores. Debe conocer los límites de recuento de instrucciones en su hardware de destino y asegurarse de no superarlos, citando nuevamente desde el enlace de Apple anterior:

Las implementaciones de OpenGL ES no son necesarias para implementar un respaldo de software cuando se exceden estos límites; en cambio, el sombreador simplemente no puede compilarse o vincularse.

Nada de esto significa que la ramificación no sea la mejor solución para sus necesidades. Identificó correctamente el hecho de que debe perfilar ambos enfoques, por lo que esa es la recomendación final. Pero tenga en cuenta que a medida que los sombreadores se vuelven más complejos, una solución basada en ramificaciones puede proporcionar una sobrecarga mucho mayor que unos pocos cambios de sombreadores.

Maximus Minimus
fuente
3

El costo de enlazar sombreadores puede no ser trivial, pero no será su cuello de botella a menos que esté renderizando miles de artículos sin agrupar todos los objetos que usan los mismos sombreadores.

Aunque no estoy seguro de si esto se aplica a los dispositivos móviles, pero las GPU no son terriblemente lentas con ramas si la condición es entre una constante y un uniforme. Ambos son válidos, ambos se han usado en el pasado y se seguirán usando en el futuro, elija el que considere más limpio en su caso.

Además, hay algunas otras formas de lograr esto: "Uber-shaders" y un pequeño truco con la forma en que se vinculan los programas de sombreadores OpenGL.

Los "Uber-shaders" son esencialmente la primera opción, menos la ramificación, pero tendrás múltiples shaders. En lugar de utilizar iflas declaraciones, se utiliza el preprocesador - #define, #ifdef, #else, #endif, y compilar diferentes versiones, incluyendo las adecuadas #defines para lo que necesita.

vec4 color;
#ifdef PER_VERTEX_COLOR
color = in_color;
#else
color = obj_color;
#endif

También puede dividir el sombreador en funciones separadas. Tenga un sombreador que defina prototipos para todas las funciones y los llame, vincule un montón de sombreadores adicionales que incluyen las implementaciones adecuadas. He usado este truco para el mapeo de sombras, para facilitar el intercambio de cómo se realiza el filtrado en todos los objetos sin tener que modificar todos los sombreadores.

//ins, outs, uniforms

float getShadowCoefficient();

void main()
{
    //shading stuff goes here

    gl_FragColor = color * getShadowCoefficient();
}

Entonces, podría tener varios otros archivos de sombreador que definan getShadowCoefficient(), los uniformes necesarios y nada más. Por ejemplo, shadow_none.glslcontiene:

float getShadowCoefficient()
{
    return 1;
}

Y shadow_simple.glslcontiene (simplificado desde mi sombreador que implementa CSM):

in vec4 eye_position;

uniform sampler2DShadow shad_tex;
uniform mat4 shad_mat;

float getShadowCoefficient()
{
    vec4 shad_coord = shad_mat * eye_position;
    return texture(shad_tex, shad_coord).x;
}

Y simplemente puede elegir si desea sombrear o no vinculando un shadow_*sombreador diferente . Es muy posible que esta solución tenga más gastos generales, pero me gustaría pensar que el compilador GLSL es lo suficientemente bueno para optimizar cualquier gasto adicional en comparación con otras formas de hacerlo. No he realizado ninguna prueba sobre esto, pero es la forma en que me gusta hacerlo.

Robert Rouhani
fuente