Existe-t-il un moyen d'utiliser un nombre arbitraire de lumières dans un fragment shader?

19

Existe-t-il un moyen de passer un nombre arbitraire d'emplacements lumineux (et de couleurs) pour le fragment shader et de les boucler dans le shader?

Sinon, comment simuler plusieurs lumières? Par exemple, en ce qui concerne l'éclairage directionnel diffus, vous ne pouvez pas simplement transmettre une somme des poids légers pour le shader.

NotRoyal
la source
Je n'ai pas travaillé avec WebGL, mais dans OpenGL, vous avez au maximum 8 sources lumineuses. À mon avis, si vous voulez passer plus que cela, vous devez utiliser par exemple des variables uniformes.
zacharmarz
L'ancienne méthode consistait à toujours passer toutes les lumières, les lumières inutilisées étaient réglées à 0 luminance et n'affectaient donc pas la scène. Probablement plus utilisé ;-)
Patrick Hughes
7
Lorsque vous utilisez Google comme ça, n'utilisez pas le terme «WebGL» - la technologie est trop jeune pour que les gens puissent y penser, même s'ils abordent ces problèmes. Prenons par exemple cette recherche : «J'ai de la chance» aurait fonctionné. N'oubliez pas qu'un problème WebGL devrait se traduire correctement par le même problème OpenGL.
Jonathan Dickinson
Pour plus de 8 lumières dans le rendu direct, j'utilise généralement un shader multi-passes et donne à chaque passe un groupe différent de 8 lumières à traiter, en utilisant un mélange additif.
ChrisC

Réponses:

29

Il existe généralement deux méthodes pour résoudre ce problème. De nos jours, ils sont appelés rendu direct et rendu différé. Il y a une variation sur ces deux que je vais discuter ci-dessous.

Rendu vers l'avant

Rendez chaque objet une fois pour chaque lumière qui l'affecte. Cela inclut la lumière ambiante. Vous utilisez un mode de mélange additif ( glBlendFunc(GL_ONE, GL_ONE)), de sorte que les contributions de chaque lumière sont ajoutées les unes aux autres. Étant donné que la contribution de différentes lumières est additive, le framebuffer obtient finalement la valeur

Vous pouvez obtenir le HDR en effectuant un rendu sur un framebuffer à virgule flottante. Vous effectuez ensuite un dernier passage sur la scène pour réduire les valeurs d'éclairage HDR à une plage visible; ce serait également là que vous implémentez la floraison et d'autres post-effets.

Une amélioration courante des performances de cette technique (si la scène contient beaucoup d'objets) consiste à utiliser un "pré-passage", dans lequel vous effectuez le rendu de tous les objets sans dessiner quoi que ce soit dans le tampon d'image couleur (utilisez glColorMaskpour désactiver les écritures couleur). Cela remplit simplement le tampon de profondeur. De cette façon, si vous rendez un objet derrière un autre, le GPU peut rapidement ignorer ces fragments. Il doit toujours exécuter le vertex shader, mais il peut ignorer les calculs de shader de fragments généralement plus chers.

C'est plus simple à coder et plus facile à visualiser. Et sur certains matériels (principalement les GPU mobiles et embarqués), il peut être plus efficace que l'alternative. Mais sur le matériel haut de gamme, l'alternative gagne généralement pour les scènes avec beaucoup de lumières.

Rendu différé

Le rendu différé est un peu plus compliqué.

L'équation d'éclairage que vous utilisez pour calculer la lumière d'un point sur une surface utilise les paramètres de surface suivants:

  • Position de surface
  • Normales de surface
  • Couleur diffuse en surface
  • Couleur spéculaire de surface
  • Brillance spéculaire de la surface
  • Peut-être d'autres paramètres de surface (selon la complexité de votre équation d'éclairage)

Dans le rendu vers l'avant, ces paramètres accèdent à la fonction d'éclairage du shader de fragment en passant directement du vertex shader, en étant tirés des textures (généralement via les coordonnées de texture passées du vertex shader), ou générés à partir du tissu entier dans le fragment shader en fonction de d'autres paramètres. La couleur diffuse peut être calculée en combinant une couleur par sommet avec une texture, en combinant plusieurs textures, peu importe.

Dans le rendu différé, nous rendons tout cela explicite. Dans la première passe, nous rendons tous les objets. Mais nous ne rendons pas les couleurs . Au lieu de cela, nous rendons les paramètres de surface . Ainsi, chaque pixel de l'écran possède un ensemble de paramètres de surface. Cela se fait via le rendu des textures hors écran. Une texture stockerait la couleur diffuse comme son RVB, et peut-être la brillance spéculaire comme l'alpha. Une autre texture stockerait la couleur spéculaire. Un tiers stockerait la normale. Etc.

La position n'est généralement pas enregistrée. Il est plutôt reconstitué dans la seconde passe par des mathématiques trop complexes pour entrer ici. Il suffit de dire que nous utilisons le tampon de profondeur et la position du fragment d'espace d'écran comme entrée pour déterminer la position d'espace caméra du point sur une surface.

