Rendern von Schriftarten mit Abdeckungsmasken, Teil 1

Bild

Als wir mit der Entwicklung unseres Leistungsprofilers begannen, wussten wir, dass wir fast das gesamte UI-Rendering selbst durchführen würden. Bald mussten wir uns entscheiden, welchen Ansatz wir zum Rendern von Schriftarten wählen sollten. Wir hatten folgende Anforderungen:

  1. Wir müssen in der Lage sein, jede Schriftart beliebiger Größe in Echtzeit zu rendern, um sie an die Systemschriftarten und deren Größe anzupassen, die von Windows-Benutzern ausgewählt wurden.
  2. Das Rendern von Schriftarten sollte sehr schnell sein. Beim Rendern von Schriftarten ist kein Bremsen zulässig.
  3. Unsere Benutzeroberfläche verfügt über eine Reihe flüssiger Animationen, sodass sich der Text reibungslos auf dem Bildschirm bewegen kann.
  4. Es sollte mit kleinen Schriftgrößen lesbar sein.

Da ich zu dieser Zeit kein großer Spezialist war, suchte ich im Internet nach Informationen und fand viele Techniken zum Rendern von Schriftarten. Ich habe auch mit dem technischen Direktor von Guerilla Games, Michail van der Leu, gesprochen. Dieses Unternehmen experimentierte mit vielen Möglichkeiten zum Rendern von Schriftarten, und die Rendering-Engine war eine der besten der Welt. Mihil skizzierte kurz seine Idee für eine neue Technik zum Rendern von Schriftarten. Obwohl wir genug von den bereits verfügbaren Techniken gehabt hätten, faszinierte mich diese Idee und ich begann sie umzusetzen, ohne auf die wunderbare Welt der Schriftwiedergabe zu achten, die sich mir öffnete.

In dieser Artikelserie werde ich die von uns verwendete Technik detailliert beschreiben und die Beschreibung in drei Teile unterteilen:

  • Im ersten Teil lernen wir, wie man Glyphen in Echtzeit mit 16xAA rendert, die aus einem einheitlichen Raster abgetastet wurden.
  • Im zweiten Teil werden wir zum gedrehten Gitter übergehen, um das Antialiasing horizontaler und vertikaler Kanten auf wunderbare Weise durchzuführen. Wir werden auch sehen, wie der fertige Shader fast vollständig auf eine Textur und eine Nachschlagetabelle reduziert wird.
  • Im dritten Teil lernen wir, wie Sie Glyphen mithilfe von Compute und CPU in Echtzeit rastern.

Sie können die fertigen Ergebnisse auch im Profiler anzeigen. Hier ist jedoch ein Beispiel für einen Bildschirm mit der mit unserem Font-Renderer gerenderten Segoe-UI-Schriftart:


Hier ist eine Erhöhung des Buchstabens S, einer gerasterten Größe von nur 6x9 Texel. Die ursprünglichen Vektordaten werden als Pfad gerendert, und das gedrehte Abtastmuster wird aus grünen und roten Rechtecken gerendert. Da es mit einer Auflösung von viel mehr als 6 × 9 gerendert wird, werden die Graustufen nicht im endgültigen Pixelton dargestellt, sondern es wird der Farbton des Subpixels angezeigt. Dies ist eine sehr nützliche Debugging-Visualisierung, um sicherzustellen, dass alle Berechnungen auf Subpixel-Ebene ordnungsgemäß funktionieren.


Idee: Beschichtung statt Schatten lagern


Das Hauptproblem, mit dem sich Schriftrenderer befassen müssen, ist die Anzeige skalierbarer Vektorschriftdaten in einem festen Pixelraster. Die Methode des Übergangs vom Vektorraum zu fertigen Pixeln in verschiedenen Techniken ist sehr unterschiedlich. Bei den meisten dieser Techniken werden Kurvendaten vor dem Rendern in einen temporären Speicher (z. B. eine Textur) gerastert, um eine bestimmte Größe in Pixel zu erhalten. Temporärer Speicher wird als Glyphen-Cache verwendet: Wenn dieselbe Glyphe mehrmals gerendert wird, werden Glyphen aus dem Cache entnommen und wiederverwendet, um eine erneute Rasterung zu vermeiden.

