Utiliser une LUT pour accélérer un shader lourd de trig pour les appareils mobiles

10

J'essaie de faire fonctionner ce shader sur un très ancien iDevice, ainsi que finalement Androids.

Même lorsque je réduis le code à 2 fonctions sinus par fragment, le shader tourne à environ 20fps.

J'ai envisagé de prendre une feuille du livre des anciennes techniques d'ombrage et de créer un tableau qui contient un tas de valeurs de trig prédéfinies et de les utiliser d'une manière ou d'une autre pour approximer le shader.

Dans le shader que j'ai lié ci-dessus, je simule déjà qu'en arrondissant les valeurs envoyées à la fonction trig la plus à gauche de la souris (tout en bas) diminue la qualité du shader. C'est en fait assez cool car très proche du côté gauche, il ressemble à un shader complètement différent et assez cool.

Quoi qu'il en soit, j'ai deux dilemmes:

  1. Je ne sais pas quel est le moyen le plus efficace d'avoir un tableau de valeurs comme 360 ​​dans un shader GLSL, constant ou uniforme?
  2. Je ne peux pas comprendre comment mettre un nombre dans une plage comme d'habitude si je voulais un angle entre 0 et 360 (oui je sais que les GPU utilisent des radians), je le ferais comme ça.

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

    Cependant, GLSL n'autorise pas les boucles while ni les fonctions récursives.

J.Doe
la source
Je ne sais pas s'il serait pratique de mettre en œuvre cela, mais cela aiderait-il à espacer les valeurs sinusoïdes précalculées de manière inégale, avec des valeurs plus étroitement regroupées où la pente de la courbe sinusoïdale est la plus raide et moins de valeurs là où elle se stabilise et ne change pas autant? Cela permettrait-il une plus grande précision là où elle est nécessaire, sans avoir besoin de stocker un grand nombre de valeurs?
trichoplax
5
Concernant # 2, la modfonction intégrée est ce que vous voulez. Vous écririez mod(angle, 360.0).
Nathan Reed
1
@trichoplax brillante idée mais je ne sais pas comment vous pourriez alors rechercher des valeurs sur la table. Disons que nous les avons mis dans un tableau avec certains d'entre eux plus concentrés. Comment trouver le bon indice?
J.Doe
6
Que diriez-vous de mettre vos valeurs dans une texture 1D à 3 canaux? De cette façon, vous pouvez obtenir le péché, le cos et le bronzage pour le prix d'une recherche de texture unique. Si vous mappez un angle de 0 à 2 ppp à 0 à 1 UV et utilisez le mode de texture répétée, vous n'avez même pas besoin de l'appel de mod, il sera automatiquement encapsulé et vous pourrez également obtenir des approximations filtrées linéairement entre vos valeurs stockées plutôt que enclenchement au plus proche.
russ
3
La plupart du temps, vous pouvez éliminer les fonctions trigonométriques lorsqu'elles sont utilisées pour la géométrie en n'utilisant pas l'angle, mais commencer et terminer avec la paire sin / cos et utiliser les identités trigonométriques pour les demi-angles et autres.
ratchet freak

Réponses:

8

