Troika: Textgliederung

Erstellt am 20. Aug. 2020  ·  39Kommentare  ·  Quelle: protectwise/troika

Ich versuche herauszufinden, wie man einen schwarzen Textumriss um weißen Text herum hat, damit der Text leicht gelesen werden kann. Ich bin mir nicht sicher, aber es hört sich so an, als müsste ich einen Shader für Troika-Three-Text verwenden? Können Sie eine Anleitung geben, wie Sie diesen Effekt erzielen können?

Danke!

Hilfreichster Kommentar

Danke für die Info zu Mapbox, @anzorb. Ich werde mir ihre Implementierung auf jeden Fall genauer ansehen, aber aus diesem Blogbeitrag (sie nennen die Gliederung einen "Heiligenschein") klingt es so, als würden sie den SDF-Alpha-Schwellenwertansatz verwenden, wie ich zuvor erwähnt habe. Aber ihre SDF-Generierungs- und Atlas-Packstrategie unterscheidet sich von meiner und hat nicht die gleichen Probleme mit ungleichmäßigen Abstandsfeldgrößen pro Glyphe. Es hätte jedoch wahrscheinlich die gleiche Begrenzung der maximalen Umrissbreite.

Ich habe tatsächlich einige Fortschritte bei _screen space_ Umrissen gemacht, indem ich Standardderivate verwendet habe. Bildschirmabstand bedeutet, dass der Umriss unabhängig von der Größe/Maßstab oder Neigung des Textes zB 1 Bildschirmpixel breit wäre. Wenn das Ziel nur darin besteht, Kontrast zum Hintergrund hinzuzufügen, scheint dies so gut wie (oder sogar vorzuziehen) zu sein wie Umrisse, die mit der Textgröße skalieren. Hast du dazu eine Meinung?

Alle 39 Kommentare

Ich habe fast das Gefühl, dass das, was ich brauche, vielleicht durch das debugSDF-Flag erreicht wird, wenn ich es nur einfärben könnte.
image
Das Anzeigen der SDF-Datei gibt mir im Grunde einen Umriss, der wahrscheinlich funktionieren würde, wenn ich ihn einfärben, die Größe anpassen und möglicherweise etwas schärfer machen könnte.

Dies sollte nicht zu schwer sein. Ich habe vor einiger Zeit angefangen, mit textOutline als Feature zu spielen, habe es aber nie beendet.

Theoretisch können die Glyphenkanten erweitert werden, indem einfach der Wert im vorzeichenbehafteten Abstandsfeld geändert wird, das der Kante entspricht. Das ist 0.5 im aktuellen Shader-Code , aber das könnte konfigurierbar gemacht werden, indem es als Uniform übergeben wird.

Idealerweise könnte der Shader sowohl die normale Glyphe als auch alle Umrisse um sie herum in einem einzigen Draw-Aufruf zeichnen, also bräuchten Sie ein paar neue Uniformen: die Umrissbreite (nicht sicher, in welchen Einheiten das sein würde?) und die Farbe von den Umrissbereich.

Ich habe vor einiger Zeit angefangen, mit textOutline als Feature zu spielen, habe es aber nie beendet.

Ich würde mich sehr freuen, wenn es nativ unterstützt würde :)
Ich bin jedoch sehr neu bei Shadern, daher bin ich mir nicht sicher, wie viel Hilfe ich sein kann. Ich werde dieses Thema genau beobachten.

Ich habe mich ein wenig damit beschäftigt und schnell begriffen, warum ich es ursprünglich nicht beendet hatte. Während die SDF _kann_ für die Gliederung verwendet werden kann, gibt es einige Vorbehalte:

  • Die maximal mögliche Breite des Umrisses wird durch die Ausdehnung des Distanzfeldes selbst begrenzt (~8 Texel in der 64x64-SDF-Textur).
  • Das entspricht nicht einer konsistenten visuellen Größe über alle Glyphen hinweg, da die SDF-Textur auf die Grenzen jeder Glyphe skaliert wird.

Größere Glyphen wie "W" können also einen anständig dicken Umriss bekommen, aber kleine wie Punkte können nur einen sehr dünnen Umriss bekommen. Und jede Glyphe muss einen anderen SDF-Cutoff-Punkt verwenden, um ihre Umrisse visuell konsistent zu machen.

Ich glaube nicht, dass das noch unüberwindbar ist, aber es ist nicht so einfach, wie ich anfangs dachte.

@lojjic , ich liebe diese Bibliothek 🥇, aber die Textgliederung ist eine Voraussetzung für unser Projekt ...

Ich habe mich ein wenig damit beschäftigt und schnell begriffen, warum ich es ursprünglich nicht beendet hatte. Während die SDF _kann_ für die Gliederung verwendet werden kann, gibt es einige Vorbehalte:

  • Die maximal mögliche Breite des Umrisses wird durch die Ausdehnung des Distanzfeldes selbst begrenzt (~8 Texel in der 64x64-SDF-Textur).
  • Das entspricht nicht einer konsistenten visuellen Größe über alle Glyphen hinweg, da die SDF-Textur auf die Grenzen jeder Glyphe skaliert wird.

