Usando un LUT para acelerar un sombreador trigonométrico pesado para dispositivos móviles

10

Estoy tratando de hacer que este sombreador se ejecute en un iDevice muy antiguo, así como eventualmente en Android.

Incluso cuando reduzco el código a 2 funciones sinusoidales por fragmento, el sombreador se ejecuta a aproximadamente 20 fps.

He considerado sacar una hoja del libro de viejas técnicas de sombreado y crear una matriz que contenga un montón de valores trigonométricos predefinidos y de alguna manera usarlos para aproximar el sombreador.

En el sombreador que vinculé anteriormente, ya estoy simulando que al redondear los valores enviados a la función trigonométrica, cuanto más a la izquierda del mouse (mientras está hacia abajo), disminuye la calidad del sombreador. En realidad, es bastante genial porque muy cerca del lado izquierdo parece un sombreador completamente diferente y bastante genial.

De todos modos tengo dos dilemas:

  1. No sé cuál es la forma más eficiente de tener una matriz de valores similares a 360 en un sombreador GLSL, constante o uniforme.
  2. No puedo encontrar la manera de poner un número en un rango, por lo general, si quisiera un ángulo entre 0 y 360 (sí, sé que las GPU usan radianes). Lo haría así.

    func range(float angle)
    {
       float temp = angle
       while (temp > 360) {temp -= 360;}
       while (temp < 0)   {temp += 360;}
       return temp;
    }
    

    Sin embargo, GLSL no permite bucles while o funciones recursivas.

J.Doe
fuente
No sé si sería práctico implementar esto, pero sería útil tener los valores de seno calculados previamente espaciados de manera desigual, con valores agrupados más estrechamente donde la pendiente de la curva sinusoidal es más pronunciada y menos valores donde se nivela y no cambia tanto? ¿Esto permitiría una mayor precisión donde se necesita, sin necesidad de almacenar una gran cantidad de valores?
trichoplax
55
Con respecto al n. ° 2, la modfunción incorporada es lo que desea. Tu escribirias mod(angle, 360.0).
Nathan Reed
1
idea brillante de @trichoplax, pero no sé cómo podrías buscar valores en la tabla entonces. Digamos que los colocamos en una matriz con algunos de ellos más concentrados. ¿Cómo podríamos encontrar el índice correcto?
J.Doe
66
¿Qué tal poner sus valores en una textura 1D de 3 canales? De esa manera, puede obtener pecado, cos y broncearse por el precio de una sola búsqueda de textura. Si asigna un ángulo de 0 - 2pi a 0 - 1 UV y usa el modo de repetición de textura, ni siquiera necesita la llamada de modulación, se 'ajustará' automáticamente, y también puede obtener aproximaciones con filtro lineal entre sus valores almacenados en lugar de chasqueando a la más cercana.
russ
3
La mayoría de las veces puede eliminar las funciones trigonométricas cuando se usa para geometría al no usar el ángulo, sino comenzar y terminar con el par sin / cos y usar identidades trigonométricas para ángulos medios y tal.
monstruo de trinquete

Respuestas:

8

Se ha sugerido en comentarios repetidamente, pero nadie sintió la necesidad de dar una respuesta adecuada, por lo que, en aras de la exhaustividad, una solución directa y común a este problema podría ser usar una textura como tabla de búsqueda, específicamente una textura 1D que contiene todos los valores de su función para el posible rango de entrada (es decir, / ). Esto tiene varias ventajas:[0,360)[0,2π)

  • Utiliza coordenadas normalizadas, es decir, accede a la textura mapeando sus ángulos de a . Esto significa que su sombreador no tiene que preocuparse por la cantidad específica de valores . Puede ajustar su tamaño de acuerdo con la memoria / velocidad frente a la compensación de calidad que desee (y especialmente en hardware antiguo / incrustado, de todos modos es posible que desee una potencia de dos como tamaño de textura).[0,360][0,1]
  • Obtiene el beneficio adicional de no tener que hacer su ajuste de intervalo en forma de bucle (aunque, de todos modos, no necesitaría bucles y podría usar una operación de módulo). Solo utilícelo GL_REPEATcomo modo de ajuste para la textura y comenzará automáticamente al principio nuevamente cuando acceda con argumentos> 1 (y de manera similar para argumentos negativos).
  • Y también obtienes el beneficio de interpolar linealmente entre dos valores en la matriz básicamente de forma gratuita (o digamos casi gratis) al usar GL_LINEARcomo filtro de textura, de esta manera obtienes valores que ni siquiera almacenaste. Por supuesto, la interpolación lineal no es 100% precisa para las funciones trigonométricas, pero ciertamente es mejor que ninguna interpolación.
  • Puede almacenar más de un valor en la textura utilizando una textura RGBA (o la cantidad de componentes que necesite). De esta manera, puede obtener, por ejemplo, sin y cos con una sola búsqueda de textura.
  • Para sin y cos solo necesita almacenar valores en todos modos, lo que naturalmente puede ampliar desde el rango normalizado de un formato común de punto fijo de 8 bits. Sin embargo, eso podría no ser suficiente precisión para sus necesidades. Algunas personas sugirieron usar valores de punto flotante de 16 bits, ya que son más precisos que los valores de punto fijo normalizados de 8 bits habituales pero menos intensivos en memoria que los flotadores reales de 32 bits. Pero, de nuevo, tampoco sé si su implementación admite texturas de punto flotante para empezar. De lo contrario, tal vez pueda usar 2 componentes de punto fijo de 8 bits y combinarlos en un solo valor con algo como (o incluso más componentes para un grano más fino). Esto le permite beneficiarse de las texturas de múltiples componentes una vez más.[1,1][0,1]float sin = 2.0 * (texValue.r + texValue.g / 256.0) - 1.0;

Por supuesto, todavía tiene que evaluarse si esta es una mejor solución, ya que el acceso a la textura tampoco es totalmente gratuito, así como cuál sería la mejor combinación de tamaño y formato de textura.

En cuanto a rellenar la textura con datos y abordar uno de sus comentarios, debe tener en cuenta que el filtrado de textura devuelve el valor exacto en el centro de texel , es decir, una coordenada de textura a la mitad del tamaño del texel. Entonces sí, debe generar valores en .5texels , es decir, algo así en el código de la aplicación:

float texels[256];
for(unsigned int i = 0; i < 256; ++i)
    texels[i] = sin((i + .5f) / 256.f) * TWO_PI);
glTexImage1D(GL_TEXTURE_1D, 0, ..., 256, 0, GL_RED, GL_FLOAT, texels);

Sin embargo, es posible que aún desee comparar el rendimiento de este enfoque con un enfoque que usa una matriz uniforme pequeña (es decir uniform float sinTable[361], o tal vez menos en la práctica, vigile el límite de su implementación en el tamaño de matriz uniforme) que simplemente carga con los valores respectivos usando glUniform1fvy acceda ajustando su ángulo a usando la función y redondeándolo al valor más cercano:[0,360)mod

angle = mod(angle, 360.0);
float value = sinTable[int(((angle < 0.0) ? (angle + 360.0) : angle) + 0.5)];
Chris dice reinstalar a Mónica
fuente
1
Aquí hay una extensión interesante para almacenar tablas de búsqueda en texturas. (Ab) utiliza la interpolación de textura N-lineal para obtener una interpolación de orden superior (también conocida como mejor que la lineal) de puntos de datos, superficies, volúmenes e hipervolúmenes. blog.demofox.org/2016/02/22/…
Alan Wolfe