Gibt es eine Möglichkeit, eine beliebige Anzahl von Lichtquellen in einem Fragment-Shader zu verwenden?

19

Gibt es eine Möglichkeit, eine beliebige Anzahl von Lichtpositionen (und Farben) für den Fragment-Shader zu übergeben und diese im Shader zu durchlaufen?

Wenn nicht, wie sollen dann mehrere Lichter simuliert werden? In Bezug auf diffuses direktionales Licht können Sie beispielsweise nicht einfach eine Summe der Lichtstärken für den Shader übergeben.

NotRoyal
quelle
Ich habe noch nicht mit WebGL gearbeitet, aber in OpenGL gibt es maximal 8 Lichtquellen. Wenn Sie meiner Meinung nach mehr als das übergeben möchten, müssen Sie zum Beispiel einheitliche Variablen verwenden.
Zacharmarz
Die alte Methode bestand darin, immer alle Lichter durchzulassen, nicht verwendete Lichter wurden auf 0 Luminanz gesetzt und hatten daher keinen Einfluss auf die Szene. Wahrscheinlich nicht mehr viel benutzt ;-)
Patrick Hughes
7
Wenn Sie Google-Inhalte wie diese verwenden, verwenden Sie nicht den Begriff "WebGL" - die Technologie ist zu jung, um von Menschen verwendet zu werden, auch wenn sie sich diesen Problemen nähern. Nehmen Sie diese Suche zum Beispiel, "Ich fühle mich glücklich" hätte funktioniert. Denken Sie daran, dass ein WebGL-Problem genau dasselbe OpenGL-Problem sein sollte.
Jonathan Dickinson
Für mehr als 8 Lichter im Forward-Rendering verwende ich im Allgemeinen einen Multi-Pass-Shader und gebe jedem Durchgang eine andere Gruppe von 8 Lichtern, die mithilfe von additivem Blending verarbeitet werden sollen.
ChrisC

Antworten:

29

Es gibt im Allgemeinen zwei Methoden, um damit umzugehen. Heutzutage werden sie Forward-Rendering und Deferred-Rendering genannt. Es gibt eine Variation dieser beiden, die ich unten diskutieren werde.

Forward-Rendering

Rendern Sie jedes Objekt einmal für jedes Licht, das es beeinflusst. Dies schließt das Umgebungslicht ein. Wenn Sie einen additiven Mischmodus ( glBlendFunc(GL_ONE, GL_ONE)) verwenden, werden die Beiträge der einzelnen Lichtquellen zueinander addiert. Da der Beitrag verschiedener Lichter additiv ist, erhält der Framebuffer schließlich den Wert

Sie können HDR erhalten, indem Sie in einen Gleitkomma-Framebuffer rendern. Anschließend gehen Sie die Szene abschließend durch, um die HDR-Beleuchtungswerte auf einen sichtbaren Bereich zu verkleinern. Hier können Sie auch Bloom und andere Post-Effekte implementieren.

Eine übliche Leistungsverbesserung für diese Technik (wenn die Szene viele Objekte enthält) ist die Verwendung eines "Pre-Pass", bei dem Sie alle Objekte rendern, ohne etwas in den Farbrahmenpuffer zu zeichnen ( glColorMaskzum Deaktivieren von Farbschreibvorgängen). Dies füllt nur den Tiefenpuffer. Auf diese Weise kann die GPU diese Fragmente schnell überspringen, wenn Sie ein Objekt rendern, das sich hinter einem anderen befindet. Der Vertex-Shader muss noch ausgeführt werden, die normalerweise teureren Fragment-Shader-Berechnungen können jedoch übersprungen werden.

Dies ist einfacher zu codieren und einfacher zu visualisieren. Bei einigen Hardwarekomponenten (hauptsächlich Mobil- und Embedded-GPUs) kann dies effizienter sein als die Alternative. Aber auf High-End-Hardware gewinnt die Alternative im Allgemeinen für Szenen mit viel Licht.

Verzögertes Rendern

Das verzögerte Rendern ist etwas komplizierter.

Die Beleuchtungsgleichung, mit der Sie das Licht für einen Punkt auf einer Oberfläche berechnen, verwendet die folgenden Oberflächenparameter:

  • Oberflächenposition
  • Oberflächennormalen
  • Diffuse Oberflächenfarbe
  • Spiegelnde Oberflächenfarbe
  • Oberflächenglanz spiegelnd
  • Möglicherweise andere Oberflächenparameter (abhängig von der Komplexität Ihrer Beleuchtungsgleichung)