Größere Glyphen wie "W" können also einen anständig dicken Umriss bekommen, aber kleine wie Punkte können nur einen sehr dünnen Umriss bekommen. Und jede Glyphe muss einen anderen SDF-Cutoff-Punkt verwenden, um ihre Umrisse visuell konsistent zu machen.

Ich glaube nicht, dass das noch unüberwindbar ist, aber es ist nicht so einfach, wie ich anfangs dachte.

Ich bin neugierig, können wir eine Art Nachbearbeitung mit Shadern durchführen, um einen Umriss und/oder Schatteneffekte zu erzielen? Vielen Dank im Voraus!
Hier ist der Effekt, den ich versuche zu erreichen:
Screen Shot 2020-08-31 at 4 32 03 PM

Bearbeiten: Dies ist von Mapbox gl, ich denke, sie verwenden auch SDF https://blog.mapbox.com/drawing-text-with-signed-distance-fields-in-mapbox-gl-b0933af6f817 , vielleicht können wir das analysieren ihren Code, um zu sehen, was sie getan haben. https://github.com/mapbox/mapbox-gl-js

Danke für die Info zu Mapbox, @anzorb. Ich werde mir ihre Implementierung auf jeden Fall genauer ansehen, aber aus diesem Blogbeitrag (sie nennen die Gliederung einen "Heiligenschein") klingt es so, als würden sie den SDF-Alpha-Schwellenwertansatz verwenden, wie ich zuvor erwähnt habe. Aber ihre SDF-Generierungs- und Atlas-Packstrategie unterscheidet sich von meiner und hat nicht die gleichen Probleme mit ungleichmäßigen Abstandsfeldgrößen pro Glyphe. Es hätte jedoch wahrscheinlich die gleiche Begrenzung der maximalen Umrissbreite.

Ich habe tatsächlich einige Fortschritte bei _screen space_ Umrissen gemacht, indem ich Standardderivate verwendet habe. Bildschirmabstand bedeutet, dass der Umriss unabhängig von der Größe/Maßstab oder Neigung des Textes zB 1 Bildschirmpixel breit wäre. Wenn das Ziel nur darin besteht, Kontrast zum Hintergrund hinzuzufügen, scheint dies so gut wie (oder sogar vorzuziehen) zu sein wie Umrisse, die mit der Textgröße skalieren. Hast du dazu eine Meinung?

Standardisierte Breiten sind weitaus bevorzugter als Skalierungsbreiten. Es würde sich auch auf diese Weise eher wie CSS verhalten.
Jedes text-shadow , das über CSS angewendet wird, wird unabhängig von font-size konfiguriert.
Wenn Ihre vorgeschlagene Lösung @lojjic genauso funktioniert, dann wäre das 🎉 🎉 🎉

Danke für deine prompte Antwort @lojjic!

Das Ziel ist es, Kontrast hinzuzufügen, und Ihr Vorschlag sieht großartig aus. Meinst du, die Gliederung wird statisch sein? dh keine Möglichkeit, es zu kontrollieren (wie @stephencorwin beschrieben hat, mit Text-Shadow-ish-Ansatz).

Ehrlich gesagt sehe ich das nicht als Problem an, solange das 1-Pixel auf Displays mit superhoher Dichte sichtbar ist (weil wir derzeit die Schriftgröße mit dem Pixelverhältnis des Geräts multiplizieren, um unabhängig von der Dichte die "gleichen" Proportionen zu erreichen). , ich denke, Ihr Vorschlag bedeutet, dass der Umriss auf Geräten mit höherer DPI sichtbar dünner wird, oder?

Vielen Dank im Voraus!

Ich denke, Ihr Vorschlag bedeutet, dass der Umriss auf Geräten mit höherer DPI sichtbar dünner wird, oder?

Ja, das ist ein Nachteil des Screen-Space-Ansatzes. Das mag für viele Szenarien in Ordnung sein, aber es würde mich als Designer stören, geräteübergreifend ein anderes Aussehen zu haben.

Der andere große Nachteil ist (glaube ich), dass eine Erhöhung der Halo-Dicke die Leistung beeinträchtigen würde, da mehr Texturlesevorgänge erforderlich sind. Das muss ich aber weiter recherchieren.

Auch ich brauche diese Funktion. Ich habe eine solche Funktion in einer Android-App implementiert, die ich vor Ewigkeiten entwickelt und nur für mich und Freunde verwendet habe. Ich habe begonnen, diese App mit React neu zu implementieren, und bin auf Ihr Projekt gestoßen.
Wie andere möchte ich Ihnen für diese Bibliothek danken. Ich bin erstaunt darüber.

In Bezug auf Ihren Kommentar zu mehr Texturlesevorgängen sollten Sie sich meiner Meinung nach nicht so viele Sorgen machen. Die zusätzlichen Texel, die Sie benötigen, sind die Texel, die benachbarte Fragmente benötigen, und daher denke ich, dass sie sowieso im Cache der GPU sein werden und fast nichts kosten werden.

