Troika: Esboço do texto

Criado em 20 ago. 2020  ·  39Comentários  ·  Fonte: protectwise/troika

Estou tentando descobrir como ter um contorno de texto preto em torno do texto branco para que o texto possa ser lido facilmente. Não tenho certeza, mas parece que posso precisar usar um sombreador para fazer com o texto da troika-três? Você pode fornecer alguma orientação sobre como alcançar esse efeito?

Obrigado!

Comentários muito úteis

Obrigado pelas informações sobre o Mapbox, @anzorb. Definitivamente, verificarei sua implementação mais de perto, mas a partir dessa postagem no blog (eles chamam o esboço de "halo"), parece que eles usam a abordagem de limite alfa SDF como mencionei antes. Mas sua estratégia de geração de SDF e empacotamento de atlas é diferente da minha e não tem os mesmos problemas com tamanhos de campo de distância não uniforme por glifo. Provavelmente teria a mesma limitação máxima de largura de contorno.

Na verdade, fiz algum progresso nos contornos de _espaço de tela_ usando derivações padrão. Espaço de tela significando que o contorno teria, por exemplo, 1 pixel de tela de largura, independentemente do tamanho/escala ou obliquidade do texto. Se o objetivo é apenas adicionar contraste com o plano de fundo, parece que isso pode ser tão bom quanto (ou até preferível) contornos que escalam junto com o tamanho do texto. Você tem uma opinião sobre isso?

Todos 39 comentários

Eu quase sinto que talvez o que eu preciso seja realizado pelo sinalizador debugSDF, se eu pudesse colori-lo.
image
Mostrar o sdf basicamente me dá um contorno que provavelmente funcionaria se eu pudesse colori-lo, ajustar o tamanho e potencialmente torná-lo um pouco mais nítido.

Isso não deve ser muito difícil de fazer. Comecei a jogar com textOutline como um recurso há algum tempo, mas nunca o terminei.

Em teoria, as bordas do glifo podem ser expandidas simplesmente alterando o valor no campo de distância com sinal que corresponde à borda. Isso é 0.5 no código do shader atual , mas isso pode ser configurado passando-o como um uniforme.

Idealmente, o sombreador poderia lidar com o desenho tanto do glifo normal quanto de qualquer contorno ao redor dele em uma única chamada de desenho, então você precisaria de alguns novos uniformes: a largura do contorno (não tem certeza em quais unidades isso seria?) a área de contorno.

Comecei a jogar com textOutline como um recurso há algum tempo, mas nunca o terminei.

Eu ficaria MUITO feliz se fosse suportado nativamente :)
No entanto, sou muito novo em shaders, então não tenho certeza de quanta ajuda posso ser. Estarei atento a esta questão.

Eu voltei um pouco para isso e rapidamente percebi porque eu não tinha terminado originalmente. Embora o SDF _pode_ ser usado para delinear, há algumas ressalvas:

  • A largura potencial máxima do contorno é limitada pela extensão do próprio campo de distância (~8 texels na textura SDF 64x64).
  • Isso não corresponde a um tamanho visual consistente em todos os glifos, pois a textura SDF é dimensionada para os limites de cada glifo.

Portanto, glifos maiores como "W" podem obter um contorno decentemente espesso, mas pequenos, como pontos, podem obter apenas um contorno muito fino. E cada glifo precisa usar um ponto de corte SDF diferente para tornar seus contornos visualmente consistentes.

Eu não acho que isso seja insuperável ainda, mas não é tão simples quanto eu pensava inicialmente.

@lojjic , adorando essa biblioteca 🥇, mas o esboço do texto é um requisito para o nosso projeto...

Eu voltei um pouco para isso e rapidamente percebi porque eu não tinha terminado originalmente. Embora o SDF _pode_ ser usado para delinear, há algumas ressalvas:

  • A largura potencial máxima do contorno é limitada pela extensão do próprio campo de distância (~8 texels na textura SDF 64x64).
  • Isso não corresponde a um tamanho visual consistente em todos os glifos, pois a textura SDF é dimensionada para os limites de cada glifo.

Portanto, glifos maiores como "W" podem obter um contorno decentemente espesso, mas pequenos, como pontos, podem obter apenas um contorno muito fino. E cada glifo precisa usar um ponto de corte SDF diferente para tornar seus contornos visualmente consistentes.

