Troika: Contour du texte

Créé le 20 août 2020  ·  39Commentaires  ·  Source: protectwise/troika

J'essaie de comprendre comment avoir un contour de texte noir autour du texte blanc afin que le texte puisse être facilement lu. Je ne suis pas sûr, mais il semble que j'aurais besoin d'utiliser un shader pour faire avec troika-three-text? Pouvez-vous donner des conseils sur la façon d'obtenir cet effet ?

Merci!

Commentaire le plus utile

Merci pour l'info sur Mapbox, @anzorb. Je vais certainement vérifier leur implémentation de plus près, mais d'après ce billet de blog (ils appellent le contour un "halo"), il semble qu'ils utilisent l'approche de seuil alpha SDF comme je l'ai mentionné précédemment. Mais leur stratégie de génération de SDF et de conditionnement d'atlas est différente de la mienne et n'a pas les mêmes problèmes avec des tailles de champ de distance non uniformes par glyphe. Il aurait probablement la même limitation de largeur de contour maximale.

J'ai en fait fait des progrès sur les contours de _screen space_ en utilisant des dérivées standard. Espace écran, ce qui signifie que le contour serait par exemple de 1 pixel d'écran, quelle que soit la taille/l'échelle ou l'obliquité du texte. Si l'objectif est simplement d'ajouter du contraste avec l'arrière-plan, il semble que cela pourrait être aussi bon (ou même préférable) que des contours qui s'adaptent à la taille du texte. Avez-vous une opinion là-dessus?

Tous les 39 commentaires

J'ai presque l'impression que ce dont j'ai besoin est peut-être accompli par le drapeau debugSDF, si seulement je pouvais le colorer.
image
L'affichage du sdf me donne essentiellement un aperçu qui fonctionnerait probablement si je pouvais le coloriser, ajuster la taille et potentiellement le rendre légèrement plus net.

Cela ne devrait pas être trop difficile à faire. J'ai commencé à jouer avec textOutline en tant que fonctionnalité il y a quelque temps, mais je ne l'ai jamais terminé.

En théorie, les bords du glyphe peuvent être étendus simplement en changeant la valeur dans le champ de distance signé qui correspond au bord. C'est 0.5 dans le code de shader actuel , mais cela pourrait être rendu configurable en le passant en tant qu'uniforme.

Idéalement, le shader pourrait gérer le dessin à la fois du glyphe normal et de tout contour qui l'entoure en un seul appel de dessin, vous auriez donc besoin de quelques nouveaux uniformes : la largeur du contour (vous ne savez pas dans quelles unités ce serait ?) et la couleur de la zone de contour.

J'ai commencé à jouer avec textOutline en tant que fonctionnalité il y a quelque temps, mais je ne l'ai jamais terminé.

Je serais TRÈS heureux s'il était supporté nativement :)
Cependant, je suis très nouveau dans les shaders, donc je ne sais pas à quel point je peux être utile. Je vais suivre ce sujet de près.

Je me suis un peu replongé là-dedans et j'ai vite compris pourquoi je ne l'avais pas terminé à l'origine. Alors que le SDF _peut_ être utilisé pour le contour, il y a quelques mises en garde :

  • La largeur potentielle maximale du contour est limitée par l'étendue du champ de distance lui-même (~ 8 texels dans la texture SDF 64x64).
  • Cela ne correspond pas à une taille visuelle cohérente sur tous les glyphes, car la texture SDF est mise à l'échelle en fonction des limites de chaque glyphe.

Ainsi, les glyphes plus grands comme "W" peuvent obtenir un contour assez épais, mais les petits comme les périodes ne peuvent obtenir qu'un contour très fin. Et chaque glyphe doit utiliser un point de coupure SDF différent pour rendre leurs contours visuellement cohérents.

Je ne pense pas que ce soit encore insurmontable, mais ce n'est pas aussi simple que je le pensais au départ.

@lojjic , ​​j'adore cette bibliothèque 🥇, mais le contour du texte est une exigence pour notre projet...

Je me suis un peu replongé là-dedans et j'ai vite compris pourquoi je ne l'avais pas terminé à l'origine. Alors que le SDF _peut_ être utilisé pour le contour, il y a quelques mises en garde :

  • La largeur potentielle maximale du contour est limitée par l'étendue du champ de distance lui-même (~ 8 texels dans la texture SDF 64x64).
  • Cela ne correspond pas à une taille visuelle cohérente sur tous les glyphes, car la texture SDF est mise à l'échelle en fonction des limites de chaque glyphe.

Ainsi, les glyphes plus grands comme "W" peuvent obtenir un contour assez épais, mais les petits comme les périodes ne peuvent obtenir qu'un contour très fin. Et chaque glyphe doit utiliser un point de coupure SDF différent pour rendre leurs contours visuellement cohérents.

Je ne pense pas que ce soit encore insurmontable, mais ce n'est pas aussi simple que je le pensais au départ.

Je suis curieux, pouvons-nous faire une sorte de post-traitement en utilisant des shaders pour obtenir un contour et/ou des effets d'ombre ? Merci d'avance!
Voici l'effet que j'essaie d'obtenir :
Screen Shot 2020-08-31 at 4 32 03 PM

Edit: cela vient de mapbox gl, je pense qu'ils utilisent également SDF https://blog.mapbox.com/drawing-text-with-signed-distance-fields-in-mapbox-gl-b0933af6f817 , peut-être que nous pouvons disséquer leur code pour voir ce qu'ils ont fait. https://github.com/mapbox/mapbox-gl-js

Merci pour l'info sur Mapbox, @anzorb. Je vais certainement vérifier leur implémentation de plus près, mais d'après ce billet de blog (ils appellent le contour un "halo"), il semble qu'ils utilisent l'approche de seuil alpha SDF comme je l'ai mentionné précédemment. Mais leur stratégie de génération de SDF et de conditionnement d'atlas est différente de la mienne et n'a pas les mêmes problèmes avec des tailles de champ de distance non uniformes par glyphe. Il aurait probablement la même limitation de largeur de contour maximale.

J'ai en fait fait des progrès sur les contours de _screen space_ en utilisant des dérivées standard. Espace écran, ce qui signifie que le contour serait par exemple de 1 pixel d'écran, quelle que soit la taille/l'échelle ou l'obliquité du texte. Si l'objectif est simplement d'ajouter du contraste avec l'arrière-plan, il semble que cela pourrait être aussi bon (ou même préférable) que des contours qui s'adaptent à la taille du texte. Avez-vous une opinion là-dessus?

Les largeurs standardisées sont bien plus préférées que les largeurs de mise à l'échelle. Il se comporterait plus comme CSS de cette façon aussi.
Tout text-shadow appliqué via css est configuré indépendamment du font-size .
Si votre solution proposée @lojjic fonctionne de la même manière, alors ce serait 🎉 🎉 🎉

Merci pour votre réponse rapide @lojjic!

L'objectif est d'ajouter du contraste et votre proposition est superbe. Voulez-vous dire que le contour sera statique ? c'est-à-dire aucun moyen de le contrôler (comme @stephencorwin l'a décrit, en utilisant l'approche text-shadow-ish).

Honnêtement, je ne vois pas cela comme un problème, tant que le pixel 1 est visible sur les écrans à très haute densité (car nous multiplions actuellement la taille de la police par le rapport des pixels de l'appareil pour obtenir les "mêmes" proportions, quelle que soit la densité) , je pense que votre proposition signifie que le contour sera visiblement plus fin sur les appareils à haute résolution, non ?

Merci d'avance!

Je pense que votre proposition signifie que le contour sera visiblement plus fin sur les appareils à haute résolution, non ?

Oui, c'est un inconvénient de l'approche de l'espace écran. Cela peut convenir à de nombreux scénarios, mais cela me dérangerait en tant que concepteur d'avoir un aspect différent sur les appareils.

L'autre gros inconvénient est que (je pense) l'augmentation de l'épaisseur du halo aurait un impact sur les performances en raison de la nécessité de lire plus de textures. Je dois continuer à faire des recherches sur ce sujet.

J'ai moi aussi besoin de cette fonctionnalité. J'ai implémenté une telle fonctionnalité dans une application Android que j'ai développée il y a très longtemps et que j'ai utilisée uniquement pour moi et mes amis. J'ai commencé à réimplémenter cette application avec React et je suis venu trouver votre projet.
Comme d'autres, je tiens à vous remercier pour cette bibliothèque. J'en suis émerveillé.

Concernant votre commentaire sur plus de lectures de texture, je ne pense pas que vous devriez vous en soucier autant. Les texels supplémentaires dont vous avez besoin seront les texels dont les fragments voisins ont besoin et je pense donc qu'ils seront de toute façon dans le cache du GPU et qu'ils ne coûteront presque rien.

Merci pour la contribution @FunMiles. (Colorado représente ! 😄)

Je pense que vous avez probablement raison pour les contours de 1, peut-être 2 fragments d'épaisseur. Mon inquiétude est qu'à mesure que l'épaisseur augmente, cela fait exploser le nombre de lectures de texture (épaisseur de 4 = 56 lectures ?) ainsi que la probabilité que ces lectures soient plus chères. Mais je ne fais que spéculer sur la base de la lecture de certaines choses, je ne suis en aucun cas un expert des GPU. Je n'aurai probablement qu'à l'essayer.

@lojjic (Au nord de vous ici 😄 ) J'ai essayé de trouver le code du fragment shader. J'avoue avoir du mal à comprendre les choses car il semble (et je pense l'avoir lu quelque part) que vous modifiez le shader au lieu de l'écrire entièrement.
Cependant, bien que je n'arrive pas à comprendre à quoi ressemble le shader complet et comment vous vous y branchez vraiment, je pense que je reçois une partie de la routine de base ...

En ce moment, vous lisez une distance scalaire pour le pixel avec texture2D(uTroikaSDFTexture, vTroikaSDFTextureUV).r; . Si je comprends bien ce que vous avez écrit auparavant, un problème concerne l'espace autour des glyphes. Pouvez-vous expliquer plus en détail?
Pour en revenir à votre exemple de 'W' et ',' est-ce que l'étendue vTroikaSDFTextureUV dans la texture s'adapte parfaitement autour du glyphe, ou y a-t-il un espace supplémentaire? Le shader de fragment connaît-il les limites de l'UV pour le glyphe actuel ? Si c'est le cas, pour un fragment qui tomberait à l'extérieur, vous pouvez projeter l'UV actuel aux limites et aller lire les données directement à l'intérieur.

Je pense qu'il est possible de déterminer ce qui est nécessaire avec au plus 5 lectures de texels ou avec l'utilisation de dérivés (je vois que vous vérifiez s'ils sont disponibles).

Pour en revenir à votre exemple de 'W' et ',' est-ce que l'étendue vTroikaSDFTextureUV dans la texture s'adapte parfaitement autour du glyphe, ou y a-t-il un espace supplémentaire?

Il y a un petit espace supplémentaire autour des véritables limites du chemin de chaque glyphe, juste assez pour accueillir les parties extérieures du champ de distance. C'est 8 texels dans le SDF, mais comme chaque SDF est un 64x64 uniforme mis à l'échelle sur un quad de glyphe de taille variable, cette marge visible varie d'un glyphe à l'autre.

Le shader de fragment connaît-il les limites de l'UV pour le glyphe actuel ? Si c'est le cas, pour un fragment qui tomberait à l'extérieur, vous pouvez projeter l'UV actuel aux limites et aller lire les données directement à l'intérieur.

Cette information devrait être disponible. Je ne vous suis pas sur "projeter l'UV actuel aux limites et aller lire les données directement à l'intérieur".

Je vous serais reconnaissant si vous connaissiez un moyen d'obtenir des contours plus épais avec moins de lectures de texels ! Je l'ai conceptualisé comme suit : pour un fragment qui n'est pas à l'intérieur du chemin du glyphe, effectuez une recherche radiale pour voir s'il y a des fragments dans r=tomberait dans le chemin du glyphe. C'est peut-être simplement la mauvaise façon d'y penser.

Un problème est ce qui se passe lorsque les glyphes sont assemblés côte à côte. Mettant cela de côté pour l'instant, prenons le cas d'un seul caractère dessiné avec un contour assez épais t . Le personnage est dessiné avec un rectangle autour de lui suffisamment grand pour contenir le contour.
Le fragment shader a deux informations principales : vTroikaSDFTextureUV et vTroikaGlyphUV .

Ma compréhension est que vTroikaGlyphUV a des valeurs comprises entre 0 et 1 à l'intérieur du glyphe. Ce que je voulais dire quand j'ai parlé de projection, c'est que si vTroikaGlyphUV est en dehors de ces limites, vous pouvez récupérer la distance du point "projeté" sur la limite du glyphe. Autrement dit, si uv = (1,05, 0,7), alors le point projeté est (1,0, 0,7). En utilisant des valeurs à (1,0, 0,7 + epsilon) et (1,0, 0,7-epsilon), vous pouvez récupérer un gradient à la distance et en utilisant cela, calculer la distance approximative à (1,05, 0,7).
Quand j'ai fait mon propre travail pour cela, je me souviens avoir lu un article d'une personne faisant du SDF avec un dégradé dans la texture également. Cette approche évite d'avoir à lire trois texels, mais elle fait de la texture une texture à 3 composants et ajoute de la complexité à la construction de la texture.

Est-ce que cela répond à votre question?

Maintenant, lorsque vous avez plusieurs personnages côte à côte, chacun doit être conscient des voisins, ce qui rend cela un peu plus complexe. Vous devez porter le GlyphUV et le TextureUV non seulement pour le personnage actuel, mais aussi pour le voisin. Cela peut nécessiter de découper chaque caractère en deux rectangles, afin que le voisin puisse être celui d'avant pour le premier rectangle et celui d'après pour le deuxième rectangle. Je ne sais pas comment traiter les multilignes... Je n'ai pas eu à traiter ce cas dans ma demande. Mon application était une carte et chaque étiquette était une seule ligne.

PS : J'ai peut-être mal compris votre "espace supplémentaire autour des véritables limites du chemin de chaque glyphe". Êtes-vous en train de dire que pour un X, par exemple, il est enfermé dans un rectangle et qu'il y a quatre triangles qui n'ont pas de valeurs valides ?

@FunMiles Je _pense_ peut-être que je vois où tu vas. J'aurai besoin de temps pour y réfléchir, mais j'ai d'autres échéances sur lesquelles je dois me concentrer pour le moment.

PS : Une petite note mathématique amusante :
Juste au cas où quelqu'un se demande comment le gradient peut être obtenu à partir de deux ou trois points alignés, il faut se rappeler que le gradient de la distance a une norme fixe (selon l'échelle choisie). Et le dégradé pointe définitivement vers l'extérieur de la boîte de glyphe. Ainsi, par exemple, si les 3 points de l'exemple (1.0, 0.7), (1.0, 0.7+epsilon) et (1.0, 0.7-epsilon) sont à la même distance, alors le gradient est (échelle, 0).

Bien qu'il ne soit certainement pas performant, il est possible d'utiliser des décalages. J'utilise react-three-fiber , qui exploite troika-text avec le paquet drei, mais j'ai pensé que je partagerais au cas où d'autres chercheraient une solution comme solution provisoire jusqu'à ce que la bibliothèque prenne en charge le trait de manière native :

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;

Ensuite, ailleurs, je peux l'appeler avec <StrokedText /> au lieu de <Text /> normalement :

<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 J'ai enfin eu le temps d'analyser vos suggestions. Je pense que cela aurait du sens en supposant que l'intégralité du rectangle SDF du glyphe devait contenir des valeurs de distance utiles (> 0,0). Ce n'est pas le cas actuellement; Je pense que vous vous en rendiez compte dans votre suivi sur "x", où le SDF tombe à zéro bien dans les limites du quad, il y a donc des zones importantes où il n'y a pas de gradient de distance utile à partir duquel extrapoler :

image

Peut-être que je peux envisager de changer le générateur SDF pour assurer un gradient non nul sur l'intégralité du quad... ? Penser à haute voix, cela pourrait avoir des connotations pour la précision de la distance, et pourrait introduire des artefacts entre les personnages dans la texture de l'atlas... 🤔

Peut-être que je peux envisager de changer le générateur SDF pour assurer un gradient non nul sur l'intégralité du quad

J'ai essayé cela, et oui, cela permet d'extrapoler le champ de distance au-delà des limites du quad, mais comme je le craignais, cela diminue considérablement la qualité du glyphe lui-même. C'est à prévoir avec seulement un gradient de 8 bits ; l'étaler sur une plus grande distance se traduit par une précision moindre à chaque texel. :(

Je pourrais peut-être générer un SDF séparé pour les contours uniquement - un champ plus étendu mais moins précis - encodé dans un deuxième canal de texture. Cela doublerait la taille de la texture mais ne devrait pas être beaucoup plus lent. 🤔

encodé dans un deuxième canal de texture.

@lojjic Cela semble très raisonnable et imite essentiellement ma solution de contournement en ce moment. De plus, il peut être opt-in, donc il n'y a pas de perf hit si l'utilisateur ne demande pas de contours.

@lojjic L'approche opt-in suggérée par @stephencorwin garantirait que personne ne paie plus qu'il n'est prêt à payer.

Revenons à l'aspect technique. Prenons l'image X dans votre réponse. Ai-je bien compris que pour tous les glyphes, c'est 64x64 pixels ? Avec 8 bits, si vous devez encoder tout l'espace distance, cela signifie que vous avez une précision de 2 _fractionnels_ bits. Je suppose que vous dites que cette précision n'est pas suffisante.

Il y a peut-être une astuce à utiliser pour gagner en précision à très peu de frais. Au lieu de faire une grille de 64x64 de 8 bits, compressez les données de 2x2 blocs en données de 32 bits. Une caractéristique claire de la distance signée entre deux pixels est qu'elle est toujours comprise entre [-1,1] droite et gauche, haut et bas et [-sqrt(2), sqrt(2)] en diagonale. Alors imaginez que vous utilisez 12 bits pour la SD du centre du bloc 2x2, vous auriez 5 bits pour chaque centre des pixels pour encoder la différence entre leur centre et le centre du 2x2. Ces 5 bits représentent au plus la distance sqrt(2)/2. Effectivement, vous avez une précision de 5,5 bits.
Le point central 12 bits encode au moins 6 bits de précision sur une distance maximale de 64. Techniquement, si la boîte n'avait qu'un seul point à un coin, le 12 bits devrait pouvoir encoder jusqu'à sqrt (2) * 64, mais Je ne pense pas que ce soit réellement possible puisque la boîte est vraisemblablement raisonnablement centrée autour de chaque glyphe. En fait, plus loin de tout bord de glyphe et moins les deltas ont d'informations à coder (lorsque vous êtes loin d'un bord de glyphe, le champ de gradient devient presque uniforme dans un voisinage) donc du point de vue de la théorie de l'information, il est même possible d'améliorer l'encodage
pour avoir plus d'informations réelles.... Mais je ne commencerais pas avec une telle optimisation supplémentaire.

Une conséquence intéressante de cette approche est qu'on ne laisserait pas le matériel de texturation faire une quelconque interpolation. Mais d'un autre côté, on obtiendrait des dégradés gratuitement.

Si vous avez besoin d'aide, je pourrais implémenter l'encodage/décodage de tout cela.

PS : J'ai un souvenir d'avoir vu une discussion dans l'un des README.md à propos d'un SDF plus sophistiqué. L'ai-je rêvé ? 😛 Quelqu'un peut-il me le rediriger pour voir si cet autre système pourrait aider ici ?

@FunMiles Idée de compression très astucieuse. Je garderai cela à l'esprit si les autres options échouent. Perdre l'interpolation linéaire par le matériel est un compromis que je préfère ne pas avoir à faire. 😉

Il m'est venu à l'esprit que mon problème avec pas assez de bits est exacerbé en utilisant 0,5 comme distance "zéro", donc seulement la moitié des valeurs alpha sont disponibles pour coder la distance _outside_ le glyphe. Je pourrais potentiellement changer cela pour utiliser plus de valeurs pour les distances extérieures et moins pour les distances intérieures, en gagnant en précision sur la périphérie.

Ré. un "SDF plus sophistiqué", vous pouvez vouloir dire "MSDF" où plusieurs canaux de couleur sont utilisés ?

@FunMiles Idée de compression très astucieuse. Je garderai cela à l'esprit si les autres options échouent. Perdre l'interpolation linéaire par le matériel est un compromis que je préfère ne pas avoir à faire. 😉

Je ne pense pas que vous devriez vous inquiéter de perdre cette interpolation. Le coût est très faible mais les avantages d'avoir toujours le gradient (et même un peu de courbure) lorsqu'il n'y a pas de support matériel sont d'une plus grande importance à mon avis. En effet, vous arrondissez le point d'échantillonnage et obtenez une nouvelle valeur de décalage par rapport au point d'échantillonnage arrondi. Le calcul de la couverture partielle du fragment pour l'anti-crénelage se déroule comme il le ferait normalement avec le gradient disponible.
Il m'est venu à l'esprit que mon problème avec pas assez de bits est exacerbé en utilisant 0,5 comme distance "zéro", donc seulement la moitié des valeurs alpha sont disponibles pour coder la distance _outside_ le glyphe. Je pourrais potentiellement changer cela pour utiliser plus de valeurs pour les distances extérieures et moins pour les distances intérieures, en gagnant en précision sur la périphérie.

Cela vous fera gagner au plus un peu de précision. À ne pas éternuer mais pas si important encore.

Ré. un "SDF plus sophistiqué", vous pouvez vouloir dire "MSDF" où plusieurs canaux de couleur sont utilisés ?
C'est ce que je voulais dire. J'ai trouvé un projet GitHub à ce sujet en C++. Aviez-vous quelque chose à utiliser ou ai-je juste été confus, mélangeant diverses choses que j'ai consultées il y a deux semaines ?

Je ne pense pas que le MSDF aiderait, soit dit en passant.
Puis-je vous demander si vous pouviez avoir un petit fichier .md expliquant comment vous construisez le SDF en JavaScript ? Avec cela, je pense que je pourrais créer du code pour implémenter mon idée de compression.

Le SDF est construit ici : https://github.com/protectwise/troika/blob/master/packages/troika-three-text/src/worker/SDFGenerator.js#L134 -- pas vraiment beaucoup à expliquer à ce sujet, surtout mapper les texels aux unités de police et écrire les distances mesurées dans les valeurs de texels. Nous devrons ajouter des mesures de distance supplémentaires pour le point central de chaque bloc 2x2.

Essayer d'envelopper mon cerveau complètement autour de cela... La réplication de l'interpolation bilinéaire dans le GLSL semble assez simple/bon marché, une fois que vous avez les 4 valeurs les plus proches. Pour obtenir ces valeurs lorsqu'elles sont encodées avec votre schéma de compression, je pense que cela impliquera :

  • Si exactement au centre d'un texel : soit 2 ou 3 échantillons de texture
  • Si entre les 4 texels d'un bloc 2x2 : 4 échantillons de texture
  • Si entre deux blocs voisins : ~9 échantillons de texture~ soit 7 ou 8 échantillons de texture
  • Si entre quatre blocs voisins : ~11 échantillons de texture~ 13 échantillons de texture

Est-ce que je grokking correctement ?

Oh, attendez, je pensais toujours à une texture monocanal. En utilisant quatre canaux rgba, chaque bloc de données n'est qu'une seule lecture. Donc au plus 4 échantillons de texture.

J'avais commencé à lire SDFGenerator.js. Je vais le regarder plus.

Je ne pense pas que vous ayez jamais besoin de plus que de lire un seul texel par fragment, à moins que vous ne souhaitiez améliorer spécifiquement le mélange alpha pour les cas d'angle où vous pourriez être à un point entouré de bords de glyphes de tous les côtés. Vous avez juste besoin du centre le plus proche du bloc 2x2 où se trouve le centre du fragment.
Cependant, je me rends compte qu'il peut y avoir une difficulté que je n'avais pas prévue... WebGL 1.0 est très limité dans ce qu'il fournit qui serait utile ici : Pas de type entier non signé uint pas même des entiers 32 bits ! 🤯 et pas d'opération au niveau du bit... Donc, la compression que je suggère est un peu plus délicate à écrire en utilisant des entiers 16 bits.

Tout cela est disponible en WebGL 2.0 mais je suppose que nous voulons cibler WebGL 1.0 ici ?

Tout cela est disponible en WebGL 2.0 mais je suppose que nous voulons cibler WebGL 1.0 ici ?

Je pense qu'il pourrait être raisonnable de restreindre le contour du texte à webgl 2 et de détecter simplement si l'utilisateur a cette compatibilité de navigateur. Cependant, je peux comprendre qu'il soit possible de faire à la fois une version optimale de webgl 2 avec une solution de repli de webgl 1. Imo, nous pourrions commencer avec webgl 2 et le laisser s'imprégner de la communauté avant d'essayer immédiatement de prendre en charge les deux.

Je pense que j'ai une approche alternative pour contourner le problème de précision, donc @FunMiles ne vous inquiétez pas de vous battre avec les trucs de compression pour l'instant.

hey @lojjic , ​​je vérifie juste ça. Des progrès ou des choses que nous pouvons aider ?

Remarque : Safari arrive enfin à prendre en charge WebGL2, il est donc possible de ne pas prendre en charge WebGL1 pour cette fonctionnalité.

Notre implémentation WebGL2 est en assez bon état pour que nous l'activions par défaut pour des tests plus larges.
https://trac.webkit.org/changeset/267027/webkit

J'ai un iPhone 6 sur lequel il n'y aura jamais de WebGL 2.0 😒

Fait intéressant, le WebGL 1.0 spécifie des exigences encore pires que je ne le pensais. Les entiers n'existent pas vraiment :

4.1.3 Entiers
Les nombres entiers sont principalement pris en charge en tant qu'aide à la programmation. Au niveau matériel, les entiers réels aideraient
implémentation efficace des boucles et des indices de tableau, et référencement des unités de texture. Cependant, il n'y a pas
exigence que les entiers dans le langage correspondent à un type entier dans le matériel. Il n'est pas prévu que
le matériel sous-jacent prend entièrement en charge un large éventail d'opérations sur les nombres entiers. Un ombrage OpenGL ES
L'implémentation du langage peut convertir des entiers en flottants pour opérer dessus. Il n'y a donc pas de portatif
comportement d'emballage.

Cependant je n'ai pas perdu espoir. Faut que je réfléchisse un peu...
En attendant, @lojjic , ​​pourriez-vous nous dire à quelle autre approche vous avez pensé ?

@FunMiles Bien sûr. Je suis capable d'étendre le champ de distance aux bords de la texture, tout en conservant une précision suffisante pour la forme du glyphe, en codant les valeurs de distance à l'aide d'une échelle non linéaire. Ainsi, les distances très proches du chemin du glyphe (dans les 1-2 texels) ont de nombreuses valeurs avec lesquelles travailler, tandis que celles plus éloignées en ont moins. Le shader doit juste savoir comment reconvertir à la distance linéaire d'origine. La forme correcte du glyphe est superbe et la précision inférieure des contours extrudés est à peine perceptible, car ils sont de toute façon arrondis.

J'ai prouvé cela en utilisant à la fois une échelle linéaire à deux niveaux et une échelle exponentielle. Les deux ont des avantages et des inconvénients.

Je suis maintenant en train de mettre en œuvre votre approche (très intelligente) consistant à utiliser des valeurs de bord voisines pour approximer le gradient à l'extérieur du quad. Cela a du sens jusqu'à présent, même si je ne sais pas quoi faire des zones en dehors des coins. Cela deviendra peut-être évident une fois que j'irai plus loin, mais si vous avez une réponse facile pour moi, je vous en serais reconnaissant :)

Je suis maintenant en train de mettre en œuvre votre approche (très intelligente) consistant à utiliser des valeurs de bord voisines pour approximer le gradient à l'extérieur du quad. Cela a du sens jusqu'à présent, même si je ne sais pas quoi faire des zones en dehors des coins. Cela deviendra peut-être évident une fois que j'irai plus loin, mais si vous avez une réponse facile pour moi, je vous en serais reconnaissant :)

Je ne suis pas sûr de ce que vous entendez par _à l'extérieur des coins_. Voulez-vous dire les quarts de plans enracinés à chaque coin, où la projection la plus proche est le coin lui-même ? Je pense que là, vous pouvez utiliser la règle sur un bord à la fois sur le bord vertical et horizontal et faire une moyenne pondérée en fonction de la distance à chacun.

PS : L'idée de réduire la précision de loin m'était venue plus tôt dans la journée après avoir lu le non-sens des entiers WebGL... 😛 J'ai encore espoir que la technique de compression puisse être implémentée dans WebGL 1.0 pour obtenir 11 bits de précision à moindre coût et quelques plus de bits avec plus de tests. c'est-à-dire en utilisant un rgba , le a pourrait contenir 8 bits de la moyenne puis pour chacun des rgb , ils seraient signés et le signe représenterait 1 bit chacun de précision pour la moyenne, puis enfin le reste, qui serait dans la plage 0-127 aurait la valeur 64 soustraite pour représenter 7 bits pour 3 des 4 valeurs nécessaires. La valeur réelle serait ce centre (allant de 0 à 2^11-1) + dist_i. La 4ème valeur serait récupérée en faisant moins leur somme, puisque la somme doit être la moyenne. On n'obtiendrait que 11 bits, mais c'est probablement suffisant. Pour obtenir plus de bits, il faudrait tester si la valeur ou r , g et b sont inférieures à -64, positives ou négatives, supérieures à 64. Cela serait essentiellement donner 14 bits pour le centre et seulement 6 bits pour les différences. Peut-être un bon compromis ? J'aurais besoin de tester que les garanties très laxistes que WebGL 1.0 met sur la précision n'interféreraient pas avec cette réflexion...

Ouais c'est ce que je voulais dire, les zones où U et V sont en dehors de 0-1. Merci, je vais essayer.

@lojjic Comme question secondaire, dans un article précédent, vous sembliez préoccupé par le fait d'avoir deux valeurs de texture par texel SDF à cause de la mémoire. Bien que je préfère moi-même réduire la mémoire, 256 glyphes sur une grille 64x64 ne consomment que 1/4 Mo de mémoire de texture. Je sais que certaines langues peuvent nécessiter beaucoup plus de glyphes, mais la BBC dit que pour lire le journal, seuls 2000/3000 caractères sont nécessaires. Ainsi, 4000 caractères feraient 4 Mo avec un octet par texel SDF. Ça vaut le coup de s'inquiéter ?

@FunMiles Fair point, et je n'étais pas vraiment très préoccupé par cela.

Il reste du travail en suspens à faire, mais b19cd3aff4b0876253d76b3fc66b7e2a1f16a7e5 corrige ce problème. Pour ceux qui utilisent React-Three-Fiber, cela a été publié dans Drei v2.2.0
https://github.com/pmndrs/drei/pull/156

Je sais que c'est fermé mais je veux dire merci pour le travail. Je l'ai fait fonctionner dans mon application. Il a fallu un peu de temps pour comprendre que les propriétés n'accepteraient pas seulement un nombre pour la taille du contour. Cependant, le résultat est exactement ce dont j'avais besoin.
Screen Shot 2020-11-09 at 7 44 52 PM

@FunMiles Ça a l'air sympa ! Vous _devriez_ pouvoir utiliser une valeur numérique pour outlineWidth , donc si cela ne fonctionne pas pour vous pour une raison quelconque, faites-le moi savoir.

Et merci pour votre contribution en cours de route. Je n'ai toujours pas été en mesure d'obtenir une extrapolation fluide de la distance en dehors des limites du quad en utilisant le type de technique que vous avez mentionné, donc un contour épais a actuellement des morceaux grumeleux dans les coins en particulier, mais c'est assez bon pour la plupart des cas (jusqu'à ~ 10 % de la taille de la police). Je suis définitivement ouvert à essayer d'affiner cela encore.

Cette page vous a été utile?
0 / 5 - 0 notes