Danke für den Beitrag @FunMiles. (Colorado repräsentiert! 😄)

Ich denke, Sie sind wahrscheinlich richtig für Umrisse von 1, vielleicht 2 Fragmenten dick. Meine Sorge ist, dass mit zunehmender Dicke sowohl die Anzahl der Textur-Reads (Dicke von 4 = 56 Reads?) als auch die Wahrscheinlichkeit, dass diese Reads teurer werden, explodiert. Aber ich spekuliere nur, basierend auf dem Lesen einiger Dinge, ich bin keineswegs ein Experte für GPUs. Muss ich wohl einfach ausprobieren.

@lojjic (nördlich von dir hier 😄 ) Ich habe versucht, den Code für den Fragment-Shader zu finden. Ich gebe zu, es fällt mir schwer, Dinge herauszufinden, da es scheint (und ich glaube, ich habe es irgendwo gelesen), dass Sie den Shader modifizieren, anstatt ihn vollständig zu schreiben.
Obwohl ich nicht herausfinden kann, wie der vollständige Shader aussieht und wie Sie ihn wirklich einstecken, denke ich, dass ich etwas von der Kernroutine verstehe ...

Im Moment lesen Sie einen skalaren Abstand für das Pixel mit texture2D(uTroikaSDFTexture, vTroikaSDFTextureUV).r; . Wenn ich verstehe, was Sie zuvor geschrieben haben, hat ein Problem mit dem Raum um die Glyphen zu tun. Können Sie das näher erläutern?
Um auf Ihr Beispiel von „W“ und „,“ zurückzukommen, passt die vTroikaSDFTextureUV-Spanne in der Textur genau genau um die Glyphe oder gibt es zusätzlichen Platz? Kennt der Fragment-Shader die Grenzen des UV für die aktuelle Glyphe? Wenn dies der Fall ist, können Sie für ein Fragment, das nach außen fallen würde, das aktuelle UV auf die Grenzen projizieren und die Daten direkt im Inneren lesen.

Ich denke, es ist möglich, mit höchstens 5 Texel-Lesevorgängen oder mit der Verwendung von Derivaten herauszufinden, was benötigt wird (ich sehe, Sie prüfen, ob sie verfügbar sind).

Um auf Ihr Beispiel von „W“ und „,“ zurückzukommen, passt die vTroikaSDFTextureUV-Spanne in der Textur genau genau um die Glyphe oder gibt es zusätzlichen Platz?

Um die wahren Pfadgrenzen jeder Glyphe herum ist ein wenig zusätzlicher Platz vorhanden, gerade genug, um die äußeren Teile des Entfernungsfelds aufzunehmen. Das sind 8 Texel in der SDF, aber da jede SDF eine einheitliche Größe von 64 x 64 ist, die auf ein Glyphenquad unterschiedlicher Größe skaliert ist, variiert dieser sichtbare Rand von Glyphe zu Glyphe.

Kennt der Fragment-Shader die Grenzen des UV für die aktuelle Glyphe? Wenn dies der Fall ist, können Sie für ein Fragment, das nach außen fallen würde, das aktuelle UV auf die Grenzen projizieren und die Daten direkt im Inneren lesen.

Diese Informationen sollten verfügbar sein. Ich folge Ihnen jedoch nicht bei "Projizieren Sie das aktuelle UV an die Grenzen und lesen Sie die Daten direkt darin".

Ich wäre Ihnen dankbar, wenn Sie einen Weg kennen, mit weniger Texel-Lesevorgängen dickere Umrisse zu erhalten! Ich habe es so konzipiert: Führen Sie für ein Fragment außerhalb des Glyphenpfads eine radiale Suche durch, um festzustellen, ob es Fragmente innerhalb von r= gibtwürde in den Glyphenpfad fallen. Das ist vielleicht einfach der falsche Weg, darüber nachzudenken.

Ein Problem ist, was passiert, wenn Glyphen nebeneinander angeordnet werden. Lassen wir das erstmal beiseite und nehmen wir den Fall an, dass ein einzelnes Zeichen mit einem ziemlich dicken Umriss t gezeichnet wird. Das Zeichen wird mit einem Rechteck gezeichnet, das groß genug ist, um den Umriss aufzunehmen.
Der Fragment-Shader hat zwei Hauptinformationen: vTroikaSDFTextureUV und vTroikaGlyphUV .

Mein Verständnis ist, dass vTroikaGlyphUV Werte zwischen 0 und 1 hat, wenn es sich innerhalb der Glyphe befindet. Was ich meinte, als ich über Projektion sprach, war, dass Sie, wenn vTroikaGlyphUV außerhalb dieser Grenzen liegt, die Entfernung des auf die Glyphengrenze "projizierten" Punktes abrufen können. Das heißt, wenn uv = (1,05, 0,7), dann ist der projizierte Punkt (1,0, 0,7). Durch die Verwendung von Werten bei (1,0, 0,7+epsilon) und (1,0, 0,7-epsilon) können Sie einen Gradienten zur Entfernung abrufen und damit die ungefähre Entfernung bei (1,05, 0,7) berechnen.
Als ich meine eigene Arbeit dafür gemacht habe, erinnere ich mich, dass ich einen Artikel von jemandem gelesen habe, der SDF auch mit einem Farbverlauf in der Textur gemacht hat. Dieser Ansatz vermeidet das Lesen von drei Texeln, macht die Textur jedoch zu einer 3-Komponenten-Textur und erhöht die Komplexität beim Aufbau der Textur.