Eu não acho que isso seja insuperável ainda, mas não é tão simples quanto eu pensava inicialmente.

Estou curioso, podemos fazer algum tipo de pós-processamento usando shaders para obter um contorno e/ou efeitos de sombra? Desde já, obrigado!
Aqui está o efeito que estou tentando alcançar:
Screen Shot 2020-08-31 at 4 32 03 PM

Edit: isso é do mapbox gl, acho que eles também estão usando SDF https://blog.mapbox.com/drawing-text-with-signed-distance-fields-in-mapbox-gl-b0933af6f817 , talvez possamos dissecar seu código para ver o que eles fizeram. https://github.com/mapbox/mapbox-gl-js

Obrigado pelas informações sobre o Mapbox, @anzorb. Definitivamente, verificarei sua implementação mais de perto, mas a partir dessa postagem no blog (eles chamam o esboço de "halo"), parece que eles usam a abordagem de limite alfa SDF como mencionei antes. Mas sua estratégia de geração de SDF e empacotamento de atlas é diferente da minha e não tem os mesmos problemas com tamanhos de campo de distância não uniforme por glifo. Provavelmente teria a mesma limitação máxima de largura de contorno.

Na verdade, fiz algum progresso nos contornos de _espaço de tela_ usando derivações padrão. Espaço de tela significando que o contorno teria, por exemplo, 1 pixel de tela de largura, independentemente do tamanho/escala ou obliquidade do texto. Se o objetivo é apenas adicionar contraste com o plano de fundo, parece que isso pode ser tão bom quanto (ou até preferível) contornos que escalam junto com o tamanho do texto. Você tem uma opinião sobre isso?

As larguras padronizadas são muito mais preferidas do que as larguras de escala. Ele se comportaria mais como css dessa maneira também.
Qualquer text-shadow aplicado via css é configurado independentemente do font-size .
Se sua solução proposta @lojjic funcionar da mesma maneira, isso seria 🎉 🎉 🎉

Obrigado pela sua pronta resposta @lojjic!

O objetivo é adicionar contraste e sua proposta fica ótima. Você quer dizer que o contorno será estático? ou seja, nenhuma maneira de controlá-lo (como @stephencorwin descrito, usando a abordagem text-shadow-ish).

Honestamente, não vejo isso como um problema, desde que o 1 pixel seja visível em telas de super alta densidade (porque atualmente multiplicamos o tamanho da fonte pela proporção de pixels do dispositivo para obter as "mesmas" proporções, independentemente da densidade) , acho que sua proposta significa que o contorno será visivelmente mais fino em dispositivos de DPI mais altos, não?

Desde já, obrigado!

Acho que sua proposta significa que o contorno será visivelmente mais fino em dispositivos de DPI mais altos, não?

Sim, essa é uma desvantagem da abordagem screen-space. Isso pode ser bom para muitos cenários, mas me incomodaria como designer ter uma aparência diferente entre os dispositivos.

A outra grande desvantagem é que (eu acho) aumentar a espessura do halo teria impacto no desempenho devido à necessidade de mais leituras de textura. Eu preciso continuar pesquisando isso embora.

Eu também preciso desse recurso. Eu implementei esse recurso em um aplicativo Android que desenvolvi há muito tempo e usei apenas para mim e amigos. Comecei a reimplementar este aplicativo com React e vim encontrar seu projeto.
Como outros, quero agradecer por esta biblioteca. Estou maravilhado com isso.

Em seu comentário sobre mais leituras de textura, não acho que você deva se preocupar tanto com isso. Os texels adicionais que você precisa serão os texels que os fragmentos vizinhos precisam e, portanto, acho que eles estarão no cache da GPU de qualquer maneira e custarão quase nada.

Obrigado pela contribuição @FunMiles. (Colorado representa! 😄)

Eu acho que você provavelmente está certo para contornos de 1, talvez 2 fragmentos de espessura. Minha preocupação é que, à medida que a espessura aumenta, isso está explodindo o número de leituras de textura (espessura de 4 = 56 leituras?) bem como a probabilidade de essas leituras serem mais caras. Mas estou apenas especulando com base na leitura de algumas coisas, não sou especialista em GPUs de forma alguma. Provavelmente vou ter que experimentar.