Il a été suggéré à plusieurs reprises dans les commentaires, mais personne n'a ressenti le besoin de donner une réponse appropriée.Par souci d'exhaustivité, une solution simple et courante à ce problème pourrait être d'utiliser une texture comme table de recherche, en particulier une texture 1D qui contient toutes les valeurs de votre fonction pour la plage d'entrée possible (c'est-à-dire / ]. Cela présente divers avantages:[ 0 , 2 π )[0,360)[0,2π)

  • Il utilise des coordonnées normalisées, c'est-à-dire que vous accédez à la texture en mappant vos angles de à . Cela signifie que votre shader n'a pas réellement à se soucier de la quantité spécifique de valeurs . Vous pouvez ajuster sa taille en fonction du compromis mémoire / vitesse / qualité souhaité (et en particulier sur le matériel ancien / intégré, vous voudrez peut-être une puissance de deux comme taille de texture).[ 0 , 1 ][0,360][0,1]
  • Vous obtenez l'avantage supplémentaire de ne pas avoir à effectuer votre ajustement d'intervalle de type boucle (bien que vous n'ayez pas besoin de boucles de toute façon et que vous puissiez simplement utiliser une opération de module). Il suffit de l'utiliser GL_REPEATcomme mode d'habillage pour la texture et elle recommencera automatiquement au début lors de l'accès avec des arguments> 1 (et de même pour les arguments négatifs).
  • Et vous bénéficiez également de l' interpolation linéaire entre deux valeurs du tableau, essentiellement gratuitement (ou disons presque gratuitement) en utilisant GL_LINEARcomme filtre de texture, de cette manière, vous obtenez des valeurs que vous n'avez même pas stockées. Bien sûr, l'interpolation linéaire n'est pas précise à 100% pour les fonctions trigonométriques, mais c'est certainement mieux que pas d' interpolation.
  • Vous pouvez stocker plusieurs valeurs dans la texture en utilisant une texture RGBA (ou le nombre de composants dont vous avez besoin). De cette façon, vous pouvez obtenir, par exemple, sin et cos avec une seule recherche de texture.
  • Pour sin et cos, vous n'avez de toute façon qu'à stocker des valeurs dans , que vous pouvez naturellement mettre à l' échelle à partir de la plage normalisée d'un format à virgule fixe 8 bits commun. Cependant, cela pourrait ne pas être assez précis pour vos besoins. Certaines personnes ont suggéré d'utiliser des valeurs à virgule flottante 16 bits, car elles sont plus précises que les valeurs à virgule fixe normalisées 8 bits habituelles mais moins gourmandes en mémoire que les flottants 32 bits réels. Mais là encore, je ne sais pas non plus si votre implémentation prend en charge les textures à virgule flottante pour commencer. Sinon, vous pouvez peut-être utiliser 2 composants à virgule fixe de 8 bits et les combiner en une seule valeur avec quelque chose comme (ou même plus de composants pour un grain plus fin). Cela vous permet de profiter à nouveau des textures multi-composants.[ 0 , 1 ][1,1][0,1]float sin = 2.0 * (texValue.r + texValue.g / 256.0) - 1.0;

Bien sûr, il reste à évaluer si c'est une meilleure solution, car l'accès aux textures n'est pas entièrement gratuit non plus, ainsi que la meilleure combinaison de taille et de format de texture.

En ce qui concerne le remplissage de la texture avec des données et l'adressage d'un de vos commentaires, vous devez considérer que le filtrage de texture renvoie la valeur exacte au centre du texel , c'est-à-dire une coordonnée de texture décalée de la moitié de la taille du texel. Alors oui, vous devez générer des valeurs au niveau des .5texels , c'est-à-dire quelque chose comme ça dans le code d'application:

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);

Cependant, vous souhaiterez peut-être toujours comparer les performances de cette approche avec une approche utilisant un petit tableau uniforme (c'est-à uniform float sinTable[361]- dire , ou peut-être moins en pratique, gardez un œil sur la limite de votre implémentation sur la taille du tableau uniforme) que vous chargez simplement avec les valeurs respectives en utilisant glUniform1fvet accédez en ajustant votre angle à utilisant la fonction et en l'arrondissant à la valeur la plus proche:[0,360)mod

angle = mod(angle, 360.0);
float value = sinTable[int(((angle < 0.0) ? (angle + 360.0) : angle) + 0.5)];
Chris dit de réintégrer Monica
la source
1
Voici une extension intéressante pour stocker des tables de recherche dans les textures. Il (ab) utilise l'interpolation de texture N-linéaire pour obtenir une interpolation d'ordre supérieur (mieux que linéaire) des points de données, des surfaces, des volumes et des hypervolumes. blog.demofox.org/2016/02/22/…
Alan Wolfe