Beantwortet das deine Frage?

Wenn Sie nun mehrere Charaktere nebeneinander haben, muss sich jeder der Nachbarn bewusst sein, was dies etwas komplexer macht. Sie müssen GlyphUV und TextureUV nicht nur für den aktuellen Charakter, sondern auch für den Nachbarn tragen. Dazu kann es erforderlich sein, jedes Zeichen in zwei Rechtecke zu schneiden, damit der Nachbar für das erste Rechteck der vorherige und für das zweite Rechteck der nachfolgende sein kann. Ich bin mir nicht sicher, wie ich mit Mehrzeilen umgehen soll.... Ich musste mich in meiner Bewerbung nicht mit diesem Fall befassen. Meine Anwendung war eine Karte und jedes Etikett war einzeilig.

PS: Ich habe möglicherweise Ihren "zusätzlichen Abstand um die wahren Pfadgrenzen jeder Glyphe" falsch verstanden. Wollen Sie sagen, dass zum Beispiel ein X in ein Rechteck eingeschlossen ist und dass es vier Dreiecke gibt, die keine gültigen Werte haben?

@FunMiles Ich _denke_ vielleicht sehe ich, wohin du gehst. Ich werde etwas Zeit brauchen, um darüber nachzudenken, aber ich muss mich im Moment auf andere Deadline-Arbeiten konzentrieren.

PS: Ein bisschen lustige Mathe-Anmerkung:
Nur für den Fall, dass sich jemand fragt, wie die Steigung aus zwei oder drei ausgerichteten Punkten erhalten werden kann, sei daran erinnert, dass die Steigung der Entfernung eine feste Norm hat (abhängig vom gewählten Maßstab). Und der Farbverlauf zeigt definitiv zur Außenseite der Glyphenbox. Wenn also zum Beispiel alle 3 Punkte des Beispiels (1.0, 0.7), (1.0, 0.7+epsilon) und (1.0, 0.7-epsilon) den gleichen Abstand haben, dann ist die Steigung (scale, 0).

Obwohl es definitiv nicht performant ist, ist es möglich, Offsets zu verwenden. Ich verwende react-three-fiber , das Troika-Text mit dem Paket drei nutzt, dachte aber, ich würde es teilen, falls andere nach einer Lösung als Notlösung suchen, bis die Bibliothek nativ Stroke unterstützt:

import React from 'react';
import {Text} from 'drei';

const StrokedText: React.FC<
  {
    strokeWidth?: number;
    strokeColor?: string;
    strokeResolution?: number;
    bold?: boolean;
  } & any
> = ({
  strokeWidth = 1,
  strokeColor: color = '#000000',
  strokeResolution = 100,
  position,
  bold,
  ...props
}) => {
  const font = bold ? FONTS.BOLD : undefined;
  const sharedProps = {
    ...props,
    font,
    color,
    sdfGlyphSize: 12,
    debugSDF: true,
  };

  let zOffset = 0;
  const offset = () => (zOffset += 0.001);

  return (
    <group name="Stroked Text" position={position}>
      {Array(strokeWidth)
        .fill({})
        .map((_, i) => {
          const s= i / strokeResolution;
          return (
            <React.Fragment key={i}>
              {/* <Text {...sharedProps} position={[-s, 0, offset()]} />*/}
              {/* <Text {...sharedProps} position={[s, 0, offset()]} />*/}
              <Text {...sharedProps} position={[0, -s, offset()]} />
              <Text {...sharedProps} position={[0, s, offset()]} />
              <Text {...sharedProps} position={[-s, -s, offset()]} />
              <Text {...sharedProps} position={[s, s, offset()]} />
              {/* <Text {...sharedProps} position={[-s, s, offset()]} /> */}
              {/* <Text {...sharedProps} position={[s, -s, offset()]} /> */}
            </React.Fragment>
          );
        })}
      <Text name="Text" {...props} position={[0, 0, strokeWidth ? offset() : 0]} />
    </group>
  );
};

export default StrokedText;

An anderer Stelle kann ich es dann mit <StrokedText /> anstelle von <Text /> normal aufrufen:

<StrokedText
  color="#ffffff"
  fontSize={fontSize}
  clipRect={[-0.5, -0.15, 0.5, 0.15]}
  textAlign="center"
  position={[0, 0, 0.01]}
>
  {text}
</StrokedText>

image