Beim Forward-Rendering werden diese Parameter entweder direkt vom Vertex-Shader an die Beleuchtungsfunktion des Fragment-Shaders übergeben, aus Texturen gezogen (normalerweise über Texturkoordinaten, die vom Vertex-Shader übergeben wurden) oder aus dem gesamten Stoff im Fragment-Shader basierend auf generiert andere Parameter. Die diffuse Farbe kann berechnet werden, indem eine Per-Vertex-Farbe mit einer Textur kombiniert wird, wobei mehrere Texturen kombiniert werden, was auch immer.

Bei verzögerter Wiedergabe machen wir dies alles explizit. Im ersten Durchgang rendern wir alle Objekte. Wir rendern aber keine Farben . Stattdessen rendern wir Oberflächenparameter . So hat jedes Pixel auf dem Bildschirm eine Reihe von Oberflächenparametern. Dies erfolgt über das Rendern von Off-Screen-Texturen. Eine Textur würde die diffuse Farbe als RGB und möglicherweise den spiegelnden Glanz als Alpha speichern. Eine andere Textur würde die spiegelnde Farbe speichern. Ein Drittel würde das normale speichern. Und so weiter.

Die Position wird normalerweise nicht gespeichert. Es wird stattdessen im zweiten Durchgang durch Mathematik rekonstruiert, was zu komplex ist, um hier darauf einzugehen. Es genügt zu sagen, dass wir den Tiefenpuffer und die Fragmentposition des Bildschirmbereichs als Eingabe verwenden, um die Position des Kameraraums des Punkts auf einer Oberfläche zu ermitteln.

Jetzt, da diese Texturen im Wesentlichen alle Oberflächeninformationen für jedes sichtbare Pixel in der Szene enthalten, rendern wir Vollbild-Quads. Jedes Licht erhält einen Vollbild-Quad-Render. Wir probieren aus den Oberflächenparametertexturen (und stellen die Position wieder her) und verwenden sie dann nur, um den Beitrag dieses Lichts zu berechnen. Dies wird glBlendFunc(GL_ONE, GL_ONE)dem Bild (erneut ) hinzugefügt . Wir machen das so lange, bis uns die Lichter ausgehen.

HDR ist wieder ein Nachbearbeitungsschritt.

Der größte Nachteil des verzögerten Renderns ist das Antialiasing. Es erfordert ein bisschen mehr Arbeit, um Antialias richtig auszuführen.

Der größte Vorteil ist die Leistung, wenn Ihre GPU über eine große Speicherbandbreite verfügt. Wir rendern die tatsächliche Geometrie nur einmal (oder 1 + 1 pro Licht mit Schatten, wenn wir eine Schattenzuordnung durchführen). Wir nie verbringen jederzeit auf versteckte Pixel oder Geometrie , die nicht sichtbar nach dieser ist. Die gesamte Beleuchtungsdauer wird für Dinge aufgewendet, die tatsächlich sichtbar sind.

Wenn Ihre GPU nicht über viel Speicherbandbreite verfügt, kann der Lichtdurchgang wirklich weh tun. Das Ziehen von 3-5 Texturen pro Bildschirmpixel macht keinen Spaß.

Leichter Pre-Pass

Dies ist eine Art Variation des verzögerten Renderns, die interessante Kompromisse aufweist.

Genau wie beim verzögerten Rendern rendern Sie Ihre Oberflächenparameter in einer Reihe von Puffern. Sie haben jedoch Oberflächendaten abgekürzt. Die einzigen Oberflächendaten, die Sie für diese Zeit interessieren, sind der Tiefenpufferwert (zum Rekonstruieren der Position), der Normalwert und der Glanz der Spiegel.

Dann berechnen Sie für jedes Licht nur die Beleuchtungsergebnisse. Keine Multiplikation mit Oberflächenfarben, nichts. Nur der Punkt (N, L) und der spiegelnde Term, ganz ohne die Oberflächenfarben. Die spiegelnden und diffusen Terme sollten in getrennten Puffern aufbewahrt werden. Die spiegelnden und diffusen Terme für jedes Licht werden in den beiden Puffern summiert.

Anschließend rendern Sie die Geometrie neu. Verwenden Sie dabei die Berechnungen für die gesamte spiegelnde und diffuse Beleuchtung, um die endgültige Kombination mit der Oberflächenfarbe zu erzielen und so den Gesamtreflexionsgrad zu erzielen.

Die Vorteile hier sind, dass Sie Multisampling zurückbekommen (zumindest einfacher als mit verzögert). Sie rendern weniger pro Objekt als vorwärts. Die Hauptsache ist jedoch, dass dies eine einfachere Zeit ist, um verschiedene Beleuchtungsgleichungen für verschiedene Oberflächen zu haben.

Beim verzögerten Rendern wird im Allgemeinen die gesamte Szene mit demselben Shader pro Licht gezeichnet. Daher muss jedes Objekt dieselben Materialparameter verwenden. Mit dem Lichtvorübergang können Sie jedem Objekt einen anderen Shader zuweisen, sodass der letzte Beleuchtungsschritt für sich alleine ausgeführt werden kann.

Dies bietet nicht so viel Freiheit wie der Forward-Rendering-Fall. Es ist aber immer noch schneller, wenn Sie die Texturbandbreite zur Verfügung haben.

Nicol Bolas
quelle
-1: LPP / PPL wird nicht erwähnt. -1 verzögert: Rendern ist ein sofortiger Gewinn auf jeder DX9.0-Hardware (ja, sogar auf meinem "Business" -Laptop). Dies entspricht den grundlegenden Anforderungen von 2009, es sei denn, Sie haben DX8.0 im Visier (dies kann nicht verzögert / LPP sein). Zurückgestellt / LPP ist Standard . Schließlich ist "viel Speicherbandbreite" verrückt - wir sind im Allgemeinen noch nicht einmal mit PCI-X x4 gesättigt, und außerdem verringert LPP die Speicherbandbreite erheblich. Schließlich -1 für Ihren Kommentar; Schleifen wie diese OK? Sie wissen, dass diese Schleifen 2073600-mal pro Frame passieren, oder? Auch mit der Parole der Grafikkarte ist es schlecht.
Jonathan Dickinson
1
@JonathanDickinson Ich denke, sein Punkt war, dass die Speicherbandbreite für den verzögerten / leichten Vorlauf in der Regel um ein Vielfaches größer ist als für das vorwärts gerenderte Rendern. Dies macht den aufgeschobenen Ansatz nicht ungültig. Es ist nur etwas zu berücksichtigen, wenn Sie es wählen. Übrigens: Ihre verzögerten Puffer sollten sich im Videospeicher befinden, sodass die PCI-X-Bandbreite keine Rolle spielt. Auf die interne Bandbreite der GPU kommt es an. Lange Pixel-Shader, z. B. mit einer entrollten Schleife, sind kein Grund zum Ausrasten, wenn sie nützliche Arbeit leisten. Und der Z-Buffer-Prepass-Trick ist in Ordnung. es funktioniert gut.
Nathan Reed
3
@ Jonathan Dickinson: Hier geht es um WebGL, daher ist jede Diskussion über "Shader-Modelle" irrelevant. Und welche Art von Rendering verwendet werden soll, ist kein "religiöses Thema": Es ist einfach eine Frage der Hardware, auf der Sie ausgeführt werden. Eine eingebettete GPU, bei der "Videospeicher" nur normaler CPU-RAM ist, funktioniert mit verzögertem Rendern sehr schlecht. Auf einem mobilen kachelbasierten Renderer ist es noch schlimmer . Verzögertes Rendern ist unabhängig von der Hardware kein "Sofortgewinn". Es hat seine Nachteile, genau wie jede andere Hardware.
Nicol Bolas
2
@JonathanDickinson: "Mit dem Z-Buffer Pre-Pass-Trick kämpfen Sie außerdem darum, Z-Fighting mit den Objekten zu eliminieren, die gezeichnet werden sollen." Das ist totaler Unsinn. Sie rendern dieselben Objekte mit denselben Transformationsmatrizen und demselben Vertex-Shader. Multipass-Rendering wurde in den ersten Voodoo- Tagen durchgeführt. Das ist ein gelöstes Problem. Die akkumulierte Beleuchtung ändert daran nichts.
Nicol Bolas
8
@ Jonathan Dickinson: Aber wir reden nicht über das Rendern eines Drahtgitters, oder? Wir sprechen über das Rendern der gleichen Dreiecke wie zuvor. OpenGL garantiert die Invarianz für dasselbe Objekt, das gerendert wird (sofern Sie denselben Vertex-Shader verwenden, und selbst dann gibt es das invariantSchlüsselwort, um dies für andere Fälle zu garantieren).
Nicol Bolas
4