Der Unterschied in der Technik zeigt sich deutlich darin, wie Daten in einem Zwischendatenformat gespeichert werden. Das Windows-Schriftsystem rastert beispielsweise Glyphen auf eine bestimmte Größe in Pixel. Daten werden als Farbton pro Pixel gespeichert. Der Farbton beschreibt die beste Annäherung an die Abdeckung durch die Glyphe dieses Pixels. Beim Rendern werden die Pixel einfach aus dem Glyphencache in das Zielpixelraster kopiert. Wenn Sie Daten in ein Pixelformat konvertieren, werden sie nicht gut skaliert. Beim Verkleinern werden daher unscharfe Glyphen angezeigt, und beim Vergrößern werden Glyphen angezeigt, in denen Blöcke deutlich sichtbar sind. Daher werden die Glyphen für jede endgültige Größe in den Glyphencache gerendert.

Signierte Distanzfelder verwenden einen anderen Ansatz. Anstelle des Farbtons für das Pixel wird der Abstand zum nächsten Rand des Glyphen beibehalten. Der Vorteil dieser Methode besteht darin, dass Daten bei gekrümmten Kanten viel besser skaliert werden als Schattierungen. Beim Vergrößern der Glyphe bleiben die Kurven glatt. Der Nachteil dieses Ansatzes ist, dass gerade und scharfe Kanten geglättet werden. Viel besser als SDF werden fortschrittliche Lösungen wie FreeType erreicht , in denen Farbdaten gespeichert werden.

In Fällen, in denen ein Farbton für ein Pixel beibehalten wird, müssen Sie zuerst dessen Abdeckung berechnen. Zum Beispiel enthält stb_truetype gute Beispiele dafür, wie Sie die Abdeckung und den Farbton berechnen können. Eine andere beliebte Methode zur Annäherung an die Abdeckung besteht darin, die Glyphe mit einer höheren Frequenz als der endgültigen Auflösung abzutasten. Dies zählt die Anzahl der Abtastwerte, die in die Glyphe im Zielpixelbereich passen. Die Anzahl der Treffer geteilt durch die maximale Anzahl möglicher Samples bestimmt den Farbton. Da die Abdeckung bereits für eine bestimmte Pixelgitterauflösung und -ausrichtung in einen Farbton konvertiert wurde, ist es unmöglich, Glyphen zwischen den Zielpixeln zu platzieren: Der Farbton kann die tatsächliche Abdeckung mit Abtastwerten des Zielpixelfensters nicht korrekt wiedergeben. Aus diesem Grund und aus einigen anderen Gründen, die wir später betrachten werden, unterstützen solche Systeme keine Subpixel-Bewegung.

Aber was ist, wenn wir die Glyphe zwischen den Pixeln frei bewegen müssen? Wenn der Farbton im Voraus berechnet wird, können wir nicht herausfinden, wie der Farbton sein soll, wenn Sie sich zwischen Pixeln im Zielpixelbereich bewegen. Wir können jedoch die Konvertierung von der Abdeckung in den Farbton zum Zeitpunkt des Renderns verzögern. Dazu speichern wir nicht den Schatten, sondern die Beschichtung . Wir tasten eine Glyphe mit einer Frequenz von 16 Zielauflösungen ab und speichern für jede Probe ein einzelnes Bit. Bei der Abtastung in einem 4 × 4-Raster reicht es aus, nur 16 Bit pro Pixel zu speichern. Dies wird unsere Deckmaske sein . Während des Renderns müssen wir zählen, wie viele Bits in das Zielpixelfenster gelangen, das dieselbe Auflösung wie das Texel-Repository hat, aber nicht physisch daran gebunden ist. Die folgende Animation zeigt einen Teil des Glyphen (blau), der in vier Texel gerastert ist. Jedes Texel ist in ein Raster von 4 × 4 Zellen unterteilt. Ein graues Rechteck zeigt ein Pixelfenster an, das sich dynamisch über die Glyphe bewegt. Zur Laufzeit wird die Anzahl der Abtastwerte, die in das Pixelfenster fallen, gezählt, um den Farbton zu bestimmen.


Kurz zu den grundlegenden Techniken zum Rendern von Schriftarten


Bevor ich mit der Implementierung unseres Font-Rendering-Systems fortfahre, möchte ich kurz auf die wichtigsten Techniken eingehen, die in diesem Prozess verwendet werden: Hinweise auf Font und Subpixel-Rendering (diese Technik wird unter Windows als ClearType bezeichnet). Sie können diesen Abschnitt überspringen, wenn Sie nur an Antialiasing-Techniken interessiert sind.