@FunMiles Ich hatte endlich Zeit, Ihre Vorschläge zu analysieren. Ich denke, das wäre sinnvoll, vorausgesetzt, das gesamte SDF-Rechteck der Glyphe würde nützliche (> 0,0) Entfernungswerte enthalten. Das ist derzeit nicht der Fall; Ich denke, Sie haben es in Ihrem Follow-up zu "x" erkannt, wo die SDF gut innerhalb der Grenzen des Quads auf Null abfällt, sodass es erhebliche Bereiche gibt, in denen es keinen nützlichen Entfernungsgradienten gibt, von dem aus extrapoliert werden kann:

image

Vielleicht kann ich den SDF-Generator ändern, um einen Gradienten ungleich Null über das gesamte Quad sicherzustellen ...? Lautes Denken könnte Konnotationen für Entfernungspräzision haben und Artefakte zwischen Zeichen innerhalb der Atlastextur einführen ... 🤔

Vielleicht kann ich den SDF-Generator ändern, um einen Gradienten ungleich Null über das gesamte Quad sicherzustellen

Ich habe das versucht, und ja, es ermöglicht die Extrapolation des Entfernungsfelds über die Quad-Grenzen hinaus, aber wie ich befürchtet habe, verringert es die Qualität der Glyphe selbst erheblich. Das ist bei nur einem 8-Bit-Gradienten zu erwarten; das Verteilen über eine größere Entfernung führt zu einer geringeren Genauigkeit bei jedem Texel. :(

Vielleicht könnte ich ein separates SDF nur für die Umrisse generieren – ein Feld mit höherer Streuung, aber geringerer Genauigkeit –, das in einen zweiten Texturkanal codiert ist. Es würde die Texturgröße verdoppeln, sollte aber nicht wesentlich langsamer sein. 🤔

in einen zweiten Texturkanal codiert.

@lojjic Das klingt sehr vernünftig und ahmt im Grunde meine derzeitige Problemumgehung nach. Außerdem kann es optional sein, sodass es keine Leistungseinbußen gibt, wenn der Benutzer nicht nach Umrissen fragt.

@lojjic Der von @stephencorwin vorgeschlagene Opt-in-Ansatz würde sicherstellen, dass niemand mehr zahlt, als er zu zahlen bereit ist.

Zurück zum technischen Aspekt. Nehmen wir das X-Bild in Ihrer Antwort. Verstehe ich richtig, dass es für alle Glyphen 64 x 64 Pixel sind? Wenn Sie mit 8 Bit den gesamten Entfernungsraum codieren müssen, bedeutet dies, dass Sie eine Genauigkeit von 2 _fractional_ Bits haben. Ich vermute, Sie behaupten, dass diese Genauigkeit nicht ausreicht.

Es gibt vielleicht einen Trick, um mit sehr geringen Kosten an Genauigkeit zu gewinnen. Anstatt ein Raster von 64x64 mit 8 Bit zu erstellen, komprimieren Sie die Daten von 2x2-Blöcken in 32-Bit-Daten. Ein klares Merkmal des vorzeichenbehafteten Abstands zwischen zwei Pixeln ist, dass er immer innerhalb von [-1,1] rechts und links, oben und unten und [-sqrt(2), sqrt(2)] in der Diagonalen liegt. Stellen Sie sich also vor, Sie verwenden 12 Bits für die SD der Mitte des 2x2-Blocks. Sie hätten 5 Bits für jede Mitte der Pixel, um die Differenz zwischen ihrer Mitte und der Mitte des 2x2-Blocks zu codieren. Diese 5 Bits repräsentieren höchstens sqrt(2)/2 Distanz. Effektiv haben Sie eine Genauigkeit von 5,5 Bit.
Die Mittelpunkt-12-Bits codieren mindestens 6 Bits Genauigkeit über eine Entfernung von maximal 64. Wenn die Box nur einen Punkt an einer Ecke hätte, müssten die 12 Bits technisch gesehen in der Lage sein, bis zu sqrt(2)*64 zu codieren, aber Ich glaube nicht, dass das tatsächlich möglich ist, da die Box vermutlich einigermaßen um jede Glyphe zentriert ist. Je weiter von einer Glyphenkante entfernt und je weniger Informationen die Deltas kodieren müssen (wenn Sie weit von einer Glyphenkante entfernt sind, wird das Gradientenfeld in einer Nachbarschaft fast gleichmäßig), so dass es aus Sicht der Informationstheorie gleichmäßig ist möglich, die Codierung zu verbessern
mehr Bits an tatsächlichen Informationen zu haben .... Aber ich würde nicht mit einer solchen zusätzlichen Optimierung beginnen.

Eine interessante Folge dieses Ansatzes ist, dass man die Texturierungshardware keine Interpolation durchführen lassen würde. Aber auf der anderen Seite würde man Gradienten kostenlos bekommen.

Wenn Sie Hilfe benötigen, könnte ich die Codierung/Decodierung von all dem implementieren.

PS: Ich erinnere mich, dass ich in einer der README.md eine Diskussion über ein anspruchsvolleres SDF gesehen habe. Habe ich es geträumt? 😛 Kann mich jemand darauf hinweisen, um zu sehen, ob dieses andere System hier helfen könnte?

@FunMiles Sehr clevere Komprimierungsidee. Ich werde das im Hinterkopf behalten, wenn andere Optionen fehlschlagen. Der Verlust der linearen Interpolation durch die Hardware ist ein Kompromiss, den ich lieber nicht eingehen möchte. 😉

Mir ist aufgefallen, dass mein Problem mit nicht genügend Bits durch die Verwendung von 0,5 als „Null“-Abstand verschärft wird, sodass nur die Hälfte der Alpha-Werte zum Codieren des Abstands _außerhalb_ der Glyphe verfügbar sind. Ich könnte das möglicherweise verschieben, um mehr Werte für Außenabstände und weniger für Innenabstände zu verwenden, um an der Peripherie etwas Präzision zu gewinnen.

Betreff. ein "ausgefeilteres SDF", meinst du vielleicht "MSDF", wo mehrere Farbkanäle verwendet werden?

@FunMiles Sehr clevere Komprimierungsidee. Ich werde das im Hinterkopf behalten, wenn andere Optionen fehlschlagen. Der Verlust der linearen Interpolation durch die Hardware ist ein Kompromiss, den ich lieber nicht eingehen möchte. 😉

Ich denke nicht, dass Sie sich Sorgen machen sollten, diese Interpolation zu verlieren. Die Kosten sind sehr gering, aber die Vorteile, immer den Gradienten (und sogar ein bisschen Krümmung) zu haben, wenn keine Hardwareunterstützung vorhanden ist, sind meiner Ansicht nach von größerer Bedeutung. Tatsächlich runden Sie den Abtastpunkt und erhalten einen neuen Versatzwert zum gerundeten Abtastpunkt. Das Berechnen der teilweisen Abdeckung des Fragments für das Anti-Aliasing geht weiter, wie es normalerweise mit dem verfügbaren Gradienten geschehen würde.
Mir ist aufgefallen, dass mein Problem mit nicht genügend Bits durch die Verwendung von 0,5 als „Null“-Abstand verschärft wird, sodass nur die Hälfte der Alpha-Werte zum Codieren des Abstands _außerhalb_ der Glyphe verfügbar sind. Ich könnte das möglicherweise verschieben, um mehr Werte für Außenabstände und weniger für Innenabstände zu verwenden, um an der Peripherie etwas Präzision zu gewinnen.

Das bringt Ihnen höchstens ein bisschen Genauigkeit. Nicht zu verachten, aber immer noch nicht so bedeutend.

Betreff. ein "ausgefeilteres SDF", meinst du vielleicht "MSDF", wo mehrere Farbkanäle verwendet werden?
Das ist es was ich meinte. Ich habe ein GitHub-Projekt darüber in C++ gefunden. Hatten Sie etwas, um das zu verwenden, oder war ich nur verwirrt, als ich verschiedene Dinge zerdrückte, die ich vor zwei Wochen nachgeschlagen hatte?

Ich glaube übrigens nicht, dass die MSDF helfen würde.
Darf ich fragen, ob Sie eine kleine .md-Datei haben könnten, die erklärt, wie Sie die SDF in JavaScript erstellen? Damit könnte ich, denke ich, Code erstellen, um meine Komprimierungsidee umzusetzen.

Das SDF wird hier erstellt: https://github.com/protectwise/troika/blob/master/packages/troika-three-text/src/worker/SDFGenerator.js#L134 – meistens nicht wirklich viel zu erklären Zuordnen von Texeln zu Schriftarteinheiten und Schreiben der gemessenen Entfernungen in die Texelwerte. Wir müssten dort einige zusätzliche Abstandsmessungen für den Mittelpunkt jedes 2x2-Blocks hinzufügen.

Der Versuch, mein Gehirn vollständig darum zu wickeln ... Das Replizieren der bilinearen Interpolation in der GLSL sieht einfach / billig genug aus, sobald Sie die 4 nächsten Werte haben. Um diese Werte zu erhalten, wenn sie mit Ihrem Komprimierungsschema codiert sind, denke ich, dass es Folgendes beinhalten wird:

  • Wenn genau in der Mitte eines Texels: entweder 2 oder 3 Texturproben
  • Wenn zwischen den 4 Texeln eines 2x2-Blocks: 4 Texturproben
  • Wenn zwischen zwei benachbarten Blöcken: ~9 Texturproben~ entweder 7 oder 8 Texturproben
  • Wenn zwischen vier benachbarten Blöcken: ~11 Texturproben~ 13 Texturproben

Verstehe ich das richtig?

Oh halt, ich dachte noch an eine einkanalige Textur. Bei Verwendung von vier RGBA-Kanälen ist jeder Datenblock nur ein einziger Lesevorgang. Also höchstens 4 Texturmuster.

Ich hatte angefangen, SDFGenerator.js zu lesen. Ich werde es mir genauer anschauen.

Ich glaube nicht, dass Sie jemals mehr als ein einzelnes Texel pro Fragment lesen müssen, es sei denn, Sie möchten das Alpha-Blending speziell für Eckfälle verbessern, in denen Sie sich möglicherweise an einem Punkt befinden, der auf allen Seiten von Glyphenkanten umgeben ist. Sie brauchen nur die nächste Mitte des 2x2-Blocks, wo sich die Mitte des Fragments befindet.
Mir ist jedoch klar, dass es eine Schwierigkeit geben könnte, die ich nicht vorhergesehen hatte ... WebGL 1.0 ist sehr begrenzt in dem, was es hier von Nutzen wäre: Keine vorzeichenlosen Integer-Typen uint , nicht einmal 32-Bit-Integer! 🤯 und keine bitweise Operation ... Die von mir vorgeschlagene Komprimierung ist also etwas schwieriger mit 16-Bit-Ganzzahlen zu schreiben.

All diese sind in WebGL 2.0 verfügbar, aber ich nehme an, wir wollen hier auf WebGL 1.0 abzielen?

All diese sind in WebGL 2.0 verfügbar, aber ich nehme an, wir wollen hier auf WebGL 1.0 abzielen?

Ich denke, es könnte sinnvoll sein, die Textgliederung auf Webgl 2 zu beschränken und nur festzustellen, ob der Benutzer diese Browserkompatibilität hat. Obwohl ich verstehen kann, dass möglicherweise sowohl eine optimale Webgl 2-Version als auch ein Webgl 1-Fallback verwendet werden. Imo könnten wir mit webgl 2 beginnen und das mit der Community aufsaugen lassen, bevor wir sofort versuchen, beide zu unterstützen.

Ich denke, ich habe einen alternativen Ansatz, um das Präzisionsproblem zu umgehen, also macht sich @FunMiles vorerst keine Sorgen, mit dem Komprimierungszeug zu kämpfen.

Hey @lojjic , ​​ich schaue gerade rein. Irgendwelche Fortschritte oder Dinge, bei denen wir helfen können?

Nebenbemerkung: Safari kommt endlich dazu, WebGL2 zu unterstützen, daher könnte es möglich sein, WebGL1 für diese Funktion nicht zu unterstützen.

Unsere WebGL2-Implementierung ist so gut, dass wir sie standardmäßig für breitere Tests aktivieren sollten.
https://trac.webkit.org/changeset/267027/webkit

Ich habe ein iPhone 6 auf dem es nie WebGL 2.0 geben wird 😒

Interessanterweise stellt die WebGL 1.0 noch schlimmere Anforderungen als ich dachte. Ganze Zahlen gibt es nicht wirklich:

4.1.3 Ganze Zahlen
Als Programmierhilfe werden hauptsächlich ganze Zahlen unterstützt. Auf der Hardwareebene würden echte Ganzzahlen helfen
effiziente Implementierung von Schleifen und Array-Indizes und Referenzierung von Textureinheiten. Es gibt jedoch keine
Anforderung, dass ganze Zahlen in der Sprache einem ganzzahligen Typ in der Hardware zugeordnet werden. Das ist nicht zu erwarten
Die zugrunde liegende Hardware bietet volle Unterstützung für eine Vielzahl von Ganzzahloperationen. Eine OpenGL ES-Schattierung
Die Sprachimplementierung kann Ganzzahlen in Gleitkommazahlen umwandeln, um mit ihnen zu arbeiten. Daher gibt es keine tragbaren
Wickelverhalten.

Allerdings habe ich die Hoffnung nicht aufgegeben. Ich muss nur etwas umdenken...
In der Zwischenzeit, @lojjic , ​​hast du etwas dagegen, uns zu sagen, an welchen alternativen Ansatz du gedacht hast?

@FunMiles Sicher. Ich bin in der Lage, das Abstandsfeld bis zu den Rändern der Textur zu erweitern und dabei dennoch eine ausreichende Genauigkeit für die Form der Glyphe beizubehalten, indem ich die Abstandswerte mit einer nichtlinearen Skala kodiere. Entfernungen in unmittelbarer Nähe des Pfads der Glyphe (innerhalb von 1-2 Texeln) haben also viele Werte, mit denen man arbeiten kann, während diejenigen, die weiter entfernt sind, weniger haben. Der Shader muss nur wissen, wie er zurück in die ursprüngliche lineare Entfernung konvertiert. Die richtige Form der Glyphe sieht gut aus, und die geringere Genauigkeit der extrudierten Umrisse fällt kaum auf, da sie sowieso abgerundet sind.

Ich habe dies sowohl mit einer zweistufigen linearen Skala als auch mit einer exponentiellen Skala bewiesen. Beides hat Vor- und Nachteile.

Ich bin gerade dabei, Ihren (sehr intelligenten) Ansatz zu implementieren, benachbarte Kantenwerte zu verwenden, um den Gradienten außerhalb des Quads zu approximieren. Bisher macht es Sinn, obwohl ich mir nicht sicher bin, was ich mit den Bereichen außerhalb der Ecken machen soll. Es mag offensichtlich werden, wenn ich näher darauf eingehe, aber wenn Sie eine einfache Antwort für mich haben, wäre ich Ihnen dankbar :)

