Virtuelle Texturierung ist das logische Extrem von Texturatlanten.
Ein Texturatlas ist eine einzelne riesige Textur, die Texturen für einzelne Netze enthält:
Texturatlanten wurden populär, weil das Ändern von Texturen ein vollständiges Pipeline-Flush auf der GPU verursacht. Beim Erstellen der Netze werden die UVs komprimiert / verschoben, sodass sie den korrekten „Anteil“ des gesamten Texturatlas darstellen.
Wie @ nathan-reed in den Kommentaren erwähnte, besteht einer der Hauptnachteile von Texturatlanten darin, die Umbruchmodi wie Wiederholen, Klemmen, Rahmen usw. zu verlieren. Wenn die Texturen nicht genügend Rahmen haben, können Sie dies versehentlich tun Probe aus einer angrenzenden Textur beim Filtern. Dies kann zu Blutungsartefakten führen.
Texturatlanten haben eine wesentliche Einschränkung: Größe. Grafik-APIs begrenzen die Größe einer Textur auf weiche Weise. Trotzdem ist der Grafikspeicher nur so groß. Es gibt also auch eine feste Grenze für die Texturgröße, die durch die Größe Ihres V-RAMs vorgegeben wird. Virtuelle Texturen lösen dieses Problem, indem sie Konzepte aus dem virtuellen Speicher ausleihen .
Virtuelle Texturen nutzen die Tatsache aus, dass Sie in den meisten Szenen nur einen kleinen Teil aller Texturen sehen. Es muss sich also nur diese Teilmenge der Texturen in vram befinden. Der Rest kann sich im Hauptspeicher oder auf der Festplatte befinden.
Es gibt einige Möglichkeiten, es umzusetzen, aber ich werde die von Sean Barrett in seinem GDC-Vortrag beschriebene Implementierung erläutern . (was ich sehr empfehlen zu sehen)
Wir haben drei Hauptelemente: die virtuelle Textur, die physische Textur und die Nachschlagetabelle.
Die virtuelle Textur repräsentiert den theoretischen Mega-Atlas, den wir hätten, wenn wir genug RAM hätten, um alles unterzubringen. Es existiert eigentlich nirgendwo im Speicher. Die physische Textur gibt an, über welche Pixeldaten wir tatsächlich im VRAM verfügen. Die Nachschlagetabelle ist die Zuordnung zwischen den beiden. Der Einfachheit halber teilen wir alle drei Elemente in gleich große Kacheln oder Seiten auf.
In der Nachschlagetabelle wird die Position der oberen linken Ecke der Kachel in der physischen Textur gespeichert. Wie erhalten wir also die entsprechende UV-Strahlung für die physische Textur, wenn wir der gesamten virtuellen Textur eine UV-Strahlung zuweisen?
Zuerst müssen wir den Ort der Seite innerhalb der physischen Textur finden. Dann müssen wir die Position des UV innerhalb der Seite berechnen. Schließlich können wir diese beiden Offsets addieren, um die Position des UV innerhalb der physikalischen Textur zu ermitteln
float2 pageLocInPhysicalTex = ...
float2 inPageLocation = ...
float2 physicalTexUV = pageLocationInPhysicalTex + inPageLocation;
Berechnung von pageLocInPhysicalTex
Wenn wir die Nachschlagetabelle so groß wie die Anzahl der Kacheln in der virtuellen Textur machen, können wir die Nachschlagetabelle nur mit dem nächsten Nachbarn abtasten und wir erhalten die Position der oberen linken Ecke der Seite innerhalb der physischen Textur.
float2 pageLocInPhysicalTex = lookupTable.Sample(virtTexUV, nearestNeighborSampler);
Berechnung von inPageLocation
inPageLocation ist eine UV-Koordinate, die relativ zur oberen linken Ecke der Seite und nicht zur oberen linken Ecke der gesamten Textur ist.
Eine Möglichkeit, dies zu berechnen, besteht darin, den UV-Wert oben links auf der Seite abzuziehen und dann auf die Größe der Seite zu skalieren. Dies ist jedoch ein ziemlicher Rechenaufwand. Stattdessen können wir ausnutzen, wie IEEE-Gleitkomma dargestellt wird. IEEE-Gleitkomma speichert den Bruchteil einer Zahl durch eine Reihe von Basis-2-Brüchen.
In diesem Beispiel lautet die Nummer:
number = 0 + (1/2) + (1/8) + (1/16) = 0.6875
Sehen wir uns nun eine vereinfachte Version der virtuellen Textur an:
Das 1/2 Bit sagt uns, ob wir uns in der linken Hälfte der Textur oder in der rechten befinden. Das 1/4-Bit gibt an, in welchem Viertel der Hälfte wir uns befinden. In diesem Beispiel geben die ersten beiden Bits an, auf welcher Seite wir uns befinden, da die Textur in 16 oder 4 Seiten aufgeteilt ist Bits teilen uns die Position innerhalb der Seite mit.
Die restlichen Bits erhalten wir, indem wir den float mit exp2 () verschieben und mit fract () entfernen.
float2 inPageLocation = virtTexUV * exp2(sqrt(numTiles));
inPageLocation = fract(inPageLocation);
Dabei ist numTiles ein int2, das die Anzahl der Kacheln pro Seite der Textur angibt. In unserem Beispiel wäre dies (4, 4)
Berechnen wir also die inPageLocation für den grünen Punkt (x, y) = (0.6875, 0.375)
inPageLocation = float2(0.6875, 0.375) * exp2(sqrt(int2(4, 4));
= float2(0.6875, 0.375) * int2(2, 2);
= float2(1.375, 0.75);
inPageLocation = fract(float2(1.375, 0.75));
= float2(0.375, 0.75);
Noch etwas, bevor wir fertig sind. Derzeit ist inPageLocation eine UV-Koordinate im virtuellen Texturraum. Wir wollen jedoch eine UV-Koordinate in der physischen Textur "Raum". Dazu müssen wir inPageLocation nur um das Verhältnis der virtuellen Texturgröße zur physischen Texturgröße skalieren
inPageLocation *= physicalTextureSize / virtualTextureSize;
Die fertige Funktion ist also:
float2 CalculatePhysicalTexUV(float2 virtTexUV, Texture2D<float2> lookupTable, uint2 physicalTexSize, uint2 virtualTexSize, uint2 numTiles) {
float2 pageLocInPhysicalTex = lookupTable.Sample(virtTexUV, nearestNeighborSampler);
float2 inPageLocation = virtTexUV * exp2(sqrt(numTiles));
inPageLocation = fract(inPageLocation);
inPageLocation *= physicalTexSize / virtualTexSize;
return pageLocInPhysicalTex + inPageLocation;
}