@lojjic (Norte de você aqui 😄) Tentei encontrar o código para o fragment shader. Admito que tenho dificuldade em descobrir as coisas como parece (e acho que li em algum lugar) que você modifica o shader em vez de escrevê-lo inteiramente.
No entanto, embora eu não consiga descobrir como é o shader completo e como você realmente se conecta a ele, acho que entendo um pouco da rotina principal ...

Agora você lê uma distância escalar para o pixel com texture2D(uTroikaSDFTexture, vTroikaSDFTextureUV).r; . Se estou entendendo o que você escreveu antes, uma questão tem a ver com o espaço ao redor dos glifos. Pode explicar com mais detalhes?
Voltando ao seu exemplo de 'W' e ',' o intervalo vTroikaSDFTextureUV na textura se encaixa perfeitamente ao redor do glifo ou há algum espaço adicional? O sombreador de fragmento conhece os limites do UV para o glifo atual? Se isso acontecer, para um fragmento que cairia fora, você pode projetar o UV atual nos limites e ler os dados diretamente dentro.

Eu acho que é possível descobrir o que é necessário com no máximo 5 leituras de texel ou com o uso de derivativos (vejo você verificar se eles estão disponíveis).

Voltando ao seu exemplo de 'W' e ',' o intervalo vTroikaSDFTextureUV na textura se encaixa perfeitamente ao redor do glifo ou há algum espaço adicional?

Há um pequeno espaço extra em torno dos limites do caminho verdadeiro de cada glifo, apenas o suficiente para acomodar as partes externas do campo de distância. São 8 texels no SDF, mas como cada SDF é um 64x64 uniforme dimensionado em um quad de glifos de tamanho variável, essa margem visível varia de glifo para glifo.

O sombreador de fragmento conhece os limites do UV para o glifo atual? Se isso acontecer, para um fragmento que cairia fora, você pode projetar o UV atual nos limites e ler os dados diretamente dentro.

Essa informação deve estar disponível. Eu não estou seguindo você em "projete o UV atual para os limites e vá ler os dados dentro" embora.

Eu ficaria grato se você conhecesse uma maneira de obter contornos mais grossos com menos leituras de texel! Eu tenho conceituado como: para um fragmento que não está dentro do caminho do glifo, faça uma pesquisa radial para ver se algum fragmento dentro de r=cairia dentro do caminho do glifo. Essa pode ser apenas a maneira errada de pensar sobre isso.

Um problema é o que acontece quando os glifos são colocados lado a lado. Deixando isso de lado por enquanto, vamos pegar o caso de um único caractere sendo desenhado com um contorno bem grosso t . O personagem está sendo desenhado com um retângulo em torno dele que é grande o suficiente para acomodar o contorno.
O fragment shader possui duas informações principais: vTroikaSDFTextureUV e vTroikaGlyphUV .

Meu entendimento é que vTroikaGlyphUV tem valores fixados entre 0 e 1 quando dentro do glifo. O que eu quis dizer quando falei sobre projeção foi que se vTroikaGlyphUV estiver fora desses limites, você pode recuperar a distância do ponto "projetado" no limite do glifo. Ou seja, se uv = (1,05, 0,7), então o ponto projetado é (1,0, 0,7). Usando valores em (1,0, 0,7+epsilon) e (1,0, 0,7-epsilon), você pode recuperar um gradiente para a distância e, usando isso, calcular a distância aproximada em (1,05, 0,7).
Quando fiz meu próprio trabalho para isso, lembro de ler um artigo de alguém fazendo SDF com um gradiente na textura também. Essa abordagem evita ter que ler três texels, mas torna a textura uma textura de 3 componentes e adiciona complexidade à construção da textura.

Isso responde sua pergunta?

Agora, quando você tem vários personagens lado a lado, cada um precisa estar ciente dos vizinhos, tornando isso um pouco mais complexo. Você precisa carregar o GlyphUV e TextureUV não apenas para o personagem atual, mas também para o vizinho. Isso pode exigir o corte de cada caractere em dois retângulos, para que o vizinho possa ser o anterior para o primeiro retângulo e o posterior para o segundo retângulo. Não tenho certeza de como lidar com multi-linhas.... Não tive que lidar com esse caso no meu aplicativo. Minha aplicação era um mapa e cada etiqueta era uma linha única.