Ich bin gerade dabei, Ihren (sehr intelligenten) Ansatz zu implementieren, benachbarte Kantenwerte zu verwenden, um den Gradienten außerhalb des Quads zu approximieren. Bisher macht es Sinn, obwohl ich mir nicht sicher bin, was ich mit den Bereichen außerhalb der Ecken machen soll. Es mag offensichtlich werden, wenn ich näher darauf eingehe, aber wenn Sie eine einfache Antwort für mich haben, wäre ich Ihnen dankbar :)

Ich bin mir nicht sicher, was Sie mit _außerhalb der Ecken_ meinen. Meinen Sie die Viertelebenen, die an jeder Ecke verwurzelt sind, wobei die nächste Projektion die Ecke selbst ist? Ich denke, dort können Sie die Regel für eine Kante sowohl an der vertikalen als auch an der horizontalen Kante verwenden und einen gewichteten Durchschnitt basierend auf der Entfernung zu jeder Kante erstellen.

PS: Die Idee, die Genauigkeit weit weg zu reduzieren, war mir heute früher gekommen, nachdem ich den WebGL-Integer-Unsinn gelesen hatte ... 😛 Ich habe immer noch die Hoffnung, dass die Komprimierungstechnik in WebGL 1.0 implementiert werden kann, um billig und ein paar 11 Bit Genauigkeit zu erhalten mehr Bits mit mehr Tests. dh bei Verwendung eines rgba könnte das a 8 Bits des Durchschnitts halten, dann würden sie für jedes von rgb signiert und das Vorzeichen würde jeweils 1 Bit Genauigkeit darstellen der Durchschnitt, dann schließlich der Rest, der im Bereich 0-127 liegen würde, würde den Wert 64 subtrahieren, um 7 Bits für 3 der 4 benötigten Werte darzustellen. Der tatsächliche Wert wäre die Mitte (im Bereich von 0 bis 2^11-1) + dist_i. Der 4. Wert würde wiederhergestellt werden, indem man ihre Summe minus macht, da die Summe der Durchschnitt sein muss. Man würde nur 11 Bit bekommen, aber das ist wahrscheinlich ausreichend. Um mehr Bits zu erhalten, müsste getestet werden, ob der Wert oder r , g und b kleiner als -64, positiv oder negativ, größer als 64 sind. Das wäre im Wesentlichen geben 14 Bit für die Mitte und nur 6 Bit für die Differenzen. Wahrscheinlich ein guter Kompromiss? Ich müsste testen, ob die sehr lockeren Garantien, die WebGL 1.0 auf Genauigkeit setzt, dieses Denken nicht stören würden ....

