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.
Antworten:
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 WertSie 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 (
glColorMask
zum 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:
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.
quelle
invariant
Schlüsselwort, um dies für andere Fälle zu garantieren).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.
quelle
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.
quelle