PS: Posso ter entendido mal o seu "espaço extra em torno dos limites do caminho verdadeiro de cada glifo" . Você está dizendo que para um X, por exemplo, ele está dentro de um retângulo e que existem quatro triângulos que não têm valores válidos?

@FunMiles Eu _acho_ talvez eu veja onde você está indo. Vou precisar de algum tempo para pensar nisso, mas tenho outro trabalho de prazo que preciso focar no momento.

PS: Um pouco de nota matemática divertida:
Caso alguém se pergunte como o gradiente pode ser obtido a partir de dois ou três pontos alinhados, é preciso lembrar que o gradiente da distância tem uma norma fixa (dependendo da escala escolhida). E o gradiente definitivamente aponta para fora da caixa de glifos. Então, por exemplo, se todos os 3 pontos do exemplo (1,0, 0,7), (1,0, 0,7+épsilon) e (1,0, 0,7-épsilon) estão na mesma distância, então o gradiente é (escala, 0).

Embora definitivamente não seja de alto desempenho, é possível usar deslocamentos. Estou usando react-three-fiber , que aproveita o troika-text com o pacote drei, mas imaginei que compartilharia caso outros estivessem procurando uma solução como paliativo até que a biblioteca suporte o stroke nativamente:

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;

Então, em outro lugar, posso chamá-lo com <StrokedText /> em vez de <Text /> normalmente:

<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 Finalmente tive algum tempo para analisar suas sugestões. Acho que faria sentido assumindo que a totalidade do retângulo SDF do glifo contivesse valores de distância úteis (> 0,0). Esse não é o caso atualmente; Eu acho que você estava percebendo isso em seu acompanhamento sobre "x", onde o SDF cai para zero bem dentro dos limites do quad, então há áreas significativas onde não há gradiente de distância útil para extrapolar:

image

Talvez eu possa mudar o gerador SDF para garantir um gradiente diferente de zero em toda a quadra ...? Pensando em voz alta, isso poderia ter conotações para precisão de distância, e poderia introduzir artefatos entre personagens dentro da textura do atlas... 🤔

Talvez eu possa mudar o gerador SDF para garantir um gradiente diferente de zero em toda a quadra