Während der Implementierung des Renderers habe ich immer mehr über die lange Geschichte der Entwicklung des Font-Renderings erfahren. Die Forschung konzentriert sich ausschließlich auf den einzigen Aspekt der Schriftwiedergabe - die Lesbarkeit bei kleinen Größen. Das Erstellen eines hervorragenden Renderers für große Schriftarten ist recht einfach, aber es ist unglaublich schwierig, ein System zu schreiben, das die Lesbarkeit bei kleinen Größen gewährleistet. Das Studium der Schriftwiedergabe hat eine lange Geschichte, die in ihrer Tiefe auffällt. Lesen Sie zum Beispiel über die Raster-Tragödie . Es ist logisch, dass dies das Hauptproblem für Computerspezialisten war, da in den frühen Stadien von Computern die Bildschirmauflösung ziemlich niedrig war. Dies muss eine der ersten Aufgaben gewesen sein, mit denen sich Betriebssystementwickler befassen mussten: Wie kann Text auf Geräten mit niedriger Bildschirmauflösung lesbar gemacht werden? Zu meiner Überraschung sind hochwertige Schriftwiedergabesysteme sehr pixelorientiert. Beispielsweise wird eine Glyphe so konstruiert, dass sie am Rand des Pixels beginnt, ihre Breite ein Vielfaches der Anzahl der Pixel beträgt und der Inhalt an die Pixel angepasst wird. Diese Technik wird als Vernetzung bezeichnet. Ich bin es gewohnt, mit Computerspielen und 3D-Grafiken zu arbeiten, bei denen die Welt aus Einheiten besteht und in Pixel projiziert wird. Ich war also ein wenig überrascht. Ich fand heraus, dass dies im Bereich der Schriftwiedergabe eine sehr wichtige Wahl ist.

Schauen wir uns ein mögliches Szenario für die Glyphenrasterung an, um die Bedeutung der Vernetzung zu demonstrieren. Stellen Sie sich vor, eine Glyphe wird auf einem Pixelraster gerastert, aber die Form der Glyphe stimmt nicht perfekt mit der Rasterstruktur überein:


Durch Antialiasing werden die Pixel rechts und links von der Glyphe gleich grau. Wenn die Glyphe leicht verschoben wird, damit sie besser mit den Rändern der Pixel übereinstimmt, wird nur ein Pixel gefärbt und vollständig schwarz:


Jetzt, da die Glyphe gut mit den Pixeln übereinstimmt, sind die Farben weniger verschwommen. Der Unterschied in der Schärfe ist sehr groß. Westliche Schriftarten haben viele Glyphen mit horizontalen und vertikalen Linien. Wenn sie nicht gut mit dem Pixelraster übereinstimmen, verschwimmen die Graustufen durch die Graustufen. Selbst die beste Anti-Aliasing-Technik kann dieses Problem nicht bewältigen.

Als Lösung wurde ein Hinweis auf Schriftarten vorgeschlagen. Schriftautoren sollten ihren Schriftarten Informationen darüber hinzufügen, wie Glyphen an Pixeln ausgerichtet werden sollen, wenn sie nicht perfekt passen. Das Schriftwiedergabesystem verzerrt diese Kurven, um sie am Pixelraster auszurichten. Dies erhöht die Klarheit der Schriftart erheblich, hat jedoch einen Preis:

  • Schriftarten werden leicht verzerrt . Schriftarten sehen nicht genau wie beabsichtigt aus.
  • Alle Glyphen müssen an das Pixelraster angehängt werden: der Anfang der Glyphe und die Breite der Glyphe. Daher ist es unmöglich, sie zwischen Pixeln zu animieren.

Interessanterweise gingen Apple und Microsoft bei der Lösung dieses Problems unterschiedliche Wege. Microsoft hält an absoluter Klarheit fest, und Apple versucht, Schriftarten genauer anzuzeigen. Im Internet finden Sie Leute, die sich über verschwommene Schriftarten auf Apple-Computern beschweren, aber viele Leute mögen, was sie auf Apple sehen. Das ist teilweise Geschmackssache. Hier ist der Beitrag von Joel über Software und hier der Beitrag von Peter Bilak zu diesem Thema. Wenn Sie jedoch im Internet suchen, finden Sie viel mehr Informationen.

