
In der
vorherigen Lektion haben wir unser PBR-Modell für die Arbeit mit der IBL-Methode vorbereitet. Dazu mussten wir im Voraus eine Bestrahlungskarte erstellen, die den diffusen Teil der indirekten Beleuchtung beschreibt. In dieser Lektion werden wir uns mit dem zweiten Teil des Ausdrucks des Reflexionsvermögens befassen - dem Spiegel:
Lo(p, omegao)= int begrenzt Omega(kd fracc pi+ks fracDFG4( omegao cdotn)( omegai cdotn))Li(p, omegai)n cdot omegaid omegai
Möglicherweise stellen Sie fest, dass die Cook-Torrens-Spiegelkomponente (Unterausdruck mit einem Faktor)
ks ) ist nicht konstant und hängt von der Richtung des einfallenden Lichts
sowie von der Beobachtungsrichtung ab. Die Lösung dieses Integrals für alle möglichen Einfallsrichtungen des Lichts zusammen mit allen möglichen Richtungen der Echtzeitbeobachtung ist einfach nicht möglich. Daher haben Forscher von Epic Games einen Ansatz namens
Split-Sum-Approximation vorgeschlagen , mit dem Sie unter bestimmten Bedingungen Daten für die Spiegelkomponente teilweise im Voraus vorbereiten können.
Bei diesem Ansatz wird die Spiegelkomponente des Reflexionsausdrucks in zwei Teile unterteilt, die einzeln vorgefaltet und dann in einem PBR-Shader kombiniert werden können, um als Quelle für indirekte Spiegelstrahlung verwendet zu werden. Wie bei der Erzeugung der Bestrahlungskarte empfängt der Faltungsprozess an seiner Eingabe eine HDR-Umgebungskarte.
Um die Split-Sum-Approximationsmethode zu verstehen, betrachten wir noch einmal den Ausdruck des Reflexionsvermögens, wobei nur der Unterausdruck für die Spiegelkomponente darin verbleibt (der diffuse Teil wurde in der
vorherigen Lektion separat betrachtet):
Lo(p, omegao)= int begrenzt Omega(ks fracDFG4( omegao cdotn)( omegai cdotn)Li(p, omegai)n cdot omegaid omegai= int begrenzt Omegafr(p, omegai, omegao)Li(p, omegai)n cdot omegaid omegai
Wie bei der Erstellung der Bestrahlungskarte kann dieses Integral nicht in Echtzeit gelöst werden. Daher ist es ratsam, die Karte für die Spiegelkomponente des Ausdrucks des Reflexionsvermögens auf ähnliche Weise zu berechnen und im Hauptrenderzyklus eine einfache Auswahl aus dieser Karte basierend auf der Normalen zur Oberfläche vorzunehmen. Es ist jedoch nicht alles so einfach: Die Bestrahlungskarte wurde relativ leicht erhalten, da das Integral nur davon abhing
omegai und die konstante Subexpression für die diffuse Lambertsche Komponente könnte aus dem Vorzeichen des Integrals herausgenommen werden. In diesem Fall hängt das Integral nicht nur von ab
omegai das ist aus der BRDF-Formel leicht zu verstehen:
fr(p,wi,wo)= fracDFG4( omegao cdotn)( omegai cdotn)
Der Ausdruck unter dem Integral hängt auch von ab
omegao - Für zwei Richtungsvektoren ist es fast unmöglich, aus einer zuvor erstellten kubischen Karte auszuwählen. Punktposition
p In diesem Fall können Sie nicht berücksichtigen, warum dies in der vorherigen Lektion besprochen wurde. Vorläufige Berechnung des Integrals für alle möglichen Kombinationen
omegai und
omegao unmöglich in Echtzeitaufgaben.
Die Split-Betrag-Methode von Epic Games löst dieses Problem, indem das vorläufige Berechnungsproblem in zwei unabhängige Teile aufgeteilt wird, deren Ergebnisse später kombiniert werden können, um den endgültigen berechneten Wert zu erhalten. Die Split-Sum-Methode extrahiert zwei Integrale aus dem ursprünglichen Ausdruck für die Spiegelkomponente:
Lo(p, omegao)= int Grenzen OmegaLi(p, omegai)d omegai∗ int Grenzen Omegafr(p, omegai, omegao)n cdot omegaid omegai
Das Ergebnis der Berechnung des ersten Teils wird normalerweise als
vorgefilterte Umgebungskarte bezeichnet , und es handelt sich um eine Umgebungskarte, die dem durch diesen Ausdruck angegebenen Faltungsprozess unterzogen wird. All dies ähnelt dem Verfahren zum Erhalten einer Bestrahlungskarte, aber in diesem Fall wird die Faltung unter Berücksichtigung des Rauheitswerts durchgeführt. Höhere Rauheitswerte führen zur Verwendung unterschiedlicherer Abtastvektoren im Faltungsprozess, was zu unschärferen Ergebnissen führt. Das Faltungsergebnis für jede nächste ausgewählte Rauheitsstufe wird in der nächsten Mip-Stufe der vorbereiteten Umgebungskarte gespeichert. Eine Umgebungskarte, die für fünf verschiedene Rauheitsstufen erstellt wurde, enthält beispielsweise fünf Mip-Stufen und sieht ungefähr so aus:
Probenvektoren und ihre Ausbreitung werden basierend auf der Normalverteilungsfunktion (
NDF ) des Cook-Torrens-BRDF-Modells bestimmt. Diese Funktion akzeptiert den Normalenvektor und die Beobachtungsrichtung als Eingabeparameter. Da die Beobachtungsrichtung zum Zeitpunkt der vorläufigen Berechnung nicht im Voraus bekannt ist, mussten die Entwickler von Epic Games noch eine weitere Annahme treffen: Die Blickrichtung (und damit die Richtung der Spiegelreflexion) ist immer identisch mit der Ausgangsrichtung der Probe
omegao . In Form von Code:
vec3 N = normalize(w_o); vec3 R = N; vec3 V = R;
Unter solchen Bedingungen ist die Blickrichtung beim Falten der Umgebungskarte nicht erforderlich, wodurch die Berechnung in Echtzeit möglich ist. Andererseits verlieren wir die charakteristische Verzerrung von Spiegelreflexionen, wenn sie in einem spitzen Winkel zur reflektierenden Oberfläche beobachtet werden, wie im Bild unten zu sehen ist (von
Moving Frostbite zu PBR ). Im Allgemeinen wird ein solcher Kompromiss als akzeptabel angesehen.
Der zweite Teil des Split-Summen-Ausdrucks enthält die BRDF des ursprünglichen Ausdrucks für die Spiegelkomponente. Angenommen, die Helligkeit der eingehenden Energie wird spektral durch weißes Licht für alle Richtungen dargestellt (d. H.
L(p,x)=1,0 ) kann der Wert für BRDF mit folgenden Eingabeparametern vorberechnet werden: Materialrauheit und Winkel zwischen der Normalen
n und Lichtrichtung
omegai (oder
n cdot omegai ) Der Epic Games-Ansatz umfasst das Speichern der Ergebnisse der BRDF-Berechnung für jede Kombination aus Rauheit und Winkel zwischen Normal und Lichtrichtung in Form einer zweidimensionalen Textur, die als
BRDF-Integrationskarte bezeichnet wird und später als
Nachschlagetabelle (
LUT ) verwendet wird. . Diese Referenztextur verwendet die roten und grünen Ausgangskanäle, um die Skalierung und den Versatz für die Berechnung des Fresnel-Koeffizienten der Oberfläche zu speichern, wodurch wir letztendlich den zweiten Teil des Ausdrucks für die separate Summe lösen können:

Diese Hilfstextur wird wie folgt erstellt: Horizontale Texturkoordinaten (im Bereich von [0., 1.]) werden als Eingabeparameterwerte betrachtet
n cdot omegai BRDF-Funktionen; vertikale Texturkoordinaten werden als eingegebene Rauheitswerte betrachtet.
Infolgedessen können Sie mit einer solchen Integrationskarte und einer vorverarbeiteten Umgebungskarte die Beispiele daraus kombinieren, um den Endwert des Integralausdrucks der Spiegelkomponente zu erhalten:
float lod = getMipLevelFromRoughness(roughness); vec3 prefilteredColor = textureCubeLod(PrefilteredEnvMap, refVec, lod); vec2 envBRDF = texture2D(BRDFIntegrationMap, vec2(NdotV, roughness)).xy; vec3 indirectSpecular = prefilteredColor * (F * envBRDF.x + envBRDF.y)
Diese Überprüfung der Split-Sum-Methode von Epic Games soll Ihnen einen Eindruck davon vermitteln, wie der Teil des Reflexionsausdrucks approximiert wird, der für die Spiegelkomponente verantwortlich ist. Versuchen wir nun, die Kartendaten selbst vorzubereiten.
Vorfiltern der HDR-Umgebungskarte
Das Vorfiltern der Umgebungskarte ähnelt dem, was durchgeführt wurde, um eine Bestrahlungskarte zu erhalten. Der einzige Unterschied besteht darin, dass wir jetzt die Rauheit berücksichtigen und das Ergebnis für jede Rauheitsstufe in der neuen Mip-Stufe der kubischen Karte speichern.
Zuerst müssen Sie eine neue kubische Karte erstellen, die das Ergebnis der Vorfilterung enthält. Um die erforderliche Anzahl von Mip-Ebenen zu erstellen, rufen wir einfach
glGenerateMipmaps () auf - der erforderliche Speicher wird für die aktuelle Textur zugewiesen:
unsigned int prefilterMap; glGenTextures(1, &prefilterMap); glBindTexture(GL_TEXTURE_CUBE_MAP, prefilterMap); for (unsigned int i = 0; i < 6; ++i) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 128, 128, 0, GL_RGB, GL_FLOAT, nullptr); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
Bitte beachten Sie: Da die Auswahl aus
prefilterMap auf dem Vorhandensein von Mip-Pegeln basiert, muss der Reduktionsfiltermodus auf
GL_LINEAR_MIPMAP_LINEAR gesetzt werden , um die trilineare Filterung zu aktivieren. Vorverarbeitete Bilder von Spiegelbildern werden in separaten Flächen der kubischen Karte mit einer Auflösung auf der Basis-Mip-Ebene von nur 128 x 128 Pixel gespeichert. Für die meisten Materialien ist dies völlig ausreichend. Wenn Ihre Szene jedoch eine größere Anzahl glatter, glänzender Oberflächen aufweist (z. B. ein brandneues Auto), müssen Sie möglicherweise diese Auflösung erhöhen.
In der vorherigen Lektion haben wir die Umgebungskarte gefaltet, indem wir Beispielvektoren erstellt haben, die gleichmäßig in der Hemisphäre verteilt sind
Omega mit sphärischen Koordinaten. Um eine Bestrahlung zu erhalten, ist diese Methode sehr effektiv, was nicht über die Berechnung von Spiegelreflexionen gesagt werden kann. Die Physik der Glanzlichter zeigt, dass die Richtung des spiegelnd reflektierten Lichts neben dem Reflexionsvektor liegt
r für eine normale Oberfläche
n auch wenn die Rauheit nicht Null ist:
Die verallgemeinerte Form möglicher ausgehender Reflexionsrichtungen wird als Spiegelkeule bezeichnet (Spiegelkeule; „Blütenblatt eines Spiegelstrahlungsmusters“ - möglicherweise zu ausführlich,
ca. Per. ). Mit zunehmender Rauheit wächst und dehnt sich das Blütenblatt aus. Außerdem ändert sich seine Form in Abhängigkeit von der Richtung des Lichteinfalls. Daher hängt die Form des Blütenblatts stark von den Eigenschaften des Materials ab.
Zurück zum Modell der Mikrooberflächen können wir uns vorstellen, dass die Form der Spiegelkeule die Ausrichtung der Reflexion relativ zum Medianvektor der Mikrooberflächen unter Berücksichtigung einer bestimmten Richtung des Lichteinfalls beschreibt. Um zu verstehen, dass die meisten Strahlen des reflektierten Lichts in einem Spiegelblatt liegen, das auf der Grundlage des Medianvektors ausgerichtet ist, ist es sinnvoll, Probenvektoren zu erstellen, die auf ähnliche Weise ausgerichtet sind. Andernfalls sind viele von ihnen nutzlos. Dieser Ansatz wird als
Wichtigkeitsabtastung bezeichnet .
Monte-Carlo-Integration und Signifikanzabtastung
Um die Bedeutung der Stichprobe in Bezug auf die Signifikanz vollständig zu verstehen, müssen Sie sich zunächst mit einem mathematischen Apparat wie der Monte-Carlo-Integrationsmethode vertraut machen. Diese Methode basiert auf einer Kombination aus Statistik und Wahrscheinlichkeitstheorie und hilft bei der numerischen Lösung eines statistischen Problems bei einer großen Stichprobe, ohne dass
jedes Element dieser Stichprobe berücksichtigt werden
muss .
Sie möchten beispielsweise das durchschnittliche Bevölkerungswachstum eines Landes berechnen. Um ein genaues und zuverlässiges Ergebnis zu erhalten, müsste man das Wachstum
jedes Bürgers messen und das Ergebnis mitteln. Da die Bevölkerung der meisten Länder jedoch recht groß ist, ist dieser Ansatz praktisch nicht realisierbar, da für die Ausführung zu viele Ressourcen erforderlich sind.
Ein anderer Ansatz besteht darin, eine kleinere Teilstichprobe zu erstellen, die mit wirklich zufälligen (unverzerrten) Elementen der ursprünglichen Stichprobe gefüllt ist. Als Nächstes messen Sie auch das Wachstum und mitteln das Ergebnis für diese Teilstichprobe. Sie können mindestens hundert Personen aufnehmen und ein Ergebnis erzielen, das zwar nicht absolut genau ist, aber dennoch der tatsächlichen Situation sehr nahe kommt. Die Erklärung für diese Methode liegt in der Berücksichtigung des Gesetzes der großen Zahlen. Und sein Wesen wird folgendermaßen beschrieben: das Ergebnis einer Messung in einer kleineren Teilstichprobe der Größe
N , bestehend aus wirklich zufälligen Elementen des ursprünglichen Satzes, liegt nahe am Kontrollergebnis der Messungen, die am gesamten Anfangssatz durchgeführt wurden. Darüber hinaus stimmt das ungefähre Ergebnis mit dem Wachstum überein
N .
Die Monte-Carlo-Integration ist die Anwendung des Gesetzes der großen Zahlen zur Lösung von Integralen. Anstatt das Integral zu lösen, berücksichtigen Sie den gesamten (möglicherweise unendlichen) Wertesatz
x wir benutzen
N zufällige Stichprobenpunkte und Durchschnitt des Ergebnisses. Mit Wachstum
N Das ungefähre Ergebnis kommt garantiert der exakten Lösung des Integrals nahe.
O= int limitbaf(x)dx= frac1N sumN−1i=0 fracf(x)pdf(x)
Um das Integral zu lösen, erhalten wir den Wert des Integranden für
N zufällige Punkte aus der Stichprobe innerhalb von [a, b], die Ergebnisse werden zusammengefasst und durch die Gesamtzahl der für die Mittelwertbildung genommenen Punkte geteilt. Artikel
pdf beschreibt
die Wahrscheinlichkeitsdichtefunktion , die die Wahrscheinlichkeit zeigt, mit der jeder ausgewählte Wert in der ursprünglichen Stichprobe auftritt. Zum Beispiel würde diese Funktion für das Wachstum der Bürger ungefähr so aussehen:
Es ist ersichtlich, dass wir bei Verwendung von Stichprobenpunkten eine viel höhere Chance haben, einen Wachstumswert von 170 cm zu erreichen als bei jemandem mit einem Wachstum von 150 cm.
Es ist klar, dass während der Monte-Carlo-Integration einige Stichprobenpunkte eher in der Sequenz erscheinen als andere. Daher dividieren oder multiplizieren wir in jedem Ausdruck für die Monte-Carlo-Schätzung den ausgewählten Wert mit der Wahrscheinlichkeit seines Auftretens unter Verwendung der Wahrscheinlichkeitsdichtefunktion. Im Moment haben wir bei der Bewertung des Integrals viele gleichmäßig verteilte Stichprobenpunkte erstellt: Die Chance, einen davon zu erhalten, war gleich. Daher war unsere Schätzung
unvoreingenommen , was bedeutet, dass unsere Schätzung mit zunehmender Anzahl von Stichprobenpunkten zur exakten Lösung des Integrals konvergiert.
Es gibt jedoch Bewertungsfunktionen, die
voreingenommen sind , d.h. Dies impliziert die Erstellung von Stichprobenpunkten nicht auf wirklich zufällige Weise, sondern mit einer Vorherrschaft von einer gewissen Größe oder Richtung. Mit solchen Bewertungsfunktionen kann die Monte-Carlo-Schätzung
viel schneller zur exakten Lösung konvergieren. Andererseits kann die Lösung aufgrund der Vorspannung der Bewertungsfunktion niemals konvergieren. Im allgemeinen Fall wird dies als akzeptabler Kompromiss angesehen, insbesondere bei Computergrafikproblemen, da die Schätzung dem Analyseergebnis sehr nahe kommt und nicht erforderlich ist, wenn ihre Wirkung visuell recht zuverlässig aussieht. Wie wir bald sehen werden, können Sie mit der Stichprobe nach Signifikanz (unter Verwendung einer vorgespannten Schätzfunktion) Stichprobenpunkte erstellen, die in eine bestimmte Richtung vorgespannt sind. Dies wird berücksichtigt, indem jeder ausgewählte Wert mit dem entsprechenden Wert der Wahrscheinlichkeitsdichtefunktion multipliziert oder dividiert wird.
Die Monte-Carlo-Integration ist bei Computergrafikproblemen recht häufig, da sie eine ziemlich intuitive Methode zur Schätzung des Werts kontinuierlicher Integrale durch eine numerische Methode darstellt, die sehr effektiv ist. Es reicht aus, einen Bereich oder ein Volumen zu entnehmen, in dem die Probe entnommen wird (z. B. unsere Hemisphäre)
Omega ) erstellen
N Zufällige Stichprobenpunkte, die im Inneren liegen, und führen eine gewichtete Summierung der erhaltenen Werte durch.
Die Monte-Carlo-Methode ist ein sehr umfangreiches Diskussionsthema, und hier werden wir nicht mehr auf Details eingehen, aber es gibt noch ein wichtiges Detail: Es gibt keineswegs eine Möglichkeit,
Zufallsstichproben zu erstellen. Standardmäßig ist jeder Stichprobenpunkt vollständig (psvedo) zufällig - was wir erwarten. Unter Verwendung bestimmter Eigenschaften von quasi zufälligen Sequenzen ist es jedoch möglich, Sätze von Vektoren zu erzeugen, die, obwohl zufällig, interessante Eigenschaften haben. Wenn Sie beispielsweise Zufallsstichproben für den Integrationsprozess erstellen, können Sie die sogenannten
Sequenzen mit geringer Diskrepanz verwenden , die die Zufälligkeit der erstellten Stichprobenpunkte sicherstellen, aber im allgemeinen Satz sind sie gleichmäßiger
verteilt :
Die
Quasi-Monte-Carlo-Intergrationsmethode verwendet niedrige Fehlpaarungssequenzen, um einen Satz von Probenvektoren für den Integrationsprozess zu erstellen. Monte-Carlo-Quasi-Methoden konvergieren viel schneller als der allgemeine Ansatz, der für Anwendungen mit hohen Leistungsanforderungen eine sehr attraktive Eigenschaft darstellt.
Wir kennen also die allgemeine und die Quasi-Monte-Carlo-Methode, aber es gibt noch ein Detail, das eine noch größere Konvergenzrate liefert: eine Stichprobe nach Signifikanz.
Wie bereits in der Lektion erwähnt, ist bei Spiegelreflexionen die Richtung des reflektierten Lichts in einem Spiegellappen eingeschlossen, dessen Größe und Form von der Rauheit der reflektierenden Oberfläche abhängt. Verstehen, dass irgendwelche (quasi) Zufallsstichprobenvektoren, die sich außerhalb der Spiegelkeule befinden, den integralen Ausdruck der Spiegelkomponente nicht beeinflussen, d.h. nutzlos. Es ist sinnvoll, die Erzeugung von Abtastvektoren im Bereich der Spiegelkeule unter Verwendung der vorgespannten Schätzfunktion für die Monte-Carlo-Methode zu fokussieren.
Dies ist die Essenz der Probenahme nach Bedeutung: Die Erzeugung von Probenahmevektoren ist in einem bestimmten Bereich eingeschlossen, der entlang des Medianvektors von Mikrooberflächen ausgerichtet ist und dessen Form durch die Rauheit des Materials bestimmt wird. Unter Verwendung einer Kombination der Monte-Carlo-Quasi-Methode, Sequenzen mit geringer Fehlanpassung und Verzerrung bei der Erzeugung von Abtastvektoren aufgrund der Abtastung von Bedeutung, erzielen wir sehr hohe Konvergenzraten. Da die Konvergenz zur Lösung schnell genug ist, können wir eine kleinere Anzahl von Abtastvektoren verwenden, um eine ausreichend akzeptable Schätzung zu erzielen. Die beschriebene Kombination von Methoden ermöglicht es grafischen Anwendungen im Prinzip, das Integral der Spiegelkomponente sogar in Echtzeit zu lösen, obwohl die vorläufige Berechnung immer noch ein viel rentablerer Ansatz bleibt.
Niedrige Fehlpaarungssequenz
In dieser Lektion verwenden wir noch eine vorläufige Berechnung der Spiegelkomponente des Reflexionsausdrucks für indirekte Strahlung. Und wir werden eine Stichprobe von Bedeutung unter Verwendung einer zufälligen Folge von geringer Fehlpaarung und der Monte-Carlo-Quasi-Methode verwenden. Die verwendete Sequenz ist als
Hammersley-Sequenz bekannt , deren detaillierte Beschreibung von
Holger Dammertz gegeben wird . Diese Sequenz basiert wiederum auf der
Van-der-Corput-Sequenz , die eine spezielle binäre Transformation des Dezimalbruchs relativ zum Dezimalpunkt verwendet.
Mit kniffligen bitweisen arithmetischen Tricks können Sie die Van-der-Corput-Sequenz sehr effizient direkt im Shader einstellen und basierend darauf das i-te Element der Hammersley-Sequenz aus der Auswahl in erstellen
N Artikel:
float RadicalInverse_VdC(uint bits) { bits = (bits << 16u) | (bits >> 16u); bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); return float(bits) * 2.3283064365386963e-10; // / 0x100000000 } // ---------------------------------------------------------------------------- vec2 Hammersley(uint i, uint N) { return vec2(float(i)/float(N), RadicalInverse_VdC(i)); }
Hammersley () gibt das i-te Element einer Sequenz mit geringer Fehlanpassung aus Stichproben mit mehreren Größen zurück
N .
Nicht alle OpenGL-Treiber unterstützen bitweise Vorgänge (z. B. WebGL und OpenGL ES 2.0). In bestimmten Umgebungen ist daher möglicherweise eine alternative Implementierung ihrer Verwendung erforderlich:
float VanDerCorpus(uint n, uint base) { float invBase = 1.0 / float(base); float denom = 1.0; float result = 0.0; for(uint i = 0u; i < 32u; ++i) { if(n > 0u) { denom = mod(float(n), 2.0); result += denom * invBase; invBase = invBase / 2.0; n = uint(float(n) / 2.0); } } return result; } // ---------------------------------------------------------------------------- vec2 HammersleyNoBitOps(uint i, uint N) { return vec2(float(i)/float(N), VanDerCorpus(i, 2u)); }
Ich stelle fest, dass diese Implementierung aufgrund bestimmter Einschränkungen für die Zyklusoperatoren in der alten Hardware alle 32 Bit durchläuft. Infolgedessen ist diese Version nicht so produktiv wie die erste Option - sie funktioniert jedoch auf jeder Hardware und auch ohne Bitoperationen.
Wichtigkeitsbeispiel im GGX-Modell
Anstelle einer gleichmäßigen oder zufälligen (Monte Carlo) Verteilung der erzeugten Probenvektoren innerhalb der Hemisphäre
Omega Wir werden versuchen, Vektoren so zu erzeugen, dass sie in die Hauptrichtung der Lichtreflexion tendieren, die durch den Medianvektor der Mikrooberflächen und abhängig von der Oberflächenrauheit gekennzeichnet ist. Der Abtastvorgang selbst ähnelt dem zuvor betrachteten: Öffnen Sie einen Zyklus mit einer ausreichend großen Anzahl von Iterationen, erstellen Sie ein Element einer geringen Fehlanpassungssequenz, basierend darauf erstellen wir einen Abtastvektor im Tangentenraum, übertragen diesen Vektor auf Weltkoordinaten und verwenden die Energiehelligkeit der Szene zum Abtasten. Im Prinzip beziehen sich die Änderungen nur auf die Tatsache, dass jetzt ein Element der Sequenz mit geringer Nichtübereinstimmung verwendet wird, um einen neuen Stichprobenvektor anzugeben:
const uint SAMPLE_COUNT = 4096u; for(uint i = 0u; i < SAMPLE_COUNT; ++i) { vec2 Xi = Hammersley(i, SAMPLE_COUNT);
Zusätzlich wird es für die vollständige Bildung des Probenvektors notwendig sein, ihn irgendwie in Richtung der Spiegelkeule auszurichten, die einem gegebenen Rauheitsgrad entspricht. Sie können die NDF (Normalverteilungsfunktion) aus einer theoretischen
Lektion entnehmen und mit der GGX-NDF kombinieren, um einen Beispielvektor im Bereich der Autorenschaft Epic Games zu spezifizieren:
vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) { float a = roughness*roughness; float phi = 2.0 * PI * Xi.x; float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
Das Ergebnis ist ein Probenvektor, der ungefähr entlang des Medianvektors von Mikrooberflächen für eine gegebene Rauheit und ein Element der Sequenz der geringen Fehlpaarung
Xi ausgerichtet ist . Beachten Sie, dass Epic Games das Quadrat des Rauheitswerts für eine bessere visuelle Qualität verwendet, basierend auf Disneys Originalarbeit zur PBR-Methode.
Nachdem die Implementierung der Hammersley-Sequenz und des Beispielvektor-Generierungscodes abgeschlossen ist, können wir den Vorfilter- und Faltungs-Shader-Code angeben:
#version 330 core out vec4 FragColor; in vec3 localPos; uniform samplerCube environmentMap; uniform float roughness; const float PI = 3.14159265359; float RadicalInverse_VdC(uint bits); vec2 Hammersley(uint i, uint N); vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness); void main() { vec3 N = normalize(localPos); vec3 R = N; vec3 V = R; const uint SAMPLE_COUNT = 1024u; float totalWeight = 0.0; vec3 prefilteredColor = vec3(0.0); for(uint i = 0u; i < SAMPLE_COUNT; ++i) { vec2 Xi = Hammersley(i, SAMPLE_COUNT); vec3 H = ImportanceSampleGGX(Xi, N, roughness); vec3 L = normalize(2.0 * dot(V, H) * H - V); float NdotL = max(dot(N, L), 0.0); if(NdotL > 0.0) { prefilteredColor += texture(environmentMap, L).rgb * NdotL; totalWeight += NdotL; } } prefilteredColor = prefilteredColor / totalWeight; FragColor = vec4(prefilteredColor, 1.0); }
Wir führen eine vorläufige Filterung der Umgebungskarte basierend auf einer bestimmten Rauheit durch, deren Pegel sich für jede Mip-Ebene der resultierenden kubischen Karte ändert (von 0,0 bis 1,0), und das Filterergebnis wird in der Variablen prefilteredColor gespeichert . Als nächstes wird die Variable durch das Gesamtgewicht für die gesamte Probe geteilt, und Proben mit einem geringeren Beitrag zum Endergebnis (mit einem niedrigeren NdotL- Wert ) erhöhen auch das Gesamtgewicht weniger.Speichern von Vorfilterungsdaten in Mip-Levels
Es bleibt Code zu schreiben, der OpenGL direkt anweist, die Umgebungskarte mit verschiedenen Rauheitsstufen zu filtern und die Ergebnisse dann in einer Reihe von Mip-Stufen der kubischen Zielkarte zu speichern. Hier ist der bereits vorbereitete Code aus der Lektion zur Berechnung der Bestrahlungskarte nützlich : prefilterShader.use(); prefilterShader.setInt("environmentMap", 0); prefilterShader.setMat4("projection", captureProjection); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); unsigned int maxMipLevels = 5; for (unsigned int mip = 0; mip < maxMipLevels; ++mip) {
Der Vorgang ähnelt einer Faltung der Bestrahlungskarte, diesmal sollten Sie jedoch die Größe des Bildpuffers bei jedem Schritt angeben und um die Hälfte reduzieren, um den Mip-Pegeln zu entsprechen. Außerdem muss die Mip-Ebene, bis zu der das Rendern ausgeführt wird, als Parameter für die Funktion glFramebufferTexture2D () angegeben werden .Das Ergebnis der Ausführung dieses Codes sollte eine kubische Karte sein, die zunehmend verschwommene Bilder von Reflexionen auf jeder nachfolgenden Mip-Ebene enthält. Sie können eine solche kubische Karte als Datenquelle für skybox verwenden und eine Stichprobe von einem beliebigen Mip-Level unter Null entnehmen: vec3 envColor = textureLod(environmentMap, WorldPos, 1.2).rgb;
Das Ergebnis dieser Aktion ist das folgende Bild:Es sieht aus wie eine sehr unscharfe Quellumgebungskarte. Wenn Ihr Ergebnis ähnlich ist, wird der Prozess des vorläufigen Filterns der HDR-Umgebungskarte höchstwahrscheinlich korrekt ausgeführt. Versuchen Sie, mit einer Probe verschiedener Mip-Levels zu experimentieren, und beobachten Sie mit jedem nächsten Level einen allmählichen Anstieg der Unschärfe.Faltungsartefakte vorfiltern
Für die meisten Aufgaben funktioniert der beschriebene Ansatz recht gut, aber früher oder später müssen Sie auf verschiedene Artefakte stoßen, die der Vorfilterungsprozess erzeugt. Hier sind die gebräuchlichsten und Methoden, um mit ihnen umzugehen.Die Manifestation der Nähte der kubischen Karte
Die Auswahl von Werten aus der Würfelkarte, die vom vorläufigen Filter für Oberflächen mit hoher Rauheit verarbeitet wird, führt dazu, dass Daten vom Mip-Level irgendwo näher am Ende ihrer Kette gelesen werden. Beim Abtasten von einer kubischen Karte interpoliert OpenGL standardmäßig nicht linear zwischen den Flächen der kubischen Karte. Da hohe Mip-Pegel eine geringere Auflösung haben und die Umgebungskarte unter Berücksichtigung einer viel größeren Spiegelkeule gefaltet wurde, wird das Fehlen einer Texturfilterung zwischen den Gesichtern offensichtlich:Glücklicherweise kann OpenGL diese Filterung mit einem einfachen Flag aktivieren: glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
Es reicht aus, das Flag irgendwo im Anwendungsinitialisierungscode zu setzen, und dieses Artefakt wird entfernt.Helle Punkte erscheinen
Da Spiegelreflexionen im allgemeinen Fall hochfrequente Details sowie Bereiche mit sehr unterschiedlichen Helligkeiten enthalten, erfordert ihre Faltung die Verwendung einer großen Anzahl von Abtastpunkten, um die große Streuung der Werte innerhalb der HDR-Reflexionen aus der Umgebung korrekt zu berücksichtigen. In diesem Beispiel nehmen wir bereits eine relativ große Anzahl von Proben, aber für bestimmte Szenen und hohe Rauhigkeiten des Materials reicht dies immer noch nicht aus, und Sie werden das Auftreten vieler Flecken in hellen Bereichen beobachten:Sie können die Anzahl der Proben weiter erhöhen, dies ist jedoch keine universelle Lösung, und unter bestimmten Bedingungen ist weiterhin ein Artefakt zulässig. Sie können sich jedoch der Chetan Jags- Methode zuwenden, mit der Sie die Manifestation eines Artefakts reduzieren können. Zu diesem Zweck wird im Stadium der vorläufigen Faltung die Auswahl aus der Umgebungskarte nicht direkt durchgeführt, sondern aus einer ihrer Mip-Ebenen, basierend auf dem Wert, der aus der Wahrscheinlichkeitsverteilungsfunktion des Integranden und der Rauheit erhalten wird: float D = DistributionGGX(NdotH, roughness); float pdf = (D * NdotH / (4.0 * HdotV)) + 0.0001;
Denken Sie daran, die trilineare Filterung für die Umgebungskarte zu aktivieren, um erfolgreich aus den Mip-Ebenen auswählen zu können: glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
Vergessen Sie auch nicht, mit OpenGL direkt Mip-Ebenen für die Textur zu erstellen, sondern erst, nachdem die Haupt-Mip-Ebene vollständig gebildet wurde:
Diese Methode funktioniert überraschend gut und entfernt fast alle (und häufig alle) Punkte der gefilterten Karte, selbst bei hoher Rauheit.Vorläufige Berechnung des BRDF
Wir haben also die Umgebungskarte erfolgreich mit dem Filter verarbeitet und können uns nun auf den zweiten Teil der Näherung in Form einer separaten Summe konzentrieren, die BRDF darstellt. Um den Speicher aufzufrischen, sehen Sie sich noch einmal die vollständige Aufzeichnung der ungefähren Lösung an:L o ( P , ω o ) = ∫ & OHgr; L i ( P , ω i ) d & ohgr; i * ∫ & OHgr; f r ( p , ω i , ω o ) n ⋅ ω i d & ohgr; i
Wir haben vorab den linken Teil der Summe berechnet und die Ergebnisse für verschiedene Rauheitsstufen in einer separaten kubischen Karte aufgezeichnet. Die rechte Seite erfordert die Faltung des BDRF-Ausdrucks zusammen mit den folgenden Parametern: Winkeln ⋅ ω i , Oberflächenrauheit und Fresnel-KoeffizientF 0 .
Ein Prozess ähnlich der Integration eines gespiegelten BRDF für eine vollständig weiße Umgebung oder mit konstanter Energiehelligkeit L i = 1,0 .
BRDF für drei Variablen zusammenzufassen ist keine triviale Aufgabe, aber in diesem Fall F 0 kann aus dem Ausdruck abgeleitet werden, der den Spiegel BRDF beschreibt:∫ Ω fr(p,ωi,ωo)n⋅ωidωi= ∫ Ω fr(p,ωi,ωo) F ( ω o , h )F ( ω o , h ) n⋅ωidωi
Hier F ist eine Funktion, die die Berechnung des Fresnel-Sets beschreibt. Wenn Sie den Divisor in den Ausdruck für BRDF verschieben, können Sie zum folgenden entsprechenden Datensatz wechseln:∫ Ω f r ( p , ω i , ω o )F ( & ohgr ; o , h ) F(&ohgr;o,h)n&ohgr;id& ohgr;i
Ersetzen des richtigen Eintrags F in der Fresnel-Schlick-Näherung erhalten wir:∫ Ω f r ( p , ω i , ω o )F ( ω o , h ) (F0+(1-F0)(1-ωo⋅h)5)n⋅ωid& ohgr;i
Bezeichne den Ausdruck ( 1 - ω o ⋅ h ) 5 wie
/ a l p h a , um die Entscheidung zu vereinfachenF 0 ::
∫Ωfr(p,ωi,ωo)F(ωo,h)(F0+(1−F0)α)n⋅ωidωi
∫Ωfr(p,ωi,ωo)F(ωo,h)(F0+1∗α−F0∗α)n⋅ωidωi
∫Ωfr(p,ωi,ωo)F ( ω o , h ) (F0*(1-α)+α)n⋅ωid& ohgr;i
Nächste Funktion Wir teilen F in zwei Integrale:∫ Ω f r ( p , ω i , ω o )F ( ω o , h ) (F0*(1-α))n⋅ωid& ohgr;i+∫& OHgr;fr(p,ωi,ωo)F ( ω o , h ) (α)n⋅ωid& ohgr;i
Auf diese Weise F 0 wird unter dem Integral konstant sein, und wir können es aus dem Vorzeichen des Integrals herausnehmen. Als nächstes werden wir enthüllenα in den ursprünglichen Ausdruck und erhalte den endgültigen Eintrag für BRDF als separate Summe:F0∫Ωfr(p,ωi,ωo)(1−(1−ωo⋅h)5)n⋅ωidωi+∫Ωfr(p,ωi,ωo)(1−ωo⋅h)5n⋅ωidωi
Die resultierenden zwei Integrale repräsentieren die Skala und den Versatz für den Wert F 0 . Beachten Sie dasf ( p , ω i , ω o ) enthält ein VorkommenF , weil sich diese Vorkommen gegenseitig aufheben und aus dem Ausdruck verschwinden. Mit dem bereits entwickelten Ansatz kann die BRDF-Faltung zusammen mit den Eingabedaten durchgeführt werden: Rauheit und Winkel zwischen Vektorenn und
w o .
Schreiben Sie das Ergebnis in der 2D - Textur - Karte Komplex BRDF ( BRDF Integration Karte ), die als Hilfstabellenwert für die Verwendung in den letzten Shader dienen wird, die das Endergebnis der indirekten Spiegelbeleuchtung bilden.Der BRDF-Faltungsshader arbeitet in der Ebene und verwendet direkt zweidimensionale Texturkoordinaten als Eingabeparameter für den Faltungsprozess ( NdotV und Rauheit ). Der Code ähnelt merklich einer Faltung der Vorfilterung, aber hier wird der Abtastvektor unter Berücksichtigung der geometrischen Funktion BRDF und des Fresnel-Schlick-Näherungsausdrucks verarbeitet: vec2 IntegrateBRDF(float NdotV, float roughness) { vec3 V; Vx = sqrt(1.0 - NdotV*NdotV); Vy = 0.0; Vz = NdotV; float A = 0.0; float B = 0.0; vec3 N = vec3(0.0, 0.0, 1.0); const uint SAMPLE_COUNT = 1024u; for(uint i = 0u; i < SAMPLE_COUNT; ++i) { vec2 Xi = Hammersley(i, SAMPLE_COUNT); vec3 H = ImportanceSampleGGX(Xi, N, roughness); vec3 L = normalize(2.0 * dot(V, H) * H - V); float NdotL = max(Lz, 0.0); float NdotH = max(Hz, 0.0); float VdotH = max(dot(V, H), 0.0); if(NdotL > 0.0) { float G = GeometrySmith(N, V, L, roughness); float G_Vis = (G * VdotH) / (NdotH * NdotV); float Fc = pow(1.0 - VdotH, 5.0); A += (1.0 - Fc) * G_Vis; B += Fc * G_Vis; } } A /= float(SAMPLE_COUNT); B /= float(SAMPLE_COUNT); return vec2(A, B); } // ---------------------------------------------------------------------------- void main() { vec2 integratedBRDF = IntegrateBRDF(TexCoords.x, TexCoords.y); FragColor = integratedBRDF; }
Wie Sie sehen können, wird die Faltung von BRDF als eine fast wörtliche Anordnung der obigen mathematischen Berechnungen implementiert. Die Eingabeparameter Rauheit und Winkel werden übernommen.θ wird ein Probenvektor basierend auf der Probe nach Signifikanz gebildet, der unter Verwendung der Geometriefunktion und des transformierten Fresnel-Ausdrucks für BRDF verarbeitet wird. Infolgedessen wird für jede Probe die Größe der Skalierung und Verschiebung des Werts angegebenF 0 , die am Ende gemittelt und in der Formvec2 zurückgegeben werden. In einertheoretischenLektion wurde erwähnt, dass sich die geometrische Komponente von BRDF bei der Berechnung von IBL geringfügig unterscheidet, da der Koeffizientk wird anders angegeben:k d i r e c t = ( α + 1 ) 28
k I B L = α 22
Da das Faltungs-BRDF bei der Berechnung des IBL Teil der Lösung des Integrals ist, verwenden wir den Koeffizienten k I B L zur Berechnung der Geometriefunktion im Schlick-GGX-Modell: float GeometrySchlickGGX(float NdotV, float roughness) { float a = roughness; float k = (a * a) / 2.0; float nom = NdotV; float denom = NdotV * (1.0 - k) + k; return nom / denom; } // ---------------------------------------------------------------------------- float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); float ggx2 = GeometrySchlickGGX(NdotV, roughness); float ggx1 = GeometrySchlickGGX(NdotL, roughness); return ggx1 * ggx2; }
Bitte beachten Sie, dass der Koeffizient k wird basierend auf Parameteraberechnet. Darüber hinaus wird in diesem Fall derRauheitsparameterbei der Beschreibung des Parametersanicht quadriert, was an anderen Stellen durchgeführt wurde, an denen dieser Parameter angewendet wurde. Ich bin mir nicht sicher, wo das Problem liegt: in der Arbeit von Epic Games oder in der ersten Arbeit von Disney, aber es ist erwähnenswert, dass genau diese direkte Zuordnung desRauheitswertszu demParametera dieidentische BRDF-Integrationskarte erzeugt, die in der Veröffentlichung von Epic Games vorgestellt wird.Ferner werden die BRDF-Faltungsergebnisse in Form einer 2D-Textur der Größe 512 x 512 gespeichert: unsigned int brdfLUTTexture; glGenTextures(1, &brdfLUTTexture);
Wie von Epic Games empfohlen, wird hier ein 16-Bit-Gleitkomma-Texturformat verwendet. Stellen Sie den Wiederholungsmodus unbedingt auf GL_CLAMP_TO_EDGE ein, um zu vermeiden, dass Artefakte von der Kante abgetastet werden.Als nächstes verwenden wir dasselbe Bildpufferobjekt und führen einen Shader auf der Oberfläche eines Vollbild-Quad aus: glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdfLUTTexture, 0); glViewport(0, 0, 512, 512); brdfShader.use(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); RenderQuad(); glBindFramebuffer(GL_FRAMEBUFFER, 0);
Als Ergebnis erhalten wir eine Texturabbildung, in der das Ergebnis der Faltung des Teils des Ausdrucks des für BRDF verantwortlichen Teilungsbetrags gespeichert wird:Mit den Ergebnissen der vorläufigen Filterung der Umgebungskarte und der Textur mit den Ergebnissen der BRDF-Faltung können wir das Ergebnis der Berechnung des Integrals für die indirekte Spiegelbeleuchtung basierend auf der Approximation durch eine separate Summe wiederherstellen. Der wiederhergestellte Wert wird anschließend als indirekte oder Hintergrundspiegelstrahlung verwendet.Endgültige Reflexionsberechnung im IBL-Modell
Um einen Wert zu erhalten, der die indirekte Spiegelkomponente im allgemeinen Ausdruck des Reflexionsvermögens beschreibt, müssen die berechneten Approximationskomponenten als separate Summe zu einem einzigen Ganzen „zusammengeklebt“ werden. Fügen Sie zunächst dem endgültigen Shader die entsprechenden Sampler für vorberechnete Daten hinzu: uniform samplerCube prefilterMap; uniform sampler2D brdfLUT;
Zunächst erhalten wir den Wert der indirekten Spiegelreflexion auf der Oberfläche durch Abtasten aus einer vorverarbeiteten Umgebungskarte basierend auf dem Reflexionsvektor. Bitte beachten Sie, dass hier die Auswahl des Mip-Levels für die Probenahme auf der Oberflächenrauheit basiert. Bei raueren Oberflächen ist die Reflexion unscharfer : void main() { [...] vec3 R = reflect(-V, N); const float MAX_REFLECTION_LOD = 4.0; vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb; [...] }
In der vorläufigen Faltungsphase haben wir nur 5 Mip-Levels (von null bis vier) vorbereitet. Die Konstante MAX_REFLECTION_LOD dient dazu, die Auswahl aus den generierten Mip-Levels zu begrenzen.Als nächstes treffen wir eine Auswahl aus der BRDF-Integrationskarte basierend auf der Rauheit und dem Winkel zwischen der Normalen und der Blickrichtung: vec3 F = FresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); vec2 envBRDF = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y);
Der aus der Karte erhaltene Wert enthält die Skalierungs- und Verschiebungsfaktoren für den Wert F 0 (hier nehmen wir den WertF- Fresnel-Koeffizient). Der konvertierte WertF wirddann mit dem Wert kombiniert, der aus der Vorfilterungskarte erhalten wurde, um eine ungefähre Lösung des ursprünglichen Integralausdrucks -spiegelnd- zu erhalten.So erhalten wir eine Lösung für den Teil des Ausdrucks des Reflexionsvermögens, der für die Spiegelreflexion verantwortlich ist. Um eine vollständige Lösung des PBR-IBL-Modells zu erhalten, müssen Sie diesen Wert mit der Lösung für den diffusen Teil des Reflexionsausdrucks kombinieren, den wir in derletztenLektion erhalten haben: vec3 F = FresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); vec3 kS = F; vec3 kD = 1.0 - kS; kD *= 1.0 - metallic; vec3 irradiance = texture(irradianceMap, N).rgb; vec3 diffuse = irradiance * albedo; const float MAX_REFLECTION_LOD = 4.0; vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb; vec2 envBRDF = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y); vec3 ambient = (kD * diffuse + specular) * ao;
Beachten Sie, dass der Wert spiegelnde nicht multiplizierten kS , weil es bereits eine Fresnel - Koeffizienten enthält.Lassen Sie uns unsere Testanwendung mit einem vertrauten Satz von Kugeln mit sich ändernden Eigenschaften von Metallizität und Rauheit ausführen und ihr Aussehen in der vollen Pracht von PBR betrachten:Sie können noch weiter gehen und eine Reihe von Texturen herunterladen, die dem PBR-Modell entsprechen, und Kugeln aus realen Materialien erhalten :Oder laden Sie sogar ein wunderschönes Modell zusammen mit vorbereiteten PBR-Texturen von Andrew Maximov herunter :Ich denke, Sie müssen niemanden davon überzeugen, dass das aktuelle Beleuchtungsmodell viel überzeugender aussieht. Darüber hinaus sieht die Beleuchtung unabhängig von der Umgebungskarte physisch korrekt aus. Im Folgenden werden einige völlig unterschiedliche HDR-Umgebungskarten verwendet, die die Art der Beleuchtung vollständig verändern - aber alle Bilder sehen physisch zuverlässig aus, obwohl Sie keine Parameter im Modell anpassen mussten! (Im Prinzip ist diese Vereinfachung der Arbeit mit Materialien das Hauptplus der PBR-Pipeline, und ein besseres Bild kann als angenehme Folge angesehen werden. Hinweis. )Fuh, unsere Reise in die Essenz des PBR-Renderers ist ziemlich umfangreich. Wir sind durch eine ganze Reihe von Schritten zum Ergebnis gekommen, und natürlich kann bei den ersten Ansätzen viel schief gehen. Daher empfehle ich Ihnen, bei Problemen den Beispielcode für monochrome und strukturierte Kugeln (und natürlich den Shader-Code!) Sorgfältig zu verstehen . Oder fragen Sie in den Kommentaren um Rat.Was weiter?
Ich hoffe, dass Sie durch das Lesen dieser Zeilen bereits ein Verständnis für die Arbeit des PBR-Rendermodells entwickelt und eine Testanwendung herausgefunden und erfolgreich gestartet haben. In diesen Lektionen haben wir alle erforderlichen Hilfstexturkarten für das PBR-Modell in unserer Anwendung vor dem Hauptrenderzyklus berechnet. Für Trainingsaufgaben ist dieser Ansatz geeignet, jedoch nicht für die praktische Anwendung. Erstens sollte eine solche vorläufige Vorbereitung einmal und nicht bei jedem Anwendungsstart erfolgen. Zweitens, wenn Sie weitere Umgebungskarten hinzufügen möchten, müssen Sie diese auch beim Start verarbeiten. Was ist, wenn noch ein paar Karten hinzugefügt werden? Echter Schneeball.Aus diesem Grund werden im allgemeinen Fall eine Bestrahlungskarte und eine vorverarbeitete Umgebungskarte einmal erstellt und dann auf der Festplatte gespeichert (die BRDF-Aggregationskarte hängt nicht von der Umgebungskarte ab, sodass sie im Allgemeinen einmal berechnet oder heruntergeladen werden kann). Daraus folgt, dass Sie ein Format zum Speichern von kubischen HDR-Karten einschließlich ihrer Mip-Levels benötigen. Nun, oder Sie können sie in einem der weit verbreiteten Formate speichern und laden (also unterstützt .dds das Speichern von Mip-Levels).Ein weiterer wichtiger Punkt: Um in diesen Lektionen ein tiefes Verständnis der PBR-Pipeline zu vermitteln, habe ich den gesamten Prozess der Vorbereitung des PBR-Renderings beschrieben, einschließlich vorläufiger Berechnungen von Hilfskarten für IBL. In Ihrer Praxis können Sie jedoch genauso gut eines der großartigen Dienstprogramme verwenden, die diese Karten für Sie vorbereiten: zum Beispiel cmftStudio oder IBLBaker .Wir haben auch erwägen , den Prozess nicht die Cubemap zur Herstellung von Reflexionen Proben ( Reflexionssonden) und die damit verbundenen Prozesse der kubischen Karteninterpolation und Parallaxenkorrektur. Kurz gesagt, diese Technik kann wie folgt beschrieben werden: Wir platzieren in unserer Szene viele Objekte von Reflexionsproben, die ein lokales Umgebungsbild in Form einer kubischen Karte bilden, und dann werden alle notwendigen Hilfskarten für das IBL-Modell auf ihrer Basis gebildet. Durch Interpolation von Daten aus mehreren Samples basierend auf der Entfernung von der Kamera erhalten Sie eine sehr detaillierte Beleuchtung basierend auf dem Bild, deren Qualität im Wesentlichen nur durch die Anzahl der Samples begrenzt ist, die wir bereit sind, in der Szene zu platzieren. Mit diesem Ansatz können Sie die Beleuchtung korrekt ändern, z. B. wenn Sie von einer hell beleuchteten Straße in die Dämmerung eines bestimmten Raums wechseln. Ich werde wahrscheinlich in Zukunft eine Lektion über Reflexionstests schreiben.Im Moment kann ich den folgenden Artikel zu Chetan Jags jedoch nur zur Überprüfung empfehlen.(Die Implementierung von Beispielen und vielem mehr finden Sie in der Raw-Engine des Autors der Tutorials hier , ca. Per. )Zusätzliche Materialien
- Real Shading in Unreal Engine 4 : Eine Erklärung des Ansatzes von Epic Games, den Ausdruck für die Spiegelkomponente durch eine aufgeteilte Summe zu approximieren. Basierend auf diesem Artikel wurde der Code für die IBL PBR-Lektion geschrieben.
- Physikalisch basierte Schattierung und bildbasierte Beleuchtung : Ein ausgezeichneter Artikel, der den Prozess der Einbeziehung der Berechnung der Spiegelkomponente von IBL in eine interaktive PBR-Pipeline-Anwendung beschreibt.
- Bildbasierte Beleuchtung : Ein sehr langer und detaillierter Beitrag zu spiegelnder IBL und verwandten Themen, einschließlich des Problems der Lichtsondeninterpolation.
- Moving Frostbite to PBR : , PBR «AAA».
- Physically Based Rendering – Part Three : , IBL PBR JMonkeyEngine.
- Implementation Notes: Runtime Environment Map Filtering for Image Based Lighting : HDR , .