Eu tentei isso, e sim, ele permite que o campo de distância seja extrapolado além dos limites do quad, mas como eu temia, reduz significativamente a qualidade do próprio glifo. Isso é esperado com apenas um gradiente de 8 bits; espalhá-lo por uma distância maior resulta em uma precisão menor em cada texel. :(

Talvez eu possa gerar um SDF separado apenas para os contornos - um campo de maior dispersão, mas de menor precisão - codificado em um segundo canal de textura. Dobraria o tamanho da textura, mas não deveria ser significativamente mais lento. 🤔

codificado em um segundo canal de textura.

@lojjic Isso soa muito razoável e basicamente imita minha solução alternativa agora. Além disso, pode ser opt-in, portanto, não há sucesso se o usuário não solicitar contornos.

@lojjic A abordagem opt-in sugerida por @stephencorwin garantiria que ninguém pague mais do que está disposto a pagar.

Voltando ao aspecto técnico. Vamos pegar a imagem X em sua resposta. Estou entendendo corretamente que para todos os glifos, é 64x64 pixel? Com 8 bits, se você tiver que codificar todo o espaço de distância, isso significa que você tem uma precisão de 2 _fractional_ bits. Eu acho que você está afirmando que essa precisão não é suficiente.

Talvez haja um truque a ser usado para ganhar precisão a um custo muito baixo. Ao invés de fazer uma grade de 64x64 de 8 bits, comprima os dados dos blocos de 2x2 em um dado de 32 bits. Uma característica clara da distância com sinal entre dois pixels é que ela está sempre dentro de [-1,1] direita e esquerda, para cima e para baixo e [-sqrt(2), sqrt(2)] na diagonal. Então imagine que você usa 12 bits para o SD do centro do bloco 2x2, você teria 5 bits para cada centro dos pixels para codificar a diferença entre o centro deles e o centro do 2x2. Esses 5 bits representam no máximo a distância sqrt(2)/2. Efetivamente você tem uma precisão de 5,5 bits.
Os 12 bits do ponto central codificam pelo menos 6 bits de precisão em uma distância máxima de 64. Tecnicamente, se a caixa tivesse apenas um ponto em um canto, os 12 bits precisariam ser capazes de codificar até sqrt(2)*64, mas Eu não acho que isso seja realmente possível, já que a caixa é presumivelmente centralizada em torno de cada glifo. De fato, quanto mais longe de qualquer borda de glifo e menos informações os deltas têm para codificar (quando você está longe de uma borda de glifo, o campo de gradiente se torna quase uniforme em uma vizinhança), então, do ponto de vista da teoria da informação, é ainda melhor possível melhorar a codificação
ter mais bits de informação real.... Mas eu não começaria com essa otimização adicional.

Uma consequência interessante dessa abordagem é que não se deixaria o hardware de texturização fazer qualquer interpolação. Mas por outro lado, seria possível obter gradientes de graça.

Se precisar de ajuda, posso implementar a codificação/decodificação de tudo isso.

PS: Lembro-me de ver uma discussão em um dos README.md sobre um SDF mais sofisticado. Eu sonhei? 😛 Alguém pode me apontar de volta para ver se esse outro sistema pode ajudar aqui?

@FunMiles Ideia de compressão muito inteligente. Vou manter isso em mente se outras opções falharem. Perder a interpolação linear pelo hardware é uma troca que prefiro não ter que fazer. 😉

Ocorreu-me que meu problema com bits insuficientes é exacerbado usando 0,5 como a distância "zero", portanto, apenas metade dos valores alfa estão disponíveis para codificar a distância _fora_ do glifo. Eu poderia mudar isso para usar mais valores para distâncias externas e menos para distâncias internas, ganhando alguma precisão na periferia.

Ré. um "SDF mais sofisticado", você pode querer dizer "MSDF" onde vários canais de cores são usados?

@FunMiles Ideia de compressão muito inteligente. Vou manter isso em mente se outras opções falharem. Perder a interpolação linear pelo hardware é uma troca que prefiro não ter que fazer. 😉

Eu não acho que você deve se preocupar em perder essa interpolação. O custo é muito baixo, mas os benefícios de sempre ter o gradiente (e até um pouco de curvatura) quando não há suporte de hardware é de maior importância no meu ponto de vista. Na verdade, você arredonda o ponto de amostra e obtém um novo valor de deslocamento para o ponto de amostra arredondado. O cálculo da cobertura parcial do fragmento para anti-aliasing prossegue como normalmente faria com o gradiente disponível.
Ocorreu-me que meu problema com bits insuficientes é exacerbado usando 0,5 como a distância "zero", portanto, apenas metade dos valores alfa estão disponíveis para codificar a distância _fora_ do glifo. Eu poderia mudar isso para usar mais valores para distâncias externas e menos para distâncias internas, ganhando alguma precisão na periferia.

Isso lhe dará no máximo um pouco de precisão. Não deve ser desprezado, mas não tão significativo ainda.

Ré. um "SDF mais sofisticado", você pode querer dizer "MSDF" onde vários canais de cores são usados?
Foi isso que eu quis dizer. Eu encontrei um projeto GitHub sobre isso em C++. Você tinha algo para usar ou eu fiquei confuso, misturando várias coisas que pesquisei duas semanas atrás?

Eu não acho que o MSDF ajudaria, a propósito.
Posso perguntar se você poderia ter um pequeno arquivo .md explicando como você constrói o SDF em JavaScript? Com isso, acho que poderia criar um código para implementar minha ideia de compactação.

O SDF é construído aqui: https://github.com/protectwise/troika/blob/master/packages/troika-three-text/src/worker/SDFGenerator.js#L134 -- não há muito o que explicar sobre isso, principalmente mapear texels para unidades de fonte e escrever as distâncias medidas nos valores de texel. Teríamos que adicionar algumas medidas de distância extras para o ponto central de cada bloco 2x2.

Tentando envolver meu cérebro completamente em torno disso... Replicar a interpolação bilinear no GLSL parece bastante simples/barato, uma vez que você tenha os 4 valores mais próximos. Para obter esses valores quando eles são codificados com seu esquema de compactação, acho que envolverá:

  • Se exatamente no centro de um texel: 2 ou 3 amostras de textura
  • Se entre os 4 texels de um bloco 2x2: 4 amostras de textura
  • Se entre dois blocos vizinhos: ~9 amostras de textura~ 7 ou 8 amostras de textura
  • Se entre quatro blocos vizinhos: ~11 amostras de textura~ 13 amostras de textura

Estou grokking isso corretamente?

Ah, espere, eu ainda estava pensando em uma textura de canal único. Usando quatro canais rgba, cada bloco de dados é apenas uma única leitura. Portanto, no máximo 4 amostras de textura.

Eu tinha começado a ler SDFGenerator.js. vou olhar mais.

Eu não acho que você precise mais do que ler um único texel por fragmento, a menos que você queira melhorar especificamente a mistura alfa para casos de canto onde você pode estar em um ponto cercado por bordas de glifo em todos os lados. Você só precisa do centro mais próximo do bloco 2x2 onde está o centro do fragmento.
No entanto, percebo que pode haver uma dificuldade que eu não tinha previsto... WebGL 1.0 é muito limitado no que fornece que seria útil aqui: Nenhum tipo inteiro não assinado uint nem mesmo inteiros de 32 bits! 🤯 e sem operação bit a bit... Portanto, a compactação que estou sugerindo é um pouco mais complicada de escrever usando inteiros de 16 bits.

Tudo isso está disponível no WebGL 2.0, mas presumo que queremos direcionar o WebGL 1.0 aqui?

Tudo isso está disponível no WebGL 2.0, mas presumo que queremos direcionar o WebGL 1.0 aqui?

Acho que pode ser razoável restringir o contorno de texto ao webgl 2 e apenas detectar se o usuário tem compatibilidade com esse navegador. Embora, eu possa entender possivelmente fazendo uma versão ideal do webgl 2 com um fallback do webgl 1. Imo, poderíamos começar com o webgl 2 e deixá-lo de molho com a comunidade antes de tentar imediatamente dar suporte a ambos.

Acho que tenho uma abordagem alternativa para contornar o problema de precisão, então @FunMiles não se preocupe em lutar com o material de compactação por enquanto.

ei @lojjic , apenas verificando isso. Algum progresso ou coisas em que possamos ajudar?

Nota lateral: o Safari está finalmente chegando ao suporte do WebGL2, portanto, pode ser possível não oferecer suporte ao WebGL1 para esse recurso.

Nossa implementação WebGL2 está em boas condições para que possamos habilitá-la por padrão para testes mais amplos.
https://trac.webkit.org/changeset/267027/webkit

Eu tenho um iPhone 6 no qual nunca haverá WebGL 2.0 😒

Curiosamente, o WebGL 1.0 especifica requisitos ainda piores do que eu pensava. Os inteiros realmente não existem:

4.1.3 Inteiros
Os inteiros são suportados principalmente como um auxílio de programação. No nível de hardware, inteiros reais ajudariam
implementação eficiente de loops e índices de array, e referenciando unidades de textura. No entanto, não há
requisito de que os números inteiros no idioma sejam mapeados para um tipo inteiro no hardware. Não se espera que
hardware subjacente tem suporte total para uma ampla gama de operações de inteiros. Um sombreamento OpenGL ES
A implementação da linguagem pode converter inteiros em floats para operar neles. Portanto, não há portabilidade
comportamento de embrulho.

No entanto, não perdi a esperança. Só preciso repensar um pouco...
Enquanto isso, @lojjic , ​​você se importa de nos dizer qual abordagem alternativa você pensou?

@FunMiles Claro. Eu sou capaz de estender o campo de distância para as bordas da textura, mantendo precisão suficiente para a forma do glifo, codificando os valores de distância usando uma escala não linear. Portanto, distâncias muito próximas ao caminho do glifo (dentro de 1-2 texels) têm muitos valores para trabalhar, enquanto as mais distantes têm menos. O sombreador só precisa saber como converter de volta para a distância linear original. A forma adequada do glifo parece ótima, e a menor precisão para os contornos extrudados é quase imperceptível, pois eles são arredondados de qualquer maneira.

Eu provei isso usando uma escala linear de duas camadas e uma escala exponencial. Ambos têm prós e contras.

Agora estou no meio da implementação de sua abordagem (muito inteligente) de usar valores de borda vizinhos para aproximar o gradiente fora do quadrilátero. Está fazendo sentido até agora, embora eu não tenha certeza do que fazer com as áreas fora dos cantos. Pode se tornar óbvio quando eu for mais a fundo, mas se você tiver uma resposta fácil para mim, eu ficaria agradecido :)