Da die DPI-Auflösung in modernen Bildschirmen rapide zunimmt, stellt sich die Frage, ob in Zukunft wie heute Hinweise auf Schriftarten erforderlich sind. In meinem aktuellen Zustand finde ich Schriftarten, die eine sehr wertvolle Technik zum klaren Rendern von Schriftarten darstellen. Die in meinem Artikel beschriebene Technik könnte jedoch in Zukunft eine interessante Alternative werden, da Glyphen ohne Verzerrung frei auf der Leinwand platziert werden können. Und da dies im Wesentlichen eine Anti-Aliasing-Technik ist, kann sie für jeden Zweck verwendet werden und nicht nur zum Rendern von Schriftarten.

Abschließend werde ich kurz auf das Rendern von Subpixeln eingehen . In der Vergangenheit wurde den Menschen klar, dass Sie die horizontale Auflösung des Bildschirms mithilfe der einzelnen roten, grünen und blauen Strahlen eines Computermonitors verdreifachen können. Jedes Pixel wird aus diesen Strahlen aufgebaut, die physikalisch getrennt sind. Unser Auge mischt ihre Werte und erzeugt eine einzelne Pixelfarbe. Wenn die Glyphe nur einen Teil des Pixels bedeckt, wird nur der Strahl eingeschaltet, der der Glyphe überlagert ist, wodurch sich die horizontale Auflösung verdreifacht. Wenn Sie das Bildschirmbild mit einer Technik wie ClearType vergrößern, können Sie die Farben an den Rändern des Glyphen sehen:


Interessanterweise kann der Ansatz, den ich in diesem Artikel diskutieren werde, auf das Rendern von Subpixeln erweitert werden. Ich habe den Prototyp bereits implementiert. Der einzige Nachteil ist, dass wir aufgrund der zusätzlichen Filterung in Techniken wie ClearType mehr Texturproben nehmen müssen. Vielleicht werde ich das in Zukunft in Betracht ziehen.

Glyphen-Rendering mit einem einheitlichen Raster


Angenommen, wir haben eine Glyphe mit einer 16-fachen Auflösung des Ziels abgetastet und in einer Textur gespeichert. Wie das geht, werde ich im dritten Teil des Artikels beschreiben. Ein Abtastmuster ist ein einheitliches Gitter, dh 16 Abtastpunkte sind gleichmäßig über das Texel verteilt. Jede Glyphe wird mit der gleichen Auflösung wie die Zielauflösung gerendert, wir speichern 16 Bits pro Texel und jedes Bit entspricht einem Sample. Wie wir bei der Berechnung der Abdeckungsmaske sehen werden, ist die Speicherreihenfolge der Proben wichtig. Im Allgemeinen sehen die Abtastpunkte und ihre Positionen für ein Texel folgendermaßen aus:


Texel bekommen


Wir werden das Pixelfenster um die in den Texeln gespeicherten Abdeckungsbits verschieben. Wir müssen die folgende Frage beantworten: Wie viele Samples werden in unser Pixelfenster gelangen? Es wird durch das folgende Bild veranschaulicht:


Hier sehen wir vier Texel, denen teilweise eine Glyphe überlagert ist. Ein Pixel (blau markiert) bedeckt einen Teil der Texel. Wir müssen bestimmen, wie viele Abtastwerte unser Pixelfenster kreuzt. Zuerst brauchen wir folgendes:

  • Berechnen Sie die relative Position des Pixelfensters im Vergleich zu 4 Texeln.
  • Holen Sie sich die Texel, mit denen sich unser Pixelfenster schneidet.

Unsere Implementierung basiert auf OpenGL, daher beginnt der Ursprung des Texturraums unten links. Beginnen wir mit der Berechnung der relativen Position des Pixelfensters. Die an den Pixel-Shader übergebene UV-Koordinate ist die UV-Koordinate der Pixelmitte. Unter der Annahme, dass UVs normalisiert sind, können wir UVs zunächst in Texelraum konvertieren, indem wir sie mit der Größe der Textur multiplizieren. Wenn Sie 0,5 von der Mitte des Pixels abziehen, erhalten Sie die untere linke Ecke des Pixelfensters. Durch Abrunden dieses Wertes berechnen wir die untere linke Position des linken unteren Texels. Das Bild zeigt ein Beispiel für diese drei Punkte im Texelraum:


Der Unterschied zwischen der unteren linken Ecke des Pixels und der unteren linken Ecke des Texelgitters ist die relative Position des Pixelfensters in normalisierten Koordinaten. In diesem Bild beträgt die Position des Pixelfensters [0,69, 0,37]. Im Code:

vec2 bottomLeftPixelPos = uv * size -0.5;
vec2 bottomLeftTexelPos = floor(bottomLeftPixelPos);
vec2 weigth = bottomLeftPixelPos - bottomLeftTexelPos;


Mit der Anweisung texturGather können vier Texel gleichzeitig abgerufen werden. Es ist nur in OpenGL 4.0 und höher verfügbar, sodass Sie stattdessen vier texelFetch ausführen können. Wenn wir nur die Textur-Gather-UV-Koordinaten übergeben, entsteht bei einer perfekten Übereinstimmung des Pixelfensters mit dem Texel ein Problem:


Hier sehen wir drei horizontale Texel mit einem Pixelfenster (blau dargestellt), das genau zum zentralen Texel passt. Das berechnete Gewicht liegt nahe bei 1,0, aber textureGather wählte stattdessen das mittlere und das rechte Texel. Der Grund dafür ist, dass die von textureGather durchgeführten Berechnungen geringfügig von der Berechnung des Gleitkommagewichts abweichen können. Der Unterschied bei der Abrundung von GPU-Berechnungen und Gleitkomma-Gewichtsberechnungen führt zu Störungen um die Pixelzentren.

Um dieses Problem zu lösen, müssen Sie sicherstellen, dass die Gewichtsberechnungen garantiert mit der Textur-Gather-Stichprobe übereinstimmen. Zu diesem Zweck werden niemals Pixelzentren abgetastet, sondern immer in der Mitte des 2 × 2-Texelgitters. Aus der berechneten und bereits abgerundeten unteren Position des linken Texels fügen wir das vollständige Texel hinzu, um in die Mitte des Texelgitters zu gelangen.


Dieses Bild zeigt, dass bei Verwendung der Mitte des Texelgitters die vier von textureGather aufgenommenen Abtastpunkte immer in der Mitte der Texel liegen. Im Code:

vec2 centerTexelPos = (bottomLeftTexelPos + vec2(1.0, 1.0)) / size;
uvec4 result = textureGather(fontSampler, centerTexelPos, 0);


Horizontale Maske des Pixelfensters


Wir haben vier Texel und zusammen bilden sie ein Raster von 8 × 8 Abdeckungsbits. Um die Bits in einem Pixelfenster zu zählen, müssen wir zuerst die Bits außerhalb des Pixelfensters zurücksetzen. Dazu erstellen wir eine Pixelfenstermaske und führen ein bitweises UND zwischen der Pixelmaske und den Texelabdeckungsmasken durch. Horizontale und vertikale Maskierung werden getrennt durchgeführt.

Die horizontale Pixelmaske sollte sich zusammen mit dem horizontalen Gewicht bewegen, wie in dieser Animation gezeigt:


Das Bild zeigt eine 8-Bit-Maske mit dem Wert 0x0F0 nach rechts verschoben (Nullen werden links eingefügt). In der Animation wird eine Maske linear mit dem Gewicht animiert, in der Realität ist eine Bitverschiebung jedoch eine schrittweise Operation. Die Maske ändert ihren Wert, wenn das Pixelfenster den Rand des Samples überschreitet. In der nächsten Animation wird dies in roten und grünen Spalten angezeigt, die Schritt für Schritt animiert werden. Der Wert ändert sich nur, wenn sich die Zentren der Stichproben schneiden:


Damit sich die Maske nur in der Mitte der Zelle, aber nicht an ihren Rändern bewegt, reicht eine einfache Rundung aus:

unsigned int pixelMask = 0x0F0 >> int(round(weight.x * 4.0));

Jetzt haben wir eine Pixelmaske aus einer vollständigen 8-Bit-Zeichenfolge, die zwei Texel umfasst. Wenn wir den richtigen Speichertyp in unserer 16-Bit-Abdeckungsmaske auswählen, gibt es Möglichkeiten, linkes und rechtes Texel zu kombinieren und eine horizontale Pixelmaskierung für jeweils eine vollständige 8-Bit-Zeile durchzuführen. Dies wird jedoch bei der vertikalen Maskierung problematisch, wenn wir zu gedrehten Gittern wechseln. Daher kombinieren wir stattdessen zwei linke und zwei rechte Texel miteinander, um zwei 32-Bit-Abdeckungsmasken zu erstellen. Wir maskieren die linken und rechten Ergebnisse getrennt.