Sie müssen verzögertes Rendering oder Pre-Pass-Beleuchtung verwenden . Einige der älteren Pipelines mit festen Funktionen (sprich: keine Shader) unterstützen bis zu 16 oder 24 Lichter - aber das war's . Durch verzögertes Rendern wird die Lichtgrenze beseitigt. aber auf Kosten eines viel komplizierteren Renderingsystems.

Anscheinend unterstützt WebGL MRT, was für jede Form von verzögertem Rendern unbedingt erforderlich ist - es könnte also machbar sein; Ich bin mir nur nicht sicher, wie plausibel es ist.

Alternativ können Sie Unity 5 untersuchen, bei dem das Rendern sofort verschoben wurde.

Eine andere einfache Möglichkeit, dies zu bewältigen, besteht darin, die Lichter einfach zu priorisieren (möglicherweise basierend auf dem Abstand zum Player und ob sie sich im Kamerastumpf befinden) und nur die Top 8 zu aktivieren. Viele AAA-Titel haben dies ohne großen Einfluss geschafft auf die Qualität der Ausgabe (zum Beispiel Far Cry 1).

Sie können sich auch vorberechnete Lightmaps ansehen . Spiele wie Quake 1 haben eine Menge davon - und sie können ziemlich klein sein (die bilineare Filterung mildert ausgedehnte Lichtkarten ziemlich gut). Leider schließt die Vorstellung von 100% dynamische Lichter vorausberechnet, aber es funktioniert wirklich sehen toll . Sie könnten dies mit Ihrer Begrenzung auf 8 Lichter kombinieren, sodass beispielsweise nur Raketen oder dergleichen ein echtes Licht haben würden - aber Lichter an der Wand oder dergleichen wären Lichtkarten.

Randnotiz: Du willst sie nicht in einem Shader überfliegen? Verabschieden Sie sich von Ihrer Leistung. Eine GPU ist keine CPU und funktioniert nicht so wie beispielsweise JavaScript. Denken Sie daran, dass jedes Pixel, das Sie rendern (wenn es sogar überschrieben wird), die Schleife ausführen muss. Wenn Sie also 1920 x 1080 ausführen und eine einfache Schleife 16-mal ausführen, wird alles in dieser Schleife 33177600-mal ausgeführt. Auf Ihrer Grafikkarte werden viele dieser Fragmente parallel ausgeführt, aber diese Schleifen fressen immer noch ältere Hardware.

Jonathan Dickinson
quelle
-1: "Sie müssen verzögertes Rendern verwenden" Dies ist überhaupt nicht der Fall. Verzögertes Rendern ist sicherlich ein Weg, dies zu tun, aber es ist nicht der einzige Weg. Schleifen sind auch nicht so schlecht in Bezug auf die Leistung, insbesondere wenn sie auf einheitlichen Werten basieren (dh: jedes Fragment hat keine andere Schleifenlänge).
Nicol Bolas
1
Bitte lesen Sie den 4. Absatz.
Jonathan Dickinson
2

Sie können einen Pixel-Shader verwenden, der n Lichter unterstützt (wobei n eine kleine Zahl wie 4 oder 8 ist), und die Szene mehrmals neu zeichnen, indem Sie jedes Mal einen neuen Stapel von Lichtern übergeben und diese additiv mischen, um sie alle miteinander zu kombinieren.

Das ist die Grundidee. Natürlich sind viele Optimierungen erforderlich, um dies für eine Szene mit angemessener Größe schnell genug zu machen. Zeichnen Sie nicht alle Lichter, sondern nur die sichtbaren (Kegelstumpf- und Okklusions-Keulen). Zeichnen Sie nicht bei jedem Durchgang die gesamte Szene neu, sondern nur die Objekte, die sich in Reichweite der Lichter in diesem Durchgang befinden. Es gibt mehrere Versionen des Shaders, die eine unterschiedliche Anzahl von Lichtquellen unterstützen (1, 2, 3, ...), sodass Sie keine Zeit damit verschwenden, mehr Lichtquellen auszuwerten, als Sie benötigen.

Verzögertes Rendern, wie in der anderen Antwort erwähnt, ist eine gute Wahl, wenn Sie viele kleine Lichter haben, aber es ist nicht der einzige Weg.

Nathan Reed
quelle