Agora estou no meio da implementação de sua abordagem (muito inteligente) de usar valores de borda vizinhos para aproximar o gradiente fora do quadrilátero. Está fazendo sentido até agora, embora eu não tenha certeza do que fazer com as áreas fora dos cantos. Pode se tornar óbvio quando eu for mais a fundo, mas se você tiver uma resposta fácil para mim, eu ficaria agradecido :)

Não tenho certeza do que você quer dizer com _fora dos cantos_. Você quer dizer os quartos planos enraizados em cada canto, onde a projeção mais próxima é o próprio canto? Acho que aí você pode usar a régua em uma aresta tanto na aresta vertical quanto na horizontal e fazer uma média ponderada com base na distância de cada uma.

PS: A ideia de reduzir a precisão para longe me ocorreu hoje cedo depois de ler o absurdo inteiro do WebGL... 😛 Ainda tenho esperança de que a técnica de compactação possa ser implementada no WebGL 1.0 para obter 11 bits de precisão de forma barata e alguns mais bits com mais testes. ou seja, usando um rgba , o a poderia conter 8 bits da média então para cada rgb , eles seriam assinados e o sinal representaria 1 bit cada de precisão para a média, então finalmente o restante, que estaria na faixa de 0-127 teria o valor 64 subtraído para representar 7 bits para 3 dos 4 valores necessários. O valor real seria center (variando de 0 a 2^11-1) + dist_i. O 4º valor seria recuperado fazendo menos sua soma, pois a soma deve ser a média. Um só obteria 11 bits, mas isso provavelmente é suficiente. Para obter mais bits, seria necessário testar se o valor ou r , g e b são menores que -64, positivos ou negativos, maiores que 64. Isso seria essencialmente dê 14 bits para o centro e apenas 6 bits para as diferenças. Provavelmente um bom compromisso? Eu precisaria testar se as garantias muito frouxas que o WebGL 1.0 coloca em precisão não interfeririam nesse pensamento....