Masken für linke Texel verwenden die oberen 4 Bits der Pixelmaske, und Masken für rechte Texel verwenden die unteren 4 Bits. In einem einheitlichen Raster hat jede Zeile dieselbe horizontale Maske, sodass wir die Maske für jede Zeile einfach kopieren können. Danach ist die horizontale Maske fertig:

unsigned int leftRowMask = pixelMask >> 4;
unsigned int rightRowMask = pixelMask & 0xF;
unsigned int leftMask = (leftRowMask << 12) | (leftRowMask << 8) | (leftRowMask << 4) | leftRowMask;
unsigned int rightMask = (rightRowMask << 12) | (rightRowMask << 8) | (rightRowMask << 4) | rightRowMask;


Zum Maskieren kombinieren wir zwei linke und zwei rechte Texel und maskieren dann die horizontalen Linien:

unsigned int left = ((topLeft & leftMask) << 16) | (bottomLeft & leftMask);
unsigned int right = ((topRight & rightMask) << 16) | (bottomRight & rightMask);


Das Ergebnis könnte nun so aussehen:


Wir können die Bits dieses Ergebnisses bereits mit der Anweisung bitCount zählen. Wir sollten nicht durch 16, sondern durch 32 teilen, da wir nach der vertikalen Maskierung immer noch 32 potenzielle Bits haben können und nicht 16. Hier ist das vollständige Rendern des Glyphen in diesem Stadium:


Hier sehen wir einen vergrößerten Buchstaben S, der basierend auf den ursprünglichen Vektordaten (weißer Umriss) und der Visualisierung der Abtastpunkte gerendert wurde. Wenn der Punkt grün ist, befindet er sich innerhalb der Glyphe, wenn rot, dann nicht. Graustufen zeigt die zu diesem Zeitpunkt berechneten Farbtöne an. Beim Rendern von Schriftarten gibt es viele Möglichkeiten für Fehler, von der Rasterung über die Speicherung von Daten in einem Texturatlas bis hin zur Berechnung des endgültigen Farbtons. Solche Visualisierungen sind unglaublich nützlich für die Validierung von Berechnungen. Sie sind besonders wichtig für das Debuggen von Artefakten auf Subpixel-Ebene.

Vertikale Maskierung


Jetzt sind wir bereit, die vertikalen Bits zu maskieren. Um vertikal zu maskieren, verwenden wir eine etwas andere Methode. Um mit der vertikalen Verschiebung fertig zu werden, ist es wichtig, sich daran zu erinnern, wie wir die Bits gespeichert haben: in zeilenweiser Reihenfolge. Die unterste Zeile sind die vier niedrigstwertigen Bits, und die oberste Zeile sind die vier höchstwertigen Bits. Wir können einfach eins nach dem anderen reinigen und sie basierend auf der vertikalen Position des Pixelfensters verschieben.

Wir werden eine einzige Maske erstellen, die die gesamte Höhe von zwei Texeln abdeckt. Infolgedessen möchten wir vier vollständige Zeilen Texel speichern und den Rest maskieren, dh die Maske besteht aus 4 × 4 Bits, was 0xFFFF entspricht. Basierend auf der Position des Pixelfensters verschieben wir die unteren Linien und löschen die oberen Linien.

int shiftDown = int(round(weightY * 4.0)) * 4;
left = (left >> shiftDown) & 0xFFFF;
right = (right >> shiftDown) & 0xFFFF;


Infolgedessen haben wir auch die vertikalen Bits außerhalb des Pixelfensters maskiert:


Jetzt reicht es aus, die in den Texeln verbleibenden Bits zu zählen, was mit der bitCount-Operation möglich ist. Teilen Sie dann das Ergebnis durch 16 und erhalten Sie den gewünschten Farbton!

float shade = (bitCount(left) + bitCount(right)) / 16.0;

Jetzt sieht das vollständige Rendern des Briefes folgendermaßen aus:


Fortsetzung folgt ...


Im zweiten Teil werden wir den nächsten Schritt machen und sehen, wie Sie diese Technik auf gedrehte Gitter anwenden können. Wir werden dieses Schema berechnen:


Und wir werden sehen, dass fast alles auf mehrere Tabellen reduziert werden kann.

Vielen Dank an Sebastian Aaltonen ( @SebAaltonen ) für seine Hilfe bei der Lösung des TexturGather-Problems und natürlich an Michael van der Leu ( @MvdleeuwGG ) für seine Ideen und interessanten Gespräche am Abend.

Source: https://habr.com/ru/post/de433670/


All Articles