Donc, maintenant que ces textures contiennent essentiellement toutes les informations de surface pour chaque pixel visible de la scène, nous commençons à rendre les quads en plein écran. Chaque lumière obtient un rendu quad en plein écran. Nous échantillonnons à partir des textures des paramètres de surface (et reconstituons la position), puis nous les utilisons simplement pour calculer la contribution de cette lumière. Ceci est ajouté (encore glBlendFunc(GL_ONE, GL_ONE)) à l'image. Nous continuons à faire cela jusqu'à ce que nous manquions de lumière.

HDR est à nouveau une étape post-processus.

Le plus gros inconvénient du rendu différé est l'anticrénelage. Il faut un peu plus de travail pour l'antialias correctement.

Le plus gros avantage, si votre GPU a beaucoup de bande passante mémoire, est la performance. Nous ne rendons la géométrie réelle qu'une seule fois (ou 1 + 1 par lumière qui a des ombres, si nous faisons du mapping d'ombres). Nous ne passons jamais de temps sur les pixels cachés ou la géométrie qui n'est pas visible après cela. Tout le temps de passage de l'éclairage est consacré à des choses réellement visibles.

Si votre GPU n'a pas beaucoup de bande passante mémoire, le passage de lumière peut vraiment commencer à faire mal. Tirer de 3 à 5 textures par pixel d'écran n'est pas amusant.

Pré-passage léger

Il s'agit en quelque sorte d'une variation du rendu différé qui présente des compromis intéressants.

Tout comme dans le rendu différé, vous restituez vos paramètres de surface à un ensemble de tampons. Cependant, vous disposez de données de surface abrégées; les seules données de surface qui vous intéressent cette fois-ci sont la valeur du tampon de profondeur (pour reconstruire la position), la normale et la brillance spéculaire.

Ensuite, pour chaque lumière, vous calculez uniquement les résultats d'éclairage. Pas de multiplication avec les couleurs de surface, rien. Juste le point (N, L) et le terme spéculaire, complètement sans les couleurs de surface. Les termes spéculaires et diffus doivent être conservés dans des tampons séparés. Les termes spéculaires et diffus pour chaque lumière sont résumés dans les deux tampons.

Ensuite, vous restituez la géométrie, en utilisant les calculs d'éclairage spéculaire et diffus total pour faire la combinaison finale avec la couleur de la surface, produisant ainsi la réflectance globale.

Les avantages ici sont que vous récupérez le multi-échantillonnage (au moins, plus facile qu'avec le différé). Vous effectuez moins de rendu par objet que le rendu direct. Mais l'essentiel sur le report que cela fournit est un moment plus facile d'avoir différentes équations d'éclairage pour différentes surfaces.

Avec le rendu différé, vous dessinez généralement la scène entière avec le même shader par lumière. Ainsi, chaque objet doit utiliser les mêmes paramètres de matériau. Avec le pré-passage de la lumière, vous pouvez donner à chaque objet un shader différent, afin qu'il puisse faire la dernière étape d'éclairage par lui-même.

Cela ne donne pas autant de liberté que le cas de rendu direct. Mais c'est encore plus rapide si vous avez la bande passante de texture à revendre.

Nicol Bolas
la source
-1: omission de mentionner LPP / PPL. -1 différé: le rendu est une victoire instantanée sur n'importe quel matériel DX9.0 (oui même sur mon ordinateur portable «professionnel») - ce qui est des exigences de base vers 2009. Sauf si vous ciblez DX8.0 (qui ne peut pas faire différé / LPP) Deferred / LPP est par défaut . Enfin, `` beaucoup de bande passante mémoire '' est fou - nous ne saturons généralement pas encore PCI-X x4, de plus, LPP réduit considérablement la bande passante mémoire. Enfin, -1 pour votre commentaire; boucles comme ça OK? Vous savez que ces boucles se produisent 2073600 fois par image, non? Même avec le parrélisme de la carte graphique, c'est mauvais.
Jonathan Dickinson
1
@JonathanDickinson Je pense que son point de vue était que la bande passante mémoire pour le pré-passage différé / léger est généralement plusieurs fois plus grande que pour le rendu direct. Cela n'invalide pas l'approche différée; c'est juste quelque chose à considérer lors du choix. BTW: vos tampons différés doivent être dans la mémoire vidéo, donc la bande passante PCI-X n'est pas pertinente; c'est la bande passante interne du GPU qui compte. Les longs shaders de pixels, par exemple avec une boucle déroulée, ne sont rien à craindre s'ils font un travail utile. Et il n'y a rien de mal à l'astuce de pré-passe z-buffer; ça fonctionne bien.
Nathan Reed
3
@JonathanDickinson: Il s'agit de WebGL, donc toute discussion sur les "modèles de shader" est hors de propos. Et quel type de rendu à utiliser n'est pas un "sujet religieux": c'est simplement une question de matériel sur lequel vous utilisez. Un GPU intégré, où la "mémoire vidéo" n'est que de la RAM CPU normale, fonctionnera très mal avec un rendu différé. Sur un rendu basé sur des tuiles mobiles, c'est encore pire . Le rendu différé n'est pas un "gain instantané" quel que soit le matériel; il a ses compromis, comme tout matériel.
Nicol Bolas
2
@JonathanDickinson: "De plus, avec l'astuce de pré-passe z-buffer, vous allez avoir du mal à éliminer les combats z avec les objets qui devraient être dessinés." C'est un non-sens total. Vous rendez les mêmes objets avec les mêmes matrices de transformation et le même vertex shader. Le rendu multipass a été effectué dans le Voodoo 1 jours; c'est un problème résolu . L'accumulation d'éclairage ne change rien à cela.
Nicol Bolas
8
@JonathanDickinson: Mais nous ne parlons pas de rendre un filaire, n'est-ce pas? Nous parlons de rendre les mêmes triangles qu'avant. OpenGL garantit l' invariance pour le même objet rendu (tant que vous utilisez le même vertex shader, bien sûr, et même alors, il y a le invariantmot - clé pour le garantir pour d'autres cas).
Nicol Bolas
4