Ja, das meinte ich, die Bereiche, in denen sowohl U als auch V außerhalb von 0-1 liegen. Danke, das werde ich mal ausprobieren.

@lojjic Als Nebenfrage schienen Sie in einem früheren Beitrag aufgrund des Speichers besorgt zu sein, zwei Texturwerte pro SDF-Texel zu haben. Obwohl ich es vorziehe, den Speicher zu reduzieren, verbrauchen 256 Glyphen auf einem 64x64-Raster nur 1/4 MB Texturspeicher. Ich weiß, dass einige Sprachen viel mehr Glyphen erfordern, aber die BBC sagt, dass zum Lesen der Zeitung nur 2000/3000 Zeichen benötigt werden. 4000 Zeichen würden also 4 MB mit einem Byte pro Texel-SDF ergeben. Lohnt es sich, sich Sorgen zu machen?

@FunMiles Fairer Punkt, und ich war ehrlich gesagt nicht sehr besorgt darüber.

Es sind noch einige ausstehende Arbeiten zu erledigen, aber b19cd3aff4b0876253d76b3fc66b7e2a1f16a7e5 behebt dieses Problem. Für diejenigen, die React-Three-Fiber verwenden, wurde dies in drei v2.2.0 veröffentlicht
https://github.com/pmndrs/drei/pull/156

Ich weiß, dass dies geschlossen ist, aber ich möchte mich für die Arbeit bedanken. Ich habe es in meiner App zum Laufen gebracht. Ich musste ein bisschen herausfinden, dass die Eigenschaften nicht nur eine Zahl für die Umrissgröße akzeptieren würden. Das Ergebnis ist jedoch genau das, was ich brauchte.
Screen Shot 2020-11-09 at 7 44 52 PM

@FunMiles Sieht gut aus! Sie _sollten_ in der Lage sein, einen numerischen Wert für outlineWidth zu verwenden, also lassen Sie es mich wissen, wenn das aus irgendeinem Grund bei Ihnen nicht funktioniert.

Und vielen Dank für Ihren Input auf dem Weg. Ich war immer noch nicht in der Lage, mit der von Ihnen erwähnten Technik eine reibungslose Extrapolation der Entfernung außerhalb der Quad-Grenzen zu erhalten, daher hat eine dicke Kontur derzeit insbesondere an den Ecken klumpige Teile, aber sie ist für die meisten Fälle gut genug (bis zu ~ 10 % der Schriftgröße). Ich bin definitiv offen für den Versuch, das noch zu verfeinern.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen