"Es gibt nichts Schlimmeres als ein klares Bild eines verschwommenen Konzepts." - Fotograf Ansel Adams
Im
ersten Teil des Artikels haben wir einen Whited Ray Tracer erstellt, der perfekte Reflexionen und scharfe Schatten nachzeichnen kann. Uns fehlen jedoch die Auswirkungen von Unschärfe: diffuse Reflexion, glänzende Reflexionen und weiche Schatten.
Basierend auf dem
Code, den wir
bereits haben , werden wir die 1986 von James Cajia formulierte
Rendering-Gleichung iterativ lösen und unseren Renderer in einen
Pfad-Tracer verwandeln, der die oben genannten Effekte übertragen kann. Wir werden wieder C # für Skripte und HLSL für Shader verwenden. Der Code wird auf
Bitbucket hochgeladen.
Dieser Artikel ist viel mathematischer als der vorherige, aber seien Sie nicht beunruhigt. Ich werde versuchen, jede Formel so klar wie möglich zu erklären. Die Formeln werden hier benötigt, um zu sehen, was passiert und
warum unser Renderer funktioniert. Ich empfehle daher, sie zu verstehen. Wenn etwas nicht klar ist, stellen Sie Fragen in den Kommentaren zum Originalartikel.
Das Bild unten wird mithilfe der
Graffiti Shelter- Karte von der HDRI Haven-Website gerendert. Andere Bilder in diesem Artikel wurden mit der
Kiara 9 Dusk- Karte gerendert.
Rendering-Gleichung
Aus formaler Sicht besteht die Aufgabe des fotorealistischen Renderers darin, die Rendering-Gleichung zu lösen, die wie folgt geschrieben ist:
L ( x , v e c o m e g a o ) = L e ( x , v e c o m e g a o ) + i n t O m e g a f r ( x , v e c o m e g a i , v e c o m e g a o ) ( vec omegai cdot vecn)L(x, vec omegai)d vec omegai
Lassen Sie es uns analysieren. Unser oberstes Ziel ist es, die Helligkeit des Bildschirmpixels zu bestimmen. Die Rendering-Gleichung gibt uns die Beleuchtungsstärke an
L(x, vec omegao) von einem Punkt kommen
x (Einfallspunkt des Strahls) in Richtung
vec omegao (die Richtung, in die der Strahl fällt). Die Oberfläche selbst kann eine Lichtquelle sein, die Licht emittiert
Le(x, vec omegao) in unsere Richtung. Die meisten Oberflächen tun dies nicht, daher reflektieren sie nur Licht von außen. Deshalb wird das Integral verwendet. Es sammelt Licht aus allen möglichen Richtungen der Hemisphäre.
Omega um das Normale herum (daher berücksichtigen wir dabei die Beleuchtung, die
von oben auf die Oberfläche fällt und nicht
von innen , was für durchscheinende Materialien erforderlich sein kann).
Der erste Teil ist
fr wird als bidirektionale Reflexionsverteilungsfunktion (BRDF) bezeichnet. Diese Funktion beschreibt visuell die Art des Materials, mit dem wir es zu tun haben: Metall oder Dielektrikum, dunkel oder hell, glänzend oder matt. BRDF bestimmt den Anteil der Beleuchtung
vec omegai was sich in der Richtung widerspiegelt
vec omegao . In der Praxis wird dies unter Verwendung eines Dreikomponentenvektors mit Werten von Rot, Grün und Blau im Intervall implementiert
[0,1] .
Zweiter Teil -
( vec omegai cdot vecn) Ist das Äquivalent von
1 cos theta wo
theta - Winkel zwischen einfallendem Licht und Oberflächennormale
vecn . Stellen Sie sich eine Säule paralleler Lichtstrahlen vor, die senkrecht auf die Oberfläche fallen. Stellen Sie sich nun denselben Strahl vor, der in einem flachen Winkel auf die Oberfläche fällt. Das Licht wird über einen größeren Bereich verteilt, aber es bedeutet auch, dass jeder Punkt dieses Bereichs dunkler aussieht. Cosine wird benötigt, um dies zu berücksichtigen.
Schließlich wird die Beleuchtung selbst von erhalten
vec omegai wird rekursiv unter Verwendung der gleichen Gleichung bestimmt. Das heißt, die Beleuchtung am Punkt
x Hängt vom einfallenden Licht aus allen möglichen Richtungen in der oberen Hemisphäre ab. In jede dieser Richtungen von einem Punkt
x Es gibt noch einen anderen Punkt
x prime deren Helligkeit wiederum von dem Licht abhängt, das aus allen möglichen Richtungen der oberen Hemisphäre dieses Punktes fällt. Alle Berechnungen werden wiederholt.
Hier ist, was hier passiert: Dies ist eine unendlich rekursive Integralgleichung mit einer unendlichen Anzahl von halbkugelförmigen Integrationsbereichen. Wir können diese Gleichung nicht direkt lösen, aber es gibt eine ziemlich einfache Lösung.
1 Vergiss es nicht! Wir werden oft über Kosinus sprechen und immer das Skalarprodukt im Auge behalten. Als
veca cdot vecb= | veca | | vecb | cos( theta) und wir haben es mit
Richtungen (Einheitsvektoren) zu tun, dann ist das Skalarprodukt
der Kosinus bei den meisten Computergrafikaufgaben.
Monte Carlo kommt zur Rettung
Die Monte-Carlo-Integration ist eine numerische Integrationstechnik, mit der wir jedes Integral anhand einer endlichen Anzahl von Zufallsstichproben näherungsweise berechnen können. Darüber hinaus garantiert Monte Carlo die Konvergenz zur richtigen Entscheidung - je mehr Proben wir nehmen, desto besser. Hier ist seine verallgemeinerte Form:
F N a p p r o x f r ein C 1 N s u m N n = 0 f r a c f ( x n ) , p ( x n )
Daher das Integral der Funktion
f ( x n ) kann ungefähr berechnet werden, indem Zufallsstichproben in der Integrationsdomäne gemittelt werden. Jede Stichprobe wird durch die Wahrscheinlichkeit ihrer Auswahl geteilt.
p ( x n ) . Aus diesem Grund hat die am häufigsten ausgewählte Probe mehr Gewicht als die am seltensten ausgewählte.
Bei einheitlichen Proben in der Hemisphäre (jede Richtung hat die gleiche Wahrscheinlichkeit, ausgewählt zu werden) ist die Wahrscheinlichkeit von Proben konstant:
p ( o m e g a ) = f r a c 1 2 p i (weil
2 p i Ist die Oberfläche einer einzelnen Hemisphäre). Wenn wir das alles zusammenfügen, erhalten wir Folgendes:
L(x, vec omegao) ungefährLe(x, vec omegao)+ frac1N sumN.n=0 colorGreen2 pifr(x, vec omegai, vec omegao)( vec omegai cdot vecn)L(x, vec omegai)
Strahlung
Le(x, vec omegao) Ist nur der Wert, der von unserer
Shade
Funktion zurückgegeben wird.
frac1N läuft bereits in unserer
AddShader
Funktion. Multiplikation mit
L(x, vec omegai) passiert, wenn wir den Strahl reflektieren und weiter verfolgen. Unsere Aufgabe ist es, dem grünen Teil der Gleichung Leben einzuhauchen.
Voraussetzungen
Bevor wir uns auf eine Reise begeben, kümmern wir uns um einige Aspekte: Sammeln von Samples, deterministische Szenen und Shader-Zufälligkeit.
Akkumulation
Aus irgendeinem Grund
OnRenderImage
mir Unity die HDR-Textur nicht als
destination
in
OnRenderImage
. Das Format
R8G8B8A8_Typeless
hat bei mir funktioniert, sodass die Genauigkeit schnell zu niedrig wird, um eine große Anzahl von Samples zu akkumulieren. Um dies zu handhaben, fügen
private RenderTexture _converged
zum
private RenderTexture _converged
C #
private RenderTexture _converged
. Dies ist unser Puffer, der die Ergebnisse mit hoher Genauigkeit sammelt, bevor sie auf dem Bildschirm angezeigt werden. Wir initialisieren / geben die Textur auf die gleiche Weise wie
_target
in der
InitRenderTexture
Funktion frei. Verdoppeln Sie in der
Render
Funktion das Blitting:
Graphics.Blit(_target, _converged, _addMaterial); Graphics.Blit(_converged, destination);
Deterministische Szenen
Wenn Sie Änderungen am Rendering vornehmen, um den Effekt zu bewerten, ist es hilfreich, diese mit früheren Ergebnissen zu vergleichen. Bisher erhalten wir mit jedem Neustart des Wiedergabemodus oder jeder Neukompilierung des Skripts eine neue zufällige Szene. Um dies zu vermeiden, fügen Sie das
public int SphereSeed
dem
public int SphereSeed
C #
public int SphereSeed
und der folgenden Zeile am Anfang von
SetUpScene
:
Random.InitState(SphereSeed);
Jetzt können wir die Seed-Szenen manuell einstellen. Geben Sie eine beliebige Zahl ein und schalten Sie
RayTracingMaster
/ wieder ein, bis Sie die richtige Szene erhalten.
Die folgenden Parameter wurden für Beispielbilder verwendet: Sphere Seed 1223832719, Sphere Radius [5, 30], Spheres Max 10000, Sphere Placement Radius 100.
Shader Zufälligkeit
Bevor wir mit der stochastischen Abtastung beginnen, müssen wir dem Shader Zufälligkeit hinzufügen. Ich werde die
kanonische Zeichenfolge verwenden, die ich im Netzwerk gefunden habe und die der
Einfachheit halber geändert wurde:
float2 _Pixel; float _Seed; float rand() { float result = frac(sin(_Seed / 100.0f * dot(_Pixel, float2(12.9898f, 78.233f))) * 43758.5453f); _Seed += 1.0f; return result; }
Initialisieren Sie
_Pixel
direkt in
CSMain
als
_Pixel = id.xy
damit jedes Pixel unterschiedliche Zufallswerte verwenden kann.
_Seed
in der Funktion
_Seed
von C # aus initialisiert.
RayTracingShader.SetFloat("_Seed", Random.value);
Die Qualität der hier erzeugten Zufallszahlen ist instabil. In Zukunft lohnt es sich, diese Funktion zu untersuchen und zu testen, indem der Einfluss von Parametern analysiert und mit anderen Ansätzen verglichen wird. Aber im Moment werden wir es einfach benutzen und auf das Beste hoffen.
Probenahme auf der Hemisphäre
Fangen wir noch einmal an: Wir brauchen zufällige Richtungen, die gleichmäßig in der Hemisphäre verteilt sind. Diese nicht triviale Aufgabe für den gesamten Umfang wird in
diesem Artikel von Corey Simon ausführlich beschrieben. Es ist leicht, sich an die Hemisphäre anzupassen. So sieht der Shader-Code aus:
float3 SampleHemisphere(float3 normal) { // float cosTheta = rand(); float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta)); float phi = 2 * PI * rand(); float3 tangentSpaceDir = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); // return mul(tangentSpaceDir, GetTangentSpace(normal)); }
Richtungen werden für eine auf der positiven Z-Achse zentrierte Halbkugel erzeugt, daher müssen wir sie so transformieren, dass sie auf der gewünschten Normalen zentriert sind. Wir erzeugen eine Tangente und eine Binormale (zwei Vektoren orthogonal zur Normalen und orthogonal zueinander). Zuerst wählen wir einen Hilfsvektor aus, um die Tangente zu erzeugen. Dazu nehmen wir die positive X-Achse und kehren nur dann zum positiven Z zurück, wenn es normalerweise (ungefähr) mit der X-Achse ausgerichtet ist. Dann können wir das Vektorprodukt verwenden, um die Tangente und dann die Binormale zu erzeugen.
float3x3 GetTangentSpace(float3 normal) {
Lambert-Streuung
Nachdem wir nun einheitliche zufällige Richtungen haben, können wir mit der Implementierung des ersten BRDF fortfahren. Für die diffuse Reflexion wird am häufigsten das Lambert BRDF verwendet, das überraschend einfach ist:
fr(x, vec omegai, vec omegao)= frackd pi wo
kd - Dies ist Albedo-Oberfläche. Fügen wir es in unsere Monte-Carlo-Rendering-Gleichung ein (ich werde den Emissionsgrad noch nicht berücksichtigen) und sehen, was passiert:
L(x, vec omegao) approx frac1N sumNn=0 colorBlueViolet2kd( vec omegai cdot vecn)L(x, vec omegai)
Fügen wir diese Gleichung sofort in den Shader ein. Ersetzen Sie in der
Shade
Funktion den Code im
if (hit.distance < 1.#INF)
durch die folgenden Zeilen:
// ray.origin = hit.position + hit.normal * 0.001f; ray.direction = SampleHemisphere(hit.normal); ray.energy *= 2 * hit.albedo * sdot(hit.normal, ray.direction); return 0.0f;
Die neue Richtung des reflektierten Strahls wird unter Verwendung unserer Funktion homogener Halbkugelproben bestimmt. Die Energie des Strahls wird mit dem entsprechenden Teil der oben gezeigten Gleichung multipliziert. Da die Oberfläche keine Beleuchtung
AddShader
(sie reflektiert nur direkt oder indirekt vom Himmel empfangenes Licht), geben wir 0 zurück. Vergessen Sie hier nicht, dass
AddShader
die Proben mittelt, sodass wir uns keine Sorgen machen müssen
frac1N sum .
CSMain
enthält bereits eine Multiplikation mit
L(x, vec omegai) (der nächste reflektierte Strahl), so dass wir nicht mehr viel Arbeit haben.
sdot
ist eine
sdot
, die ich für mich selbst definiert habe. Es gibt einfach das Ergebnis des Skalarprodukts mit einem zusätzlichen Koeffizienten zurück und begrenzt es dann auf das Intervall
[0,1] ::
float sdot(float3 x, float3 y, float f = 1.0f) { return saturate(dot(x, y) * f); }
Lassen Sie uns zusammenfassen, was unser Code bisher tut.
CSMain
erzeugt die Primärstrahlen der Kamera und ruft
Shade
. Beim Überqueren der Oberfläche erzeugt diese Funktion wiederum einen neuen Strahl (gleichmäßig zufällig in der Halbkugel um die Normalen) und berücksichtigt den BRDF des Materials und den Kosinus in der Energie des Strahls. Am Schnittpunkt des Strahls mit dem Himmel nehmen wir HDRI (unsere einzige Beleuchtungsquelle) auf und geben die Beleuchtung zurück, die mit der Energie des Strahls multipliziert wird (d. H. Das Ergebnis aller vorherigen Schnittpunkte, beginnend mit der Kamera). Dies ist eine einfache Stichprobe, die sich mit einem konvergenten Ergebnis mischt. Infolgedessen wird die Auswirkung in jeder Probe berücksichtigt.
frac1N .
Es ist Zeit, alles in Arbeit zu überprüfen. Da Metalle keine diffuse Reflexion haben,
SetUpScene
wir sie zunächst in der
SetUpScene
Funktion eines C #
Random.value
(rufen
Random.value
hier jedoch
Random.value
auf, um den Determinismus der Szene
Random.value
):
bool metal = Random.value < 0.0f;
Starten Sie den Wiedergabemodus und sehen Sie, wie das anfänglich verrauschte Bild gelöscht wird und zu einem schönen Rendering konvergiert:
Phong Spiegelbild
Nicht schlecht für nur ein paar Codezeilen (und einen kleinen Teil der Mathematik). Lassen Sie uns das Bild verfeinern, indem wir Spiegelreflexionen mit Phongs BRDF hinzufügen. Fongs ursprüngliche Formulierung hatte ihre Probleme (mangelnde Beziehungen und Energieeinsparung), aber glücklicherweise haben
andere Menschen sie beseitigt . Das erweiterte BRDF wird unten gezeigt.
vec omegar Ist die Richtung des perfekt reflektierten Lichts und
alpha Ist ein Phong-Indikator, der die Rauheit steuert:
fr(x, vec omegai, vec omegao)=ks frac alpha+22 pi( vec omegar cdot vec omegao) alpha
Ein interaktives zweidimensionales
Diagramm zeigt, wie das BRDF für Phong wann aussieht
alpha=15 für einen in einem Winkel von 45 ° einfallenden Strahl. Versuchen Sie, den Wert zu ändern.
alpha .
Fügen Sie dies in unsere Monte-Carlo-Rendering-Gleichung ein:
L(x, vec omegao) approx frac1N sumNn=0 colorbrownks( alpha+2)( vec omegar cdot vec omegao) alpha( vec omegai cdot vecn)L(x, vec omegai)
Und zum Schluss fügen wir dies dem bestehenden Lambert BRDF hinzu:
L(x, vec omegao) approx frac1N sumNn=0[ colorBlueViolet2kd+ colorbrownks( alpha+2)( vec omegar cdot vec omegao) alpha]( vec omegai cdot vecn)L(x, vec omegai)
Und so sehen sie im Code zusammen mit Lambert-Streuung aus:
Beachten Sie, dass wir das Skalarprodukt durch ein etwas anderes, aber äquivalentes (reflektiertes) Produkt ersetzt haben
omegao statt
omegai )
SetUpScene
nun Metallmaterialien wieder in die
SetUpScene
Funktionen und überprüfen Sie, wie es funktioniert.
Experimentieren mit verschiedenen Werten
alpha Möglicherweise stellen Sie ein Problem fest: Selbst eine geringe Leistung erfordert viel Zeit für die Konvergenz, und bei hoher Leistung ist das Rauschen besonders auffällig. Selbst nach ein paar Minuten Wartezeit ist das Ergebnis alles andere als ideal, was für eine so einfache Szene nicht akzeptabel ist.
alpha=15 und
alpha=300 mit 8192 Beispielen sieht das so aus:
Warum ist das passiert? Immerhin hatten wir vorher so schöne ideale Reflexionen (
alpha= infty )! .. Das Problem ist, dass wir
homogene Proben erzeugen und diese nach BRDF gewichten. Mit hohen Phong-Werten ist der BRDF für alle klein, aber diese Richtungen kommen einer perfekten Reflexion sehr nahe, und es ist sehr unwahrscheinlich, dass wir sie anhand unserer
homogenen Proben zufällig auswählen.
Wenn wir dagegen tatsächlich eine dieser Richtungen kreuzen, ist der BRDF riesig, um alle anderen kleinen Stichproben zu kompensieren. Das Ergebnis ist eine sehr große Dispersion. Pfade mit mehreren Spiegelreflexionen sind noch schlechter und führen zu Rauschen, das in den Bildern sichtbar ist.
Verbesserte Probenahme
Um unseren Pfad-Tracer praktisch zu machen, müssen wir das Paradigma ändern. Anstatt wertvolle Proben in Bereichen zu verschwenden, in denen sie unwichtig sind (da sie einen sehr niedrigen BRDF- und / oder Kosinuswert erhalten), lassen Sie uns
wichtige Proben generieren .
In einem ersten Schritt werden wir unsere idealen Überlegungen zurückgeben und dann sehen, wie diese Idee verallgemeinert werden kann. Dazu unterteilen wir die Schattierungslogik in diffuse und spiegelnde Reflexion. Für jede Stichprobe wählen wir zufällig die eine oder andere aus (abhängig vom Verhältnis
kd und
ks ) Im Falle einer diffusen Reflexion werden wir an homogenen Proben haften, aber für das Spiegelbild werden wir den Strahl explizit in die einzig wichtige Richtung reflektieren. Da jetzt weniger Proben für jede Art von Reflexion ausgegeben werden, müssen wir den Einfluss entsprechend erhöhen, um den gleichen Gesamtwert zu erhalten:
energy
ist eine kleine Hilfsfunktion, die Farbkanäle mittelt:
float energy(float3 color) { return dot(color, 1.0f / 3.0f); }
Also haben wir einen schöneren Whited Ray Tracer aus dem vorherigen Teil erstellt, aber jetzt mit wirklich diffuser Schattierung (was weiche Schatten, Umgebungsokklusion und diffuse globale Beleuchtung impliziert):
Wichtigkeitsbeispiel
Schauen wir uns noch einmal die grundlegende Monte-Carlo-Formel an:
FN approx frac1N sumNn=0 fracf(xn)p(xn)
Wie Sie sehen können, teilen wir den Einfluss jeder Stichprobe (Stichprobe) auf die Wahrscheinlichkeit der Auswahl dieser bestimmten Stichprobe. Bisher haben wir homogene Hemisphärenproben verwendet, also hatten wir eine Konstante
p( omega)= frac12 pi . Wie wir oben gesehen haben, ist dies beispielsweise beim Phong BRDF, der in einer sehr kleinen Anzahl von Richtungen groß ist, alles andere als optimal.
Stellen Sie sich vor, wir könnten eine Wahrscheinlichkeitsverteilung finden, die genau zur integrierbaren Funktion passt:
p(x)=f(x) . Dann passiert folgendes:
FN approx frac1N sumNn=01
Jetzt haben wir keine Proben, die sehr wenig dazu beitragen. Es ist weniger wahrscheinlich, dass diese Stichproben ausgewählt werden. Dies verringert die Varianz des Ergebnisses erheblich und beschleunigt die Konvergenz des Renderings.
In der Praxis ist es unmöglich, eine solche ideale Verteilung zu finden, da einige Teile der integrierbaren Funktion (in unserem Fall BRDF × Kosinus × einfallendes Licht) unbekannt sind (dies ist am offensichtlichsten für einfallendes Licht), aber die Verteilung der Proben nach BRDF × Kosinus oder sogar nur nach BRDF hilft uns. Dieses Prinzip nennt man Stichproben nach Wichtigkeit.
Kosinusprobe
In den folgenden Schritten müssen wir unsere homogene Verteilung der Proben durch die Verteilung gemäß der Kosinusregel ersetzen. Vergessen Sie nicht, anstatt homogene Proben mit Kosinus zu multiplizieren
und ihren Einfluss zu
verringern , möchten wir eine proportional
geringere Anzahl von Proben erzeugen.
Dieser Artikel von Thomas Poole beschreibt, wie das geht. Wir werden den
alpha
Parameter zu unserer
SampleHemisphere
Funktion
SampleHemisphere
. Die Funktion bestimmt den Index der Cosinusauswahl: 0 für eine einheitliche Stichprobe, 1 für die Cosinusauswahl oder höher für höhere Phong-Werte. Im Code sieht es so aus:
float3 SampleHemisphere(float3 normal, float alpha) {
Jetzt ist die Wahrscheinlichkeit jeder Probe gleich
p( omega)= frac alpha+12 pi( vec omega cdot vecn) alpha . Die Schönheit dieser Gleichung mag nicht sofort offensichtlich erscheinen, aber wenig später werden Sie sie verstehen.
Lambert-Probe nach Bedeutung
Für den Anfang werden wir das Rendering der diffusen Reflexion verfeinern. In unserer homogenen Verteilung wird bereits die Lambert-Konstante BRDF verwendet, die wir jedoch durch Hinzufügen des Kosinus verbessern können. Die Wahrscheinlichkeitsverteilung der Stichprobe nach Kosinus (wobei
alpha=1 ) ist gleich
frac( vec omegai cdot vecn) pi , was unsere Monte-Carlo-Formel für diffuse Reflexion vereinfacht:
L(x, vec omegao) approx frac1N sumNn=0 colorBlueVioletkdL(x, vec omegai)
Dies wird unsere diffuse Schattierung etwas beschleunigen. Kommen wir nun zum eigentlichen Thema.
Fongov-Probenahme nach Wichtigkeit
Für Phong BRDF ist das Verfahren ähnlich. Diesmal haben wir das Produkt zweier Kosinusse: den Standardkosinus aus der Rendering-Gleichung (wie im Fall der diffusen Reflexion), multipliziert mit dem BRDF-Eigenkosinus. Wir werden uns nur mit dem letzten befassen.
Fügen wir die Wahrscheinlichkeitsverteilung aus den obigen Beispielen in die Phong-Gleichung ein. Eine detaillierte Schlussfolgerung findet sich in
Lafortune und Willems: Verwendung des modifizierten Phong-Reflexionsmodells für physikalisch basiertes Rendering (1994) :
L(x, vec omegao) approx frac1N sumNn=0 colorbrownks frac alpha+2 alpha+1( vec omegai cdot vecn)L(x, vec omegai)
Diese Änderungen reichen aus, um Probleme mit der hohen Leistung in Phong zu beseitigen und unser Rendering in einer viel vernünftigeren Zeit zu konvergieren.
Material
Lassen Sie uns abschließend unsere Szenengenerierung erweitern, um sich ändernde Werte für die Glätte und das Emissionsvermögen der Kugeln zu schaffen!
public Vector3 emission
in
struct Sphere
aus einem C #
public Vector3 emission
public float smoothness
public Vector3 emission
und die
public Vector3 emission
. Da wir die Größe der Struktur geändert haben, müssen wir den Schritt beim Erstellen des Rechenpuffers ändern (4 × die Anzahl der Gleitkommazahlen, erinnerst du dich?).
SetUpScene
Sie die
SetUpScene
Funktion Werte für Glätte und
SetUpScene
einfügen.
struct RayHit
im Shader beide Variablen zu
struct Sphere
und
struct RayHit
und initialisieren Sie sie dann in
CreateRayHit
. Und schließlich legen Sie beide Werte in
IntersectGroundPlane
(fest codiert, fügen Sie alle Werte ein) und
IntersectSphere
(Abrufen von Werten aus
Sphere
) fest.
Ich möchte Glättungswerte auf die gleiche Weise wie im Standard-Unity-Shader verwenden, der sich von einem eher willkürlichen Fong-Exponenten unterscheidet. Hier ist eine gute Konvertierung, die in der
Shade
Funktion verwendet werden kann:
float SmoothnessToPhongAlpha(float s) { return pow(1000.0f, s * s); }
float alpha = SmoothnessToPhongAlpha(hit.smoothness);
Die Verwendung des Emissionsvermögens erfolgt durch Rückgabe eines Werts in
Shade
:
return hit.emission;
Ergebnisse
Atme tief ein. Entspannen Sie sich und warten Sie, bis das Bild zu einem so schönen Bild wird:
Glückwunsch! Sie haben es geschafft, durch das Dickicht mathematischer Ausdrücke zu kommen. Wir haben einen Pfad-Tracer implementiert, der diffuse und gespiegelte Schattierungen durchführt, die Stichproben nach Wichtigkeit kennenlernen und dieses Konzept sofort anwenden, sodass das Rendering in Minuten und nicht in Stunden oder Tagen konvergiert.
Im Vergleich zum vorherigen Artikel war dieser Artikel ein großer Schritt in Bezug auf die Komplexität, verbesserte aber auch die Qualität des Ergebnisses erheblich. Das Arbeiten mit mathematischen Berechnungen braucht Zeit, rechtfertigt sich jedoch, da es Ihr Verständnis für das Geschehen erheblich vertiefen kann und es Ihnen ermöglicht, den Algorithmus zu erweitern, ohne die physikalische Zuverlässigkeit zu beeinträchtigen.
Danke fürs Lesen! Im dritten Teil werden wir (für eine Weile) den Dschungel der Probenahme und Beschattung verlassen und in die Zivilisation zurückkehren, um die Herren Möller und Trumbor zu treffen. Wir müssen mit ihnen über Dreiecke sprechen.
Über den Autor: David Curie ist Entwickler von Three Eyed Games, Volkswagen Virtual Engineering Lab-Programmierer, Computergrafikforscher und Heavy-Metal-Musiker.