Vous devez utiliser un rendu différé ou un éclairage pré-passage . Certains des anciens pipelines à fonction fixe (lire: pas de shaders) supportaient jusqu'à 16 ou 24 lumières - mais c'est tout . Le rendu différé élimine la limite de lumière; mais au prix d'un système de rendu beaucoup plus compliqué.

Apparemment, WebGL prend en charge MRT, ce qui est absolument nécessaire pour toute forme de rendu différé - il pourrait donc être faisable; Je ne sais tout simplement pas à quel point c'est plausible.

Vous pouvez également enquêter sur Unity 5 - qui a différé le rendu dès la sortie de la boîte.

Un autre moyen simple de résoudre ce problème est de simplement hiérarchiser les lumières (peut-être, en fonction de la distance du joueur et de leur présence dans le tronc de la caméra) et d'activer uniquement le top 8. De nombreux titres AAA ont réussi à le faire sans beaucoup d'impact sur la qualité de la sortie (par exemple, Far Cry 1).

Vous pouvez également consulter des cartes lumineuses pré-calculées . Des jeux comme Quake 1 en ont tiré beaucoup - et ils peuvent être assez petits (le filtrage bilinéaire adoucit assez bien les cartes lumineuses étirées). Exclut précalculée Malheureusement , la notion de lumières dynamiques 100%, mais il n'a vraiment grand . Vous pouvez combiner cela avec votre limite de 8 lumières, donc par exemple, seules les fusées ou autres auraient une vraie lumière - mais les lumières sur le mur ou autres seraient des cartes lumineuses.

Note latérale: vous ne voulez pas les boucler dans un shader? Dites adieu à votre performance. Un GPU n'est pas un processeur et n'est pas conçu pour fonctionner de la même manière que, par exemple, JavaScript. N'oubliez pas que chaque pixel que vous restituez (même s'il est écrasé) doit effectuer la boucle - donc si vous prenez une course à 1920x1080 et une boucle simple qui s'exécute 16 fois, vous exécutez efficacement tout ce qui se trouve à l'intérieur de cette boucle 33177600 fois. Votre carte graphique exécutera un grand nombre de ces fragments en parallèle, mais ces boucles consommeront toujours du matériel plus ancien.

Jonathan Dickinson
la source
-1: "Vous devez utiliser le rendu différé" Ce n'est pas vrai du tout. Le rendu différé est certainement un moyen de le faire, mais ce n'est pas le seul . De plus, les boucles ne sont pas si mauvaises en termes de performances, surtout si elles sont basées sur des valeurs uniformes (c'est-à-dire que chaque fragment n'a pas une longueur de boucle différente).
Nicol Bolas
1
Veuillez lire le 4ème paragraphe.
Jonathan Dickinson
2

Vous pouvez utiliser un pixel shader qui prend en charge n lumières (où n est un petit nombre comme 4 ou 8) et redessiner la scène plusieurs fois, en passant à chaque fois un nouveau lot de lumières et en utilisant un mélange additif pour les combiner toutes ensemble.

Voilà l'idée de base. Bien sûr, il y a beaucoup d'optimisations nécessaires pour rendre cela assez rapide pour une scène de taille raisonnable. Ne dessinez pas toutes les lumières, juste celles visibles (abattement du tronc et de l'occlusion); ne redessinez pas la scène entière à chaque passage, juste les objets à portée des lumières de ce passage; avoir plusieurs versions du shader qui prennent en charge différents nombres de lumières (1, 2, 3, ...) afin de ne pas perdre de temps à évaluer plus de lumières que nécessaire.

Le rendu différé comme mentionné dans l'autre réponse est un bon choix lorsque vous avez beaucoup de petites lumières, mais ce n'est pas le seul moyen.

Nathan Reed
la source