Sim, é isso que eu quis dizer, as áreas onde U e V estão fora de 0-1. Obrigado, vou dar uma chance.

@lojjic Como uma pergunta à parte, em um post anterior, você parecia preocupado em ter dois valores de textura por texel SDF por causa da memória. Embora eu prefira reduzir a memória, 256 glifos em uma grade de 64x64 estão consumindo apenas 1/4 MB de memória de textura. Eu sei que alguns idiomas podem exigir muito mais glifos, mas ainda assim, a BBC diz que para ler o jornal, são necessários apenas 2000/3000 caracteres. Portanto, 4000 caracteres dariam 4 MB com um byte por texel SDF. Vale a pena se preocupar?

@FunMiles Fair point, e eu não estava muito preocupado com isso tbh.

Restam alguns trabalhos pendentes, mas b19cd3aff4b0876253d76b3fc66b7e2a1f16a7e5 corrige esse problema. Para aqueles que usam react-three-fiber, isso foi lançado no drei v2.2.0
https://github.com/pmndrs/drei/pull/156

Eu sei que está fechado, mas quero agradecer pelo trabalho. Consegui fazer funcionar no meu aplicativo. Demorou um pouco para descobrir que as propriedades não aceitariam apenas um número para o tamanho do contorno. No entanto, o resultado é exatamente o que eu precisava.
Screen Shot 2020-11-09 at 7 44 52 PM

@FunMiles Parece bom! Você _deve_ ser capaz de usar um valor numérico para outlineWidth , portanto, se isso não funcionar para você por algum motivo, me avise.

E obrigado por sua contribuição ao longo do caminho. Eu ainda não consegui obter uma extrapolação suave da distância fora dos limites do quad usando o tipo de técnica que você mencionou, então um contorno grosso atualmente tem pedaços irregulares nos cantos em particular, mas é bom o suficiente para a maioria dos casos (até ~ 10% do tamanho da fonte). Estou definitivamente aberto a tentar refinar isso ainda.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

lojjic picture lojjic  ·  18Comentários

drcmda picture drcmda  ·  11Comentários

lojjic picture lojjic  ·  11Comentários

Ocelyn picture Ocelyn  ·  13Comentários

asbjornlystrup picture asbjornlystrup  ·  7Comentários